feat(ui): Text Frame Options laid out as InDesign's, with its General options

InDesign's Text Frame Options has a section list (General, Baseline
Options, Column Rules, ...). General holds Columns (Fixed Number / Fixed
Width with Number, Width, Gutter), Balance Columns, Inset Spacing as a
2x2 grid with a link button, Vertical Justification and Ignore Text
Wrap; Baseline Options holds First Baseline Offset and Min. DesignCraft
showed one flat list without the columns type, width, balance, link,
ignore-wrap or baseline settings, although the model keeps them.

- dialogs: a section list (General, Baseline Options, Column Rules);
  General as above (Width shows what Fixed Number columns get, editable
  for Fixed Width; a side typed while linked sets all four insets);
  Baseline Options; Column Rules as before. Enum captions translated.
- OK sends the new fields only when edited, as before.
- engine: `object.textFrameOptions` takes `columnWidth` (Fixed Width)
  and `firstBaselineMin`, which compose already honours.

LYH Studio episode 14 sets two columns (9:45) and a 0.1875 in inset
(10:57) here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
moppeeer
2026-10-13 14:46:54 +02:00
co-authored by Claude Opus 5.5
parent e862e0a613
commit 61ea089149
9 changed files with 265 additions and 23 deletions
+1
View File
@@ -129,6 +129,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
Newest first.
- **2026-10-13** · Text Frame Options is laid out as InDesign's: General (Columns: Fixed Number or Fixed Width with Number, Width and Gutter; Balance Columns; Inset Spacing with the link button; Vertical Justification; Ignore Text Wrap), Baseline Options (First Baseline Offset and Min) and Column Rules. `object.textFrameOptions` takes `columnWidth` and `firstBaselineMin`. No readiness percentage change.
- **2026-10-12** · macOS Dock Quit uses the same unsaved-document prompts as window and menu Quit. Repeated requests keep the current answers; Cancel retains every tab and unsaved recovery checkpoint, and completed Save/Don't Save answers exit through the ordinary event loop. No readiness percentage change.
- **2026-10-12** · Orderly native exit writes the latest recovery state after finalizing current quit approvals; incomplete or stale plans keep their documents, and Undo to the saved state clears stale recovery. Unavailable or failed recovery is reported. Cancelable macOS Dock Quit remains open (#544). No readiness percentage change.
- **2026-10-12** · Recovered documents keep a checkpoint until saved, closed or reverted, including a second crash before autosave. Restarted document IDs and explicit recovery folders preserve other snapshots; failed republishing retains the original. No readiness percentage change.
+7 -1
View File
@@ -532,7 +532,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Text Frame Options…",
["Object"],
Some("Cmd+B"),
"{columns?, gutter?, inset?: number|[t,l,b,r] (null keeps that side), verticalJustification?: top|center|bottom|justify, firstBaseline?, autoSize?, ignoreWrap?, balanceColumns?, columnRule?: bool, columnRuleWeight? (pt), columnRuleColor? (swatch name), columnRuleTint? (0..1), columnRuleOffset? (pt, horizontal), columnRuleTopInset? (pt), columnRuleBottomInset? (pt), vertical?: bool (sets the story direction of the frames' stories, as Type ▸ Story Direction), ids?}",
"{columns?, gutter?, inset?: number|[t,l,b,r] (null keeps that side), verticalJustification?: top|center|bottom|justify, columnsKind?: fixedNumber|fixedWidth|flexibleWidth, columnWidth? (pt, Fixed Width), firstBaseline?: ascent|capHeight|leading|xHeight|fixed, firstBaselineMin? (pt), autoSize?, ignoreWrap?, balanceColumns?, columnRule?: bool, columnRuleWeight? (pt), columnRuleColor? (swatch name), columnRuleTint? (0..1), columnRuleOffset? (pt, horizontal), columnRuleTopInset? (pt), columnRuleBottomInset? (pt), vertical?: bool (sets the story direction of the frames' stories, as Type ▸ Story Direction), ids?}",
has_selection,
text_frame_options
),
@@ -1654,6 +1654,12 @@ fn text_frame_options(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(v) = p.get("ignoreWrap").and_then(Value::as_bool) {
o.ignore_wrap = v;
}
if let Some(v) = p.get("columnWidth").and_then(Value::as_f64) {
o.column_width = v.max(0.0);
}
if let Some(v) = p.get("firstBaselineMin").and_then(Value::as_f64) {
o.first_baseline_min = v.max(0.0);
}
if let Some(v) = p.get("balanceColumns").and_then(Value::as_bool) {
o.balance_columns = v;
}
+173 -22
View File
@@ -410,6 +410,13 @@ fn pdf_export(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) {
}
/// The options of the selected text frame (the first selected item, or the frame of the caret).
/// The selected text frame's width (its own, before rotation).
fn selected_frame_width(app: &DesignApp) -> Option<f64> {
let st = app.session.active()?;
let fid = st.selection.items.first().copied().or_else(|| st.selection.text.and_then(|t| t.frame))?;
Some(st.doc.item(fid)?.inner_bounds().width())
}
fn selected_frame_options(app: &DesignApp) -> Option<designcraft_doc::TextFrameOptions> {
let st = app.session.active()?;
let fid = st.selection.items.first().copied().or_else(|| st.selection.text.and_then(|t| t.frame))?;
@@ -426,8 +433,19 @@ fn seed_text_frame_options(app: &DesignApp, d: &mut Dialog) {
let o = selected_frame_options(app).unwrap_or_default();
let units = app.session.active().map(|s| s.doc.settings.horizontal_units).unwrap_or(Unit::Picas);
let fm = |v: f64| json!(format_measure(v, units));
// Fixed Number: the width each column gets in the frame (shown, as in InDesign).
let inner = selected_frame_width(app).map(|w| w - o.inset[1] - o.inset[3]).unwrap_or(0.0);
let n = o.columns.max(1) as f64;
let width = match o.columns_kind {
designcraft_doc::ColumnsKind::FixedWidth if o.column_width > 0.0 => o.column_width,
_ => ((inner - o.gutter * (n - 1.0)) / n).max(0.0),
};
let kind = |v: Value| v.as_str().unwrap_or_default().to_string();
let shown = json!({
"columns": o.columns, "gutter": fm(o.gutter),
"columnsKind": kind(serde_json::to_value(o.columns_kind).unwrap_or_default()), "columnWidth": fm(width),
"balanceColumns": o.balance_columns, "ignoreWrap": o.ignore_wrap,
"firstBaseline": kind(serde_json::to_value(o.first_baseline).unwrap_or_default()), "firstBaselineMin": fm(o.first_baseline_min),
"insetTop": fm(o.inset[0]), "insetLeft": fm(o.inset[1]), "insetBottom": fm(o.inset[2]), "insetRight": fm(o.inset[3]),
"verticalJustification": serde_json::to_value(o.vertical_justification).unwrap_or(Value::Null),
"columnRule": o.column_rule, "columnRuleWeight": format_measure(o.column_rule_weight, Unit::Points), "columnRuleColor": o.column_rule_color,
@@ -439,6 +457,7 @@ fn seed_text_frame_options(app: &DesignApp, d: &mut Dialog) {
d.fields.entry(k.clone()).or_insert_with(|| v.clone());
}
}
d.fields.entry("insetLinked".to_string()).or_insert_with(|| json!(o.inset.iter().all(|v| (v - o.inset[0]).abs() < 1e-9)));
d.fields.insert("_shown".into(), shown);
}
@@ -451,38 +470,113 @@ fn percent(t: f32) -> String {
fn text_frame_options_dialog(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog) {
let lang = app.ui.language.clone();
let tr = |s: &'static str| crate::i18n::tr(&lang, s);
egui::Grid::new("tfo").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| {
crate::rtl::label(ui, tr("Number of columns"));
text_field(ui, d, "columns", 80.0);
if d.s("section").is_empty() {
d.fields.insert("section".into(), json!("general"));
}
ui.set_min_width(560.0);
ui.horizontal_top(|ui| {
// A fixed height: the separator would otherwise stretch the dialog to the window.
ui.set_min_height(330.0);
ui.set_max_height(330.0);
section_list(&lang, ui, d, &[("general", "General"), ("baseline", "Baseline Options"), ("rules", "Column Rules")]);
ui.separator();
ui.vertical(|ui| match d.s("section").as_str() {
"baseline" => {
crate::rtl::label(ui, egui::RichText::new(tr("First Baseline")).font(semibold(12.0)));
egui::Grid::new("tfo_fb").num_columns(4).spacing([8.0, 6.0]).show(ui, |ui| {
crate::rtl::label(ui, tr("Offset:"));
translated_combo(
&lang,
ui,
d,
"firstBaseline",
&[("ascent", "Ascent"), ("capHeight", "Cap Height"), ("leading", "Leading"), ("xHeight", "x Height"), ("fixed", "Fixed")],
);
crate::rtl::label(ui, tr("Min:"));
text_field(ui, d, "firstBaselineMin", 70.0);
ui.end_row();
});
}
"rules" => column_rules(app, ui, d),
_ => general(&lang, ui, d),
});
});
}
/// Text Frame Options › General: columns, inset spacing, vertical justification, text wrap.
fn general(lang: &str, ui: &mut egui::Ui, d: &mut Dialog) {
let tr = |s: &'static str| crate::i18n::tr(lang, s);
crate::rtl::label(ui, egui::RichText::new(tr("Columns")).font(semibold(12.0)));
egui::Grid::new("tfo_cols").num_columns(4).spacing([8.0, 6.0]).show(ui, |ui| {
crate::rtl::label(ui, tr("Columns:"));
translated_combo(lang, ui, d, "columnsKind", &[("fixedNumber", "Fixed Number"), ("fixedWidth", "Fixed Width")]);
ui.end_row();
crate::rtl::label(ui, tr("Gutter"));
text_field(ui, d, "gutter", 80.0);
crate::rtl::label(ui, tr("Number:"));
text_field(ui, d, "columns", 70.0);
// Fixed Number: the width follows from the frame; Fixed Width: it's set here.
crate::rtl::label(ui, tr("Width:"));
ui.add_enabled_ui(d.s("columnsKind") == "fixedWidth", |ui| text_field(ui, d, "columnWidth", 70.0));
ui.end_row();
crate::rtl::label(ui, tr("Gutter:"));
text_field(ui, d, "gutter", 70.0);
ui.end_row();
});
check(ui, d, "balanceColumns", tr("Balance Columns"));
ui.add_space(6.0);
crate::rtl::label(ui, egui::RichText::new(tr("Inset spacing")).font(semibold(12.0)));
egui::Grid::new("tfo_inset").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| {
for (label, key) in [("Top", "insetTop"), ("Bottom", "insetBottom"), ("Left", "insetLeft"), ("Right", "insetRight")] {
crate::rtl::label(ui, tr(label));
text_field(ui, d, key, 80.0);
ui.end_row();
}
});
ui.add_space(6.0);
egui::Grid::new("tfo_vj").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| {
crate::rtl::label(ui, tr("Vertical justification"));
let cur = d.s("verticalJustification");
egui::ComboBox::from_id_salt("vj").selected_text(crate::rtl::widget(ui, crate::i18n::tr(&lang, &cur))).show_ui(ui, |ui| {
for v in ["top", "center", "bottom", "justify"] {
if ui.selectable_label(cur == v, crate::rtl::widget(ui, crate::i18n::tr(&lang, v))).clicked() {
d.fields.insert("verticalJustification".into(), json!(v));
crate::rtl::label(ui, egui::RichText::new(tr("Inset Spacing")).font(semibold(12.0)));
// While linked, a side typed in sets all four ("Make all settings the same").
let sides = ["insetTop", "insetLeft", "insetBottom", "insetRight"];
let before: Vec<String> = sides.iter().map(|k| d.s(k)).collect();
// Two rows, Top | Left and Bottom | Right, the link between the columns (fixed-width
// captions; the second column starts where the first row's does).
let mut col2_x: Option<f32> = None;
for (r, row) in [[("Top:", "insetTop"), ("Left:", "insetLeft")], [("Bottom:", "insetBottom"), ("Right:", "insetRight")]].into_iter().enumerate() {
ui.horizontal(|ui| {
for (k, (label, key)) in row.into_iter().enumerate() {
if k == 1 {
match col2_x {
Some(x) => ui.add_space((x - ui.cursor().min.x).max(0.0)),
None => col2_x = Some(ui.cursor().min.x),
}
}
let x0 = ui.cursor().min.x;
crate::rtl::label(ui, tr(label));
ui.add_space(((if k == 0 { 56.0 } else { 44.0 }) - (ui.cursor().min.x - x0)).max(0.0));
text_field(ui, d, key, 70.0);
if k == 0 && r == 0 {
let linked = d.b("insetLinked");
let tip = tr("Make all settings the same");
if crate::widgets::icon_toggle_sized(ui, if linked { "link" } else { "link-broken" }, linked, tip, egui::vec2(22.0, 21.0))
.clicked()
{
d.fields.insert("insetLinked".into(), json!(!linked));
}
}
}
});
}
if d.b("insetLinked")
&& let Some(k) = sides.iter().zip(&before).find(|(k, b)| d.s(k) != **b).map(|(k, _)| *k)
{
let v = d.fields.get(k).cloned().unwrap_or_default();
for side in sides {
d.fields.insert(side.into(), v.clone());
}
}
ui.add_space(6.0);
crate::rtl::label(ui, egui::RichText::new(tr("Vertical Justification")).font(semibold(12.0)));
egui::Grid::new("tfo_vj").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| {
crate::rtl::label(ui, tr("Align:"));
translated_combo(lang, ui, d, "verticalJustification", &[("top", "Top"), ("center", "Center"), ("bottom", "Bottom"), ("justify", "Justify")]);
ui.end_row();
});
ui.add_space(6.0);
crate::rtl::label(ui, egui::RichText::new(tr("Column Rules")).font(semibold(12.0)));
check(ui, d, "ignoreWrap", tr("Ignore Text Wrap"));
}
/// Text Frame Options › Column Rules.
fn column_rules(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog) {
let tr = |s: &'static str| crate::i18n::tr(&app.ui.language, s);
check(ui, d, "columnRule", tr("Insert Column Rule"));
let swatches: Vec<String> = app
.session
@@ -554,6 +648,23 @@ fn confirm_text_frame_options(app: &mut DesignApp, d: &mut Dialog) -> Result<Val
if edited(d, "verticalJustification") {
p.insert("verticalJustification".into(), json!(d.s("verticalJustification")));
}
for key in ["columnsKind", "firstBaseline"] {
if edited(d, key) {
p.insert(key.into(), json!(d.s(key)));
}
}
// The width counts only for Fixed Width columns (Fixed Number shows it).
if d.s("columnsKind") == "fixedWidth" && (edited(d, "columnWidth") || edited(d, "columnsKind")) {
p.insert("columnWidth".into(), json!(d.m("columnWidth").ok_or_else(|| bad("the column width is not a measure"))?));
}
if edited(d, "firstBaselineMin") {
p.insert("firstBaselineMin".into(), json!(d.m("firstBaselineMin").ok_or_else(|| bad("the first baseline minimum is not a measure"))?));
}
for key in ["balanceColumns", "ignoreWrap"] {
if edited(d, key) {
p.insert(key.into(), json!(d.b(key)));
}
}
if edited(d, "columnRule") {
p.insert("columnRule".into(), json!(d.b("columnRule")));
}
@@ -4350,6 +4461,46 @@ mod tests {
assert!(dialog.fields["status"].as_str().unwrap().contains("page size out of range"));
}
#[test]
fn text_frame_options_set_columns_baseline_and_wrap_as_indesign_does() {
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({})).unwrap();
// A 300 pt frame, 2 columns, 12 pt gutter (picas in the dialog).
let r = app.run("frame.create", json!({"rect": [36, 36, 336, 236], "content": "text", "text": "Hello"})).unwrap();
let id = designcraft_doc::ItemId(r["id"].as_u64().unwrap());
app.run("object.textFrameOptions", json!({"ids": [id.0], "columns": 2, "gutter": 12})).unwrap();
let opts = |app: &DesignApp| app.session.doc().unwrap().doc.item(id).unwrap().text_frame().unwrap().options.clone();
let mut d = Dialog::new("textFrameOptions", json!({}));
seed_text_frame_options(&app, &mut d);
// Fixed Number shows each column's width: (300 - 12) / 2 = 144 pt.
assert_eq!((d.s("columnsKind"), d.s("columnWidth"), d.s("firstBaseline")), ("fixedNumber".into(), "12p0".into(), "ascent".into()));
for (k, v) in [
("columnsKind", json!("fixedWidth")),
("columnWidth", json!("10p0")),
("firstBaseline", json!("capHeight")),
("firstBaselineMin", json!("0p6")),
("balanceColumns", json!(true)),
("ignoreWrap", json!(true)),
] {
d.fields.insert(k.into(), v);
}
app.ui.dialog = Some(d);
confirm(&mut app).unwrap();
let o = opts(&app);
assert_eq!(o.columns_kind, designcraft_doc::ColumnsKind::FixedWidth);
assert_eq!((o.column_width, o.first_baseline_min, o.columns), (120.0, 6.0, 2));
assert_eq!(o.first_baseline, designcraft_doc::FirstBaseline::CapHeight);
assert!(o.balance_columns && o.ignore_wrap);
// Fixed Number again: the width isn't sent.
let mut d = Dialog::new("textFrameOptions", json!({}));
seed_text_frame_options(&app, &mut d);
assert_eq!(d.s("columnWidth"), "10p0", "Fixed Width shows its own width");
d.fields.insert("columnsKind".into(), json!("fixedNumber"));
app.ui.dialog = Some(d);
confirm(&mut app).unwrap();
assert_eq!((opts(&app).columns_kind, opts(&app).column_width), (designcraft_doc::ColumnsKind::FixedNumber, 120.0));
}
#[test]
fn text_frame_options_change_only_what_was_edited() {
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
+14
View File
@@ -55,6 +55,20 @@ const TABLE: &[(&str, [&str; 5])] = &[
("Cut", ["Ausschneiden", "Couper", "Cortar", "カット", "剪切"]),
("Copy", ["Kopieren", "Copier", "Copiar", "コピー", "复制"]),
("Paste", ["Einfügen", "Coller", "Pegar", "ペースト", "粘贴"]),
("Baseline Options", ["Grundlinienoptionen", "Options de ligne de base", "Opciones de línea de base", "ベースラインオプション", "基线选项"]),
("Fixed Number", ["Feste Anzahl", "Nombre fixe", "Número fijo", "固定数", "固定数字"]),
("Fixed Width", ["Feste Breite", "Largeur fixe", "Anchura fija", "固定幅", "固定宽度"]),
("Number:", ["Anzahl:", "Nombre :", "Número:", "数:", "数字:"]),
("Gutter:", ["Spaltenabstand:", "Gouttière :", "Medianil:", "間隔:", "栏间距:"]),
("Balance Columns", ["Spalten ausgleichen", "Équilibrer les colonnes", "Equilibrar columnas", "段を均等化", "平衡栏"]),
("Inset Spacing", ["Versatzabstand", "Marge intérieure", "Espaciado de margen interior", "フレーム内マージン", "内边距"]),
("Top:", ["Oben:", "Haut :", "Superior:", "上:", "上:"]),
("Left:", ["Links:", "Gauche :", "Izquierda:", "左:", "左:"]),
("Bottom:", ["Unten:", "Bas :", "Inferior:", "下:", "下:"]),
("Right:", ["Rechts:", "Droite :", "Derecha:", "右:", "右:"]),
("Vertical Justification", ["Vertikale Ausrichtung", "Justification verticale", "Justificación vertical", "垂直比率", "垂直对齐"]),
("Align:", ["Ausrichten:", "Alignement :", "Alinear:", "整列:", "对齐:"]),
("Ignore Text Wrap", ["Textumfluss ignorieren", "Ignorer l’habillage", "Ignorar ceñir texto", "テキストの回り込みを無視", "忽略文本绕排"]),
("Reverse Path", ["Pfadrichtung umkehren", "Inverser le tracé", "Invertir trazado", "パスの方向反転", "反转路径"]),
("Set Anchor", ["Ankerpunkt festlegen", "Définir le point d’ancrage", "Definir punto de ancla", "アンカーポイントを設定", "设置锚点"]),
("Clear", ["Löschen", "Effacer", "Borrar", "消去", "清除"]),
+14
View File
@@ -30,6 +30,20 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "قص"),
("Copy", "نسخ"),
("Paste", "لصق"),
("Baseline Options", "خيارات خط الأساس"),
("Fixed Number", "عدد ثابت"),
("Fixed Width", "عرض ثابت"),
("Number:", "العدد:"),
("Gutter:", "التباعد:"),
("Balance Columns", "موازنة الأعمدة"),
("Inset Spacing", "تباعد الإزاحة الداخلية"),
("Top:", "أعلى:"),
("Left:", "يسار:"),
("Bottom:", "أسفل:"),
("Right:", "يمين:"),
("Vertical Justification", "الضبط العمودي"),
("Align:", "المحاذاة:"),
("Ignore Text Wrap", "تجاهل التفاف النص"),
("Reverse Path", "عكس المسار"),
("Set Anchor", "تعيين نقطة الارتساء"),
("Clear", "مسح"),
+14
View File
@@ -28,6 +28,20 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Taglia"),
("Copy", "Copia"),
("Paste", "Incolla"),
("Baseline Options", "Opzioni linea di base"),
("Fixed Number", "Numero fisso"),
("Fixed Width", "Larghezza fissa"),
("Number:", "Numero:"),
("Gutter:", "Spaziatura:"),
("Balance Columns", "Bilancia colonne"),
("Inset Spacing", "Spaziatura interna"),
("Top:", "Superiore:"),
("Left:", "Sinistra:"),
("Bottom:", "Inferiore:"),
("Right:", "Destra:"),
("Vertical Justification", "Giustificazione verticale"),
("Align:", "Allinea:"),
("Ignore Text Wrap", "Ignora contorna con testo"),
("Reverse Path", "Inverti tracciato"),
("Set Anchor", "Imposta ancoraggio"),
("Clear", "Cancella"),
+12
View File
@@ -30,6 +30,18 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "カット"),
("Copy", "コピー"),
("Paste", "ペースト"),
("Baseline Options", "ベースラインオプション"),
("Column Rules", "段落境界線"),
("Insert Column Rule", "段落境界線を挿入"),
("Fixed Number", "固定数"),
("Fixed Width", "固定幅"),
("Number:", "数:"),
("Gutter:", "間隔:"),
("Balance Columns", "段を均等化"),
("Inset Spacing", "フレーム内マージン"),
("Vertical Justification", "垂直比率"),
("Align:", "整列:"),
("Ignore Text Wrap", "テキストの回り込みを無視"),
("Reverse Path", "パスの方向反転"),
("Set Anchor", "アンカーポイントを設定"),
("Clear", "消去"),
+14
View File
@@ -31,6 +31,20 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Recortar"),
("Copy", "Copiar"),
("Paste", "Colar"),
("Baseline Options", "Opções de linha de base"),
("Fixed Number", "Número fixo"),
("Fixed Width", "Largura fixa"),
("Number:", "Número:"),
("Gutter:", "Medianiz:"),
("Balance Columns", "Equilibrar colunas"),
("Inset Spacing", "Espaçamento interno"),
("Top:", "Superior:"),
("Left:", "Esquerda:"),
("Bottom:", "Inferior:"),
("Right:", "Direita:"),
("Vertical Justification", "Justificação vertical"),
("Align:", "Alinhar:"),
("Ignore Text Wrap", "Ignorar disposição do texto"),
("Reverse Path", "Inverter demarcador"),
("Set Anchor", "Definir ponto de ancoragem"),
("Clear", "Limpar"),
+16
View File
@@ -31,6 +31,22 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Вирізати"),
("Copy", "Копіювати"),
("Paste", "Вставити"),
("Baseline Options", "Параметри базової лінії"),
("Column Rules", "Лінії між колонками"),
("Insert Column Rule", "Вставити лінію між колонками"),
("Fixed Number", "Фіксована кількість"),
("Fixed Width", "Фіксована ширина"),
("Number:", "Кількість:"),
("Gutter:", "Проміжок:"),
("Balance Columns", "Вирівняти колонки"),
("Inset Spacing", "Внутрішні відступи"),
("Top:", "Угорі:"),
("Left:", "Ліворуч:"),
("Bottom:", "Унизу:"),
("Right:", "Праворуч:"),
("Vertical Justification", "Вертикальне вирівнювання"),
("Align:", "Вирівняти:"),
("Ignore Text Wrap", "Ігнорувати обтікання текстом"),
("Reverse Path", "Розвернути контур"),
("Set Anchor", "Задати прив’язку"),
("Clear", "Очистити"),