mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 21:57:04 +00:00
fix(text): review findings for the Standard composers
Unshaped complex scripts: no joiner boxes, no kashidas or path bending as if joined, a toggle that keeps shared Standard shaping, a test font fix, and doc wording for the scripts left unshaped.
This commit is contained in:
@@ -220,7 +220,7 @@ fn all_four_composers_round_trip() {
|
||||
use vectorcraft_doc::{TextKind, TextObject};
|
||||
let one = |c: Composer| {
|
||||
let mut d = Document::new(200.0, 200.0);
|
||||
let mut t = TextObject::point(kurbo::Point::ZERO, "ક\u{ACD}ક", Default::default());
|
||||
let mut t = TextObject::point(vectorcraft_geom::Point::ZERO, "ક\u{ACD}ક", Default::default());
|
||||
t.para.composer = c;
|
||||
t.kind = TextKind::Point;
|
||||
let id = d.alloc_id();
|
||||
|
||||
@@ -392,7 +392,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"type.lineComposer",
|
||||
"Toggle Line Composer",
|
||||
"Cmd+Alt+Shift+C",
|
||||
"{} the selected type's paragraphs (or those the Type tool's selection touches) use the Single-line Composer when they all use the Every-line Composer, else the Every-line Composer, each keeping its shaping: Middle Eastern & South Asian or Standard (text.setFormat {composer}) → \"singleLine\" | \"everyLine\" | \"standardSingleLine\" | \"standardEveryLine\"",
|
||||
"{} the selected type's paragraphs (or those the Type tool's selection touches) use the Single-line Composer when they all use the Every-line Composer, else the Every-line Composer; paragraphs that all use a Standard composer keep it, any other mix takes the Middle Eastern & South Asian one (text.setFormat {composer}) → \"singleLine\" | \"everyLine\" | \"standardSingleLine\" | \"standardEveryLine\"",
|
||||
),
|
||||
(
|
||||
"type.hyphenate",
|
||||
|
||||
@@ -164,6 +164,7 @@ impl OtFeatures {
|
||||
extra: u32::MAX >> (32 - EXTRA_FEATURES.len()),
|
||||
extra_values: [0; EXTRA_FEATURES.len()],
|
||||
swash_value: 0,
|
||||
unshaped_complex: false,
|
||||
};
|
||||
change(&mut off);
|
||||
change(&mut on);
|
||||
|
||||
@@ -78,10 +78,11 @@ fn joining(c: char) -> Joining {
|
||||
}
|
||||
|
||||
/// Is the cluster of glyph `a` joined to the cluster of glyph `b` that follows it in `text`: both
|
||||
/// right to left in one face, `a`'s last character other than a mark joining the letter after it,
|
||||
/// and `b` starting with a letter that joins the one before it?
|
||||
/// right to left in one face and set as joining letters (not by a Standard composer), `a`'s last
|
||||
/// character other than a mark joining the letter after it, and `b` starting with a letter that
|
||||
/// joins the one before it?
|
||||
fn joined(text: &str, a: &SGlyph, b: &SGlyph) -> bool {
|
||||
if a.face.id() != b.face.id() || a.level != b.level || !a.level.is_rtl() || a.byte + a.len != b.byte {
|
||||
if a.face.id() != b.face.id() || a.level != b.level || !a.level.is_rtl() || a.byte + a.len != b.byte || !(a.cursive && b.cursive) {
|
||||
return false;
|
||||
}
|
||||
let last = text.get(a.byte..a.byte + a.len).and_then(|s| s.chars().rev().find(|&c| joining(c) != Joining::Transparent));
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::shape::{
|
||||
Punct, SGlyph, Tcy, cap_x_heights, hyphen_glyph, is_cjk, mark_word_ends, no_line_end, no_line_start, punct, run_face, shape_range, shape_runs,
|
||||
soft_line_start, style_metrics,
|
||||
};
|
||||
use crate::{Composer, FirstBaseline, InlineGlyph, LayoutOptions, LineInfo, ListMarker, OtFeatures, PositionedGlyph, TextLayout, VerticalAlign};
|
||||
use crate::{FirstBaseline, InlineGlyph, LayoutOptions, LineInfo, ListMarker, OtFeatures, PositionedGlyph, TextLayout, VerticalAlign};
|
||||
|
||||
mod bidi;
|
||||
mod breaking;
|
||||
|
||||
@@ -8,9 +8,9 @@ use super::vertical::{
|
||||
EM_CENTER, MojikumiRules, align_shift, compress_punctuation, dot_quarter, glyph_em, halve_punctuation, punct_half, space_japanese_and_latin,
|
||||
};
|
||||
use super::{
|
||||
Affine, BidiInfo, Burasagari, CharStyle, Composer, Ctx, EPS, Flex, Justify, Kinsoku, KinsokuShoriType, LeadingModel, LineFit, LineInfo,
|
||||
ListMarker, ParaStyle, Params, Point, Punct, Range, SGlyph, SingleWordJustify, TextObject, apply_desired, cap_x_heights, compose, fit_line,
|
||||
hyphen_glyph, is_cjk, is_word_space, kashida, mark_word_ends, no_line_end, no_line_start, style_metrics,
|
||||
Affine, BidiInfo, Burasagari, CharStyle, Ctx, EPS, Flex, Justify, Kinsoku, KinsokuShoriType, LeadingModel, LineFit, LineInfo, ListMarker,
|
||||
ParaStyle, Params, Point, Punct, Range, SGlyph, SingleWordJustify, TextObject, apply_desired, cap_x_heights, compose, fit_line, hyphen_glyph,
|
||||
is_cjk, is_word_space, kashida, mark_word_ends, no_line_end, no_line_start, style_metrics,
|
||||
};
|
||||
use crate::justify::is_format_control;
|
||||
use crate::shape::{keep_inseparable_pairs, keep_korean_words};
|
||||
|
||||
@@ -123,7 +123,8 @@ pub(crate) fn joins(script: Script) -> bool {
|
||||
}
|
||||
|
||||
/// Is `script` one a Standard composer leaves unshaped: Indic (conjuncts, half forms, marks placed
|
||||
/// by the font's layout tables), Arabic-like ([`joins`]) and Hebrew?
|
||||
/// by the font's layout tables), the joining scripts ([`joins`]: Arabic, Syriac, N'Ko, Mongolian…)
|
||||
/// and Hebrew?
|
||||
fn is_complex_script(script: Script) -> bool {
|
||||
joins(script)
|
||||
|| matches!(
|
||||
@@ -1395,11 +1396,12 @@ fn shape_segment(text: &str, context: &Range<usize>, seg: &Segment, feats: &OtFe
|
||||
let text_seg = &text[range.clone()];
|
||||
// The segment's script: that of its first character of a script of its own.
|
||||
let first_script = text_seg.chars().map(|c| c.script()).find(|s| !matches!(s, Script::Common | Script::Inherited));
|
||||
let cursive = first_script.is_some_and(joins);
|
||||
// A Standard composer sets a complex script glyph by glyph (no layout tables, nominal glyphs).
|
||||
// shortcut: decided for the whole segment by its first strong script, as `cursive` is; segments
|
||||
// already split where the script changes.
|
||||
let unshaped = feats.unshaped_complex && first_script.is_some_and(is_complex_script);
|
||||
// Unshaped letters don't join, so they are not bent along a path as joined ones are.
|
||||
let cursive = first_script.is_some_and(joins) && !unshaped;
|
||||
let full = st.size.max(0.0);
|
||||
// Superscript/subscript and small capitals shrink the glyphs; line metrics keep the full size.
|
||||
let (_, pos_shift) = st.position.scale_shift(full);
|
||||
@@ -1593,15 +1595,17 @@ fn shape_segment(text: &str, context: &Range<usize>, seg: &Segment, feats: &OtFe
|
||||
let c = st.digits.map(c);
|
||||
let chars: Vec<char> = if upper { c.to_uppercase().collect() } else { vec![c] };
|
||||
for u in chars {
|
||||
let g = cmap.map(u).unwrap_or_default();
|
||||
let adv = gm.advance_width(g).unwrap_or(face.upem as f32 * 0.5);
|
||||
// Format characters (joiners, direction marks) have no glyph of their own in most
|
||||
// fonts, and the shaper hides them: draw the space's glyph with no advance.
|
||||
let (g, adv) = if crate::justify::is_format_control(u) {
|
||||
(cmap.map(' ').unwrap_or_default(), 0.0)
|
||||
} else {
|
||||
let g = cmap.map(u).unwrap_or_default();
|
||||
(g, gm.advance_width(g).unwrap_or(face.upem as f32 * 0.5))
|
||||
};
|
||||
raw.push((g.to_u32(), cl, adv.round() as i32, 0, 0));
|
||||
}
|
||||
}
|
||||
// Shaped right-to-left text comes out in visual order, which layout expects.
|
||||
if unshaped && level.is_rtl() {
|
||||
raw.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
shaped
|
||||
|
||||
@@ -33,8 +33,8 @@ fn g(c: char) -> u32 {
|
||||
u32::from(source_sans_gid(c).expect("a Source Sans glyph"))
|
||||
}
|
||||
|
||||
/// Under the ME&SA composers the conjunct forms (Devanagari: the vowel sign I drawn before its
|
||||
/// syllable and Ka's half form; Gujarati: the half form); under the Standard ones each character
|
||||
/// Under the ME&SA composers the conjunct forms (the vowel sign I drawn before its syllable and Ka's
|
||||
/// half form, in Devanagari and in Gujarati); under the Standard ones each character
|
||||
/// has its own glyph, in text order, as the font maps it.
|
||||
#[test]
|
||||
fn standard_composers_set_indic_text_glyph_by_glyph() {
|
||||
@@ -42,7 +42,7 @@ fn standard_composers_set_indic_text_glyph_by_glyph() {
|
||||
let ids = |v: Vec<(u32, usize)>| v.into_iter().map(|(gid, _)| gid).collect::<Vec<_>>();
|
||||
for composer in [Composer::SingleLine, Composer::EveryLine] {
|
||||
assert_eq!(ids(drawn(&db, DEVANAGARI, composer)), [g('i'), g('k'), g('K')], "{composer:?} Devanagari");
|
||||
assert_eq!(ids(drawn(&db, GUJARATI, composer)), [g('k'), g('K'), g('i')], "{composer:?} Gujarati");
|
||||
assert_eq!(ids(drawn(&db, GUJARATI, composer)), [g('i'), g('k'), g('K')], "{composer:?} Gujarati");
|
||||
}
|
||||
for composer in [Composer::StandardSingleLine, Composer::StandardEveryLine] {
|
||||
let plain = [g('K'), g('.'), g('K'), g('i')];
|
||||
@@ -94,3 +94,18 @@ fn composers_split_into_breaking_and_shaping() {
|
||||
}
|
||||
assert_eq!(Composer::default(), EveryLine, "new type and old documents are ME&SA Every-line");
|
||||
}
|
||||
|
||||
/// A joiner or non-joiner has no glyph in most fonts and the shaper hides it; a Standard composer
|
||||
/// draws it as no width at all, not as a missing-glyph box.
|
||||
#[test]
|
||||
fn standard_composers_give_format_characters_no_width() {
|
||||
let db = db();
|
||||
let text = "क\u{200C}क";
|
||||
for composer in [Composer::StandardSingleLine, Composer::StandardEveryLine] {
|
||||
let l = layout(&db, &object(text, composer));
|
||||
let widths: Vec<f64> = l.glyphs.iter().map(|g| g.advance).collect();
|
||||
assert_eq!(widths.len(), 3, "{composer:?}: {widths:?}");
|
||||
assert_eq!(widths[1], 0.0, "{composer:?}: the non-joiner takes no room: {widths:?}");
|
||||
assert!(widths[0] > 0.0 && widths[2] > 0.0, "{composer:?}: {widths:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"type.lineComposer",
|
||||
"Toggle Line Composer",
|
||||
"Cmd+Alt+Shift+C",
|
||||
"{} the selected type's paragraphs (or those the Type tool's selection touches) use the Single-line Composer when they all use the Every-line Composer, else the Every-line Composer, each keeping its shaping: Middle Eastern & South Asian or Standard (text.setFormat {composer}) → \"singleLine\" | \"everyLine\" | \"standardSingleLine\" | \"standardEveryLine\"",
|
||||
"{} the selected type's paragraphs (or those the Type tool's selection touches) use the Single-line Composer when they all use the Every-line Composer, else the Every-line Composer; paragraphs that all use a Standard composer keep it, any other mix takes the Middle Eastern & South Asian one (text.setFormat {composer}) → \"singleLine\" | \"everyLine\" | \"standardSingleLine\" | \"standardEveryLine\"",
|
||||
),
|
||||
(
|
||||
"type.hyphenate",
|
||||
|
||||
@@ -54,13 +54,15 @@ pub(crate) fn align(app: &mut VectorcraftApp, justify: &str) -> Result<Value, St
|
||||
}
|
||||
|
||||
/// `type.lineComposer` (Cmd+Alt+Shift+C): the Single-line Composer for the paragraphs the panel
|
||||
/// acts on when they all use the Every-line Composer, else the Every-line Composer; each keeps its
|
||||
/// shaping (a Standard composer toggles to the other Standard one). Returns the composer set.
|
||||
/// acts on when they all break their lines every-line (either shaping), else the Every-line
|
||||
/// Composer. Paragraphs that all use a Standard composer keep it; any other mix takes the Middle
|
||||
/// Eastern & South Asian one. Returns the composer set.
|
||||
pub(crate) fn toggle_composer(app: &mut VectorcraftApp) -> Result<Value, String> {
|
||||
text_style(app).ok_or("select some type first")?;
|
||||
let shared = shared_composer(app);
|
||||
let to_single = shared.is_some_and(|c| !c.is_single_line());
|
||||
let id = composer_id(shared.unwrap_or_default().with_breaking(to_single));
|
||||
let to_single = shared_para(app, |p| p.composer.is_single_line()) == Some(false);
|
||||
let standard = shared_para(app, |p| p.composer.shapes_complex_scripts()) == Some(false);
|
||||
let base = if standard { Composer::StandardEveryLine } else { Composer::EveryLine };
|
||||
let id = composer_id(base.with_breaking(to_single));
|
||||
format(app, json!({ "composer": id }));
|
||||
Ok(json!(id))
|
||||
}
|
||||
|
||||
@@ -97,11 +97,16 @@ fn toggling_the_line_composer_keeps_the_shaping() {
|
||||
assert_eq!(toggle_composer(&mut app).unwrap(), json!(first), "{from}");
|
||||
assert_eq!(toggle_composer(&mut app).unwrap(), json!(second), "{from} again");
|
||||
}
|
||||
// Paragraphs that differ take Every-line, as before the Standard composers existed.
|
||||
// Paragraphs that break differently take Every-line, as before the Standard composers existed.
|
||||
app.run("text.setFormat", json!({"id": id, "composer": "standardSingleLine"})).unwrap();
|
||||
app.run("text.setFormat", json!({"id": id, "start": 2, "end": 3, "composer": "singleLine"})).unwrap();
|
||||
app.session.set_tool_option("selectAll", &json!(true));
|
||||
assert_eq!(toggle_composer(&mut app).unwrap(), json!("everyLine"));
|
||||
// Paragraphs that both break every-line but differ in shaping: Single-line, Middle Eastern & South Asian.
|
||||
app.run("text.setFormat", json!({"id": id, "composer": "everyLine"})).unwrap();
|
||||
app.run("text.setFormat", json!({"id": id, "start": 2, "end": 3, "composer": "standardEveryLine"})).unwrap();
|
||||
app.session.set_tool_option("selectAll", &json!(true));
|
||||
assert_eq!(toggle_composer(&mut app).unwrap(), json!("singleLine"));
|
||||
}
|
||||
|
||||
/// The Paragraph panel menu lists the four composers, and ticks the one every selected paragraph has.
|
||||
@@ -126,15 +131,23 @@ fn the_paragraph_menu_sets_each_of_the_four_composers() {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let texts = items(&mut app);
|
||||
for label in [
|
||||
"Middle Eastern & South Asian Single-line Composer",
|
||||
"Middle Eastern & South Asian Every-line Composer",
|
||||
"Standard Single-line Composer",
|
||||
"Standard Every-line Composer",
|
||||
] {
|
||||
assert!(texts.iter().any(|t| t == label), "{label} in {texts:?}");
|
||||
let labels = [
|
||||
("singleLine", "Middle Eastern & South Asian Single-line Composer"),
|
||||
("everyLine", "Middle Eastern & South Asian Every-line Composer"),
|
||||
("standardSingleLine", "Standard Single-line Composer"),
|
||||
("standardEveryLine", "Standard Every-line Composer"),
|
||||
];
|
||||
// The menu lists all four, each row ticked (and only that one) when every selected paragraph has it.
|
||||
for (value, _) in labels {
|
||||
app.run("text.setFormat", json!({"id": id, "composer": value})).unwrap();
|
||||
app.session.set_tool_option("selectAll", &json!(true));
|
||||
let texts = items(&mut app);
|
||||
for (v, label) in labels {
|
||||
let row = if v == value { format!("✓ {label}") } else { format!(" {label}") };
|
||||
assert!(texts.iter().any(|t| *t == row), "{value}: {row:?} in {texts:?}");
|
||||
}
|
||||
}
|
||||
// Choosing a row sets its composer: the row is a `text.setFormat {composer}` with the row's id.
|
||||
for (value, want) in [
|
||||
("standardEveryLine", Composer::StandardEveryLine),
|
||||
("singleLine", Composer::SingleLine),
|
||||
@@ -142,6 +155,7 @@ fn the_paragraph_menu_sets_each_of_the_four_composers() {
|
||||
] {
|
||||
app.run("text.setFormat", json!({"id": id, "composer": value})).unwrap();
|
||||
assert_eq!(shared_composer(&app), Some(want), "{value}");
|
||||
assert_eq!(composer_id(want), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
File diff suppressed because one or more lines are too long
+6
-4
@@ -10485,8 +10485,9 @@ attributes as `para.composer`, saved only when it is not Every-line, which is th
|
||||
Standard composers existed). `singleLine` and `everyLine` shape Indic, Arabic and Hebrew type with the font's layout
|
||||
tables, as all type is set; `standardSingleLine` and `standardEveryLine` break lines the same way but set such type
|
||||
glyph by glyph as the font maps its characters (no conjuncts, reordering, mark placement or joining forms), for legacy
|
||||
fonts that keep a script on Latin character codes. Latin and other scripts keep their kerning and ligatures under every
|
||||
composer. An older VectorCraft cannot open a file that uses a Standard composer. `.ai`, EPS and PDF files carry no composer.
|
||||
fonts that keep a script on Latin character codes. The scripts a Standard composer leaves unshaped are Indic, Arabic and the
|
||||
other joining scripts (Syriac, N'Ko, Mongolian, Adlam…) and Hebrew; Latin, Greek, Cyrillic, CJK, Thai and the rest keep their
|
||||
kerning and ligatures under every composer. Letters left unshaped do not join, so Insert Kashidas adds no tatweel between them. An older VectorCraft cannot open a file that uses a Standard composer. `.ai`, EPS and PDF files carry no composer.
|
||||
Every-line, the default, picks the breaks of the whole paragraph together in area type:
|
||||
justified text gets even word spacing, ragged text (left, centre or right aligned) an even rag, so it may move a word
|
||||
that fits to the next line to avoid a lone short word on the last line or a full line next to a short one.
|
||||
@@ -10500,8 +10501,9 @@ no room for all of it, or when the search for its breaks passes VectorCraft's ow
|
||||
2,000,000 baseline steps for one paragraph, 2,000,000 lines and 4,000,000 steps for all the paragraphs of a frame).
|
||||
The UI command `type.lineComposer` (Cmd+Alt+Shift+C, in remote mode `press_key {key: "C", mods: {cmd: true, alt: true,
|
||||
shift: true}}` or `invoke_menu`) sets the Single-line form when every paragraph of the selected type (or every paragraph the
|
||||
Type tool's selection touches) uses an Every-line one, else the Every-line form, keeping each paragraph's shaping (`singleLine`
|
||||
or `everyLine`; a Standard composer toggles between `standardSingleLine` and `standardEveryLine`), and returns the composer it set.
|
||||
Type tool's selection touches) uses an Every-line one, else the Every-line form. Paragraphs that all use a Standard composer
|
||||
keep it (`standardSingleLine` or `standardEveryLine`); any other mix takes the Middle Eastern & South Asian one (`singleLine`
|
||||
or `everyLine`). It returns the composer it set.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"text.setFormat","params":{"ids":[7],"composer":"singleLine"}}}
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ against Illustrator's public documentation; **estimated** unless noted.
|
||||
| Middle Eastern: bidirectional layout, Paragraph Direction | done | PDF export keeps harakat and zero-advance Noto Arabic dot glyphs in one compatible batch per layout line, with per-glyph vertical offsets (#6309). `crates/pdf/src/tests/realtext.rs` (`mark_text_*`) checks decoded cluster mappings, mixed Arabic/Latin/digits, contour placement against outline export, vertical columns, differing path rotations and PDF/X-1a/3/4. Editable-text import still needs ActualText support (#6165); separately styled or differently rotated clusters and external viewer search/copy remain outside this export-only evidence. |
|
||||
| Middle Eastern and Indic: digit types (Character panel menu › Digits: Default, Arabic, Hindi, Farsi; Devanagari, Bengali, Gurmukhi, Gujarati, Odia, Tamil, Telugu, Kannada, Malayalam) | done | `text.setFormat` / `text.setRangeStyle {digits}` (`"devanagari"`, `"bengali"`, `"gurmukhi"`, `"gujarati"`, `"odia"`, `"tamil"`, `"telugu"`, `"kannada"`, `"malayalam"` added, #4942); the text keeps the typed digits, the glyphs and SVG export follow the style; carried by character styles. Not yet: `.ai` export of the setting; Indic digits typed as such stay as typed under another system. Evidence: `text::tests::indic_digits_map_to_their_scripts_block`, `tests::bidi::indic_digits_draw_the_scripts_own_digits`, `typecmd::area_tests::indic_digits_round_trip_through_the_commands_and_the_file` |
|
||||
| Middle Eastern: Kashidas | done | Paragraph Insert Kashidas sets the length for justified Arabic area type; Character-menu Kashidas allows or excludes selected characters, on by default. A kashida is inserted only between two allowed joined characters. The character switch is retained in character styles and the native file; the length is retained in paragraph styles and the native file. Paint-only harakat (coloured or hidden marks) stay in one shaping cluster so letters keep joining; when the character-level pass matches, a glyph may take a later in-cluster character's run only if GDEF names it a mark (class 3) and that character is a harakat—otherwise the cluster start (a mark may miss its colour; a base never takes a mark's). Inserted tatweels take the base letter's paint—not a trailing mark's. Fonts without GDEF and some AAT/`morx` ligatures (e.g. Geeza Pro Allah) can leave a mark painted as its letter. No Character-panel harakat colour command yet. Evidence: `tests::harakat`. [Public help](https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/justify-text-using-kashida-and-hyphenation.html). |
|
||||
| Middle Eastern & South Asian composers and Standard composers (Paragraph panel menu, paragraph styles, `text.setFormat {composer}`) | done / partial | #4935: per paragraph, four choices. Middle Eastern & South Asian Single-line and Every-line are how all type was already set (complex-script shaping on) and stay the default, so existing documents do not change. Standard Single-line and Every-line break lines the same way and set Indic, Arabic and Hebrew type glyph by glyph as the font maps its characters (no conjuncts, reordering, mark placement or joining), for legacy fonts that keep a script on Latin character codes; Latin and other scripts keep their kerning and ligatures. The native file and paragraph styles keep the choice (`standardSingleLine` / `standardEveryLine`; an older VectorCraft cannot read a file that uses them). `.ai`, EPS and PDF import and export carry no composer: this app has no recorded format for it, so a Standard paragraph opened from or saved to those files comes back Middle Eastern & South Asian. Evidence: `tests::composers`, `tests::panelcmds::text_set_format_takes_the_four_composers`, `native_v3::all_four_composers_round_trip` |
|
||||
| Middle Eastern & South Asian composers and Standard composers (Paragraph panel menu, paragraph styles, `text.setFormat {composer}`) | done / partial | #4935: per paragraph, four choices. Middle Eastern & South Asian Single-line and Every-line are how all type was already set (complex-script shaping on) and stay the default, so existing documents do not change. Standard Single-line and Every-line break lines the same way and set Indic, Arabic and Hebrew type glyph by glyph as the font maps its characters (no conjuncts, reordering, mark placement or joining), for legacy fonts that keep a script on Latin character codes. Indic, Arabic and the other joining scripts and Hebrew are the scripts left unshaped (their letters do not join, so Insert Kashidas adds no tatweel); Latin, Greek, Cyrillic, CJK, Thai and the rest keep their kerning and ligatures. The native file and paragraph styles keep the choice (`standardSingleLine` / `standardEveryLine`; an older VectorCraft cannot read a file that uses them). `.ai`, EPS and PDF import and export carry no composer: this app has no recorded format for it, so a Standard paragraph opened from or saved to those files comes back Middle Eastern & South Asian. Evidence: `tests::composers`, `tests::panelcmds::text_set_format_takes_the_four_composers`, `native_v3::all_four_composers_round_trip` |
|
||||
| Middle Eastern: split caret | missing | Character Direction is done; editable bidirectional SVG text remains incomplete. |
|
||||
| CJK: input methods (marked text, candidate window) | done | |
|
||||
| CJK: kinsoku (Hard, Soft, None), the predefined mojikumi sets (None, YakumonoHankaku, GyomatsuYakumonoHankaku, GyomatsuYakumonoZenkaku, YakumonoZenkaku, with VectorCraft's own spacing for each) and Line-end Punctuation Half Width, tate-chu-yoko (auto) | done / partial | custom kinsoku sets, custom mojikumi sets and the Mojikumi Settings dialog missing |
|
||||
|
||||
Reference in New Issue
Block a user