CJK 1.4: Korean breaks at spaces
Korean is set word by word (KLREQ): Hangul, in any language, and Han ideographs in Korean-language text take no line breaks between characters, so lines break at spaces. Glyphs carry their language tag for this. A paragraph attribute, koreanCharBreaks (type.para, paragraph styles, Paragraph Style Options > Justification), breaks between syllables instead. It is DesignCraft-only; IDML doesn't carry it. A paragraph's kinsoku set decides which breaks it forbids, and Korean still breaks at spaces under one. Kinsoku still holds, also when the single-line composer has to split a word wider than the measure: the split moves back so no line starts with closing punctuation.
This commit is contained in:
parent
832939691c
commit
e0fb78a230
|
|
@ -111,7 +111,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
|||
- Keyboard: Tab / Shift+Tab hide and show all panels / all but Tools (`window.hidePanels`, `window.hidePanelsExceptTools`); Cmd+F6 / Cmd+Shift+F6 cycle documents; panel fields are reached with Tab and Escape returns to the layout.
|
||||
- Smart guides and snapping (View ▸ Snap to Guides / Snap to Document Grid / Smart Guides, `view.snapPreferences` and Preferences ▸ Guides & Pasteboard): moving, drawing, resizing, rotating and pen/anchor drags snap in order to the document and baseline grids, ruler guides, margins and columns, other objects' edges and centres (parent-page items too, by visible stroked bounds), equal spacing, matching dimensions and matching rotation, with guides, gap marks and measurements drawn on the canvas. Shift and Cmd constrain the gesture.
|
||||
- View ▸ Rotate Spread (`view.rotateSpread`, `layout.rotateSpreadView`): the spread in view turns in quarter turns on screen (stored with the document; output is unaffected). The canvas maps each spread through its own transform, so tools, snapping, resize handles, guides, overlays and rendering all follow the turned spread.
|
||||
- Vertical type (Vertical Type Tool, Type ▸ Story Direction, `object.textFrameOptions {vertical}`, IDML StoryOrientation): lines run top to bottom and follow each other right to left; CJK characters stay upright and other text turns, on screen and in PDF; CJK line breaking with basic kinsoku (also in horizontal text). Punctuation and brackets take their vertical forms (OpenType `vert`/`vrt2`) in vertical frames. Tate-chu-yoko (Type ▸ Tate-Chu-Yoko, `type.tateChuYoko`, IDML `Tatechuyoko`) sets a run such as "12" side by side within one em of the line. Ruby (Type ▸ Ruby…, `type.ruby`, IDML RubyString; group ruby spread 1-2-1 or centred) and kenten emphasis dots (`type.kenten`, IDML KentenKind) are set over their text — to its right in vertical frames — without changing line breaks. Vertical metrics (`vmtx`), warichu and ruby options (per-character ruby, positions, overhang) aren't there yet.
|
||||
- Vertical type (Vertical Type Tool, Type ▸ Story Direction, `object.textFrameOptions {vertical}`, IDML StoryOrientation): lines run top to bottom and follow each other right to left; CJK characters stay upright and other text turns, on screen and in PDF; CJK line breaking with basic kinsoku (also in horizontal text); Korean breaks at spaces, word by word (Hangul in any language, hanja in Korean text), or between syllables when the paragraph asks for it (`type.para {koreanCharBreaks}`, Paragraph Style Options ▸ Justification; not written to IDML). Punctuation and brackets take their vertical forms (OpenType `vert`/`vrt2`) in vertical frames. Tate-chu-yoko (Type ▸ Tate-Chu-Yoko, `type.tateChuYoko`, IDML `Tatechuyoko`) sets a run such as "12" side by side within one em of the line. Ruby (Type ▸ Ruby…, `type.ruby`, IDML RubyString; group ruby spread 1-2-1 or centred) and kenten emphasis dots (`type.kenten`, IDML KentenKind) are set over their text — to its right in vertical frames — without changing line breaks. Vertical metrics (`vmtx`), warichu and ruby options (per-character ruby, positions, overhang) aren't there yet.
|
||||
- Math expressions (Type ▸ Insert Math Expression…, `math.insert`, `math.svg`): a LaTeX subset (fractions, scripts, roots, big operators with limits, Greek, relations and operators with math spacing, `\left…\right`, `\text`) typeset by our own engine into SVG, placed inline at the cursor or as a frame; the LaTeX is kept as alt text and can be edited.
|
||||
- Object Layer Options for placed PDFs (`object.pdfLayers`, `object.layerOptions`, Object ▸ Object Layer Options…): the file's optional-content layers listed and shown or hidden per frame on screen; in exported PDFs such graphics go out as 300 ppi images of their visible layers.
|
||||
- Effects: Inner Glow (edge or centre), Bevel and Emboss (inner bevel lit from the light angle), Satin (`object.innerGlow`, `object.bevel`, `object.satin`); PDF export now keeps soft effects: objects with shadows, glows, feathers, bevels or satin go out as 300 ppi transparent images of their appearance (threaded text frames excepted).
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ pub struct Spacing {
|
|||
pub hyph_zone: f64,
|
||||
/// Optical margin alignment: punctuation hangs outside the measure (counted as extra width).
|
||||
pub optical: bool,
|
||||
/// Korean text breaks between syllables instead of at spaces.
|
||||
pub korean_char_breaks: bool,
|
||||
}
|
||||
|
||||
impl Default for Spacing {
|
||||
|
|
@ -68,6 +70,7 @@ impl Default for Spacing {
|
|||
ragged_stretch: 24.0,
|
||||
hyph_zone: 0.0,
|
||||
optical: false,
|
||||
korean_char_breaks: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -168,15 +171,41 @@ const INF: f64 = 10000.0;
|
|||
/// hangul), except where kinsoku forbids it: no line starts with closing punctuation, small
|
||||
/// kana or the prolonged sound mark, and none ends with opening brackets.
|
||||
pub fn cjk_break_between(a: char, b: char) -> bool {
|
||||
let cjk = |c: char| crate::upright_in_vertical(c) || matches!(c as u32, 0x3000..=0x303F);
|
||||
if !(cjk(a) || cjk(b)) {
|
||||
return false;
|
||||
}
|
||||
(is_cjk(a) || is_cjk(b)) && kinsoku_allows(a, b)
|
||||
}
|
||||
|
||||
fn is_cjk(c: char) -> bool {
|
||||
crate::upright_in_vertical(c) || matches!(c as u32, 0x3000..=0x303F)
|
||||
}
|
||||
|
||||
fn kinsoku_allows(a: char, b: char) -> bool {
|
||||
const NO_START: &str = "、。,.・:;?!ー)」』】〕〉》}]〙〗ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶ々〻ゝゞヽヾ!),.:;?]}";
|
||||
const NO_END: &str = "(「『【〔〈《{[〘〖([{";
|
||||
!NO_START.contains(b) && !NO_END.contains(a)
|
||||
}
|
||||
|
||||
/// Hangul, and Han ideographs (hanja) in Korean text: Korean is set word by word (KLREQ), so
|
||||
/// these letters break like Western ones, at spaces.
|
||||
fn korean_letter(g: &Glyph) -> bool {
|
||||
let c = g.ch as u32;
|
||||
let hangul = matches!(c, 0x1100..=0x11FF | 0x3130..=0x318F | 0xA960..=0xA97F | 0xAC00..=0xD7FF);
|
||||
let han = matches!(c, 0x3400..=0x4DBF | 0x4E00..=0x9FFF | 0xF900..=0xFAFF | 0x20000..=0x3FFFF);
|
||||
hangul || (han && g.lang == Some("ko"))
|
||||
}
|
||||
|
||||
/// Is the place between glyphs `a` and `b` one where CJK text may break (next to an ideograph, kana
|
||||
/// or hangul)? Korean letters take no breaks between characters unless the paragraph asks for
|
||||
/// character-based breaking.
|
||||
pub(crate) fn cjk_pair(a: &Glyph, b: &Glyph, korean_char_breaks: bool) -> bool {
|
||||
let cjk = |g: &Glyph| is_cjk(g.ch) && (korean_char_breaks || !korean_letter(g));
|
||||
cjk(a) || cjk(b)
|
||||
}
|
||||
|
||||
/// [`cjk_break_between`] for glyphs `a` and `b` (see [`cjk_pair`]).
|
||||
fn cjk_break(a: &Glyph, b: &Glyph, sp: &Spacing) -> bool {
|
||||
cjk_pair(a, b, sp.korean_char_breaks) && kinsoku_allows(a.ch, b.ch)
|
||||
}
|
||||
|
||||
pub fn is_forced(c: char) -> bool {
|
||||
use designcraft_doc::story::*;
|
||||
matches!(c, FORCED_LINE_BREAK | COLUMN_BREAK | FRAME_BREAK | PAGE_BREAK)
|
||||
|
|
@ -310,7 +339,7 @@ fn items(glyphs: &[Glyph], hyph_after: &[bool], sp: &Spacing) -> (Vec<Item>, Vec
|
|||
let mut p = Item::penalty(0.0, 50.0, true);
|
||||
p.hang = right_hang(g, sp);
|
||||
push_inword(&mut it, &mut ig, p, i + 1, sp);
|
||||
} else if matches!(g.ch, '\u{2013}' | '\u{2014}' | '/') || g.break_after.unwrap_or_else(|| cjk_break_between(g.ch, glyphs[i + 1].ch)) {
|
||||
} else if matches!(g.ch, '\u{2013}' | '\u{2014}' | '/') || g.break_after.unwrap_or_else(|| cjk_break(g, &glyphs[i + 1], sp)) {
|
||||
let mut p = Item::penalty(0.0, 0.0, false);
|
||||
p.hang = right_hang(g, sp);
|
||||
push_inword(&mut it, &mut ig, p, i + 1, sp);
|
||||
|
|
@ -590,6 +619,14 @@ fn breaks_from_positions(items: &[Item], ig: &[usize], glyphs: &[Glyph], chain:
|
|||
out
|
||||
}
|
||||
|
||||
/// Where a line with no break opportunity before glyph `i` (a word wider than the measure) is
|
||||
/// split: before `i`, or earlier where kinsoku forbids that (no line starts with closing
|
||||
/// punctuation).
|
||||
fn emergency_split(glyphs: &[Glyph], start: usize, i: usize) -> usize {
|
||||
let allowed = |j: usize| matches!((glyphs.get(j.wrapping_sub(1)), glyphs.get(j)), (Some(a), Some(b)) if kinsoku_allows(a.ch, b.ch));
|
||||
(start + 1..=i).rev().find(|&j| allowed(j)).unwrap_or(i)
|
||||
}
|
||||
|
||||
/// Greedy first-fit breaking (single-line composer; also used for paragraphs with tabs).
|
||||
pub fn greedy(glyphs: &[Glyph], hyph_after: &[bool], sp: &Spacing, width: &dyn Fn(usize) -> f64) -> Vec<Break> {
|
||||
let n = glyphs.len();
|
||||
|
|
@ -638,7 +675,7 @@ pub fn greedy(glyphs: &[Glyph], hyph_after: &[bool], sp: &Spacing, width: &dyn F
|
|||
(last_space.map_or(i, |s| s.0), false, false)
|
||||
}
|
||||
Some((p, h)) => (p, h, false),
|
||||
None => (i, false, false),
|
||||
None => (emergency_split(glyphs, start, i), false, false),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
@ -648,7 +685,7 @@ pub fn greedy(glyphs: &[Glyph], hyph_after: &[bool], sp: &Spacing, width: &dyn F
|
|||
}
|
||||
if i + 1 < n && !glyphs[i + 1].is_space() && !g.no_break && g.break_after != Some(false) {
|
||||
if matches!(g.ch, '-' | '\u{2010}' | '\u{2013}' | '\u{2014}' | '/')
|
||||
|| g.break_after.unwrap_or_else(|| cjk_break_between(g.ch, glyphs[i + 1].ch))
|
||||
|| g.break_after.unwrap_or_else(|| cjk_break(g, &glyphs[i + 1], sp))
|
||||
{
|
||||
last_ok = Some((i + 1, false));
|
||||
} else if hyph_after[i] && may_hyphenate(hyphens) {
|
||||
|
|
|
|||
|
|
@ -1128,6 +1128,7 @@ fn spacing_for(pp: &ParaProps, base_size: f64) -> Spacing {
|
|||
ragged_stretch: base_size * 2.0,
|
||||
hyph_zone: if pp.align.is_justified() { 0.0 } else { pp.hyph_zone },
|
||||
optical: pp.optical_margin,
|
||||
korean_char_breaks: pp.korean_char_breaks,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1146,7 +1147,8 @@ fn apply_desired_spacing(glyphs: &mut [Glyph], pp: &ParaProps) {
|
|||
if let Some(next) = right.first() {
|
||||
let a = g.ch;
|
||||
let b = next.ch;
|
||||
let cjk = upright_in_vertical(a) || upright_in_vertical(b);
|
||||
// A kinsoku set rules where CJK text may break; Korean still breaks at spaces.
|
||||
let cjk = breaker::cjk_pair(g, next, pp.korean_char_breaks);
|
||||
if g.break_after != Some(false) {
|
||||
if let Some(set) = &pp.kinsoku {
|
||||
if !set.allows(a, b) {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ pub struct Glyph {
|
|||
pub allow_kashidas: bool,
|
||||
pub safe_tatweel_before: bool,
|
||||
pub shaping_rtl: bool,
|
||||
/// The character's language as a BCP 47 tag ([`designcraft_doc::language_tag`]).
|
||||
pub lang: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl Glyph {
|
||||
|
|
@ -752,6 +754,7 @@ fn control_glyph(face: &Arc<FontFace>, p: &CharProps, auto_leading: TypeEnv, sty
|
|||
allow_kashidas: p.allow_kashidas,
|
||||
safe_tatweel_before: false,
|
||||
shaping_rtl: false,
|
||||
lang: designcraft_doc::language_tag(&p.language),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -851,6 +854,7 @@ fn shape_segment(
|
|||
allow_kashidas: p.allow_kashidas,
|
||||
safe_tatweel_before: sg.safe_tatweel_before,
|
||||
shaping_rtl: sg.rtl,
|
||||
lang,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1493,3 +1493,99 @@ fn cjk_fallback_follows_the_language() {
|
|||
let (primary, han) = faces_of("English: USA");
|
||||
assert_eq!(Some(han.family.clone()), db.fallback_for('直', primary.id(), None).map(|f| f.family.clone()));
|
||||
}
|
||||
|
||||
const KO_WORDS: &str = "한국어 문장은 띄어쓰기 단위로 줄을 바꿉니다 한국어 문장은 띄어쓰기 단위로 줄을 바꿉니다";
|
||||
const KO_HANJA: &str = "大韓民國 憲法은 國民의 權利를 保障한다 大韓民國 憲法은 國民의 權利를 保障한다";
|
||||
const KO_PUNCT: &str = "가나다라마。바사아자차」카타파하가、나다라마바。사아자차카」타파하가나、다라마바사。아자차카타」파하가나다、";
|
||||
/// Syllables for words wider than the frame.
|
||||
const KO_LONG: &str = "가나다라마바사아자차카타파하」";
|
||||
|
||||
/// A Korean paragraph in a narrow frame (70 pt), set in a stand-in font covering the samples.
|
||||
fn korean_doc(text: &str, language: &str, para: ParaAttrs) -> (Document, StoryId) {
|
||||
let chars: Vec<char> = [KO_WORDS, KO_HANJA, KO_PUNCT, KO_LONG].concat().chars().collect();
|
||||
let font = designcraft_fonts::testing::font_with("DC Test Korean", &chars).unwrap();
|
||||
designcraft_fonts::FontDb::global().add_font(font);
|
||||
let (mut d, sid, _) = doc_with(text, Rect::new(36.0, 36.0, 106.0, 400.0), para);
|
||||
d.story_mut(sid).unwrap().format_chars(0..text.len(), |f| {
|
||||
f.over.font_family = Some("DC Test Korean".into());
|
||||
f.over.language = Some(language.into());
|
||||
});
|
||||
(d, sid)
|
||||
}
|
||||
|
||||
/// The lines (after the first) that start inside a space-separated word.
|
||||
fn lines_starting_mid_word(d: &Document, sid: StoryId, text: &str) -> Vec<String> {
|
||||
let cs = compose_story(d, sid, &ComposeOptions::default());
|
||||
let lines = all_lines(&cs);
|
||||
assert!(lines.len() > 2, "wraps: {} lines", lines.len());
|
||||
lines.iter().skip(1).filter(|l| !text[..l.range.start].ends_with(' ')).map(|l| text[l.range.clone()].to_string()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn korean_breaks_at_spaces() {
|
||||
for composer in [designcraft_doc::Composer::Paragraph, designcraft_doc::Composer::SingleLine] {
|
||||
for align in [Align::Left, Align::LeftJustified] {
|
||||
let para = ParaAttrs { composer: Some(composer), align: Some(align), ..Default::default() };
|
||||
// By script: Hangul is set word by word whatever the language.
|
||||
for language in ["Korean", "English: USA"] {
|
||||
let (d, sid) = korean_doc(KO_WORDS, language, para.clone());
|
||||
let mid = lines_starting_mid_word(&d, sid, KO_WORDS);
|
||||
assert!(mid.is_empty(), "{composer:?} {align:?} {language}: {mid:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn korean_breaks_at_spaces_under_a_kinsoku_set() {
|
||||
let set = designcraft_doc::cjk::Kinsoku::named("KoreanKinsoku");
|
||||
assert!(set.is_some());
|
||||
for composer in [designcraft_doc::Composer::Paragraph, designcraft_doc::Composer::SingleLine] {
|
||||
let para = ParaAttrs { composer: Some(composer), kinsoku: Some(set.clone()), ..Default::default() };
|
||||
let (d, sid) = korean_doc(KO_WORDS, "Korean", para.clone());
|
||||
let mid = lines_starting_mid_word(&d, sid, KO_WORDS);
|
||||
assert!(mid.is_empty(), "{composer:?}: {mid:?}");
|
||||
let (d, sid) = korean_doc(KO_WORDS, "Korean", ParaAttrs { korean_char_breaks: Some(true), ..para });
|
||||
assert!(!lines_starting_mid_word(&d, sid, KO_WORDS).is_empty(), "{composer:?}: breaks between syllables on request");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn korean_character_breaks_on_request() {
|
||||
let para = ParaAttrs { korean_char_breaks: Some(true), composer: Some(designcraft_doc::Composer::SingleLine), ..Default::default() };
|
||||
let (d, sid) = korean_doc(KO_WORDS, "Korean", para);
|
||||
assert!(!lines_starting_mid_word(&d, sid, KO_WORDS).is_empty(), "breaks between syllables");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hanja_in_korean_text_breaks_at_spaces() {
|
||||
let para = ParaAttrs { composer: Some(designcraft_doc::Composer::SingleLine), ..Default::default() };
|
||||
let (d, sid) = korean_doc(KO_HANJA, "Korean", para.clone());
|
||||
let mid = lines_starting_mid_word(&d, sid, KO_HANJA);
|
||||
assert!(mid.is_empty(), "{mid:?}");
|
||||
// Han in Japanese text breaks between characters.
|
||||
let (d, sid) = korean_doc(KO_HANJA, "Japanese", para);
|
||||
assert!(!lines_starting_mid_word(&d, sid, KO_HANJA).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn korean_lines_keep_kinsoku() {
|
||||
// Words of 9 to 14 syllables and a closing bracket: in one of them the bracket is what
|
||||
// overflows the frame.
|
||||
let syllables: Vec<char> = KO_LONG.chars().collect();
|
||||
let long = (9..=14).map(|n| format!("{}」 가나", syllables[..n].iter().collect::<String>()));
|
||||
for text in std::iter::once(KO_PUNCT.to_string()).chain(long) {
|
||||
let text = text.as_str();
|
||||
for char_breaks in [false, true] {
|
||||
for composer in [designcraft_doc::Composer::Paragraph, designcraft_doc::Composer::SingleLine] {
|
||||
let para = ParaAttrs { korean_char_breaks: Some(char_breaks), composer: Some(composer), ..Default::default() };
|
||||
let (d, sid) = korean_doc(text, "Korean", para);
|
||||
let cs = compose_story(&d, sid, &ComposeOptions::default());
|
||||
let lines = all_lines(&cs);
|
||||
assert!(lines.len() > 1, "wraps");
|
||||
let texts: Vec<&str> = lines.iter().map(|l| &text[l.range.clone()]).collect();
|
||||
assert!(texts.iter().all(|t| !t.starts_with(['、', '。', '」'])), "kinsoku ({char_breaks}, {composer:?}): {texts:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -699,6 +699,9 @@ attr_set! {
|
|||
/// Preserved vendor policy; non-default policies require a dedicated composer.
|
||||
arabic_justification: String = String::new(),
|
||||
paragraph_kashida_width: Option<f64> = None,
|
||||
/// Korean text breaks between any two syllables (character-based breaking); otherwise
|
||||
/// it breaks at spaces, word by word.
|
||||
korean_char_breaks: bool = false,
|
||||
// Keeps
|
||||
keep_with_next: u32 = 0,
|
||||
keep_lines_together: bool = false,
|
||||
|
|
|
|||
|
|
@ -1377,3 +1377,22 @@ mod arabic_tests {
|
|||
assert_eq!(s.doc().unwrap().doc.story(sid).unwrap().direction, designcraft_doc::TextDirection::LeftToRight);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod korean_breaks_tests {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::Session;
|
||||
|
||||
#[test]
|
||||
fn korean_character_breaks_are_a_paragraph_attribute() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let r = s.execute("frame.create", &json!({"rect": [72, 72, 400, 200], "content": "text", "text": "한국어 문장"})).unwrap();
|
||||
let sid = designcraft_doc::StoryId(r["story"].as_u64().unwrap());
|
||||
s.execute("type.para", &json!({"attrs": {"koreanCharBreaks": true}})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().doc.story(sid).unwrap().paras[0].para.korean_char_breaks, Some(true));
|
||||
s.execute("style.paragraph.create", &json!({"name": "Korean", "para": {"koreanCharBreaks": true}})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().doc.styles.para("Korean").unwrap().para.korean_char_breaks, Some(true));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2088,6 +2088,15 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo
|
|||
d.fields.insert("p.kashidas".into(), json!(k));
|
||||
}
|
||||
ui.end_row();
|
||||
crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Korean Line Breaks:"));
|
||||
let mut kb = cur(d, "p.koreanCharBreaks", &pv["koreanCharBreaks"]).as_bool().unwrap_or(false);
|
||||
if ui
|
||||
.checkbox(&mut kb, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Between syllables (not only at spaces)")))
|
||||
.changed()
|
||||
{
|
||||
d.fields.insert("p.koreanCharBreaks".into(), json!(kb));
|
||||
}
|
||||
ui.end_row();
|
||||
});
|
||||
}
|
||||
"color" => {
|
||||
|
|
|
|||
|
|
@ -1916,6 +1916,17 @@ const TABLE: &[(&str, [&str; 5])] = &[
|
|||
"在两端对齐的阿拉伯语文本中",
|
||||
],
|
||||
),
|
||||
("Korean Line Breaks:", ["Korean Line Breaks:", "Korean Line Breaks:", "Korean Line Breaks:", "Korean Line Breaks:", "韩文换行:"]),
|
||||
(
|
||||
"Between syllables (not only at spaces)",
|
||||
[
|
||||
"Between syllables (not only at spaces)",
|
||||
"Between syllables (not only at spaces)",
|
||||
"Between syllables (not only at spaces)",
|
||||
"Between syllables (not only at spaces)",
|
||||
"在音节之间(不仅在空格处)",
|
||||
],
|
||||
),
|
||||
("Binding:", ["Binding:", "Binding:", "Binding:", "Binding:", "装订方向:"]),
|
||||
("Ruby:", ["Ruby:", "Ruby:", "Ruby:", "Ruby:", "注音:"]),
|
||||
(
|
||||
|
|
|
|||
|
|
@ -335,6 +335,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
|||
("Glyph Scaling:", "تحجيم الحروف:"),
|
||||
("Insert Kashidas:", "إدراج الكشيدة:"),
|
||||
("In justified Arabic text", "في النص العربي المضبوط"),
|
||||
("Korean Line Breaks:", "فواصل الأسطر الكورية:"),
|
||||
("Between syllables (not only at spaces)", "بين المقاطع (وليس عند المسافات فقط)"),
|
||||
("Minimum Space Before First Footnote:", "أقل مسافة قبل الحاشية الأولى:"),
|
||||
("Space Between Footnotes:", "المسافة بين الحواشي:"),
|
||||
("Offset:", "الإزاحة:"),
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ presets or tables are copied. Our default mojikumi and kinsoku tables are built
|
|||
| Vertical frames, upright CJK, rotated Latin | done (per frame; glyphs turned by transforms) |
|
||||
| `vert`/`vrt2` in vertical frames | done (applied to every run, Latin included); Horizontal Kana (`hkna`) becomes `vkna` |
|
||||
| Kinsoku | named (hard, soft, Chinese, Korean) and custom kinsoku sets, bunri-kinshi, rensuuji, hanging punctuation (regular, force); without a set, fixed no-start / no-end lists and a break anywhere between CJK characters; kinsoku type (push in / push out priorities) kept but not applied |
|
||||
| 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) |
|
||||
| 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) |
|
||||
|
|
@ -46,7 +47,6 @@ presets or tables are copied. Our default mojikumi and kinsoku tables are built
|
|||
|
||||
Known bugs to fix first:
|
||||
|
||||
- Korean breaks between every syllable (it should break at spaces by default).
|
||||
- Vertical advances are the horizontal ones and upright glyphs turn about a guessed centre
|
||||
(`adv × 0.38`), so proportional and non-square glyphs sit wrong in vertical text.
|
||||
- A justified CJK line has no stretch except Single Word Justification.
|
||||
|
|
|
|||
Loading…
Reference in New Issue