diff --git a/crates/compose/src/lib.rs b/crates/compose/src/lib.rs index bc4f99232..23971ec30 100644 --- a/crates/compose/src/lib.rs +++ b/crates/compose/src/lib.rs @@ -704,23 +704,27 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C let drop_cap = split_drop_cap(db, &mut sp.glyphs, drop_end, pp.drop_cap_lines, base_leading, base_cap, drop_grid, drop_align); // List labels take the default super/subscript settings. let label_env = shape::TypeEnv { adv: Default::default(), ..env }; - match pp.list_type { + let list_label = match pp.list_type { designcraft_doc::ListType::Numbers if !pp.list_name.is_empty() => { // A named list: carries on past other paragraphs (and from earlier stories). let n = named_numbers.get(pi).copied().flatten().unwrap_or(1); - let label = format!("{}.{}", pp.number_style.format(n), pp.list_separator); - prepend_label(db, &mut sp.glyphs, &label, prange.start, &base_chars, label_env, &mut table); + Some(format!("{}.{}", pp.number_style.format(n), pp.list_separator)) } designcraft_doc::ListType::Numbers => { list_counter = pp.start_at.map_or(list_counter + 1, |s| s.max(1)); - let label = format!("{}.{}", pp.number_style.format(list_counter), pp.list_separator); - prepend_label(db, &mut sp.glyphs, &label, prange.start, &base_chars, label_env, &mut table); + Some(format!("{}.{}", pp.number_style.format(list_counter), pp.list_separator)) } - designcraft_doc::ListType::Bullets => { - let label = format!("{}{}", pp.bullet_char, pp.list_separator); - prepend_label(db, &mut sp.glyphs, &label, prange.start, &base_chars, label_env, &mut table); + designcraft_doc::ListType::Bullets => Some(format!("{}{}", pp.bullet_char, pp.list_separator)), + designcraft_doc::ListType::None => { + list_counter = 0; + None + } + }; + if let Some(label) = list_label { + let n = prepend_label(db, &mut sp.glyphs, &label, prange.start, &base_chars, label_env, &mut table); + for g in sp.glyphs.iter_mut().take(n) { + g.list_label = true; } - designcraft_doc::ListType::None => list_counter = 0, } if pi == 0 && let Some(label) = &opts.label @@ -1564,8 +1568,9 @@ fn line_metrics( base: &designcraft_doc::CharProps, ) -> (f64, Tops, f64, f64) { let src: &[Glyph] = if line.is_empty() { all.get(s..(s + 1).min(all.len())).unwrap_or(&[]) } else { line }; + let face = || db.face(&base.font_family, &base.font_style); if src.is_empty() { - let face = db.face(&base.font_family, &base.font_style); + let face = face(); let k = base_size / face.upem; let tops = Tops { typo_ascent: face.typo_ascent * k, cap: face.cap_height * k, xh: face.x_height * k }; return (face.ascent * k, tops, face.descent * k, base_leading); @@ -1575,16 +1580,26 @@ fn line_metrics( let mut desc: f64 = 0.0; let mut lead: f64 = 0.0; let reference = src.iter().max_by(|a, b| a.size.total_cmp(&b.size)); + let mut own_text = false; for g in src { let shift = g.shift + reference.map_or(0.0, |r| cjk_alignment_shift(g, r)); let up = shift.max(0.0); asc = asc.max(g.ascent + up); - tops.typo_ascent = tops.typo_ascent.max(g.typo_ascent + up); + // A list's bullet or number doesn't raise the Ascent offset: only the line's own text does. + if !g.list_label { + own_text = true; + tops.typo_ascent = tops.typo_ascent.max(g.typo_ascent + up); + } tops.cap = tops.cap.max(g.cap + up); tops.xh = tops.xh.max(g.xh + up); desc = desc.max(g.descent - shift.min(0.0)); lead = lead.max(g.leading); } + if !own_text { + // Only a label (an empty list paragraph): as an empty paragraph. + let face = face(); + tops.typo_ascent = face.typo_ascent * base_size / face.upem; + } (asc, tops, desc, lead) } @@ -2307,6 +2322,7 @@ fn place_drop_cap(dc: &DropCap, at: f64, rtl: bool) -> Vec { out } +/// Put generated `label` text before the paragraph's glyphs; returns how many glyphs it added. fn prepend_label( db: &ScopedFonts<'_>, glyphs: &mut Vec, @@ -2315,7 +2331,7 @@ fn prepend_label( base: &designcraft_doc::CharProps, env: shape::TypeEnv, table: &mut StyleTable<'_>, -) { +) -> usize { // Shape the label as a tiny standalone story so it uses the paragraph's base character style. let mut tmp = Story::new(StoryId(0)); tmp.insert(0, label); @@ -2330,8 +2346,10 @@ fn prepend_label( g }) .collect(); + let n = pre.len(); pre.append(glyphs); *glyphs = pre; + n } type LimitsKey = (usize, usize, usize, bool); diff --git a/crates/compose/src/shape.rs b/crates/compose/src/shape.rs index 84f17821f..b354da9a7 100644 --- a/crates/compose/src/shape.rs +++ b/crates/compose/src/shape.rs @@ -28,7 +28,8 @@ pub struct Glyph { /// Baseline shift (positive = up), including super/subscript. pub shift: f64, pub ascent: f64, - /// The font's typographic ascender, scaled like `ascent`: the Ascent first baseline offset. + /// The font's typographic ascender at the point size (vertical scale doesn't apply): the Ascent + /// first baseline offset. pub typo_ascent: f64, pub descent: f64, /// The leading this character asks for (absolute, or auto = size × auto %). @@ -68,6 +69,9 @@ pub struct Glyph { /// Upright in a vertical frame: shaped top to bottom (`adv` is its vertical advance) and hung /// from its vertical origin on the line's centre (see [`crate::PlacedGlyph::vertical_xf`]). pub upright: bool, + /// Part of the bullet or number the paragraph's list adds: not the line's own text, so it + /// doesn't raise the Ascent first baseline. + pub list_label: bool, } impl Glyph { @@ -872,7 +876,7 @@ fn control_glyph(face: &Arc, p: &CharProps, auto_leading: TypeEnv, sty sy: k * p.v_scale, shift, ascent, - typo_ascent: face.typo_ascent * k * p.v_scale, + typo_ascent: face.typo_ascent * k, descent, leading, cap, @@ -898,6 +902,7 @@ fn control_glyph(face: &Arc, p: &CharProps, auto_leading: TypeEnv, sty shaping_rtl: false, lang: designcraft_doc::language_tag(&p.language), upright: false, + list_label: false, } } @@ -991,7 +996,7 @@ fn shape_segment( sy: k * p.v_scale, shift, ascent, - typo_ascent: face.typo_ascent * k * p.v_scale, + typo_ascent: face.typo_ascent * k, descent, leading, cap, @@ -1021,6 +1026,7 @@ fn shape_segment( shaping_rtl: sg.rtl, lang, upright: up, + list_label: false, }); } } diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs index ec0744df3..a9ebce96c 100644 --- a/crates/compose/src/tests.rs +++ b/crates/compose/src/tests.rs @@ -1395,18 +1395,6 @@ fn ascent_first_baseline_uses_the_typographic_ascender() { assert!(close(at(FirstBaseline::XHeight, 0.0), top + 0.486 * 20.0)); assert!(close(at(FirstBaseline::Leading, 0.0), top + 24.0)); assert!(close(at(FirstBaseline::Fixed, 0.0), top)); - // Vertical scale scales it like the ascent. - let (mut d, sid, fid) = doc_with("Hxg", Rect::new(36.0, 36.0, 300.0, 300.0), ParaAttrs::default()); - d.story_mut(sid).unwrap().format_chars(0..3, |f| { - f.over.font_family = Some("TypoAscent 07".into()); - f.over.size = Some(20.0); - f.over.v_scale = Some(1.5); - }); - d.item_mut(fid).unwrap().text_frame_mut().unwrap().options.inset = [4.0, 0.0, 0.0, 0.0]; - let l = &compose_story(&d, sid, &ComposeOptions::default()).frames[0].lines[0]; - assert!(close(l.baseline, top + 21.0), "{}", l.baseline); - // The line box keeps the font's ascent. - assert!(close(l.ascent, 30.0), "{}", l.ascent); // A mixed first line takes its tallest typographic ascender: 0.7 × 20 pt beats 1 em × 10 pt … let (mut d, sid, fid) = doc_with("Hxg Hxg", Rect::new(36.0, 36.0, 300.0, 300.0), ParaAttrs::default()); let st = d.story_mut(sid).unwrap(); @@ -1451,6 +1439,102 @@ fn vertical_frames_keep_the_ascent_first_baseline() { assert!((b - 20.0).abs() < 0.01, "{b}"); } +#[test] +fn a_visible_frame_stroke_insets_the_text() { + use designcraft_doc::{Stroke, StrokeAlign}; + let line = |stroke: Option<(StrokeAlign, &str)>| { + let (mut d, sid, fid) = doc_with("Hxg", Rect::new(36.0, 36.0, 300.0, 300.0), ParaAttrs::default()); + let it = d.item_mut(fid).unwrap(); + if let Some((align, swatch)) = stroke { + it.stroke = Stroke { weight: 4.0, align, swatch: swatch.into(), ..Stroke::default() }; + } + it.text_frame_mut().unwrap().options.inset = [4.0; 4]; + let l = &compose_story(&d, sid, &ComposeOptions::default()).frames[0].lines[0]; + (l.baseline, l.x0, l.x1) + }; + let black = designcraft_color::swatch::BLACK; + let (b, x0, x1) = line(None); + // The part of the 4 pt stroke inside the frame: half of it centred, all of it inside, none + // outside or without a colour; on the left and right as on the top. + for (stroke, d) in [ + ((StrokeAlign::Center, black), 2.0), + ((StrokeAlign::Inside, black), 4.0), + ((StrokeAlign::Outside, black), 0.0), + ((StrokeAlign::Inside, designcraft_color::swatch::NONE), 0.0), + ] { + let (sb, sx0, sx1) = line(Some(stroke)); + assert!((sb - (b + d)).abs() < 1e-6, "{stroke:?}: baseline {sb} vs {b}"); + assert!((sx0 - (x0 + d)).abs() < 1e-6 && (sx1 - (x1 - d)).abs() < 1e-6, "{stroke:?}: {sx0}..{sx1} vs {x0}..{x1}"); + } +} + +#[test] +fn list_labels_do_not_raise_the_ascent_first_baseline() { + use designcraft_doc::{CharAttrs, ListType}; + // The paragraph's own character size (the label's) is 40 pt; its text is 10 pt (Source Sans 3: + // typo ascender 1 em). + let line = |text: &str, list: ListType| { + let (mut d, sid, _) = doc_with(text, Rect::new(36.0, 36.0, 300.0, 300.0), ParaAttrs { list_type: Some(list), ..Default::default() }); + let st = d.story_mut(sid).unwrap(); + for p in &mut st.paras { + p.chars = CharAttrs { font_family: Some("Source Sans 3".into()), size: Some(40.0), ..Default::default() }; + } + st.format_chars(0..text.len(), |f| f.over.size = Some(10.0)); + let l = compose_story(&d, sid, &ComposeOptions::default()).frames[0].lines[0].clone(); + (l.baseline, l.ascent, l.leading) + }; + let (b, asc, lead) = line("Hxg Hxg", ListType::None); + // 1 em of the 10 pt text below the frame top. + assert!((b - 46.0).abs() < 0.01, "{b}"); + for list in [ListType::Bullets, ListType::Numbers] { + let (lb, lasc, llead) = line("Hxg Hxg", list); + assert!((lb - b).abs() < 1e-6, "{list:?}: {lb} vs {b}"); + // The 40 pt label still sets the line's ascent and leading. + assert!(lasc > asc + 20.0 && (llead - 48.0).abs() < 1e-6 && (lead - 12.0).abs() < 1e-6, "{list:?}: {lasc} {asc} {llead} {lead}"); + } + // An empty bulleted paragraph sits like an empty paragraph. + let (eb, _, _) = line("", ListType::None); + let (ebl, _, _) = line("", ListType::Bullets); + assert!((ebl - eb).abs() < 1e-6, "{ebl} vs {eb}"); +} + +#[test] +fn spaces_count_toward_the_ascent_first_baseline() { + // 10 pt text with a 40 pt space between its words (Source Sans 3: typo ascender 1 em). + let (mut d, sid, _) = doc_with("Hxg Hxg", Rect::new(36.0, 36.0, 300.0, 300.0), ParaAttrs::default()); + let st = d.story_mut(sid).unwrap(); + st.format_chars(0..7, |f| { + f.over.font_family = Some("Source Sans 3".into()); + f.over.size = Some(10.0); + }); + st.format_chars(3..4, |f| f.over.size = Some(40.0)); + let l = &compose_story(&d, sid, &ComposeOptions::default()).frames[0].lines[0]; + assert!((l.baseline - (36.0 + 40.0)).abs() < 0.01, "{}", l.baseline); +} + +#[test] +fn vertical_scale_does_not_move_the_ascent_first_baseline() { + let db = designcraft_fonts::FontDb::global(); + db.add_font(typo_test_font("TypoAscentVSc", Some(700))); + let line = |v_scale: f64| { + let (mut d, sid, fid) = doc_with("Hxg", Rect::new(36.0, 36.0, 300.0, 300.0), ParaAttrs::default()); + d.story_mut(sid).unwrap().format_chars(0..3, |f| { + f.over.font_family = Some("TypoAscentVSc".into()); + f.over.size = Some(20.0); + f.over.v_scale = Some(v_scale); + }); + d.item_mut(fid).unwrap().text_frame_mut().unwrap().options.inset = [4.0, 0.0, 0.0, 0.0]; + let l = &compose_story(&d, sid, &ComposeOptions::default()).frames[0].lines[0]; + (l.baseline, l.ascent) + }; + let ((b100, a100), (b200, a200)) = (line(1.0), line(2.0)); + // The typo ascender of the nominal size (0.7 × 20 pt) at 100 % and at 200 %. + assert!((b100 - (36.0 + 4.0 + 14.0)).abs() < 0.01, "{b100}"); + assert!((b200 - b100).abs() < 0.01, "{b200} vs {b100}"); + // The line box keeps the scaled ascent. + assert!((a100 - 20.0).abs() < 0.01 && (a200 - 40.0).abs() < 0.01, "{a100} {a200}"); +} + #[test] fn cap_height_and_x_height_first_baselines_use_the_fonts_cap_and_x_heights() { use designcraft_doc::FirstBaseline; @@ -2758,3 +2842,29 @@ fn vertical_lines_fit_the_em_box() { assert!((l.baseline - col.y0 - ascent).abs() < 1e-9, "{family}: {} {}", l.baseline, col.y0); } } + +/// A drop cap set in one of the document's own fonts measures its side bearing in that font. +#[test] +fn drop_cap_in_a_document_font_aligns_its_left_edge() { + use designcraft_geom::Shape as _; + const FAMILY: &str = "DocFont Drop Cap"; + let text = format!("Once {LOREM}"); + let mut chars: Vec = text.chars().collect(); + chars.sort_unstable(); + chars.dedup(); + let folder = std::env::temp_dir().join(format!("dc-compose-dropcap-docfont-{}", std::process::id())); + std::fs::create_dir_all(&folder).unwrap(); + std::fs::write(folder.join("dropcap.ttf"), designcraft_fonts::testing::font_with_glyph(FAMILY, &chars, 'O').unwrap()).unwrap(); + let scope = designcraft_fonts::FontDb::global().load_document_fonts(&folder).scope; + let para = ParaAttrs { left_indent: Some(10.0), ..drop_cap(2, 1) }; + let (mut d, sid, _) = doc_with(&text, Rect::new(0.0, 0.0, 300.0, 1000.0), para); + d.font_scope = scope; + d.story_mut(sid).unwrap().format_chars(0..text.len(), |f| f.over.font_family = Some(FAMILY.into())); + let cs = compose_story(&d, sid, &ComposeOptions::default()); + let dc = &all_lines(&cs)[0].glyphs[0]; + assert_eq!(dc.face.family, FAMILY); + let ink_left = dc.x + designcraft_fonts::FontDb::global().outline(dc.face.get(), dc.gid).bounding_box().x0 * dc.sx; + assert!(ink_left - dc.x > 0.5, "the O has a visible side bearing"); + assert!((ink_left - 10.0).abs() < 1e-6, "ink at {ink_left}"); + let _ = std::fs::remove_dir_all(&folder); +} diff --git a/crates/doc/src/item.rs b/crates/doc/src/item.rs index b06f4fe6f..dd690b093 100644 --- a/crates/doc/src/item.rs +++ b/crates/doc/src/item.rs @@ -1141,13 +1141,29 @@ impl Item { b.inflate(w, w) } - /// The frame's text area (inner space) after inset. + /// The text frame's text area (inner space): inside the part of a visible stroke that lies + /// within the frame, then the inset, on each edge. pub fn text_area(&self) -> Rect { let r = self.inner_bounds(); - let inset = self.text_frame().map(|t| t.options.inset).unwrap_or([0.0; 4]); + let s = self.stroke_inside(); + let inset = self.text_frame().map(|t| t.options.inset).unwrap_or([0.0; 4]).map(|v| v + s); Rect::new(r.x0 + inset[1], r.y0 + inset[0], (r.x1 - inset[3]).max(r.x0 + inset[1]), (r.y1 - inset[2]).max(r.y0 + inset[0])) } + /// How far a visible stroke reaches inside the path: half its weight centred, all of it inside, + /// none outside. + fn stroke_inside(&self) -> f64 { + let w = self.stroke.weight; + if self.stroke.is_none() || !w.is_finite() { + return 0.0; + } + match self.stroke.align { + StrokeAlign::Center => w / 2.0, + StrokeAlign::Inside => w, + StrokeAlign::Outside => 0.0, + } + } + /// Spread-space centre. pub fn center(&self) -> Point { self.bounds().center() @@ -1192,6 +1208,32 @@ mod tests { assert_eq!(it.visible_bounds(), Rect::new(8.0, 18.0, 112.0, 72.0)); } + #[test] + fn text_area_starts_inside_the_stroke() { + let mut it = Item::new(ItemId(1), LayerId(1), Shape::Rectangle, shapes::rectangle(Rect::new(0.0, 0.0, 100.0, 50.0))); + it.content = Content::Text(TextFrame { story: StoryId(2), options: TextFrameOptions { inset: [5.0, 6.0, 7.0, 8.0], ..Default::default() } }); + let unstroked = Rect::new(6.0, 5.0, 92.0, 43.0); + let stroked = |align: StrokeAlign, swatch: &str| { + let mut it = it.clone(); + it.stroke = Stroke { weight: 4.0, align, swatch: swatch.into(), ..Stroke::default() }; + it.text_area() + }; + let black = designcraft_color::swatch::BLACK; + // The part of the stroke inside the frame, on every edge, then the inset. + assert_eq!(stroked(StrokeAlign::Center, black), Rect::new(8.0, 7.0, 90.0, 41.0)); + assert_eq!(stroked(StrokeAlign::Inside, black), Rect::new(10.0, 9.0, 88.0, 39.0)); + assert_eq!(stroked(StrokeAlign::Outside, black), unstroked); + // A stroke without a colour doesn't count. + assert_eq!(stroked(StrokeAlign::Inside, designcraft_color::swatch::NONE), unstroked); + // Nor does a weight that isn't a number. + it.stroke = Stroke { weight: f64::NAN, ..Stroke::default() }; + assert_eq!(it.text_area(), unstroked); + // A stroke wider than the frame leaves no room for text. + it.stroke = Stroke { weight: 200.0, align: StrokeAlign::Inside, ..Stroke::default() }; + let a = it.text_area(); + assert!(a.width() == 0.0 && a.height() == 0.0, "{a:?}"); + } + #[test] fn group_bounds_union_children() { let a = Item::new(ItemId(1), LayerId(1), Shape::Rectangle, shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0))); diff --git a/crates/doc/src/tests.rs b/crates/doc/src/tests.rs index fca520ed8..8af281d3b 100644 --- a/crates/doc/src/tests.rs +++ b/crates/doc/src/tests.rs @@ -134,6 +134,34 @@ fn removing_a_frame_from_its_thread_keeps_the_text_in_the_others() { assert!(d.remove_from_thread(b).is_err()); } +#[test] +fn a_frame_removed_from_a_vertical_thread_stays_vertical() { + let mut d = doc(); + let lid = d.default_layer(); + let (a, sa) = d.add_text_frame(SpreadRef::Doc(0), Rect::new(10.0, 10.0, 100.0, 100.0), lid, "縦書き", ParaFormat::default()).unwrap(); + let (b, _) = d.add_text_frame(SpreadRef::Doc(0), Rect::new(110.0, 10.0, 200.0, 100.0), lid, "", ParaFormat::default()).unwrap(); + d.story_mut(sa).unwrap().vertical = true; + d.thread(a, b).unwrap(); + d.remove_from_thread(b).unwrap(); + d.check().unwrap(); + assert!(d.frame_vertical(d.item(a).unwrap())); + assert!(d.frame_vertical(d.item(b).unwrap()), "the removed frame keeps the story's direction"); +} + +#[test] +fn a_frame_removed_from_a_right_to_left_thread_keeps_its_column_order() { + let mut d = doc(); + let lid = d.default_layer(); + let (a, sa) = d.add_text_frame(SpreadRef::Doc(0), Rect::new(10.0, 10.0, 100.0, 100.0), lid, "نص", ParaFormat::default()).unwrap(); + let (b, _) = d.add_text_frame(SpreadRef::Doc(0), Rect::new(110.0, 10.0, 200.0, 100.0), lid, "", ParaFormat::default()).unwrap(); + d.story_mut(sa).unwrap().direction = crate::TextDirection::RightToLeft; + d.thread(a, b).unwrap(); + d.remove_from_thread(b).unwrap(); + d.check().unwrap(); + let own = d.item(b).unwrap().text_frame().unwrap().story; + assert_eq!(d.story(own).unwrap().direction, crate::TextDirection::RightToLeft); +} + #[test] fn frames_keep_their_story_direction_through_threading() { let mut d = doc(); diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index e40000095..68189326c 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -900,4 +900,24 @@ mod document_fonts_tests { assert!(glyph_faces(&s).iter().all(|(_, f)| f.family == FAMILY)); let _ = std::fs::remove_dir_all(&dir); } + + #[test] + fn bold_and_italic_link_the_document_fonts_styles() { + const FAMILY: &str = "DocFont Linked"; + let dir = temp_dir("linked"); + std::fs::write(dir.join(DOCUMENT_FONTS_FOLDER).join("linked.ttf"), font_with(FAMILY, &['H', 'e', 'l', 'o']).unwrap()).unwrap(); + let path = dir.join("Linked.designcraft"); + write_document(&path, FAMILY); + + let mut s = Session::new(); + open(&mut s, &path); + let sid = *s.doc().unwrap().doc.stories.keys().next().unwrap(); + s.execute("text.select", &json!({"story": sid.0, "anchor": 0, "focus": 0})).unwrap(); + // The family is the document's alone: its Regular is the style without bold or italic. + let r = s.execute("type.bold", &json!({"on": false})).unwrap(); + assert_eq!(r["fontStyle"], "Regular", "{r}"); + let r = s.execute("type.italic", &json!({"on": false})).unwrap(); + assert_eq!(r["fontStyle"], "Regular", "{r}"); + let _ = std::fs::remove_dir_all(&dir); + } } diff --git a/crates/idml/src/export.rs b/crates/idml/src/export.rs index c62674409..fcd688284 100644 --- a/crates/idml/src/export.rs +++ b/crates/idml/src/export.rs @@ -1360,9 +1360,6 @@ impl<'a> Ex<'a> { if let Some(v) = a.single_word_justify { el.set("SingleWordJustification", names::align_out(v)); } - if let Some(v) = a.kashidas { - el.set("Kashidas", names::kashidas_out(v)); - } n!(keep_with_next, "KeepWithNext"); b!(keep_lines_together, "KeepLinesTogether"); b!(keep_all_lines, "KeepAllLinesTogether"); diff --git a/crates/idml/src/import.rs b/crates/idml/src/import.rs index 38259f69b..440047054 100644 --- a/crates/idml/src/import.rs +++ b/crates/idml/src/import.rs @@ -1286,7 +1286,6 @@ impl<'r> Importer<'r> { a.glyph_scale_max = frac("MaximumGlyphScaling"); a.auto_leading = frac("AutoLeading"); a.single_word_justify = e.prop("SingleWordJustification").and_then(|v| names::align_in(v.trim())); - a.kashidas = e.prop("Kashidas").and_then(|v| names::kashidas_in(v.trim())); a.keep_with_next = u("KeepWithNext"); a.keep_lines_together = e.boolean("KeepLinesTogether"); a.keep_all_lines = e.boolean("KeepAllLinesTogether"); diff --git a/crates/idml/src/names.rs b/crates/idml/src/names.rs index 9a1c4ca1b..daa57d5fb 100644 --- a/crates/idml/src/names.rs +++ b/crates/idml/src/names.rs @@ -447,13 +447,6 @@ pub fn auto_size_in(s: &str) -> AutoSize { .find(|m| auto_size_out(*m) == s) .unwrap_or(AutoSize::Off) } -/// Insert Kashidas (`Kashidas`): InDesign's default, or off. -pub fn kashidas_out(on: bool) -> &'static str { - if on { "DefaultKashidas" } else { "KashidasOff" } -} -pub fn kashidas_in(s: &str) -> Option { - [true, false].into_iter().find(|on| kashidas_out(*on) == s) -} /// Auto-size reference points in our 0..9 order (row-major, top-left first). pub const REF_POINTS: [&str; 9] = [ diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index 3d5cd0223..062d74b20 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -1289,8 +1289,7 @@ pub fn character_panel(app: &mut DesignApp, ui: &mut egui::Ui) { }); ui.horizontal(|ui| { // Bold and Italic pick the family's own styles (style linking). - let (weight, italic) = - designcraft_fonts::FontDb::global().traits_of(c["fontFamily"].as_str().unwrap_or_default(), c["fontStyle"].as_str().unwrap_or_default()); + let (weight, italic) = super::fonts(app).traits_of(c["fontFamily"].as_str().unwrap_or_default(), c["fontStyle"].as_str().unwrap_or_default()); let tip = |app: &DesignApp, name: &str, id: &str| { let name = crate::i18n::tr(&app.ui.language, name); match crate::menus::shortcut_of(app, id) {