mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-09 03:51:38 +00:00
Compose: apply the CJK leading model and character alignment only under the Japanese composers
Every IDML root style carries LeadingModelAkiBelow and AlignEmCenter, but only the Japanese composers (HL Composer J, HL Single J) use them; the Adobe and World-Ready composers set lines baseline to baseline with every character on the Roman baseline. Applying them everywhere moved lines down after a size change and lowered smaller-em glyphs, pushing the last lines of tight frames (and whole keep-together paragraphs) into overset. Composer gains Japanese and JapaneseSingleLine, read from and written to IDML; the composer choice in the UI keeps the kind.
This commit is contained in:
1 parent
92649ffdb0
commit
5dc8383a50
9 files changed
+120
-30
No files matched your search
@@ -29,7 +29,7 @@ use std::rc::Rc;
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use designcraft_doc::{
|
use designcraft_doc::{
|
||||||
Align, Composer, Document, FirstBaseline, GridAlign, ItemId, ParaProps, SpanColumns, StartParagraph, Story, StoryId, TabAlign, TextFrameOptions,
|
Align, Document, FirstBaseline, GridAlign, ItemId, ParaProps, SpanColumns, StartParagraph, Story, StoryId, TabAlign, TextFrameOptions,
|
||||||
VerticalJustification, WrapMode, story,
|
VerticalJustification, WrapMode, story,
|
||||||
};
|
};
|
||||||
use designcraft_fonts::{FontDb, ScopedFonts};
|
use designcraft_fonts::{FontDb, ScopedFonts};
|
||||||
@@ -681,7 +681,7 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C
|
|||||||
let mut gl = sp.glyphs.clone();
|
let mut gl = sp.glyphs.clone();
|
||||||
apply_desired_spacing(&mut gl, &pp);
|
apply_desired_spacing(&mut gl, &pp);
|
||||||
let hy = hyphenation_points(&story.text, &gl, &pp, &hyph_exceptions, &foreign_ranges(doc, story, prange.clone(), &base_chars));
|
let hy = hyphenation_points(&story.text, &gl, &pp, &hyph_exceptions, &foreign_ranges(doc, story, prange.clone(), &base_chars));
|
||||||
let breaks = if pp.composer == Composer::SingleLine || gl.iter().any(|g| g.ch == '\t') || gl.len() > 4000 {
|
let breaks = if pp.composer.single_line() || gl.iter().any(|g| g.ch == '\t') || gl.len() > 4000 {
|
||||||
let tab = |j: usize, x: f64, i: usize| {
|
let tab = |j: usize, x: f64, i: usize| {
|
||||||
let ind = pp.left_indent + if j == 0 { pp.first_line_indent } else { 0.0 };
|
let ind = pp.left_indent + if j == 0 { pp.first_line_indent } else { 0.0 };
|
||||||
tab_advance(&pp.tabs, pp.left_indent, ind + x, gl.get(i + 1..).unwrap_or_default()).0
|
tab_advance(&pp.tabs, pp.left_indent, ind + x, gl.get(i + 1..).unwrap_or_default()).0
|
||||||
@@ -755,6 +755,14 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C
|
|||||||
prepend_label(db, &mut sp.glyphs, label, prange.start, &base_chars, label_env, &mut table);
|
prepend_label(db, &mut sp.glyphs, label, prange.start, &base_chars, label_env, &mut table);
|
||||||
}
|
}
|
||||||
let mut glyphs = sp.glyphs;
|
let mut glyphs = sp.glyphs;
|
||||||
|
if !pp.composer.japanese() {
|
||||||
|
// Every IDML's root style carries a leading model and character alignment, which only
|
||||||
|
// the Japanese composers use.
|
||||||
|
for g in &mut glyphs {
|
||||||
|
g.leading_model = designcraft_doc::cjk::LeadingModel::Roman;
|
||||||
|
g.character_alignment = designcraft_doc::cjk::CharacterAlignment::Baseline;
|
||||||
|
}
|
||||||
|
}
|
||||||
let bidi_text = bidi::paragraph_text(&mut glyphs, &story.text);
|
let bidi_text = bidi::paragraph_text(&mut glyphs, &story.text);
|
||||||
let bidi_info = unicode_bidi::BidiInfo::new(
|
let bidi_info = unicode_bidi::BidiInfo::new(
|
||||||
&bidi_text,
|
&bidi_text,
|
||||||
@@ -896,7 +904,7 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C
|
|||||||
};
|
};
|
||||||
let rest = &glyphs[g0..];
|
let rest = &glyphs[g0..];
|
||||||
let rest_h = &hyph_after[g0..];
|
let rest_h = &hyph_after[g0..];
|
||||||
let breaks: Vec<Break> = if pp.composer == Composer::SingleLine || has_tabs || rest.len() > 4000 {
|
let breaks: Vec<Break> = if pp.composer.single_line() || has_tabs || rest.len() > 4000 {
|
||||||
// Where each line starts relative to the tab origin, as `layout_line` places it.
|
// Where each line starts relative to the tab origin, as `layout_line` places it.
|
||||||
let tab = |j: usize, x: f64, i: usize| {
|
let tab = |j: usize, x: f64, i: usize| {
|
||||||
let x0 = slots.get(j).map_or(col.x0, |s| s.0);
|
let x0 = slots.get(j).map_or(col.x0, |s| s.0);
|
||||||
|
|||||||
+30
-15
@@ -1,5 +1,5 @@
|
|||||||
use designcraft_doc::build::NewDocument;
|
use designcraft_doc::build::NewDocument;
|
||||||
use designcraft_doc::{Align, Document, ParaAttrs, ParaFormat, SpreadRef};
|
use designcraft_doc::{Align, Composer, Document, ParaAttrs, ParaFormat, SpreadRef};
|
||||||
use designcraft_geom::Rect;
|
use designcraft_geom::Rect;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -1469,38 +1469,53 @@ fn cjk_custom_kinsoku_disables_selected_breaks() {
|
|||||||
assert_eq!(all_lines(&cs).len(), 1);
|
assert_eq!(all_lines(&cs).len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn japanese() -> ParaAttrs {
|
||||||
|
ParaAttrs { composer: Some(Composer::Japanese), ..Default::default() }
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cjk_center_leading_measures_em_centers_across_different_sizes() {
|
fn cjk_center_leading_measures_em_centers_across_different_sizes() {
|
||||||
let (mut d, sid, _) = doc_with("A\nB", Rect::new(0.0, 0.0, 300.0, 200.0), ParaAttrs::default());
|
let lines_with = |para: ParaAttrs| {
|
||||||
d.story_mut(sid).unwrap().format_chars(0..3, |f| {
|
let (mut d, sid, _) = doc_with("A\nB", Rect::new(0.0, 0.0, 300.0, 200.0), para);
|
||||||
f.over.leading = Some(designcraft_doc::Leading::Points(30.0));
|
d.story_mut(sid).unwrap().format_chars(0..3, |f| {
|
||||||
f.over.leading_model = Some(designcraft_doc::cjk::LeadingModel::Center);
|
f.over.leading = Some(designcraft_doc::Leading::Points(30.0));
|
||||||
});
|
f.over.leading_model = Some(designcraft_doc::cjk::LeadingModel::Center);
|
||||||
d.story_mut(sid).unwrap().format_chars(0..1, |f| f.over.size = Some(24.0));
|
});
|
||||||
let cs = compose_story(&d, sid, &ComposeOptions::default());
|
d.story_mut(sid).unwrap().format_chars(0..1, |f| f.over.size = Some(24.0));
|
||||||
let lines = all_lines(&cs);
|
let cs = compose_story(&d, sid, &ComposeOptions::default());
|
||||||
|
all_lines(&cs).into_iter().cloned().collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
let lines = lines_with(japanese());
|
||||||
assert_eq!(lines.len(), 2);
|
assert_eq!(lines.len(), 2);
|
||||||
let center = |l: &Line| {
|
let center = |l: &Line| {
|
||||||
let g = &l.glyphs[0];
|
let g = &l.glyphs[0];
|
||||||
let (a, b) = g.face.vertical_metrics();
|
let (a, b) = g.face.vertical_metrics();
|
||||||
l.baseline + (b - a) / (2.0 * (a + b)) * g.face.units_per_em() * g.sy
|
l.baseline + (b - a) / (2.0 * (a + b)) * g.face.units_per_em() * g.sy
|
||||||
};
|
};
|
||||||
assert!((center(lines[1]) - center(lines[0]) - 30.0).abs() < 1e-6);
|
assert!((center(&lines[1]) - center(&lines[0]) - 30.0).abs() < 1e-6);
|
||||||
assert!((lines[1].baseline - lines[0].baseline - 30.0).abs() > 0.1);
|
assert!((lines[1].baseline - lines[0].baseline - 30.0).abs() > 0.1);
|
||||||
|
// The other composers set lines baseline to baseline.
|
||||||
|
let lines = lines_with(ParaAttrs::default());
|
||||||
|
assert!((lines[1].baseline - lines[0].baseline - 30.0).abs() < 1e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cjk_em_center_alignment_moves_small_characters() {
|
fn cjk_em_center_alignment_moves_small_characters() {
|
||||||
let (mut d, sid, _) = doc_with("AB", Rect::new(0.0, 0.0, 300.0, 100.0), ParaAttrs::default());
|
let line_with = |para: ParaAttrs| {
|
||||||
d.story_mut(sid).unwrap().format_chars(0..1, |f| f.over.size = Some(24.0));
|
let (mut d, sid, _) = doc_with("AB", Rect::new(0.0, 0.0, 300.0, 100.0), para);
|
||||||
d.story_mut(sid).unwrap().format_chars(1..2, |f| f.over.character_alignment = Some(designcraft_doc::cjk::CharacterAlignment::EmCenter));
|
d.story_mut(sid).unwrap().format_chars(0..1, |f| f.over.size = Some(24.0));
|
||||||
let cs = compose_story(&d, sid, &ComposeOptions::default());
|
d.story_mut(sid).unwrap().format_chars(0..2, |f| f.over.character_alignment = Some(designcraft_doc::cjk::CharacterAlignment::EmCenter));
|
||||||
let line = &cs.frames[0].lines[0];
|
compose_story(&d, sid, &ComposeOptions::default()).frames[0].lines[0].clone()
|
||||||
|
};
|
||||||
|
let line = line_with(japanese());
|
||||||
let center = |g: &PlacedGlyph| {
|
let center = |g: &PlacedGlyph| {
|
||||||
let (a, b) = g.face.vertical_metrics();
|
let (a, b) = g.face.vertical_metrics();
|
||||||
g.y + (b - a) / (2.0 * (a + b)) * g.face.units_per_em() * g.sy
|
g.y + (b - a) / (2.0 * (a + b)) * g.face.units_per_em() * g.sy
|
||||||
};
|
};
|
||||||
assert!((center(&line.glyphs[0]) - center(&line.glyphs[1])).abs() < 1e-6);
|
assert!((center(&line.glyphs[0]) - center(&line.glyphs[1])).abs() < 1e-6);
|
||||||
|
// The other composers keep every character on the baseline.
|
||||||
|
let line = line_with(ParaAttrs::default());
|
||||||
|
assert!(line.glyphs.iter().all(|g| g.y.abs() < 1e-9), "{:?}", line.glyphs.iter().map(|g| g.y).collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -415,6 +415,25 @@ pub enum Composer {
|
|||||||
Paragraph,
|
Paragraph,
|
||||||
/// Greedy, line by line.
|
/// Greedy, line by line.
|
||||||
SingleLine,
|
SingleLine,
|
||||||
|
/// The Japanese paragraph composer: total fit, with the CJK leading model and character
|
||||||
|
/// alignment.
|
||||||
|
Japanese,
|
||||||
|
/// The Japanese single-line composer.
|
||||||
|
JapaneseSingleLine,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Composer {
|
||||||
|
/// Breaks lines greedily, one at a time.
|
||||||
|
pub fn single_line(self) -> bool {
|
||||||
|
matches!(self, Composer::SingleLine | Composer::JapaneseSingleLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Japanese composer: lines follow the leading model ([`crate::cjk::LeadingModel`]) and
|
||||||
|
/// characters their [`crate::cjk::CharacterAlignment`]. The others set lines baseline to
|
||||||
|
/// baseline with every character on the Roman baseline.
|
||||||
|
pub fn japanese(self) -> bool {
|
||||||
|
matches!(self, Composer::Japanese | Composer::JapaneseSingleLine)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a nested style runs to (Drop Caps and Nested Styles).
|
/// What a nested style runs to (Drop Caps and Nested Styles).
|
||||||
|
|||||||
@@ -1389,6 +1389,8 @@ impl<'a> Ex<'a> {
|
|||||||
match c {
|
match c {
|
||||||
designcraft_doc::Composer::Paragraph => "HL Composer",
|
designcraft_doc::Composer::Paragraph => "HL Composer",
|
||||||
designcraft_doc::Composer::SingleLine => "HL Single",
|
designcraft_doc::Composer::SingleLine => "HL Single",
|
||||||
|
designcraft_doc::Composer::Japanese => "HL Composer J",
|
||||||
|
designcraft_doc::Composer::JapaneseSingleLine => "HL Single J",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1289,9 +1289,7 @@ impl<'r> Importer<'r> {
|
|||||||
GridAlign::AllLines
|
GridAlign::AllLines
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
a.composer = e
|
a.composer = e.prop("Composer").map(|c| composer_in(&c));
|
||||||
.prop("Composer")
|
|
||||||
.map(|c| if c.contains("Single") { designcraft_doc::Composer::SingleLine } else { designcraft_doc::Composer::Paragraph });
|
|
||||||
a.hyphenate = e.boolean("Hyphenation");
|
a.hyphenate = e.boolean("Hyphenation");
|
||||||
a.hyph_min_word = u("HyphenateWordsLongerThan");
|
a.hyph_min_word = u("HyphenateWordsLongerThan");
|
||||||
a.hyph_after_first = u("HyphenateAfterFirst");
|
a.hyph_after_first = u("HyphenateAfterFirst");
|
||||||
@@ -2421,6 +2419,18 @@ fn ace_char(v: &str) -> Option<char> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `HL Composer` and `HL Single`, their World-Ready (`… Optyca`) and Japanese (`… J`) forms.
|
||||||
|
fn composer_in(name: &str) -> designcraft_doc::Composer {
|
||||||
|
use designcraft_doc::Composer as C;
|
||||||
|
let name = name.trim().trim_start_matches("$ID/");
|
||||||
|
match (name.contains("Single"), name.ends_with(" J") || name.contains("Japanese")) {
|
||||||
|
(false, false) => C::Paragraph,
|
||||||
|
(true, false) => C::SingleLine,
|
||||||
|
(false, true) => C::Japanese,
|
||||||
|
(true, true) => C::JapaneseSingleLine,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn margins_of(m: &El) -> (Margins, Columns) {
|
fn margins_of(m: &El) -> (Margins, Columns) {
|
||||||
let g = |k: &str, d: f64| m.num(k).unwrap_or(d);
|
let g = |k: &str, d: f64| m.num(k).unwrap_or(d);
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -787,6 +787,33 @@ fn cjk_character_attributes_import_from_independent_xml_and_round_trip() {
|
|||||||
assert_eq!(a.leading_model, b.leading_model);
|
assert_eq!(a.leading_model, b.leading_model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn composers_import_by_kind_and_round_trip() {
|
||||||
|
use designcraft_doc::Composer as C;
|
||||||
|
let para = |composer: &str, text: &str| {
|
||||||
|
format!(
|
||||||
|
r#"<ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/$ID/[No paragraph style]" Composer="{composer}"><CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]"><Content>{text}</Content><Br /></CharacterStyleRange></ParagraphStyleRange>"#
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let cases = [
|
||||||
|
("HL Composer", C::Paragraph),
|
||||||
|
("HL Single Optyca", C::SingleLine),
|
||||||
|
("HL Composer Optyca", C::Paragraph),
|
||||||
|
("HL Composer J", C::Japanese),
|
||||||
|
("$ID/HL Single J", C::JapaneseSingleLine),
|
||||||
|
];
|
||||||
|
let body: String = cases.iter().enumerate().map(|(i, (name, _))| para(name, &format!("p{i}"))).collect();
|
||||||
|
let story =
|
||||||
|
format!(r#"<idPkg:Story xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging"><Story Self="s1">{body}</Story></idPkg:Story>"#);
|
||||||
|
let doc = import_idml(&fixture_with_story(&story)).unwrap();
|
||||||
|
let back = import_idml(&export_idml(&doc)).unwrap();
|
||||||
|
for d in [&doc, &back] {
|
||||||
|
let st = d.stories.values().find(|s| s.text.contains("p0")).unwrap();
|
||||||
|
let got: Vec<_> = st.paras.iter().take(cases.len()).map(|p| p.para.composer).collect();
|
||||||
|
assert_eq!(got, cases.iter().map(|(_, c)| Some(*c)).collect::<Vec<_>>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn warichu_imports_from_idml_and_round_trips() {
|
fn warichu_imports_from_idml_and_round_trips() {
|
||||||
let story = r#"<idPkg:Story xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging"><Story Self="s1">
|
let story = r#"<idPkg:Story xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging"><Story Self="s1">
|
||||||
|
|||||||
@@ -2576,12 +2576,13 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo
|
|||||||
ui.end_row();
|
ui.end_row();
|
||||||
}
|
}
|
||||||
crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Composer:"));
|
crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Composer:"));
|
||||||
let single = cur(d, "p.composer", &pv["composer"]).as_str() == Some("singleLine");
|
let (single, [paragraph, single_line]) =
|
||||||
|
crate::panels::properties::composer_choice(cur(d, "p.composer", &pv["composer"]).as_str());
|
||||||
if ui.selectable_label(!single, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Paragraph Composer"))).clicked() {
|
if ui.selectable_label(!single, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Paragraph Composer"))).clicked() {
|
||||||
d.fields.insert("p.composer".into(), json!("paragraph"));
|
d.fields.insert("p.composer".into(), json!(paragraph));
|
||||||
}
|
}
|
||||||
if ui.selectable_label(single, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Single-line Composer"))).clicked() {
|
if ui.selectable_label(single, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Single-line Composer"))).clicked() {
|
||||||
d.fields.insert("p.composer".into(), json!("singleLine"));
|
d.fields.insert("p.composer".into(), json!(single_line));
|
||||||
}
|
}
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Insert Kashidas:"));
|
crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Insert Kashidas:"));
|
||||||
|
|||||||
@@ -1480,12 +1480,12 @@ pub fn paragraph_panel(app: &mut DesignApp, ui: &mut egui::Ui) {
|
|||||||
});
|
});
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
caption(ui, crate::i18n::tr(&app.ui.language, "Composer"));
|
caption(ui, crate::i18n::tr(&app.ui.language, "Composer"));
|
||||||
let single = p["composer"].as_str() == Some("singleLine");
|
let (single, [paragraph, single_line]) = composer_choice(p["composer"].as_str());
|
||||||
if ui.selectable_label(!single, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Paragraph"))).clicked() {
|
if ui.selectable_label(!single, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Paragraph"))).clicked() {
|
||||||
let _ = app.run("type.para", json!({"attrs": {"composer": "paragraph"}}));
|
let _ = app.run("type.para", json!({"attrs": {"composer": paragraph}}));
|
||||||
}
|
}
|
||||||
if ui.selectable_label(single, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Single-line"))).clicked() {
|
if ui.selectable_label(single, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Single-line"))).clicked() {
|
||||||
let _ = app.run("type.para", json!({"attrs": {"composer": "singleLine"}}));
|
let _ = app.run("type.para", json!({"attrs": {"composer": single_line}}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Paragraph Border and Shading.
|
// Paragraph Border and Shading.
|
||||||
@@ -2131,6 +2131,14 @@ fn variable_font_axes(app: &mut DesignApp, ui: &mut egui::Ui, family: &str, styl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether `composer` (a paragraph's `composer` attribute) is a single-line composer, and the
|
||||||
|
/// paragraph and single-line composers of its kind (Japanese or not), for the composer choice.
|
||||||
|
pub(crate) fn composer_choice(composer: Option<&str>) -> (bool, [&'static str; 2]) {
|
||||||
|
let single = matches!(composer, Some("singleLine" | "japaneseSingleLine"));
|
||||||
|
let kind = if composer.is_some_and(|c| c.starts_with("japanese")) { ["japanese", "japaneseSingleLine"] } else { ["paragraph", "singleLine"] };
|
||||||
|
(single, kind)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use egui_kittest::{Harness, kittest::Queryable};
|
use egui_kittest::{Harness, kittest::Queryable};
|
||||||
|
|||||||
+3
-3
@@ -37,7 +37,7 @@ presets or tables are copied. Our default mojikumi and kinsoku tables are built
|
|||||||
| Korean line breaking | at spaces (Hangul everywhere, hanja in Korean text); per-paragraph character-based breaking (`koreanCharBreaks`); DesignCraft-only, not in IDML |
|
| Korean line breaking | at spaces (Hangul everywhere, hanja in Korean text); per-paragraph character-based breaking (`koreanCharBreaks`); DesignCraft-only, not in IDML |
|
||||||
| Mojikumi | tables and paragraph references kept through IDML; not applied (Preflight says so) |
|
| Mojikumi | tables and paragraph references kept through IDML; not applied (Preflight says so) |
|
||||||
| Aki, tsume, jidori | done ([cjk-typography.md](cjk-typography.md)) |
|
| Aki, tsume, jidori | done ([cjk-typography.md](cjk-typography.md)) |
|
||||||
| Character alignment, leading model | em box top / centre / bottom, ICF from ascender and descender, roman baseline; aki above / below, centre (centre down as centre) |
|
| Character alignment, leading model | em box top / centre / bottom, ICF from ascender and descender, roman baseline; aki above / below, centre (centre down as centre); with the Japanese composers (`Composer::Japanese`, `JapaneseSingleLine`, IDML `HL Composer J` / `HL Single J`), the others set lines baseline to baseline on the Roman baseline |
|
||||||
| Tate-chu-yoko | manual, with offsets; no auto, 3+ digits overflow the em |
|
| Tate-chu-yoko | manual, with offsets; no auto, 3+ digits overflow the em |
|
||||||
| Ruby | group ruby only, fixed 50 % size, no options, doesn't affect leading |
|
| Ruby | group ruby only, fixed 50 % size, no options, doesn't affect leading |
|
||||||
| Kenten | the IDML kinds and a custom character, drawn as characters; no position, size, alignment, colour or font |
|
| Kenten | the IDML kinds and a custom character, drawn as characters; no position, size, alignment, colour or font |
|
||||||
@@ -86,8 +86,8 @@ working (tsume removes the side bearings before mojikumi runs).
|
|||||||
built-in hard and soft sets from JLREQ's classes (today's named sets are common punctuation
|
built-in hard and soft sets from JLREQ's classes (today's named sets are common punctuation
|
||||||
lists), and the kinsoku type (push in first / push out first / push out only / prioritise the
|
lists), and the kinsoku type (push in first / push out first / push out only / prioritise the
|
||||||
adjustment amount), which needs the Japanese composer's push-in and push-out candidates.
|
adjustment amount), which needs the Japanese composer's push-in and push-out candidates.
|
||||||
- A `Composer::Japanese` (paragraph) and `Composer::JapaneseSingleLine` beside the Western
|
- The Japanese composers (`Composer::Japanese`, `Composer::JapaneseSingleLine`) break lines as
|
||||||
composers. In `breaker::items` the Japanese composer emits:
|
the Western ones do. In `breaker::items` they are to emit:
|
||||||
- boxes per glyph,
|
- boxes per glyph,
|
||||||
- mojikumi glue between glyphs with priority tiers (compress punctuation first, then
|
- mojikumi glue between glyphs with priority tiers (compress punctuation first, then
|
||||||
expand inter-ideograph space, then Western/CJK spacing, then letter-spacing),
|
expand inter-ideograph space, then Western/CJK spacing, then letter-spacing),
|
||||||
|
|||||||
Reference in new issue
Block a user