mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-14 14:22:39 +00:00
Merge pull request #2659 from dotCooCoo/feat/justification-auto-leading-single-word
Justification: the dialog sets Auto Leading and Single Word Justification
This commit is contained in:
16 files changed
+219
-3
No files matched your search
@@ -7689,7 +7689,7 @@ fn shown_rule(d: &Dialog, prefix: &str, which: &str, current: &Value) -> Value {
|
||||
}
|
||||
|
||||
/// The paragraph attributes of the Justification controls.
|
||||
pub const JUSTIFY_ATTRS: [&str; 12] = [
|
||||
pub const JUSTIFY_ATTRS: [&str; 14] = [
|
||||
"wordSpaceMin",
|
||||
"wordSpaceDesired",
|
||||
"wordSpaceMax",
|
||||
@@ -7699,6 +7699,8 @@ pub const JUSTIFY_ATTRS: [&str; 12] = [
|
||||
"glyphScaleMin",
|
||||
"glyphScaleDesired",
|
||||
"glyphScaleMax",
|
||||
"autoLeading",
|
||||
"singleWordJustify",
|
||||
"composer",
|
||||
"kashidas",
|
||||
"koreanCharBreaks",
|
||||
@@ -7711,9 +7713,35 @@ const JUSTIFY_ROWS: [(&str, &str, (f64, f64)); 3] = [
|
||||
("Glyph Scaling", "glyphScale", (50.0, 200.0)),
|
||||
];
|
||||
|
||||
/// The allowed Auto Leading, in percent of the type size.
|
||||
const AUTO_LEADING_RANGE: (f64, f64) = (0.0, 500.0);
|
||||
|
||||
/// The Auto Leading stored for a percent typed into its field: the whole percent the field shows.
|
||||
fn auto_leading_typed(pct: f64) -> f64 {
|
||||
// Adding 0.0 turns a rounded -0 into 0.
|
||||
(pct.round() + 0.0) / 100.0
|
||||
}
|
||||
|
||||
/// The Single Word Justification choices: (attribute value, label).
|
||||
const SINGLE_WORD_CHOICES: [(&str, &str); 4] =
|
||||
[("left", "Align Left"), ("center", "Align Center"), ("right", "Align Right"), ("fullyJustified", "Full Justify")];
|
||||
|
||||
/// Checks the edited spacing values: each within its range, and minimum ≤ desired ≤ maximum
|
||||
/// (values left mixed are not compared).
|
||||
/// (values left mixed are not compared); an edited Auto Leading within its range, and an edited
|
||||
/// Single Word Justification one of its choices.
|
||||
fn check_justification(d: &Dialog, prefix: &str, current: &Value) -> Result<(), String> {
|
||||
if let Some(v) = d.fields.get(&format!("{prefix}autoLeading")) {
|
||||
let (lo, hi) = AUTO_LEADING_RANGE;
|
||||
let pct = v.as_f64().map(|x| x * 100.0).filter(|x| x.is_finite()).ok_or("Justification: Auto Leading is not a number")?;
|
||||
if !(lo..=hi).contains(&pct) {
|
||||
return Err(format!("Justification: Auto Leading must be between {lo}% and {hi}%"));
|
||||
}
|
||||
}
|
||||
if let Some(v) = d.fields.get(&format!("{prefix}singleWordJustify"))
|
||||
&& !SINGLE_WORD_CHOICES.iter().any(|(k, _)| v.as_str() == Some(k))
|
||||
{
|
||||
return Err(format!("Justification: Single Word Justification cannot be {v}"));
|
||||
}
|
||||
for (label, stem, (lo, hi)) in JUSTIFY_ROWS {
|
||||
let mut edited = false;
|
||||
let mut known = Vec::new();
|
||||
@@ -7916,6 +7944,35 @@ fn justification(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, prefix: &str, cu
|
||||
}
|
||||
ui.end_row();
|
||||
}
|
||||
crate::rtl::label(ui, crate::i18n::tr(lang, "Auto Leading:"));
|
||||
let auto = cur(d, "autoLeading").as_f64().map(|x| x * 100.0);
|
||||
if let Some(n) = ui.scope(|ui| crate::widgets::number(ui, &format!("ps{prefix}autoLeading"), auto, "%", 60.0, 0)).inner {
|
||||
d.fields.insert(format!("{prefix}autoLeading"), json!(auto_leading_typed(n)));
|
||||
}
|
||||
ui.end_row();
|
||||
let single_label = crate::rtl::label(ui, crate::i18n::tr(lang, "Single Word Justification:"));
|
||||
let single = cur(d, "singleWordJustify");
|
||||
// Mixed (null) shows blank until a choice is made.
|
||||
let shown = SINGLE_WORD_CHOICES.iter().find(|(k, _)| single.as_str() == Some(k)).map_or("", |(_, label)| label);
|
||||
craft_ui::ComboBox::from_id_salt(("singleWordJustify", prefix))
|
||||
.selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, shown)))
|
||||
.width(130.0)
|
||||
.show_ui(ui, |ui| {
|
||||
for (v, label) in SINGLE_WORD_CHOICES {
|
||||
if ui.selectable_label(single.as_str() == Some(v), crate::rtl::widget(ui, crate::i18n::tr(lang, label))).clicked() {
|
||||
// The paragraphs' own choice is no edit, so OK adds no undo step for it.
|
||||
let key = format!("{prefix}singleWordJustify");
|
||||
if current["singleWordJustify"].as_str() == Some(v) {
|
||||
d.fields.remove(&key);
|
||||
} else {
|
||||
d.fields.insert(key, json!(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.response
|
||||
.labelled_by(single_label.id);
|
||||
ui.end_row();
|
||||
crate::rtl::label(ui, crate::i18n::tr(lang, "Composer:"));
|
||||
let composer = cur(d, "composer");
|
||||
// Mixed (null) selects neither; a Japanese composer keeps its kind.
|
||||
|
||||
@@ -1130,6 +1130,129 @@ fn justification_dialog_leaves_mixed_fields_alone_and_refuses_bad_values() {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Justification dialog shows Auto Leading and Single Word Justification, OK sets them on the
|
||||
/// selected paragraph as one undo step, bad values keep the dialog open, and both values survive
|
||||
/// saving and opening the native file and an IDML export.
|
||||
#[test]
|
||||
fn justification_dialog_sets_auto_leading_and_single_word_justification() {
|
||||
use egui_kittest::{Harness, kittest::Queryable};
|
||||
let (mut app, story) = two_paragraphs();
|
||||
app.run("app.justificationDialog", json!({})).unwrap();
|
||||
let d = app.ui.dialog.clone().unwrap();
|
||||
let current = d.fields["current"].clone();
|
||||
assert_eq!((current["autoLeading"].clone(), current["singleWordJustify"].clone()), (json!(1.2), json!("fullyJustified")));
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1000.0, 800.0)).build_ui_state(
|
||||
|ui, app: &mut DesignApp| {
|
||||
let key = egui::Id::unique("justification_single_word_fonts");
|
||||
if ui.data(|d| d.get_temp::<bool>(key)).is_none() {
|
||||
crate::theme::install_fonts(ui.ctx(), "");
|
||||
ui.data_mut(|d| d.insert_temp(key, true));
|
||||
return;
|
||||
}
|
||||
show(app, ui.ctx());
|
||||
},
|
||||
app,
|
||||
);
|
||||
h.run_steps(3);
|
||||
assert!(h.query_by_label("Auto Leading:").is_some());
|
||||
assert!(h.query_by_label("Single Word Justification:").is_some());
|
||||
use egui::accesskit::Role;
|
||||
// The dropdown shows the paragraph's Full Justify. Picking Full Justify again is no edit.
|
||||
h.get_by(|n| n.role() == Role::ComboBox && n.value().as_deref() == Some("Full Justify")).click();
|
||||
h.run_steps(3);
|
||||
h.get_by(|n| n.role() != Role::ComboBox && n.label().as_deref() == Some("Full Justify")).click();
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().ui.dialog.as_ref().unwrap().fields.contains_key("singleWordJustify"), "the paragraph's own choice is no edit");
|
||||
// Pick Align Center.
|
||||
h.get_by(|n| n.role() == Role::ComboBox && n.value().as_deref() == Some("Full Justify")).click();
|
||||
h.run_steps(3);
|
||||
h.get_by_label("Align Center").click();
|
||||
h.run_steps(3);
|
||||
let mut app = std::mem::replace(h.state_mut(), DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()));
|
||||
drop(h);
|
||||
let ctx = egui::Context::default();
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().fields["singleWordJustify"], "center", "the choice is the edit");
|
||||
|
||||
// Out of range or not a choice: an error, the dialog stays open, nothing changes.
|
||||
let d = app.ui.dialog.clone().unwrap();
|
||||
let before = undo_steps(&app);
|
||||
let bad = [
|
||||
("autoLeading", json!(5.01)),
|
||||
("autoLeading", json!(-0.01)),
|
||||
("autoLeading", json!(f64::NAN)),
|
||||
("singleWordJustify", json!("leftJustified")),
|
||||
];
|
||||
for (key, v) in bad {
|
||||
app.ui.dialog = Some(d.clone());
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert(key.into(), v.clone());
|
||||
assert!(confirm(&mut app).is_err(), "{key} = {v}");
|
||||
assert!(app.ui.dialog.as_ref().is_some_and(|d| d.fields.contains_key("justifyError")), "{key}: the dialog shows why");
|
||||
}
|
||||
assert_eq!(undo_steps(&app), before);
|
||||
|
||||
// A typed percent is stored as the whole percent the field shows, so 500.4% (shown as 500%)
|
||||
// and -0.4% (shown as 0%) are accepted.
|
||||
for (typed, stored) in [(500.4, 5.0), (-0.4, 0.0), (149.6, 1.5)] {
|
||||
assert_eq!(auto_leading_typed(typed), stored, "{typed}%");
|
||||
let mut probe = d.clone();
|
||||
probe.fields.insert("autoLeading".into(), json!(auto_leading_typed(typed)));
|
||||
assert_eq!(check_justification(&probe, "", ¤t), Ok(()), "{typed}%");
|
||||
}
|
||||
|
||||
app.ui.dialog = Some(d);
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("autoLeading".into(), json!(1.5));
|
||||
draw(&mut app, &ctx);
|
||||
confirm(&mut app).unwrap();
|
||||
assert_eq!(undo_steps(&app), before + 1, "one undo step");
|
||||
let both = |app: &mut DesignApp, story: &Value, at| (para_attr(app, story, at, "autoLeading"), para_attr(app, story, at, "singleWordJustify"));
|
||||
assert_eq!(both(&mut app, &story, 0), (json!(1.5), json!("center")));
|
||||
assert_eq!(both(&mut app, &story, 4), (json!(1.2), json!("fullyJustified")), "only the selected paragraph");
|
||||
|
||||
// Both paragraphs selected: the fields that differ show blank, and OK with no edit adds no undo step.
|
||||
app.run("text.select", json!({"story": story, "anchor": 0, "focus": 7})).unwrap();
|
||||
app.run("app.justificationDialog", json!({})).unwrap();
|
||||
draw(&mut app, &ctx);
|
||||
let current = app.ui.dialog.as_ref().unwrap().fields["current"].clone();
|
||||
assert_eq!((current["autoLeading"].clone(), current["singleWordJustify"].clone()), (Value::Null, Value::Null));
|
||||
let before = undo_steps(&app);
|
||||
confirm(&mut app).unwrap();
|
||||
assert_eq!(undo_steps(&app), before);
|
||||
|
||||
// Saved and opened again, natively and through IDML.
|
||||
let native = app.run("file.serialize", json!({})).unwrap();
|
||||
app.run("file.openBytes", json!({"name": "justify.designcraft", "base64": native["base64"]})).unwrap();
|
||||
assert_eq!(both(&mut app, &story, 0), (json!(1.5), json!("center")));
|
||||
let idml = app.run("file.exportIdml", json!({})).unwrap();
|
||||
app.run("file.openBytes", json!({"name": "justify.idml", "base64": idml["base64"]})).unwrap();
|
||||
let doc = &app.session.doc().unwrap().doc;
|
||||
let sid = doc.stories.iter().find(|(_, s)| s.text == "One\nTwo").map(|(id, _)| *id).expect("the story comes back");
|
||||
let story = serde_json::to_value(sid).unwrap();
|
||||
assert_eq!(both(&mut app, &story, 0), (json!(1.5), json!("center")));
|
||||
assert_eq!(both(&mut app, &story, 4), (json!(1.2), json!("fullyJustified")));
|
||||
}
|
||||
|
||||
/// Paragraph Style Options › Justification sets Auto Leading and Single Word Justification on the
|
||||
/// style, and refuses an Auto Leading out of range.
|
||||
#[test]
|
||||
fn paragraph_style_options_justification_sets_auto_leading_and_single_word_justification() {
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
app.run("style.paragraph.create", json!({"name": "Headline"})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
app.ui.dialog = Some(Dialog::new("paragraphStyleOptions", json!({"name": "Headline", "section": "justify"})));
|
||||
draw(&mut app, &ctx);
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("p.autoLeading".into(), json!(6.0));
|
||||
assert!(confirm(&mut app).unwrap_err().contains("Auto Leading"));
|
||||
let style = |app: &DesignApp| app.session.doc().unwrap().doc.styles.para("Headline").unwrap().para.clone();
|
||||
assert_eq!(style(&app).auto_leading, None, "nothing saved");
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("p.autoLeading".into(), json!(1.0));
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("p.singleWordJustify".into(), json!("right"));
|
||||
draw(&mut app, &ctx);
|
||||
confirm(&mut app).unwrap();
|
||||
let para = style(&app);
|
||||
assert_eq!((para.auto_leading, para.single_word_justify), (Some(1.0), Some(designcraft_doc::Align::Right)));
|
||||
}
|
||||
|
||||
fn command_app(id: &str, fields: Value) -> DesignApp {
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.ui.dialog = Some(Dialog::new(&format!("cmd:{id}"), fields));
|
||||
|
||||
@@ -611,6 +611,9 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("In justified Arabic text", "في النص العربي المضبوط"),
|
||||
("Korean Line Breaks:", "فواصل الأسطر الكورية:"),
|
||||
("Between syllables (not only at spaces)", "بين المقاطع (وليس عند المسافات فقط)"),
|
||||
("Auto Leading:", "التباعد التلقائي بين الأسطر:"),
|
||||
("Single Word Justification:", "ضبط الكلمة المفردة:"),
|
||||
("Full Justify", "ضبط كامل"),
|
||||
("Minimum Space Before First Footnote:", "أقل مسافة قبل الحاشية الأولى:"),
|
||||
("Space Between Footnotes:", "المسافة بين الحواشي:"),
|
||||
("Offset:", "الإزاحة:"),
|
||||
|
||||
@@ -1185,6 +1185,9 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("In justified Arabic text", "V arabském textu zarovnaném do bloku"),
|
||||
("Korean Line Breaks:", "Zalomení řádků v korejštině:"),
|
||||
("Between syllables (not only at spaces)", "Mezi slabikami (nejen u mezer)"),
|
||||
("Auto Leading:", "Automatický proklad:"),
|
||||
("Single Word Justification:", "Zarovnání jednoho slova:"),
|
||||
("Full Justify", "Plné zarovnání"),
|
||||
("Binding:", "Vazba:"),
|
||||
("Ruby:", "Ruby:"),
|
||||
("Set over the selected text; empty removes it.", "Umístí se nad vybraný text; prázdné pole ho odstraní."),
|
||||
|
||||
@@ -1155,6 +1155,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("In justified Arabic text", "Im arabischen Blocksatz"),
|
||||
("Korean Line Breaks:", "Koreanische Zeilenumbrüche:"),
|
||||
("Between syllables (not only at spaces)", "Zwischen Silben (nicht nur an Leerzeichen)"),
|
||||
("Auto Leading:", "Automatischer Zeilenabstand:"),
|
||||
("Single Word Justification:", "Ausrichtung einzelner Wörter:"),
|
||||
("Binding:", "Bindung:"),
|
||||
("Ruby:", "Ruby:"),
|
||||
("Set over the selected text; empty removes it.", "Wird über den ausgewählten Text gesetzt; leer entfernt es."),
|
||||
|
||||
@@ -156,6 +156,9 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Before", "Előtte"),
|
||||
("Before Last:", "Az utolsó előtt:"),
|
||||
("Between syllables (not only at spaces)", "Szótagok között (nem csak szóközöknél)"),
|
||||
("Auto Leading:", "Automatikus sorköz:"),
|
||||
("Single Word Justification:", "Egyetlen szó igazítása:"),
|
||||
("Full Justify", "Teljes sorkizárás"),
|
||||
("Bevel", "Levágott sarok"),
|
||||
("Bevel and Emboss", "Domborítás"),
|
||||
("Beveled Rectangle", "Levágott sarkú téglalap"),
|
||||
|
||||
@@ -1329,6 +1329,9 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("In justified Arabic text", "Nel testo arabo giustificato"),
|
||||
("Korean Line Breaks:", "Interruzioni di riga coreane:"),
|
||||
("Between syllables (not only at spaces)", "Tra le sillabe (non solo agli spazi)"),
|
||||
("Auto Leading:", "Interlinea automatica:"),
|
||||
("Single Word Justification:", "Giustificazione parola singola:"),
|
||||
("Full Justify", "Giustifica tutto"),
|
||||
("Binding:", "Rilegatura:"),
|
||||
("Ruby:", "Ruby:"),
|
||||
("Set over the selected text; empty removes it.", "Applicato sopra il testo selezionato; se vuoto viene rimosso."),
|
||||
|
||||
@@ -1351,6 +1351,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("In justified Arabic text", "両端揃えのアラビア語テキストで使用"),
|
||||
("Korean Line Breaks:", "韓国語の改行:"),
|
||||
("Between syllables (not only at spaces)", "音節間で改行 (スペース以外も可)"),
|
||||
("Auto Leading:", "自動行送り:"),
|
||||
("Single Word Justification:", "単語のみの行の揃え:"),
|
||||
("Binding:", "綴じ方向:"),
|
||||
("Ruby:", "ルビ:"),
|
||||
("Set over the selected text; empty removes it.", "選択したテキストの上にルビを付けます。空欄にすると削除します。"),
|
||||
|
||||
@@ -1254,6 +1254,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("In justified Arabic text", "ორმხრივად სწორებულ არაბულ ტექსტში"),
|
||||
("Korean Line Breaks:", "კორეული სტრიქონების წყვეტა:"),
|
||||
("Between syllables (not only at spaces)", "მარცვლებს შორის (არა მხოლოდ ჰარებთან)"),
|
||||
("Auto Leading:", "ავტომატური სტრიქონთაშორისი:"),
|
||||
("Single Word Justification:", "ერთი სიტყვის სწორება:"),
|
||||
("Binding:", "აკინძვა:"),
|
||||
("Ruby:", "რუბი:"),
|
||||
("Set over the selected text; empty removes it.", "დაისმება მონიშნული ტექსტის თავზე; ცარიელი წაშლის მას."),
|
||||
|
||||
@@ -1198,6 +1198,9 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("In justified Arabic text", "W justowanym tekście arabskim"),
|
||||
("Korean Line Breaks:", "Podział wierszy w koreańskim:"),
|
||||
("Between syllables (not only at spaces)", "Między sylabami (nie tylko na spacjach)"),
|
||||
("Auto Leading:", "Interlinia automatyczna:"),
|
||||
("Single Word Justification:", "Justowanie pojedynczego słowa:"),
|
||||
("Full Justify", "Pełne justowanie"),
|
||||
("Binding:", "Oprawa:"),
|
||||
("Ruby:", "Ruby:"),
|
||||
("Set over the selected text; empty removes it.", "Nad zaznaczonym tekstem; puste usuwa."),
|
||||
|
||||
@@ -1962,6 +1962,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Utilities", "Utilitários"),
|
||||
("Korean Line Breaks:", "Quebras de linha em coreano:"),
|
||||
("Between syllables (not only at spaces)", "Entre sílabas (não apenas nos espaços)"),
|
||||
("Auto Leading:", "Entrelinha automática:"),
|
||||
("Single Word Justification:", "Justificação de palavra única:"),
|
||||
("Show Font Names in English", "Mostrar nomes de fontes em inglês"),
|
||||
("Missing Glyphs", "Glifos ausentes"),
|
||||
("Draw Missing Glyphs from Fallback Fonts", "Desenhar glifos ausentes com fontes substitutas"),
|
||||
|
||||
@@ -1175,6 +1175,9 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("In justified Arabic text", "В арабском тексте, выровненном по ширине"),
|
||||
("Korean Line Breaks:", "Переносы корейского текста:"),
|
||||
("Between syllables (not only at spaces)", "Между слогами (а не только на пробелах)"),
|
||||
("Auto Leading:", "Автоинтерлиньяж:"),
|
||||
("Single Word Justification:", "Выключка одного слова:"),
|
||||
("Full Justify", "Полная выключка"),
|
||||
("Binding:", "Переплет:"),
|
||||
("Ruby:", "Руби:"),
|
||||
("Set over the selected text; empty removes it.", "Размещается над выделенным текстом; пустое значение удаляет подпись."),
|
||||
|
||||
@@ -409,6 +409,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("In justified Arabic text", "I marginaljusterad arabisk text"),
|
||||
("Korean Line Breaks:", "Koreanska radbrytningar:"),
|
||||
("Between syllables (not only at spaces)", "Mellan stavelser (inte bara vid blanksteg)"),
|
||||
("Auto Leading:", "Automatiskt radavstånd:"),
|
||||
("Single Word Justification:", "Justering av enstaka ord:"),
|
||||
("Minimum Space Before First Footnote:", "Minsta avstånd före första fotnoten:"),
|
||||
("Space Between Footnotes:", "Avstånd mellan fotnoter:"),
|
||||
("Offset:", "Förskjutning:"),
|
||||
|
||||
@@ -3480,6 +3480,11 @@ pub(super) const TABLE: &[(&str, [&str; 5])] = &[
|
||||
"在音节之间(不仅在空格处)",
|
||||
],
|
||||
),
|
||||
("Auto Leading:", ["Automatischer Zeilenabstand:", "Interlignage automatique :", "Interlineado automático:", "自動行送り:", "自动行距:"]),
|
||||
(
|
||||
"Single Word Justification:",
|
||||
["Ausrichtung einzelner Wörter:", "Justification des mots isolés :", "Justificación de palabra única:", "単語のみの行の揃え:", "单字齐行:"],
|
||||
),
|
||||
("Binding:", ["Binding:", "Reliure :", "Binding:", "Binding:", "装订方向:"]),
|
||||
("Ruby:", ["Ruby:", "Ruby :", "Ruby:", "Ruby:", "旁注:"]),
|
||||
(
|
||||
|
||||
@@ -1326,6 +1326,9 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("In justified Arabic text", "В арабському тексті, вирівняному за шириною"),
|
||||
("Korean Line Breaks:", "Переноси корейського тексту:"),
|
||||
("Between syllables (not only at spaces)", "Між складами (а не лише на пробілах)"),
|
||||
("Auto Leading:", "Автоінтерліньяж:"),
|
||||
("Single Word Justification:", "Вирівнювання одного слова:"),
|
||||
("Full Justify", "Повне вирівнювання"),
|
||||
("Binding:", "Напрямок палітурки:"),
|
||||
("Ruby:", "Фонетичні підписи:"),
|
||||
("Set over the selected text; empty removes it.", "Розміщується над виділеним текстом; порожнє значення вилучає підпис."),
|
||||
|
||||
@@ -35,7 +35,7 @@ Fidelity: **unmeasured** unless stated.
|
||||
| Paragraph Composer | Adobe's multi-line composer | Knuth–Plass total fit, fitness classes, hyphen penalties | ~ | same family of algorithm, Adobe's weights unknown; breaks will differ on some paragraphs |
|
||||
| Single-line Composer | greedy | greedy with the same ranges | ✓ | |
|
||||
| World-Ready composers | Paragraph / Single-line World-Ready | RTL, Arabic and bidi in the main composer | ~ | Indic scripts (Devanagari etc.) shape through harfrust but are untested |
|
||||
| Justification | word/letter spacing, glyph scaling min/desired/max, single-word, last line | all of these | ✓ | a justified line with a tab justifies after the tab (#284); set per paragraph in Type › Justification… and the Paragraph panel menu (#531) |
|
||||
| Justification | word/letter spacing, glyph scaling min/desired/max, single-word, last line | all of these | ✓ | a justified line with a tab justifies after the tab (#284); set per paragraph in Type › Justification… and the Paragraph panel menu (#531); Auto Leading (0% to 500%) and Single Word Justification are set there and in Paragraph Style Options › Justification |
|
||||
| Hyphenation | per-language dictionaries (~40), zone, limits, capitalized, last word, across columns | Liang patterns: English (trained from the public-domain Moby list), Spanish (Bezos), Portuguese (hyph-pt), Norwegian (hyph-no), Danish (hyph-da), Greek (hyph-el-monoton), German 2006 (hyph-de-1996), French (hyph-fr), Italian (hyph-it) and Dutch (Tutelaers); zone, limits, exceptions in the user dictionary; Hyphenate Across Column off keeps automatic hyphens off the last line of a column or frame; discretionary U+00AD breaks with both composers regardless of automatic hyphenation and replaces pattern points within its word (#1955) | ~ | other languages don't hyphenate (#87 reported "doesn't work") |
|
||||
| Spelling | per-language dictionaries | English (Moby) + document/user dictionary | ~ | |
|
||||
| Kerning | Metrics, Optical, manual, none | Metrics, Optical (outline-based: pairs pulled towards the font's stem gap, `crates/compose/src/optical.rs`), manual, none | ~ | Optical is an approximation of Adobe's algorithm (#181), not a match: imported display text can still break differently |
|
||||
|
||||
Reference in new issue
Block a user