mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-24 10:16:59 -06:00
Merge pull request #1163 from MaridWSH/fonts/reload-changed-fonts
Fonts: Type › Reload Fonts reads fonts changed on disk again (#328, #547)
This commit is contained in:
@@ -348,6 +348,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-16** · Type › Reload Fonts (`font.reload`): fonts installed, edited or removed while DesignCraft runs are read again without a restart, and the open documents recompose with them (#328, #547). An edited font file is noticed by its length and modification time; bundled, added and document fonts stay as they are. InDesign does this by itself, so noticing changes automatically is still open (gap 6). No readiness percentage change.
|
||||
- **2026-10-16** · Parents can be deleted from their Pages-panel context menu or `layout.parents.delete {id}`. Applied-page and based-on references are cleared, local override copies survive, and Undo restores the parent. Addresses the deletion portion of #956; no readiness percentage change.
|
||||
- **2026-10-17** · File › Package writes the document's and the IDML's links relative to the package (`Links/logo.png`, `file:Links/logo.png`), so a package holds no path from the machine that made it; opening a document resolves them to the files beside it. Links › Copy Link(s) To still links by full path. (#340). No readiness percentage change.
|
||||
- **2026-10-18** · View › Screen Mode: Bleed and Slug do what they say. Both were selectable from the toolbar, the View menu and `view.screenMode` but drew the canvas exactly as Normal did, guides and all; they are preview modes now, and what each page shows reaches the bleed box or the slug box instead of stopping at the trim. The slug guide is drawn as well, in the Slug colour from Preferences › Guides & Pasteboard, which nothing had read. No readiness percentage change.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Type › Find/Replace Font: the fonts a document uses (missing ones flagged) and replacing one
|
||||
//! font with another everywhere — local formatting and style definitions.
|
||||
//! font with another everywhere — local formatting and style definitions. Type › Reload Fonts:
|
||||
//! the fonts installed, edited or removed since they were read.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
@@ -8,7 +9,7 @@ use designcraft_doc::{CharAttrs, Document, Story};
|
||||
use designcraft_fonts::FontSource;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::{CommandSpec, bad, cmd, has_doc, str_param};
|
||||
use super::{CommandSpec, always, bad, cmd, has_doc, str_param};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
@@ -25,9 +26,30 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
replace
|
||||
),
|
||||
cmd!(
|
||||
noundo "font.reload",
|
||||
"Reload Fonts",
|
||||
["Type"],
|
||||
None,
|
||||
"{} — scans the font folders again and reads again the installed font files changed or removed since they were read, then recomposes the open documents → {cataloged, changed: [path]}",
|
||||
always,
|
||||
reload
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// Fonts installed, edited or removed while the app runs (#328, #547): the font database reads
|
||||
/// them again, then every open document is composed again with what is installed now.
|
||||
fn reload(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
let reloaded = designcraft_fonts::FontDb::global().reload_fonts();
|
||||
// A new cache: render jobs still holding the old one can't bring back the old fonts' layout.
|
||||
s.cache = Arc::new(designcraft_compose::Cache::new());
|
||||
for st in &mut s.docs {
|
||||
st.bump_revision();
|
||||
}
|
||||
Ok(json!({"cataloged": reloaded.cataloged, "changed": reloaded.changed}))
|
||||
}
|
||||
|
||||
/// Every story's text, including table cells and footnotes.
|
||||
pub(super) fn for_each_story(d: &Document, f: &mut dyn FnMut(&Story)) {
|
||||
let mut pending: Vec<&Story> = d.stories.values().map(AsRef::as_ref).collect();
|
||||
@@ -233,6 +255,22 @@ fn replace(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn reload_fonts_recomposes_open_documents_without_editing_them() {
|
||||
let mut s = Session::new();
|
||||
let r = s.execute("font.reload", &json!({})).unwrap();
|
||||
assert!(r["cataloged"].is_u64() && r["changed"].is_array(), "{r}");
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
s.execute("frame.create", &json!({"rect": [72, 72, 400, 300], "content": "text", "text": "Some text here."})).unwrap();
|
||||
let (revision, undo) = (s.doc().unwrap().revision, s.doc().unwrap().history.undo.len());
|
||||
let cache = Arc::clone(&s.cache);
|
||||
s.execute("font.reload", &json!({})).unwrap();
|
||||
assert!(!Arc::ptr_eq(&cache, &s.cache), "the layout composed with the old fonts is let go");
|
||||
let st = s.doc().unwrap();
|
||||
assert_ne!(st.revision, revision, "the canvas draws the document again");
|
||||
assert_eq!(st.history.undo.len(), undo, "reloading fonts isn't an edit");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replacement_resolves_inherited_family_and_style_and_rejects_missing_targets() {
|
||||
let mut s = Session::new();
|
||||
|
||||
@@ -370,6 +370,24 @@ impl FontFace {
|
||||
}
|
||||
}
|
||||
|
||||
/// A font file's length and modification time.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type FileStamp = (u64, Option<std::time::SystemTime>);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn file_stamp(path: &std::path::Path) -> Option<FileStamp> {
|
||||
std::fs::metadata(path).ok().map(|m| (m.len(), m.modified().ok()))
|
||||
}
|
||||
|
||||
/// What [`FontDb::reload_fonts`] did: the faces cataloged by the rescan, and the installed font
|
||||
/// files that changed or were removed since they were read (their faces are read again, from the
|
||||
/// new file, the next time they are looked up).
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct ReloadedFonts {
|
||||
pub cataloged: usize,
|
||||
pub changed: Vec<std::path::PathBuf>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug)]
|
||||
struct CatalogEntry {
|
||||
@@ -412,6 +430,10 @@ pub struct FontDb {
|
||||
outlines: Mutex<HashMap<(u32, u32), Arc<BezPath>>>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
catalog: RwLock<Vec<CatalogEntry>>,
|
||||
/// Each installed font file read into the shared fonts, with its (length, modification time)
|
||||
/// when it was read: [`FontDb::reload_fonts`] reads again the files that changed since.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
installed_files: Mutex<HashMap<std::path::PathBuf, FileStamp>>,
|
||||
/// The folders the system font scan reads (the platform's font folders for [`FontDb::global`]).
|
||||
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||
font_dirs: Vec<std::path::PathBuf>,
|
||||
@@ -1067,6 +1089,8 @@ impl FontDb {
|
||||
outlines: Mutex::new(HashMap::new()),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
catalog: RwLock::new(Vec::new()),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
installed_files: Mutex::new(HashMap::new()),
|
||||
font_dirs,
|
||||
font_files: Vec::new,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -1221,6 +1245,56 @@ impl FontDb {
|
||||
count
|
||||
}
|
||||
|
||||
/// Fonts installed, changed or removed since they were read (#328): scan the font folders
|
||||
/// again (see [`FontDb::load_system_fonts`]), then let go of the shared faces whose installed
|
||||
/// file changed (length or modification time) or is gone, so the next lookup reads the file as
|
||||
/// it is now, or finds the font missing. What was composed with the old faces must be composed
|
||||
/// again. Faces are never freed (see [`FaceRef`]): each reload of a changed file keeps the old
|
||||
/// copy. Bundled, added and document fonts are left alone. Nothing to do on wasm.
|
||||
pub fn reload_fonts(&self) -> ReloadedFonts {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
// Rescan first: a lookup after the faces are dropped finds the new files.
|
||||
let cataloged = self.load_system_fonts();
|
||||
let changed: Vec<std::path::PathBuf> = {
|
||||
let mut files = self.installed_files.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut changed: Vec<_> = files.iter().filter(|(p, stamp)| file_stamp(p).as_ref() != Some(*stamp)).map(|(p, _)| p.clone()).collect();
|
||||
changed.sort();
|
||||
for p in &changed {
|
||||
files.remove(p);
|
||||
}
|
||||
changed
|
||||
};
|
||||
if !changed.is_empty() {
|
||||
let stale = |f: &FontFace| matches!(&f.source, FontSource::Installed(p) if changed.contains(p));
|
||||
let mut dropped = std::collections::HashSet::new();
|
||||
self.faces.write().unwrap_or_else(|e| e.into_inner()).retain(|f| {
|
||||
let keep = !stale(f);
|
||||
if !keep {
|
||||
dropped.insert(f.id);
|
||||
}
|
||||
keep
|
||||
});
|
||||
self.instances.write().unwrap_or_else(|e| e.into_inner()).retain(|(base, _), f| !dropped.contains(base) && !stale(f));
|
||||
self.outlines.lock().unwrap_or_else(|e| e.into_inner()).retain(|(face, _), _| !dropped.contains(face));
|
||||
log::debug!("reloading {} changed font files ({} faces)", changed.len(), dropped.len());
|
||||
}
|
||||
ReloadedFonts { cataloged, changed }
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
ReloadedFonts::default()
|
||||
}
|
||||
|
||||
/// Read an installed font file, noting its length and modification time as they were before
|
||||
/// the read (a change during it is then found by the next reload).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn read_installed(&self, path: &std::path::Path) -> Option<Vec<u8>> {
|
||||
let stamp = file_stamp(path)?;
|
||||
let data = std::fs::read(path).ok()?;
|
||||
self.installed_files.lock().unwrap_or_else(|e| e.into_inner()).insert(path.to_path_buf(), stamp);
|
||||
Some(data)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn scan_font_dirs(&self) -> usize {
|
||||
let mut found = Vec::new();
|
||||
@@ -1294,7 +1368,7 @@ impl FontDb {
|
||||
// The whole family arrives at once (see `insert_faces`).
|
||||
let mut faces = Vec::new();
|
||||
for p in paths {
|
||||
if let Ok(data) = std::fs::read(&p) {
|
||||
if let Some(data) = self.read_installed(&p) {
|
||||
faces.extend(self.new_faces(data, FontSource::Installed(p)));
|
||||
}
|
||||
}
|
||||
@@ -1499,7 +1573,7 @@ impl FontDb {
|
||||
if std::fs::metadata(&p).map(|m| m.len() > 40 << 20).unwrap_or(true) {
|
||||
continue;
|
||||
}
|
||||
let Ok(data) = std::fs::read(&p) else { continue };
|
||||
let Some(data) = self.read_installed(&p) else { continue };
|
||||
let hit =
|
||||
enumerate_faces(&data).iter().any(|(i, _, _, _)| skrifa::FontRef::from_index(&data, *i).is_ok_and(|f| f.charmap().map(c).is_some()));
|
||||
if hit && self.add_faces(data, FontSource::Installed(p)) > 0 && covered(self) {
|
||||
|
||||
@@ -20,7 +20,7 @@ mod vertical;
|
||||
pub use features::{FontFeatures, font_features};
|
||||
pub use fontdb::{
|
||||
DOCUMENT_FONTS_FOLDER, DocumentFonts, FALLBACK_FAMILY, FaceRef, FontDb, FontFace, FontSource, MAX_DOCUMENT_FONT_BYTES, MAX_DOCUMENT_FONT_FILES,
|
||||
MAX_DOCUMENT_FONTS_TOTAL, ScopedFonts, base_style, bundled, same_style, system_font_dirs,
|
||||
MAX_DOCUMENT_FONTS_TOTAL, ReloadedFonts, ScopedFonts, base_style, bundled, same_style, system_font_dirs,
|
||||
};
|
||||
pub use group::{FamilyInfo, FontGroup, sort_for_menu};
|
||||
pub use harfrust::Feature;
|
||||
|
||||
@@ -389,3 +389,53 @@ fn installed_and_loaded_fonts_are_found_by_postscript_name() {
|
||||
assert_eq!(db.postscript_face("Source Serif 4"), None);
|
||||
assert_eq!(db.postscript_face("NoSuchFont-Regular"), None);
|
||||
}
|
||||
|
||||
/// Write `font` to `path` with a modification time `secs` from now, so a rewrite of the same
|
||||
/// length still reads as changed however coarse the file system's clock.
|
||||
fn write_font(path: &Path, font: &[u8], secs: u64) {
|
||||
std::fs::write(path, font).unwrap();
|
||||
let file = std::fs::File::options().write(true).open(path).unwrap();
|
||||
file.set_modified(std::time::SystemTime::now() + std::time::Duration::from_secs(secs)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reloading_reads_changed_font_files_again_and_keeps_the_others() {
|
||||
use crate::testing::font_with_glyph;
|
||||
let dir = font_dir("reload");
|
||||
let family = "DC Test Reloaded";
|
||||
let path = dir.join("Reloaded.ttf");
|
||||
write_font(&path, &font_with_glyph(family, &['a'], 'W').unwrap(), 10);
|
||||
let db = FontDb::with_font_dirs(vec![dir.clone()]);
|
||||
let before = db.face(family, "Regular");
|
||||
let wide = before.advance(before.glyph_for('a'));
|
||||
let kept = db.face(FAMILY, "Regular");
|
||||
// The font is edited and installed again under the same name (#328).
|
||||
write_font(&path, &font_with_glyph(family, &['a'], 'i').unwrap(), 20);
|
||||
assert_eq!(db.face(family, "Regular").id(), before.id(), "a lookup alone doesn't read the file again");
|
||||
let reloaded = db.reload_fonts();
|
||||
assert_eq!(reloaded.changed, std::slice::from_ref(&path));
|
||||
assert_eq!(reloaded.cataloged, 3);
|
||||
let after = db.face(family, "Regular");
|
||||
assert_ne!(after.id(), before.id());
|
||||
let narrow = after.advance(after.glyph_for('a'));
|
||||
assert!(narrow < wide, "the edited font sets 'a' as its i: {narrow} vs {wide}");
|
||||
assert_eq!(db.face(FAMILY, "Regular").id(), kept.id(), "unchanged files aren't read again");
|
||||
assert_eq!(db.reload_fonts().changed, Vec::<PathBuf>::new(), "nothing changed since");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reloading_lets_go_of_removed_fonts_and_finds_new_ones() {
|
||||
let dir = font_dir("reload-removed");
|
||||
let removed = dir.join("Removed.ttf");
|
||||
write_font(&removed, &crate::testing::font_with("DC Test Removed", &['a']).unwrap(), 10);
|
||||
let db = FontDb::with_font_dirs(vec![dir.clone()]);
|
||||
assert_eq!(db.face("DC Test Removed", "Regular").family, "DC Test Removed");
|
||||
std::fs::remove_file(&removed).unwrap();
|
||||
write_font(&dir.join("Added.ttf"), &crate::testing::font_with("DC Test Added Later", &['a']).unwrap(), 10);
|
||||
assert_eq!(db.reload_fonts().changed, [removed]);
|
||||
assert!(!db.has_family("DC Test Removed"));
|
||||
assert_eq!(db.face("DC Test Removed", "Regular").family, FALLBACK_FAMILY);
|
||||
assert!(db.has_family("DC Test Added Later"));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
@@ -580,6 +580,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Flattener Preview", "გაბრტყელების წინასწარი ნახვა"),
|
||||
("Float Panel", "მცურავი პანელი"),
|
||||
("Fonts in Document", "დოკუმენტის შრიფტები"),
|
||||
("Reload Fonts", "შრიფტების ხელახლა ჩატვირთვა"),
|
||||
("Footnotes", "სქოლიოები"),
|
||||
("Form Fields", "ფორმის ველები"),
|
||||
("Frame Fitting Options…", "ჩარჩოს ზომაზე მორგების პარამეტრები…"),
|
||||
|
||||
@@ -836,7 +836,7 @@ impl DesignApp {
|
||||
}
|
||||
}
|
||||
if r.is_ok()
|
||||
&& matches!(id, "font.replace" | "edit.undo" | "edit.redo")
|
||||
&& matches!(id, "font.replace" | "font.reload" | "edit.undo" | "edit.redo")
|
||||
&& let Some(dialog) = self.ui.dialog.as_mut().filter(|d| d.id == "findFont")
|
||||
{
|
||||
dialog.fields.remove("_fonts");
|
||||
|
||||
@@ -461,6 +461,7 @@ pub const MENUS: &[(&str, &[&str])] = &[
|
||||
"-",
|
||||
"cmd:type.createOutlines",
|
||||
"ui:app.findFontDialog",
|
||||
"cmd:font.reload",
|
||||
">Change Case",
|
||||
"cmd:type.changeCase|UPPERCASE|{\"case\": \"upper\"}",
|
||||
"cmd:type.changeCase|lowercase|{\"case\": \"lower\"}",
|
||||
@@ -5004,6 +5005,7 @@ mod tests {
|
||||
"-",
|
||||
"Create Outlines",
|
||||
"Find/Replace Font…",
|
||||
"Reload Fonts",
|
||||
"Change Case >",
|
||||
"-",
|
||||
"Type on a Path >",
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# Where DesignCraft falls short of InDesign
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** minor (gap 3: Package links are relative, #340; gap 27 closed: the loaded text cursor threads into a frame inside a group (#1075); .indd and .indt open through indd-utils; gap 0 shrunk: text threads by hand; threading gaps added; Optical kerning from outlines (#181); gap 6: macOS lists fonts that font services and managers activate; shared print spooler; image clipping paths from IDML; Tabs panel listed as present (#343); book chapter update safety; gap 16: Based on Parent leftovers; bounded control click/drag input; swatches on placed greyscale and bitmap images) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** minor (gap 6 shrunk: Type › Reload Fonts reads fonts installed, edited or removed while running (#328); gap 3: Package links are relative, #340; gap 27 closed: the loaded text cursor threads into a frame inside a group (#1075); .indd and .indt open through indd-utils; gap 0 shrunk: text threads by hand; threading gaps added; Optical kerning from outlines (#181); gap 6: macOS lists fonts that font services and managers activate; shared print spooler; image clipping paths from IDML; Tabs panel listed as present (#343); book chapter update safety; gap 16: Based on Parent leftovers; bounded control click/drag input; swatches on placed greyscale and bitmap images) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
|
||||
Every known shortfall against InDesign 2026, one entry each, ranked by how much it stops an
|
||||
InDesign professional from doing real work. This is the work list: agents pick from the top.
|
||||
@@ -92,7 +92,8 @@ Kind: **F** feature · **U** UI/UX · **T** typography · **FF** file format ·
|
||||
|
||||
### 6. Installed fonts not always found (F)
|
||||
- **Missing:** fonts in user font folders used for text (#161, #110, #107), fonts that font
|
||||
services and managers activate on Windows, reload when fonts change on disk (#328).
|
||||
services and managers activate on Windows, noticing by itself when fonts change on disk (#328:
|
||||
Type › Reload Fonts, `font.reload`, reads them again on request since 2026-10-16).
|
||||
- **Evidence:** on macOS the scan reads the font files CoreText has available, so fonts that
|
||||
Adobe Fonts and font managers activate are listed (#261, #327, 2026-10-11); not yet confirmed
|
||||
by the reporters.
|
||||
|
||||
@@ -286,7 +286,7 @@ It is evidence of breadth: each item exists. It is not a claim of InDesign-level
|
||||
- Ruler guides: drag out of the rulers (page guides, or spread guides on the pasteboard), drag to move, drop on a ruler to delete; Layout › Create Guides (rows/columns with gutters, fit to margins or page); Delete All Guides on Spread; `guide.*` commands.
|
||||
- Links panel: every placed graphic with status (OK / Modified / Missing / Embedded, checked against the file on disk), page and effective PPI (low resolution flagged); Relink…, Go To, Update, Embed; `links.*` commands.
|
||||
- Linked text files: `file.place {link: true}` or Preferences › File Handling › Create Links When Placing Text Files keeps a placed .docx/.rtf/.txt story linked to its file (path, size and hash, and the style map and conflict option used). The Links panel shows it OK / Modified / Missing; Update imports the file again into the same frames and thread with the saved style map, replacing edits made in the layout (the Links panel and menu ask first; the `links.update` command updates and returns a warning; undoable); Relink, Go To and Unlink. Saved in the native format; IDML exports the text unlinked.
|
||||
- Missing fonts: highlighted pink on screen (never in output), listed by Preflight; Type › Find/Replace Font (fonts in the document, missing first; replace in text, cells, footnotes and styles); `font.list` / `font.replace`.
|
||||
- Missing fonts: highlighted pink on screen (never in output), listed by Preflight; Type › Find/Replace Font (fonts in the document, missing first; replace in text, cells, footnotes and styles); `font.list` / `font.replace`. Type › Reload Fonts (`font.reload`) scans the font folders again and reads again the installed font files edited or removed since they were read, then recomposes the open documents: a font installed or edited while DesignCraft runs needs no restart (#328, #547). InDesign notices such changes by itself.
|
||||
- Missing glyphs, as in InDesign: a character the applied font lacks is drawn as that font's missing-glyph box (its .notdef; a drawn box when the font's is empty), on screen and in PDF (as a path, so PDF/A and PDF/UA exports take it), and Preflight lists the characters by font. Preferences › Composition › Draw Missing Glyphs from Fallback Fonts (`document.preferences {glyphFallback}`, stored with the document) draws them from other fonts instead; it is off in new documents and IDML imports, on in documents saved before it existed. A missing font's substitute always uses fallback fonts.
|
||||
- Damage-region repaint: after an edit the canvas re-renders only the frames whose composed text (or the items that) changed and patches its texture — pixel-identical to a full render; typing in a frame repaints in under a millisecond instead of a full-viewport render (22 ms measured on the sample).
|
||||
- Native engine saves, Save a Copy, snippets, books, libraries, swatches and engine/MCP exports use same-folder atomic replacement through craft-libs/craft-atomic, as do desktop Services writes and CLI raster exports. Failed writes before replacement retain the original, and already-open readers retain the old bytes. On Unix a new file gets the permissions ordinary file creation gives it (the umask applies) and a replaced file keeps its permissions. Parent-folder flushing is best effort, so this is not a power-loss durability guarantee.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Typography and composition parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Optical kerning from outlines (#181); macOS lists fonts that font services and managers activate; Justification dialog for the selected paragraphs; anchored objects wrap text; Tabs panel listed as present (#343); character stroke behind fill) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** minor (Type › Reload Fonts reads fonts installed, edited or removed since start-up (#328, #547); Optical kerning from outlines (#181); macOS lists fonts that font services and managers activate; Justification dialog for the selected paragraphs; anchored objects wrap text; Tabs panel listed as present (#343); character stroke behind fill) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
|
||||
Typesetting is what InDesign is bought for. A feature can exist and still set text differently
|
||||
from InDesign: different line breaks, different page count, overset where InDesign fits. This
|
||||
@@ -67,7 +67,7 @@ Fidelity: **unmeasured** unless stated.
|
||||
| CJK composition | mojikumi, kinsoku, aki, tsume, ruby, kenten, warichu, frame grid | most; mojikumi tables kept but not applied; ruby group-only; no frame grid | ~ | cjk.md, cjk-typography.md |
|
||||
| Arabic / Hebrew | kashida, digits, diacritic positioning, RTL stories and tables | ✓ | ~ | vendor justification presets unsupported; list bullets and multi-column RTL (#100) |
|
||||
| Missing glyphs and fonts | pink highlight, substitution | ✓ | ✓ | |
|
||||
| Installed fonts | every activated font | system font folders; on macOS also fonts that font managers and Adobe Fonts activate; user folders not always found | ~ | #161, #110, #107, #327, #261 |
|
||||
| Installed fonts | every activated font, picked up as installed or changed | system font folders; on macOS also fonts that font managers and Adobe Fonts activate; user folders not always found; fonts installed, edited or removed while running found by Type › Reload Fonts, not automatically | ~ | #161, #110, #107, #327, #261, #328 |
|
||||
| Composition highlights | H&J, keeps, custom tracking/kerning, substituted fonts | ✓ | ✓ | |
|
||||
|
||||
Measured from the table above (2026-10-10): 34 rows, 18 ✓, 16 ~, 0 ✗, scoring ✓ = 1 and ~ = 0.5:
|
||||
|
||||
Reference in New Issue
Block a user