Merge #1565: Hyphenation Settings: InDesign's dialog from the Paragraph panel menu, and in Paragraph Style Options

This commit is contained in:
aiongg
2026-10-24 05:52:41 +00:00
15 changed files with 440 additions and 24 deletions
+1
View File
@@ -357,6 +357,7 @@ Newest first.
- **2026-10-22** · Autoflow (Shift-click with the loaded text cursor, autoflow on Place, `text.thread {autoflow}`) fills the pages that follow before it adds any, as InDesign does: one frame per column on each following page, whatever else the page holds, then new pages at the end of the document. It inserted pages right after the story's last frame, in front of the document's own pages. Frames it set past the end of the text go again; a page the document had stays, and a page autoflow added only for such a frame goes with it. Smart Text Reflow of the primary text frame is unchanged. No readiness percentage change.
- **2026-10-16** · Edit › Keyboard Shortcuts imports a set saved by InDesign's Show Set (.txt, Windows or Mac key names, UTF-8 or UTF-16): matched commands take its Default shortcuts or lose theirs, other commands give up keys it takes, and a report lists what it changed and the items it could not match (`window.importShortcuts`, #1180). Tool keys stay fixed. No readiness percentage change.
- **2026-10-23** · Translations: a missing string shows in English and fails no test; `cargo xtask i18n-report`, run by `cargo xtask ci`, prints the missing strings per language. Tests keep duplicate-key and placeholder checks and the language mechanics (lookup, context captions, plural captions, right-to-left). No readiness percentage change.
- **2026-10-22** · Hyphenation Settings, as in InDesign: the Paragraph panel menu's Hyphenation… (`app.hyphenationDialog`) sets the selected paragraphs' Hyphenate, Words with at Least, After First, Before Last, Hyphen Limit, Hyphenation Zone, the Better Spacing ↔ Fewer Hyphens slider (InDesign's 0–10 steps), Hyphenate Capitalized Words, Last Word and Across Column, with Preview, as one undo step; Paragraph Style Options › Hyphenation has the same form. The panel menu lists its items in InDesign's order. Replicated from a hyphenation tutorial: the same text in Minion Pro breaks into the same lines as InDesign's with hyphenation on, off, and at Fewer Hyphens. Hyphenate Across Column is kept and round-trips through IDML, but composition doesn't apply it yet. No readiness percentage change.
- **2026-10-22** · A ruler guide dragged with the Selection tools, out of a ruler, on its own or with other selected guides, snaps along its axis as objects do (#1221): to other ruler guides, margins, columns, bleed, page and object edges and centers (with the smart-guide mark) and the document grid, within the snap zone and following Snap to Guides and Smart Guides. Shift snaps to the ruler's ticks and Cmd suspends snapping. The preview line, the position readout and the drop use the same snapped position. No readiness percentage change.
- **2026-10-22** · macOS: the app menu has a Language submenu after Settings… with each interface language in its own name and the current one checked, as in the suite's other apps; Preferences › General › Interface Language stays. No readiness percentage change.
- **2026-10-22** · macOS menu bar: the menus keep working after the interface language or Show CJK Features changes (a rebuilt menu bar lost every click, Quit included), and its key equivalents are the shortcuts in effect, so a shortcut moved in the Keyboard Shortcuts editor leaves its old menu item. No readiness percentage change.
+299 -21
View File
@@ -2068,6 +2068,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) {
},
),
"justification" => crate::i18n::tr(&app.ui.language, "Justification"),
"hyphenation" => crate::i18n::tr(&app.ui.language, "Hyphenation Settings"),
"insertXref" => crate::i18n::tr(&app.ui.language, "New Cross-Reference"),
"findFont" => crate::i18n::tr(&app.ui.language, "Find/Replace Font"),
"polygonSettings" => crate::i18n::tr(&app.ui.language, "Polygon Settings"),
@@ -2109,6 +2110,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) {
"alert" => 440.0,
"closeDocument" | "updateLink" | "deletePages" | "newWorkspace" | "pagesPanelOptions" => 380.0,
"newDocument" => 960.0,
"hyphenation" => 560.0,
"numberingSection" => 560.0,
crate::effects_dialog::ID => 720.0,
_ => 640.0,
@@ -2361,6 +2363,21 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) {
let current = d.fields.get("current").cloned().unwrap_or_default();
justification(&app.ui.language, ui, &mut d, "", &current);
}
"hyphenation" => {
let current = d.fields.get("current").cloned().unwrap_or_default();
let units = app.session.active().map_or(Unit::Picas, |st| st.doc.settings.horizontal_units);
let preview = crate::i18n::tr(&app.ui.language, "Preview").to_string();
ui.horizontal_top(|ui| {
ui.vertical(|ui| hyphenation(&app.ui.language, ui, &mut d, "", &current, units));
ui.add_space(24.0);
check(ui, &mut d, "preview", &preview);
});
hyphenation_preview(app, &mut d);
if let Some(st) = d.fields.get("status").and_then(Value::as_str) {
ui.add_space(6.0);
crate::rtl::label(ui, egui::RichText::new(st).color(ui.visuals().error_fg_color));
}
}
"insertXref" => insert_xref(app, ui, &mut d),
"findFont" => find_font(app, ui, &mut d),
"colorPicker" => color_picker(ui, &mut d),
@@ -3173,6 +3190,24 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> {
}
app.run("type.para", json!({"attrs": attrs}))
}
"hyphenation" => {
// The preview goes; the edits are made once, as one undo step.
if d.fields.contains_key(PREVIEWED) {
app.session.cancel_interaction();
}
let attrs = hyphenation_edits(&d);
if attrs.is_empty() {
return Ok(Value::Null);
}
app.run("type.para", json!({"attrs": attrs})).inspect_err(|e| {
// The dialog stays open, with the reason, to be corrected.
app.status(e.clone());
let mut d = d;
d.fields.remove(PREVIEWED);
d.fields.insert("status".into(), json!(e));
app.ui.dialog = Some(d);
})
}
"justification" => {
let current = d.fields.get("current").cloned().unwrap_or_default();
if let Err(e) = check_justification(&d, "", &current) {
@@ -4198,27 +4233,7 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo
}
"tabs" => style_tabs(&app.ui.language, ui, d, &pv, &doc),
"rules" => paragraph_rules(app, ui, d, "p.", &pv),
"hyph" => {
let mut h = cur(d, "p.hyphenate", &pv["hyphenate"]).as_bool().unwrap_or(true);
if ui.checkbox(&mut h, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Hyphenate"))).changed() {
d.fields.insert("p.hyphenate".into(), json!(h));
}
egui::Grid::new("psh").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| {
for (label, key) in [
("Words with at Least:", "hyphMinWord"),
("After First:", "hyphAfterFirst"),
("Before Last:", "hyphBeforeLast"),
("Hyphen Limit:", "hyphLimit"),
] {
crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label));
let v = cur(d, &format!("p.{key}"), &pv[key]).as_f64();
if let Some(n) = crate::widgets::number(ui, &format!("ps{key}"), v, "", 60.0, 0) {
d.fields.insert(format!("p.{key}"), json!(n.max(0.0) as u64));
}
ui.end_row();
}
});
}
"hyph" => hyphenation(&app.ui.language, ui, d, "p.", &pv, units),
"justify" => justification(&app.ui.language, ui, d, "p.", &pv),
"color" => character_color(&app.ui.language, ui, d, &CharFields { base: &cv, sparse: false }, &doc),
"advanced" => advanced_character_formats(&app.ui.language, ui, d, &CharFields { base: &cv, sparse: false }),
@@ -5812,6 +5827,149 @@ fn check_justification(d: &Dialog, prefix: &str, current: &Value) -> Result<(),
/// `current` holds the resolved paragraph attributes, `null` where the selected paragraphs differ
/// (the field shows empty); each edit is stored as `{prefix}{attr}`, so OK changes only the fields
/// edited.
/// Paragraph attributes the Hyphenation Settings dialog sets.
pub const HYPH_ATTRS: [&str; 10] = [
"hyphenate",
"hyphMinWord",
"hyphAfterFirst",
"hyphBeforeLast",
"hyphLimit",
"hyphZone",
"hyphWeight",
"hyphCapitalized",
"hyphLastWord",
"hyphAcrossColumn",
];
/// InDesign's Hyphenation Settings: Hyphenate; Words with at Least, After First and Before Last
/// (letters), Hyphen Limit (hyphens) and Hyphenation Zone; the Better Spacing ↔ Fewer Hyphens
/// slider (0–10, as InDesign's HyphenWeight); Hyphenate Capitalized Words, Last Word and Across
/// Column. Edited fields are stored as `{prefix}{key}`; the others show `current` (a mixed value
/// is null and shows blank). The Hyphenation dialog for the selected paragraphs and Paragraph
/// Style Options › Hyphenation both use it.
fn hyphenation(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, prefix: &str, current: &Value, units: Unit) {
let tr = |s: &str| crate::i18n::tr(lang, s).to_string();
let cur = |d: &Dialog, k: &str| d.fields.get(&format!("{prefix}{k}")).cloned().unwrap_or_else(|| current[k].clone());
// Labels right-aligned against their fields, as InDesign's.
let label_right = |ui: &mut egui::Ui, text: &str| {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.add(egui::Label::new(crate::rtl::widget(ui, text)).wrap_mode(egui::TextWrapMode::Extend))
});
};
let checkbox = |ui: &mut egui::Ui, d: &mut Dialog, key: &str, label: &str| {
let v = cur(d, key);
// Mixed shows indeterminate and starts off, so the first click turns it on for all.
let mut on = v.as_bool().unwrap_or(false);
if ui.add(egui::Checkbox::new(&mut on, crate::rtl::widget(ui, tr(label))).indeterminate(v.is_null())).changed() {
d.fields.insert(format!("{prefix}{key}"), json!(on));
}
};
checkbox(ui, d, "hyphenate", "Hyphenate");
ui.add_space(4.0);
egui::Grid::new(("hyph", prefix)).num_columns(3).spacing([8.0, 6.0]).show(ui, |ui| {
// InDesign's ranges.
for (label, key, unit, max) in [
("Words with at Least:", "hyphMinWord", "letters", 25.0),
("After First:", "hyphAfterFirst", "letters", 15.0),
("Before Last:", "hyphBeforeLast", "letters", 15.0),
("Hyphen Limit:", "hyphLimit", "hyphens", 25.0),
] {
label_right(ui, &tr(label));
let min = if key == "hyphLimit" { 0.0 } else { 1.0 };
if let Some(n) = crate::widgets::spin_number(ui, &format!("hy{prefix}{key}"), cur(d, key).as_f64(), "", 60.0, 0) {
d.fields.insert(format!("{prefix}{key}"), json!(n.round().clamp(min, max) as u64));
}
crate::rtl::label(ui, tr(unit));
ui.end_row();
}
label_right(ui, &tr("Hyphenation Zone:"));
if let Some(n) = crate::widgets::spin_measure(ui, &format!("hy{prefix}zone"), cur(d, "hyphZone").as_f64(), units, 60.0) {
d.fields.insert(format!("{prefix}hyphZone"), json!(if n.is_finite() { n.clamp(0.0, 8640.0) } else { 36.0 }));
}
ui.end_row();
});
ui.add_space(6.0);
// Better Spacing ↔ Fewer Hyphens, in InDesign's eleven steps.
ui.horizontal(|ui| {
crate::rtl::label(ui, tr("Better Spacing"));
let w = cur(d, "hyphWeight").as_f64();
let mut step = w.map_or(5.0, |w| (w * 10.0).round()).clamp(0.0, 10.0);
// The ticks go under the knob: their place in the paint order is taken first.
let ticks = ui.painter().add(egui::Shape::Noop);
let r = ui
.scope(|ui| {
ui.spacing_mut().slider_width = 180.0;
ui.add(egui::Slider::new(&mut step, 0.0..=10.0).step_by(1.0).show_value(false).handle_shape(egui::style::HandleShape::Circle))
})
.inner;
// The tick marks under the track.
let t = crate::theme::Tokens::get(ui.ctx());
let inner = r.rect.shrink2(egui::vec2(r.rect.height() / 2.0, 0.0));
let y = r.rect.center().y;
let marks: Vec<egui::Shape> = (0..=10)
.map(|i| {
let x = inner.left() + inner.width() * i as f32 / 10.0;
egui::Shape::line_segment([egui::pos2(x, y - 8.0), egui::pos2(x, y + 8.0)], egui::Stroke::new(1.0, t.text_dim))
})
.collect();
ui.painter().set(ticks, egui::Shape::Vec(marks));
if r.changed() {
d.fields.insert(format!("{prefix}hyphWeight"), json!(step / 10.0));
}
crate::rtl::label(ui, tr("Fewer Hyphens"));
});
ui.add_space(6.0);
egui::Grid::new(("hyphc", prefix)).num_columns(2).spacing([16.0, 6.0]).show(ui, |ui| {
checkbox(ui, d, "hyphCapitalized", "Hyphenate Capitalized Words");
checkbox(ui, d, "hyphLastWord", "Hyphenate Last Word");
ui.end_row();
checkbox(ui, d, "hyphAcrossColumn", "Hyphenate Across Column");
ui.end_row();
});
}
/// The Hyphenation dialog's edits, as `type.para` attrs.
fn hyphenation_edits(d: &Dialog) -> Map<String, Value> {
HYPH_ATTRS.iter().filter_map(|k| d.fields.get(*k).map(|v| (k.to_string(), v.clone()))).collect()
}
/// Preview on: the edits show on the page (redone when a field changes) without an undo step;
/// off, the paragraphs go back.
fn hyphenation_preview(app: &mut DesignApp, d: &mut Dialog) {
let shown = d.fields.get(PREVIEWED).cloned();
if !d.b("preview") {
if shown.is_some() {
app.session.cancel_interaction();
d.fields.remove(PREVIEWED);
}
return;
}
let params = json!({"attrs": hyphenation_edits(d)});
if shown.as_ref() == Some(&params) || d.fields.get(PREVIEW_FAILED) == Some(&params) {
return;
}
if shown.is_none() && app.session.begin_interaction("Hyphenation").is_err() {
return;
}
match app.session.preview("type.para", params.clone()) {
Ok(_) => {
d.fields.remove("status");
d.fields.remove(PREVIEW_FAILED);
d.fields.insert(PREVIEWED.into(), params);
}
// Rejected settings: the paragraphs go back, and the reason shows until they change.
Err(e) => {
app.session.cancel_interaction();
d.fields.remove(PREVIEWED);
d.fields.insert("status".into(), json!(e.to_string()));
d.fields.insert(PREVIEW_FAILED.into(), params);
}
}
}
/// The hyphenation settings a preview was refused for (not tried again until they change).
const PREVIEW_FAILED: &str = "previewFailed";
fn justification(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, prefix: &str, current: &Value) {
let cur = |d: &Dialog, k: &str| d.fields.get(&format!("{prefix}{k}")).cloned().unwrap_or_else(|| current[k].clone());
egui::Grid::new("psj").num_columns(4).spacing([8.0, 6.0]).show(ui, |ui| {
@@ -7633,6 +7791,126 @@ mod tests {
app.run("type.selectionAttrs", json!({})).unwrap()["para"][key].clone()
}
/// The hyphenation video (Graphd Tutorials, 0:39–1:13): the Paragraph panel menu's
/// Hyphenation… opens InDesign's Hyphenation Settings for the selected paragraphs. Preview
/// shows the slider moved to Fewer Hyphens on the page without an undo step, OK makes it one
/// step on the selected paragraph only, and Cancel puts it back.
#[test]
fn hyphenation_settings_preview_apply_and_cancel_on_the_selected_paragraph() {
let (mut app, story) = two_paragraphs();
let ctx = dialog_context("en");
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1400.0, 900.0));
let sid = designcraft_doc::StoryId(story.as_u64().unwrap());
let weight = |app: &DesignApp, para: usize| {
let d = &app.session.doc().unwrap().doc;
d.styles.resolve_para(&d.story(sid).unwrap().paras[para]).0.hyph_weight
};
assert!(crate::menus::menu_enabled(&app, "app.hyphenationDialog"));
app.run("app.hyphenationDialog", json!({})).unwrap();
for _ in 0..3 {
dialog_frame(&mut app, &ctx, screen, vec![]);
}
let labels = dialog_frame(&mut app, &ctx, screen, vec![]);
let d = app.ui.dialog.clone().unwrap();
assert_eq!(
(d.id.as_str(), d.fields["current"]["hyphWeight"].as_f64(), d.fields["current"]["hyphenate"].as_bool()),
("hyphenation", Some(0.5), Some(true))
);
for label in [
"Hyphenation Settings",
"Hyphenate",
"Words with at Least:",
"letters",
"Hyphen Limit:",
"hyphens",
"Hyphenation Zone:",
"Better Spacing",
"Fewer Hyphens",
"Hyphenate Capitalized Words",
"Hyphenate Last Word",
"Hyphenate Across Column",
"Preview",
] {
assert!(labels.iter().any(|(t, _, _)| t == label), "{label}: {labels:?}");
}
// Fewer Hyphens (9 of 10) with Preview: on the page, no undo step.
let before = undo_steps(&app);
let d = app.ui.dialog.as_mut().unwrap();
d.fields.insert("hyphWeight".into(), json!(0.9));
d.fields.insert("preview".into(), json!(true));
dialog_frame(&mut app, &ctx, screen, vec![]);
assert_eq!(weight(&app, 0), 0.9, "previewed");
assert_eq!(undo_steps(&app), before, "a preview is no step");
// Cancel: back as it was.
cancel(&mut app);
assert_eq!(weight(&app, 0), 0.5);
// Again, and OK: one step, the selected paragraph only.
app.run("app.hyphenationDialog", json!({})).unwrap();
let d = app.ui.dialog.as_mut().unwrap();
d.fields.insert("hyphWeight".into(), json!(0.9));
d.fields.insert("preview".into(), json!(true));
dialog_frame(&mut app, &ctx, screen, vec![]);
confirm(&mut app).unwrap();
assert_eq!((weight(&app, 0), weight(&app, 1)), (0.9, 0.5));
assert_eq!(undo_steps(&app), before + 1, "one undo step");
// Paragraph Style Options › Hyphenation has the same form.
app.ui.dialog = Some(Dialog::new("paragraphStyleOptions", json!({"name": designcraft_doc::story::BASIC_PARAGRAPH, "section": "hyph"})));
for _ in 0..3 {
dialog_frame(&mut app, &ctx, screen, vec![]);
}
let labels = dialog_frame(&mut app, &ctx, screen, vec![]);
for label in ["Hyphenation Zone:", "Better Spacing", "Fewer Hyphens", "Hyphenate Last Word", "Hyphenate Across Column"] {
assert!(labels.iter().any(|(t, _, _)| t == label), "style options: {label}");
}
app.ui.dialog = None;
app.run("edit.deselectAll", json!({})).unwrap();
assert!(!crate::menus::menu_enabled(&app, "app.hyphenationDialog"), "no paragraph");
}
/// Hyphenate differs across the selection: it shows mixed, and the first click turns it on for
/// all. Settings the command refuses show why: a preview goes back and isn't retried until they
/// change, and OK keeps the dialog open.
#[test]
fn hyphenation_settings_mixed_checkbox_and_refused_settings() {
let (mut app, story) = two_paragraphs();
let ctx = dialog_context("en");
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1400.0, 900.0));
app.run("type.para", json!({"attrs": {"hyphenate": false}})).unwrap();
app.run("text.select", json!({"story": story, "anchor": 0, "focus": 7})).unwrap();
app.run("app.hyphenationDialog", json!({})).unwrap();
assert!(app.ui.dialog.as_ref().unwrap().fields["current"]["hyphenate"].is_null(), "mixed");
for _ in 0..3 {
dialog_frame(&mut app, &ctx, screen, vec![]);
}
let labels = dialog_frame(&mut app, &ctx, screen, vec![]);
let at = labels.iter().find(|(t, _, _)| t == "Hyphenate").map(|(_, r, _)| r.left_center() - egui::vec2(10.0, 0.0)).unwrap();
click_dialog(&mut app, &ctx, screen, at);
assert_eq!(app.ui.dialog.as_ref().unwrap().fields.get("hyphenate"), Some(&json!(true)), "the first click turns it on");
// A refused setting (as a control-channel caller could send it): no preview, the reason.
let before = undo_steps(&app);
let d = app.ui.dialog.as_mut().unwrap();
d.fields.insert("hyphWeight".into(), json!("heavy"));
d.fields.insert("preview".into(), json!(true));
dialog_frame(&mut app, &ctx, screen, vec![]);
let d = app.ui.dialog.as_ref().unwrap();
assert!(!d.fields.contains_key(PREVIEWED) && d.fields.contains_key("status"), "{:?}", d.fields);
assert!(app.session.doc().unwrap().interaction.is_none(), "the preview went back");
dialog_frame(&mut app, &ctx, screen, vec![]);
assert!(app.session.doc().unwrap().interaction.is_none(), "not tried again");
// OK with it: an error, and the dialog stays open with the reason.
assert!(confirm(&mut app).is_err());
let d = app.ui.dialog.as_ref().expect("the dialog stays open");
assert!(d.fields.contains_key("status"));
assert_eq!(undo_steps(&app), before);
// Corrected, it previews and applies.
app.ui.dialog.as_mut().unwrap().fields.insert("hyphWeight".into(), json!(0.9));
dialog_frame(&mut app, &ctx, screen, vec![]);
assert!(!app.ui.dialog.as_ref().unwrap().fields.contains_key("status"));
confirm(&mut app).unwrap();
assert_eq!(para_attr(&mut app, &story, 0, "hyphenate"), true);
assert_eq!(para_attr(&mut app, &story, 4, "hyphWeight"), 0.9);
}
#[test]
fn justification_dialog_applies_to_a_local_paragraph_and_cancel_changes_nothing() {
let (mut app, story) = two_paragraphs();
+27
View File
@@ -1010,6 +1010,33 @@ const TABLE: &[(&str, [&str; 5])] = &[
],
),
("Load Styles", ["Formate laden", "Charger les styles", "Cargar estilos", "スタイルを読み込み", "加载样式"]),
("Hyphenation…", ["Silbentrennung …", "Césure…", "Separación por sílabas…", "ハイフネーション…", "连字…"]),
(
"Hyphenation Settings",
["Silbentrennungseinstellungen", "Paramètres de césure", "Configuración de separación por sílabas", "ハイフネーション設定", "连字设置"],
),
("hyphens", ["Trennstriche", "tirets", "guiones", "ハイフン", "连字符"]),
("Hyphenation Zone:", ["Silbentrennungsbereich:", "Zone de césure :", "Zona de separación:", "ハイフネーション領域 :", "连字区:"]),
("Better Spacing", ["Bessere Abstände", "Meilleur espacement", "Mejor espaciado", "スペースを優先", "更好的间距"]),
("Fewer Hyphens", ["Weniger Trennstriche", "Moins de tirets", "Menos guiones", "ハイフンを減らす", "更少的连字符"]),
(
"Hyphenate Capitalized Words",
[
"Großgeschriebene Wörter trennen",
"Césure des mots en capitales",
"Separar palabras en mayúscula",
"大文字で始まる単語をハイフネーション",
"连字大写的单词",
],
),
(
"Hyphenate Last Word",
["Letztes Wort trennen", "Césure du dernier mot", "Separar última palabra", "最後の単語をハイフネーション", "连字最后一个单词"],
),
(
"Hyphenate Across Column",
["Über Spalten trennen", "Césure entre colonnes", "Separar entre columnas", "段をまたいでハイフネーション", "跨栏连字"],
),
(
"Differs from this document",
["Weicht von diesem Dokument ab", "Diffère de ce document", "Difiere de este documento", "このドキュメントと異なります", "与此文档不同"],
+9
View File
@@ -30,6 +30,15 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "قص"),
("Copy", "نسخ"),
("Paste", "لصق"),
("Hyphenation…", "الواصلة…"),
("Hyphenation Settings", "إعدادات الواصلة"),
("hyphens", "واصلات"),
("Hyphenation Zone:", "منطقة الواصلة:"),
("Better Spacing", "تباعد أفضل"),
("Fewer Hyphens", "واصلات أقل"),
("Hyphenate Capitalized Words", "وصل الكلمات ذات الأحرف الكبيرة"),
("Hyphenate Last Word", "وصل الكلمة الأخيرة"),
("Hyphenate Across Column", "الوصل عبر الأعمدة"),
("Paste Vector Art", "لصق رسم متجه"),
("Baseline Options", "خيارات خط الأساس"),
("Fixed Number", "عدد ثابت"),
+9
View File
@@ -31,6 +31,15 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Vyjmout"),
("Copy", "Kopírovat"),
("Paste", "Vložit"),
("Hyphenation…", "Dělení slov…"),
("Hyphenation Settings", "Nastavení dělení slov"),
("hyphens", "spojovníky"),
("Hyphenation Zone:", "Zóna dělení:"),
("Better Spacing", "Lepší mezery"),
("Fewer Hyphens", "Méně spojovníků"),
("Hyphenate Capitalized Words", "Dělit slova s velkým počátečním písmenem"),
("Hyphenate Last Word", "Dělit poslední slovo"),
("Hyphenate Across Column", "Dělit přes sloupce"),
("Reverse Path", "Obrátit cestu"),
("Set Anchor", "Nastavit kotvu"),
("Clear", "Vymazat"),
+9
View File
@@ -31,6 +31,15 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Ausschneiden"),
("Copy", "Kopieren"),
("Paste", "Einfügen"),
("Hyphenation…", "Silbentrennung …"),
("Hyphenation Settings", "Silbentrennungseinstellungen"),
("hyphens", "Trennstriche"),
("Hyphenation Zone:", "Silbentrennungsbereich:"),
("Better Spacing", "Bessere Abstände"),
("Fewer Hyphens", "Weniger Trennstriche"),
("Hyphenate Capitalized Words", "Großgeschriebene Wörter trennen"),
("Hyphenate Last Word", "Letztes Wort trennen"),
("Hyphenate Across Column", "Über Spalten trennen"),
("Inches Decimal", "Zoll dezimal"),
("Back", "Zurück"),
("Warning", "Warnung"),
+9
View File
@@ -28,6 +28,15 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Taglia"),
("Copy", "Copia"),
("Paste", "Incolla"),
("Hyphenation…", "Sillabazione…"),
("Hyphenation Settings", "Impostazioni sillabazione"),
("hyphens", "trattini"),
("Hyphenation Zone:", "Area di sillabazione:"),
("Better Spacing", "Spaziatura migliore"),
("Fewer Hyphens", "Meno trattini"),
("Hyphenate Capitalized Words", "Sillaba parole con iniziale maiuscola"),
("Hyphenate Last Word", "Sillaba ultima parola"),
("Hyphenate Across Column", "Sillaba tra colonne"),
("Paste Vector Art", "Incolla grafica vettoriale"),
("Baseline Options", "Opzioni linea di base"),
("Fixed Number", "Numero fisso"),
+9
View File
@@ -30,6 +30,15 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "カット"),
("Copy", "コピー"),
("Paste", "ペースト"),
("Hyphenation…", "ハイフネーション…"),
("Hyphenation Settings", "ハイフネーション設定"),
("hyphens", "ハイフン"),
("Hyphenation Zone:", "ハイフネーション領域 :"),
("Better Spacing", "スペースを優先"),
("Fewer Hyphens", "ハイフンを減らす"),
("Hyphenate Capitalized Words", "大文字で始まる単語をハイフネーション"),
("Hyphenate Last Word", "最後の単語をハイフネーション"),
("Hyphenate Across Column", "段をまたいでハイフネーション"),
("Paste Vector Art", "ベクトルアートをペースト"),
("Baseline Options", "ベースラインオプション"),
("Column Rules", "段落境界線"),
+9
View File
@@ -31,6 +31,15 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "ამოჭრა"),
("Copy", "კოპირება"),
("Paste", "ჩასმა"),
("Hyphenation…", "დამარცვლა…"),
("Hyphenation Settings", "დამარცვლის პარამეტრები"),
("hyphens", "დეფისი"),
("Hyphenation Zone:", "დამარცვლის ზონა:"),
("Better Spacing", "უკეთესი დაშორება"),
("Fewer Hyphens", "ნაკლები დეფისი"),
("Hyphenate Capitalized Words", "მთავრული სიტყვების დამარცვლა"),
("Hyphenate Last Word", "ბოლო სიტყვის დამარცვლა"),
("Hyphenate Across Column", "სვეტებს შორის დამარცვლა"),
("Inches Decimal", "ინჩები (ათობითი)"),
("Back", "უკან"),
("Warning", "გაფრთხილება"),
+9
View File
@@ -29,6 +29,15 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Wytnij"),
("Copy", "Kopiuj"),
("Paste", "Wklej"),
("Hyphenation…", "Dzielenie wyrazów…"),
("Hyphenation Settings", "Ustawienia dzielenia wyrazów"),
("hyphens", "dywizy"),
("Hyphenation Zone:", "Strefa dzielenia:"),
("Better Spacing", "Lepsze odstępy"),
("Fewer Hyphens", "Mniej dywizów"),
("Hyphenate Capitalized Words", "Dziel wyrazy pisane wielką literą"),
("Hyphenate Last Word", "Dziel ostatni wyraz"),
("Hyphenate Across Column", "Dziel między łamami"),
("Clear", "Wyczyść"),
("Duplicate", "Powiel"),
("Select All", "Zaznacz wszystko"),
+9
View File
@@ -31,6 +31,15 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Recortar"),
("Copy", "Copiar"),
("Paste", "Colar"),
("Hyphenation…", "Hifenização…"),
("Hyphenation Settings", "Configurações de hifenização"),
("hyphens", "hifens"),
("Hyphenation Zone:", "Zona de hifenização:"),
("Better Spacing", "Melhor espaçamento"),
("Fewer Hyphens", "Menos hifens"),
("Hyphenate Capitalized Words", "Hifenizar palavras iniciadas por maiúscula"),
("Hyphenate Last Word", "Hifenizar última palavra"),
("Hyphenate Across Column", "Hifenizar entre colunas"),
("Paste Vector Art", "Colar arte vetorial"),
("Baseline Options", "Opções de linha de base"),
("Fixed Number", "Número fixo"),
+9
View File
@@ -33,6 +33,15 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Вырезать"),
("Copy", "Копировать"),
("Paste", "Вставить"),
("Hyphenation…", "Расстановка переносов…"),
("Hyphenation Settings", "Параметры расстановки переносов"),
("hyphens", "дефисов"),
("Hyphenation Zone:", "Зона переносов:"),
("Better Spacing", "Лучшие интервалы"),
("Fewer Hyphens", "Меньше переносов"),
("Hyphenate Capitalized Words", "Переносить слова с прописной буквы"),
("Hyphenate Last Word", "Переносить последнее слово"),
("Hyphenate Across Column", "Переносить между колонками"),
("Clear", "Очистить"),
("Duplicate", "Дублировать"),
("Select All", "Выделить все"),
+9
View File
@@ -29,6 +29,15 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Вирізати"),
("Copy", "Копіювати"),
("Paste", "Вставити"),
("Hyphenation…", "Перенесення…"),
("Hyphenation Settings", "Параметри перенесення"),
("hyphens", "дефісів"),
("Hyphenation Zone:", "Зона перенесення:"),
("Better Spacing", "Кращі інтервали"),
("Fewer Hyphens", "Менше перенесень"),
("Hyphenate Capitalized Words", "Переносити слова з великої літери"),
("Hyphenate Last Word", "Переносити останнє слово"),
("Hyphenate Across Column", "Переносити між колонками"),
("Paste Vector Art", "Вставити векторну графіку"),
("Baseline Options", "Параметри базової лінії"),
("Fixed Number", "Фіксована кількість"),
+19 -1
View File
@@ -115,6 +115,12 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
),
("app.spanColumnsDialog", "Span Columns…", None, "{} — span or split columns of the selected paragraphs (type.para)"),
("app.bulletsNumberingDialog", "Bullets and Numbering…", None, "{} — bullets or numbers of the selected paragraphs (type.para)"),
(
"app.hyphenationDialog",
"Hyphenation…",
None,
"{} — Hyphenation Settings of the selected paragraphs (type.para): limits, zone, Better Spacing ↔ Fewer Hyphens, Preview",
),
(
"app.justificationDialog",
"Justification…",
@@ -1297,6 +1303,18 @@ fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value
app.ui.dialog = Some(crate::dialogs::Dialog::new("paragraphLocal", fields));
Ok(Value::Null)
}
"app.hyphenationDialog" => {
let Some(a) = app.session.execute("type.selectionAttrs", &json!({"mixed": true})).ok().filter(|a| !a.is_null()) else {
return Some(Err("select text or a text frame".into()));
};
let mixed: Vec<&str> = a["paraMixed"].as_array().map(|m| m.iter().filter_map(Value::as_str).collect()).unwrap_or_default();
let current: serde_json::Map<String, Value> = crate::dialogs::HYPH_ATTRS
.iter()
.map(|k| (k.to_string(), if mixed.contains(k) { Value::Null } else { a["para"][*k].clone() }))
.collect();
app.ui.dialog = Some(crate::dialogs::Dialog::new("hyphenation", json!({"current": current})));
Ok(Value::Null)
}
"app.justificationDialog" => {
let Some(a) = app.session.execute("type.selectionAttrs", &json!({"mixed": true})).ok().filter(|a| !a.is_null()) else {
return Some(Err("select text or a text frame".into()));
@@ -2455,7 +2473,7 @@ pub fn menu_enabled(app: &DesignApp, id: &str) -> bool {
if is_window_control(id) {
return window_control_available(app, id);
}
if id == "app.justificationDialog" {
if matches!(id, "app.justificationDialog" | "app.hyphenationDialog") {
return enabled(app, "type.para");
}
if id.starts_with("fit.") {
+4 -2
View File
@@ -1748,11 +1748,13 @@ pub fn paragraph_panel(app: &mut DesignApp, ui: &mut egui::Ui) {
crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Paragraph")).strong());
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
crate::menus::menu_button(ui, "☰", |ui| {
// In the order of InDesign's Paragraph panel menu.
for (label, id) in [
("Paragraph Rules…", "app.paragraphRulesDialog"),
("Justification…", "app.justificationDialog"),
("Paragraph Borders and Shading…", "app.paragraphBordersDialog"),
("Span Columns…", "app.spanColumnsDialog"),
("Hyphenation…", "app.hyphenationDialog"),
("Paragraph Rules…", "app.paragraphRulesDialog"),
("Paragraph Borders and Shading…", "app.paragraphBordersDialog"),
("Bullets and Numbering…", "app.bulletsNumberingDialog"),
] {
if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() {