mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-27 03:36:51 -06:00
UI: CJK features behind a preference that follows the interface language
Preferences > Type > Show CJK Features (cjkFeatures; unset follows the interface language: on for Japanese, Chinese and Korean) hides the CJK-only Type menu items (Tate-Chu-Yoko, Ruby, Kenten, Warichu) and the Ruby Quick Apply entry. The commands, layout and IDML work either way.
This commit is contained in:
@@ -111,6 +111,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
||||
- Video and sound (place .mp4/.mov/.webm/.mp3/.wav…, `media.options/get`, Media panel): media frames show a poster or placeholder on screen, in print and in PDF, and export as HTML5 `<video>`/`<audio>` in EPUB. Interactive PDF export (`file.exportPdf {media: true}`, File ▸ Export PDF (Interactive)) embeds each file and plays it in a Screen annotation through a rendition action (controls, loop, play on page load). No in-app playback.
|
||||
- Right-to-left text (World-Ready basics): right-to-left runs are shaped right to left, lines are reordered by the Unicode Bidirectional Algorithm (matched against the reference implementation), Paragraph Direction (`type.para {direction}`, Paragraph panel, IDML ParagraphDirection, EPUB `dir`). Digits (`digits`: Default, Arabic, Hindi, Farsi, Native by language; Character panel for right-to-left text; IDML DigitsType) draw 0–9 in another script. Kashidas (`kashidas` paragraph attribute, on by default): justified Arabic lines stretch at one join per word (after seen/sad when possible, never inside lam–alef) with a drawn tatweel before word spaces widen. Carets follow the drawing: the caret before a right-to-left glyph sits at its right edge, arrow keys move as drawn (by words in the line's direction), clicks find the nearest drawn caret position and selections highlight the selected glyphs. Binding: Right to Left (Document Setup, `layout.documentSetup {binding}`, IDML PageBinding) makes page 1 a left page and lays spreads out right to left.
|
||||
- Interface language (Edit ▸ Interface Language, `app.language`): German, French, Spanish, Italian, Japanese, Simplified Chinese and Arabic menu titles, common menu items and panel names (our own translations). Common dialog, panel and tooltip labels are also localized, with English fallback for untranslated strings. Arabic translations cover every key in the shared interface translation table, including menu commands and utility labels. The Type menu's Story Direction, Horizontal/Vertical, Tate-Chu-Yoko, Ruby… and Kenten are translated too. Arabic labels use Unicode bidi run ordering and contextual shaping; welcome and new-document form layouts follow RTL direction independently of document binding. CJK and Arabic glyphs come from [craft-fonts](https://github.com/storytold/craft-fonts) when built with `CRAFT_FONTS_DIR` (all releases): BIZ UDPGothic and Noto Sans CJK SC for the UI (Chinese forms first in the Chinese interface), Noto Sans Arabic, and Mincho for Japanese documents (the web build embeds BIZ UDPGothic only); otherwise from system fonts. Ukrainian covers all 1,430 existing catalog keys, including publishing dialogs and built-in names, with persisted selection, grammatical count captions, English fallback and bundled Cyrillic UI glyph coverage.
|
||||
- Show CJK Features (Preferences ▸ Type, `cjkFeatures` preference): the CJK-only interface (Type ▸ Tate-Chu-Yoko, Ruby…, Kenten, Warichu) shows only while it is on; until the user sets it, it follows the interface language (on for Japanese, Chinese and Korean). It hides interface only: layout, IDML and the commands work either way.
|
||||
- Page transitions (`page.transition/transitions`, Page Transitions panel): twelve transition types with speed and direction per spread, written into exported PDFs as `/Trans` entries through an incremental update.
|
||||
- PDF/X-4 export (`file.exportPdf {standard: "x4"}`): an output intent with our own Generic CMYK ICC profile (lut16 tables generated from the parametric press model), GTS_PDFXVersion in Info and XMP, `pdf:Trapped` in XMP as in Info, RGB images without a profile tagged sRGB, no image interpolation (none in PDF/A either), and built-in PDF/X-4 checks reported as warnings. qpdf finds no errors in the result.
|
||||
- EPS place: placed at its (high-resolution) bounding box and shown, printed and exported through its TIFF preview, or a placeholder when it has none. PostScript itself isn't interpreted.
|
||||
|
||||
@@ -107,6 +107,8 @@ fn build(app: &DesignApp, parent: &Submenu, entries: &[Item], items: &mut HashMa
|
||||
build(app, &sub, children, items, counter);
|
||||
let _ = parent.append(&sub);
|
||||
}
|
||||
// The menu bar is built once, so Show CJK Features applies here from the next launch.
|
||||
Item::Cmd { id, .. } if !menus::shown(app, id) => {}
|
||||
Item::Cmd { label, id, params, shortcut } => {
|
||||
*counter += 1;
|
||||
let mid = format!("dc{counter}");
|
||||
|
||||
@@ -48,7 +48,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Preferences",
|
||||
[],
|
||||
None,
|
||||
"{showHiddenCharacters?, typographersQuotes?, polygonSides?, starInset?, scaleStrokes?, dimensionsIncludeStroke?, transformationsAreTotals?, absolutePageNumbers?, highlightHj?, highlightKeeps?, highlightCustomTracking?, highlightSubstitutedFonts?, richBlackOutput?, favoriteFonts?: [family], showFontNamesInEnglish?} → all application preferences",
|
||||
"{showHiddenCharacters?, typographersQuotes?, polygonSides?, starInset?, scaleStrokes?, dimensionsIncludeStroke?, transformationsAreTotals?, absolutePageNumbers?, highlightHj?, highlightKeeps?, highlightCustomTracking?, highlightSubstitutedFonts?, richBlackOutput?, favoriteFonts?: [family], showFontNamesInEnglish?, cjkFeatures?: bool|null (CJK-only menu items and dialogs shown; null follows the interface language)} → all application preferences",
|
||||
super::always,
|
||||
|s, p| {
|
||||
let cur = serde_json::to_value(&s.prefs).map_err(|e| bad("prefs.set", e.to_string()))?;
|
||||
|
||||
@@ -180,6 +180,10 @@ pub struct Prefs {
|
||||
/// Preferences › Type › Show Font Names in English: the font menus show CJK families by their
|
||||
/// English names instead of their native ones. Documents store the English name either way.
|
||||
pub show_font_names_in_english: bool,
|
||||
/// Preferences › Type › Show CJK Features: the menu items, dialogs and panel fields that only
|
||||
/// Japanese, Chinese and Korean typesetting uses. `None` follows the interface language. It
|
||||
/// hides interface only: layout, file formats and commands work either way.
|
||||
pub cjk_features: Option<bool>,
|
||||
/// Preferences › Type › Smart Text Reflow: pages follow the primary text frame's story
|
||||
/// (added while it oversets, empty ones at the end removed).
|
||||
pub smart_text_reflow: bool,
|
||||
@@ -234,6 +238,7 @@ impl Default for Prefs {
|
||||
rich_black_output: false,
|
||||
favorite_fonts: Vec::new(),
|
||||
show_font_names_in_english: false,
|
||||
cjk_features: None,
|
||||
smart_text_reflow: true,
|
||||
autocorrect: false,
|
||||
autocorrect_list: default_autocorrect(),
|
||||
|
||||
@@ -833,6 +833,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog, max_he
|
||||
crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Type Options")).font(semibold(12.0)));
|
||||
check(ui, d, "typographersQuotes", crate::i18n::tr(&app.ui.language, "Use Typographer's Quotes"));
|
||||
check(ui, d, "showFontNamesInEnglish", crate::i18n::tr(&app.ui.language, "Show Font Names in English"));
|
||||
check(ui, d, "cjkFeatures", crate::i18n::tr(&app.ui.language, "Show CJK Features"));
|
||||
ui.add_space(6.0);
|
||||
ui.label(crate::rtl::widget(
|
||||
ui,
|
||||
@@ -1986,17 +1987,18 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> {
|
||||
}
|
||||
}
|
||||
"preferences" => {
|
||||
app.run(
|
||||
"prefs.set",
|
||||
json!({"scaleStrokes": d.b("scaleStrokes"), "dimensionsIncludeStroke": d.b("dimensionsIncludeStroke"), "transformationsAreTotals": d.b("transformationsAreTotals"), "absolutePageNumbers": d.b("absolutePageNumbers"), "highlightHj": d.b("highlightHj"), "highlightKeeps": d.b("highlightKeeps"), "highlightCustomTracking": d.b("highlightCustomTracking"), "highlightSubstitutedFonts": d.b("highlightSubstitutedFonts"), "richBlackOutput": d.b("richBlackOutput"), "typographersQuotes": d.b("typographersQuotes"), "showFontNamesInEnglish": d.b("showFontNamesInEnglish"), "smartTextReflow": d.b("smartTextReflow"),
|
||||
let mut prefs = json!({"scaleStrokes": d.b("scaleStrokes"), "dimensionsIncludeStroke": d.b("dimensionsIncludeStroke"), "transformationsAreTotals": d.b("transformationsAreTotals"), "absolutePageNumbers": d.b("absolutePageNumbers"), "highlightHj": d.b("highlightHj"), "highlightKeeps": d.b("highlightKeeps"), "highlightCustomTracking": d.b("highlightCustomTracking"), "highlightSubstitutedFonts": d.b("highlightSubstitutedFonts"), "richBlackOutput": d.b("richBlackOutput"), "typographersQuotes": d.b("typographersQuotes"), "showFontNamesInEnglish": d.b("showFontNamesInEnglish"), "smartTextReflow": d.b("smartTextReflow"),
|
||||
"autocorrect": d.b("autocorrect"), "showAddedText": d.b("showAddedText"), "showNoteAnchors": d.b("showNoteAnchors"),
|
||||
"recoveryMinutes": d.n("recoveryMinutes").unwrap_or(0.5),
|
||||
"autocorrectList": d.s("autocorrectText").lines().filter_map(|l| {
|
||||
let (a, b) = l.split_once('→').or_else(|| l.split_once("->"))?;
|
||||
let (a, b) = (a.trim().to_lowercase(), b.trim().to_string());
|
||||
(!a.is_empty() && !b.is_empty()).then(|| json!([a, b]))
|
||||
}).collect::<Vec<_>>()}),
|
||||
)?;
|
||||
}).collect::<Vec<_>>()});
|
||||
if d.b("cjkFeatures") != d.b("cjkFeatures.initial") {
|
||||
prefs["cjkFeatures"] = json!(d.b("cjkFeatures"));
|
||||
}
|
||||
app.run("prefs.set", prefs)?;
|
||||
app.ui.dynamic_spelling = d.b("dynamicSpelling");
|
||||
if d.fields.contains_key("userWords") {
|
||||
let words: Vec<String> = d.s("userWords").lines().map(str::to_string).collect();
|
||||
|
||||
@@ -2675,6 +2675,7 @@ const TABLE: &[(&str, [&str; 5])] = &[
|
||||
"以英文显示字体名称",
|
||||
],
|
||||
),
|
||||
("Show CJK Features", ["CJK-Funktionen anzeigen", "Afficher les fonctions CJK", "Mostrar funciones CJK", "CJK 機能を表示", "显示中日韩功能"]),
|
||||
("Missing Glyphs", ["Fehlende Glyphen", "Glyphes manquants", "Glifos que faltan", "欠落グリフ", "缺失字形"]),
|
||||
(
|
||||
"Draw Missing Glyphs from Fallback Fonts",
|
||||
@@ -2818,6 +2819,12 @@ pub fn is_rtl(lang: &str) -> bool {
|
||||
lang == "ar"
|
||||
}
|
||||
|
||||
/// A Japanese, Chinese or Korean interface language (`zh-tw` included): CJK features are shown by
|
||||
/// default (see [`crate::cjk_features`]).
|
||||
pub fn is_cjk(lang: &str) -> bool {
|
||||
matches!(lang.split('-').next(), Some("ja" | "zh" | "ko"))
|
||||
}
|
||||
|
||||
fn column(lang: &str) -> Option<usize> {
|
||||
match lang {
|
||||
"de" => Some(0),
|
||||
|
||||
@@ -1024,6 +1024,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Advanced Type", "كتابة متقدمة"),
|
||||
("Composition", "التركيب"),
|
||||
("Show Font Names in English", "إظهار أسماء الخطوط بالإنجليزية"),
|
||||
("Show CJK Features", "إظهار ميزات CJK"),
|
||||
("Missing Glyphs", "الحروف الناقصة"),
|
||||
("Draw Missing Glyphs from Fallback Fonts", "رسم الحروف الناقصة بخطوط بديلة"),
|
||||
("Units & Increments", "الوحدات والزيادات"),
|
||||
|
||||
@@ -1355,6 +1355,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Advanced Type", "Testo avanzato"),
|
||||
("Composition", "Composizione"),
|
||||
("Show Font Names in English", "Mostra i nomi dei font in inglese"),
|
||||
("Show CJK Features", "Mostra le funzioni CJK"),
|
||||
("Missing Glyphs", "Glifi mancanti"),
|
||||
("Draw Missing Glyphs from Fallback Fonts", "Disegna i glifi mancanti con font di riserva"),
|
||||
("Units & Increments", "Unità e incrementi"),
|
||||
|
||||
@@ -1351,6 +1351,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Advanced Type", "高度な書式"),
|
||||
("Composition", "組版"),
|
||||
("Show Font Names in English", "フォント名を英語で表示"),
|
||||
("Show CJK Features", "CJK 機能を表示"),
|
||||
("Missing Glyphs", "欠落グリフ"),
|
||||
("Draw Missing Glyphs from Fallback Fonts", "欠落グリフを代替フォントで表示"),
|
||||
("Units & Increments", "単位と増分"),
|
||||
|
||||
@@ -982,6 +982,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Show Tag Markers", "Mostrar marcadores de marcação"),
|
||||
("Single-line", "Linha única"),
|
||||
("Smart Text Reflow", "Redistribuição inteligente de texto"),
|
||||
("Show CJK Features", "Mostrar recursos CJK"),
|
||||
("Speed", "Velocidade"),
|
||||
("Star Inset:", "Recuo da estrela:"),
|
||||
("Start Page #:", "Iniciar página nº:"),
|
||||
|
||||
@@ -1352,6 +1352,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Advanced Type", "Додаткові параметри тексту"),
|
||||
("Composition", "Компонування"),
|
||||
("Show Font Names in English", "Показувати назви шрифтів англійською"),
|
||||
("Show CJK Features", "Показувати функції CJK"),
|
||||
("Missing Glyphs", "Відсутні гліфи"),
|
||||
("Draw Missing Glyphs from Fallback Fonts", "Відображати відсутні гліфи резервними шрифтами"),
|
||||
("Units & Increments", "Одиниці та кроки"),
|
||||
|
||||
@@ -1007,6 +1007,18 @@ impl DesignApp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether CJK-only interface is shown (Preferences › Type › Show CJK Features): the explicit
|
||||
/// preference, else on for a Japanese, Chinese or Korean interface language.
|
||||
///
|
||||
/// Every menu item, dialog section and panel field that only CJK typesetting uses goes behind
|
||||
/// this check, as in InDesign, whose Roman edition leaves those features out. It hides interface
|
||||
/// only: the commands behind it run whatever it says (from scripts, the control channel and MCP).
|
||||
/// Story Direction, the vertical type tools, frame grids, Language, Digits and font naming are
|
||||
/// in both editions and stay outside it.
|
||||
pub fn cjk_features(app: &DesignApp) -> bool {
|
||||
app.session.prefs.cjk_features.unwrap_or_else(|| i18n::is_cjk(&app.ui.language))
|
||||
}
|
||||
|
||||
/// Files that open as documents (when dropped or picked) rather than being placed: DesignCraft
|
||||
/// and IDML.
|
||||
pub fn opens_as_document(name: &str) -> bool {
|
||||
|
||||
+108
-7
@@ -4,6 +4,16 @@ use serde_json::{Value, json};
|
||||
|
||||
use crate::{DesignApp, ScreenMode};
|
||||
|
||||
/// Menu and Quick Apply entries that only CJK typesetting uses: hidden unless
|
||||
/// [`crate::cjk_features`]. The commands still run from scripts, the control channel and MCP.
|
||||
pub const CJK_ONLY: &[&str] = &["type.tateChuYoko", "app.rubyDialog", "type.kenten", "type.warichu"];
|
||||
|
||||
/// Whether command `id` has a menu item or Quick Apply entry in the current interface (CJK-only
|
||||
/// ones need [`crate::cjk_features`]).
|
||||
pub fn shown(app: &DesignApp, id: &str) -> bool {
|
||||
!CJK_ONLY.contains(&id) || crate::cjk_features(app)
|
||||
}
|
||||
|
||||
/// UI commands: (id, label, shortcut, params).
|
||||
pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
|
||||
("app.newDocumentDialog", "New Document…", Some("Cmd+N"), "{}"),
|
||||
@@ -1036,6 +1046,11 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
|
||||
f["storyEditorSize"] = json!(app.ui.story_editor_size);
|
||||
let list = app.session.prefs.autocorrect_list.iter().map(|(a, b)| format!("{a} → {b}")).collect::<Vec<_>>().join("\n");
|
||||
f["autocorrectText"] = json!(list);
|
||||
// The effective setting; it becomes an explicit preference only when the user changes it,
|
||||
// so an untouched one keeps following the interface language.
|
||||
let cjk = crate::cjk_features(app);
|
||||
f["cjkFeatures"] = json!(cjk);
|
||||
f["cjkFeatures.initial"] = json!(cjk);
|
||||
f["section"] = json!("general");
|
||||
app.ui.dialog = Some(crate::dialogs::Dialog::new("preferences", f));
|
||||
Ok(Value::Null)
|
||||
@@ -2008,6 +2023,7 @@ fn menu_items(app: &mut DesignApp, ui: &mut egui::Ui, items: &[Item], path: &str
|
||||
hidden += menu_items(app, ui, children, &sub);
|
||||
});
|
||||
}
|
||||
Item::Cmd { id, .. } if !shown(app, id) => {}
|
||||
Item::Cmd { label, .. } if !app.ui.show_full_menus && app.ui.hidden_menu_items.contains(&menu_key(path, label)) => hidden += 1,
|
||||
Item::Cmd { label, id, params, shortcut: _ } => {
|
||||
let label = crate::i18n::tr(&app.ui.language, label);
|
||||
@@ -2152,7 +2168,7 @@ pub fn palette(app: &mut DesignApp, ctx: &egui::Context) {
|
||||
.desired_width(f32::INFINITY),
|
||||
);
|
||||
r.request_focus();
|
||||
let items = quick_apply_items(&app.session, &q);
|
||||
let items = quick_apply_items(&app.session, &q, crate::cjk_features(app));
|
||||
// Only a fresh Return runs the first entry: ⌘Return pressed again or held down (repeats) is
|
||||
// the shortcut, not a choice.
|
||||
let enter = ui.input(|i| {
|
||||
@@ -2201,7 +2217,7 @@ pub struct QuickItem {
|
||||
}
|
||||
|
||||
/// Quick Apply matches (styles first, as InDesign lists them), at most 14.
|
||||
pub fn quick_apply_items(session: &designcraft_engine::Session, query: &str) -> Vec<QuickItem> {
|
||||
pub fn quick_apply_items(session: &designcraft_engine::Session, query: &str, cjk: bool) -> Vec<QuickItem> {
|
||||
let ql = query.to_lowercase();
|
||||
let hit = |label: &str, id: &str| ql.is_empty() || label.to_lowercase().contains(&ql) || id.to_lowercase().contains(&ql);
|
||||
let mut out = Vec::new();
|
||||
@@ -2232,7 +2248,7 @@ pub fn quick_apply_items(session: &designcraft_engine::Session, query: &str) ->
|
||||
.filter(|c| !c.menu.is_empty() || c.shortcut.is_some())
|
||||
.map(|c| (c.id, c.label, c.shortcut))
|
||||
.chain(UI_COMMANDS.iter().map(|c| (c.0, c.1, c.2)))
|
||||
.filter(|(id, l, _)| hit(l, id))
|
||||
.filter(|(id, l, _)| hit(l, id) && (cjk || !CJK_ONLY.contains(id)))
|
||||
.map(|(id, l, sc)| QuickItem { label: l.to_string(), id: id.to_string(), params: json!({}), shortcut: sc.map(str::to_string), kind: "" });
|
||||
out.extend(commands);
|
||||
out.truncate(14);
|
||||
@@ -2817,7 +2833,7 @@ mod tests {
|
||||
})
|
||||
};
|
||||
// Rows that show a shortcut or a style kind, and rows that show neither, among the first visible ones.
|
||||
let items = quick_apply_items(&app.session, "");
|
||||
let items = quick_apply_items(&app.session, "", false);
|
||||
let first = items.iter().take(10);
|
||||
let plain = first.clone().find(|it| it.shortcut.is_none() && it.kind.is_empty()).expect("a row without a shortcut");
|
||||
let with_sc = first.clone().find(|it| it.shortcut.is_some()).expect("a row with a shortcut");
|
||||
@@ -3006,12 +3022,12 @@ mod tests {
|
||||
let mut s = designcraft_engine::Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
s.execute("style.paragraph.create", &json!({"name": "Body Copy"})).unwrap();
|
||||
let items = quick_apply_items(&s, "body");
|
||||
let items = quick_apply_items(&s, "body", false);
|
||||
assert_eq!(items[0].id, "style.paragraph.apply");
|
||||
assert_eq!(items[0].params, json!({"name": "Body Copy"}));
|
||||
assert_eq!(items[0].kind, "Paragraph Style");
|
||||
assert!(quick_apply_items(&s, "").iter().all(|i| i.kind.is_empty()));
|
||||
assert!(quick_apply_items(&s, "preferences").iter().any(|i| i.id == "app.preferences"));
|
||||
assert!(quick_apply_items(&s, "", false).iter().all(|i| i.kind.is_empty()));
|
||||
assert!(quick_apply_items(&s, "preferences", false).iter().any(|i| i.id == "app.preferences"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3102,6 +3118,91 @@ mod tests {
|
||||
assert!(items.len() > 100, "every menu's items are listed: {}", items.len());
|
||||
}
|
||||
|
||||
/// The Type menu as drawn: every item label.
|
||||
fn drawn_type_menu(app: &mut DesignApp) -> Vec<String> {
|
||||
let ty = menu_tree().into_iter().find(|(m, _)| *m == "Type").unwrap().1;
|
||||
let ctx = egui::Context::default();
|
||||
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(400.0, 4000.0));
|
||||
let mut out = ctx.run_ui(egui::RawInput { screen_rect: Some(screen), ..Default::default() }, |ui| {
|
||||
menu_items(app, ui, &ty, "Type");
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
fn collect(shape: &egui::Shape, out: &mut Vec<String>) {
|
||||
match shape {
|
||||
egui::Shape::Text(t) => out.push(t.galley.job.text.trim().to_string()),
|
||||
egui::Shape::Vec(v) => v.iter().for_each(|s| collect(s, out)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let mut labels = Vec::new();
|
||||
out.shapes.iter().for_each(|s| collect(&s.shape, &mut labels));
|
||||
labels
|
||||
}
|
||||
|
||||
/// Tate-Chu-Yoko, Ruby, Kenten and Warichu are in the Type menu of InDesign's Japanese edition
|
||||
/// only: they show while Show CJK Features is on, which follows the interface language until
|
||||
/// the user sets it. Hidden, they still run.
|
||||
#[test]
|
||||
fn cjk_menu_items_follow_show_cjk_features() {
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
let ty = menu_tree().into_iter().find(|(m, _)| *m == "Type").unwrap().1;
|
||||
let cjk: Vec<String> = ty
|
||||
.iter()
|
||||
.filter_map(|i| match i {
|
||||
Item::Cmd { label, id, .. } if CJK_ONLY.contains(&id.as_str()) => Some(label.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(cjk.len(), 4, "{cjk:?}");
|
||||
let drawn = |app: &mut DesignApp| {
|
||||
let labels = drawn_type_menu(app);
|
||||
assert!(labels.iter().any(|l| l == crate::i18n::tr(&app.ui.language, "Story Direction")), "in both editions: {labels:?}");
|
||||
let shown: Vec<bool> = cjk.iter().map(|l| labels.iter().any(|d| d == crate::i18n::tr(&app.ui.language, l))).collect();
|
||||
assert!(shown.iter().all(|s| *s == shown[0]), "{shown:?}");
|
||||
shown[0]
|
||||
};
|
||||
for (lang, on) in [("", false), ("de", false), ("ja", true), ("zh", true), ("ko", true)] {
|
||||
app.ui.language = lang.into();
|
||||
assert_eq!(crate::cjk_features(&app), on, "{lang}");
|
||||
assert_eq!(drawn(&mut app), on, "{lang}");
|
||||
}
|
||||
app.run("prefs.set", json!({"cjkFeatures": false})).unwrap();
|
||||
assert!(!drawn(&mut app), "off in a Korean interface");
|
||||
app.ui.language = String::new();
|
||||
app.run("prefs.set", json!({"cjkFeatures": true})).unwrap();
|
||||
assert!(drawn(&mut app), "on in an English interface");
|
||||
assert!(quick_apply_items(&app.session, "ruby", crate::cjk_features(&app)).iter().any(|i| i.id == "app.rubyDialog"));
|
||||
app.run("prefs.set", json!({"cjkFeatures": false})).unwrap();
|
||||
assert!(!drawn(&mut app));
|
||||
assert!(!quick_apply_items(&app.session, "ruby", crate::cjk_features(&app)).iter().any(|i| i.id == "app.rubyDialog"));
|
||||
app.run("app.rubyDialog", json!({})).unwrap();
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.id.as_str()), Some("ruby"), "hidden, not removed");
|
||||
}
|
||||
|
||||
/// Preferences › Type › Show CJK Features: left as it is, it keeps following the interface
|
||||
/// language; changed, it is kept in prefs.json.
|
||||
#[test]
|
||||
fn show_cjk_features_is_a_persisted_preference() {
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.ui.language = "ja".into();
|
||||
run_ui(&mut app, "app.preferences", &json!({})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().fields["cjkFeatures"], true);
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert_eq!(app.session.prefs.cjk_features, None, "untouched: follows the language");
|
||||
run_ui(&mut app, "app.preferences", &json!({})).unwrap().unwrap();
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("cjkFeatures".into(), json!(false));
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert_eq!(app.session.prefs.cjk_features, Some(false));
|
||||
assert!(!crate::cjk_features(&app));
|
||||
// prefs.json round trip; a file from before the setting follows the language.
|
||||
let saved = serde_json::to_vec(&app.session.prefs).unwrap();
|
||||
assert_eq!(serde_json::from_slice::<designcraft_engine::Prefs>(&saved).unwrap().cjk_features, Some(false));
|
||||
assert_eq!(serde_json::from_str::<designcraft_engine::Prefs>(r#"{"scaleStrokes": false}"#).unwrap().cjk_features, None);
|
||||
assert_eq!(app.run("prefs.set", json!({"cjkFeatures": null})).unwrap()["cjkFeatures"], Value::Null);
|
||||
assert!(crate::cjk_features(&app), "follows the Japanese interface again");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn menu_button_fits_its_anchor_and_resets_scroll_on_reopen() {
|
||||
// Workspace/panel menus may be in the middle; the status-bar zoom menu opens upwards.
|
||||
|
||||
@@ -46,6 +46,7 @@ presets or tables are copied. Our default mojikumi and kinsoku tables are built
|
||||
| Missing glyphs | as in InDesign: characters the applied font lacks are drawn as its missing-glyph box (screen and PDF) and listed by Preflight; the document setting Draw Missing Glyphs from Fallback Fonts (Preferences › Composition, off in new documents and IDML imports, on in documents saved before it existed) draws them from fallback fonts instead |
|
||||
| Font menus | Western families first, then Japanese, Simplified Chinese, Traditional Chinese and Korean groups (from the font's `meta` languages, CJK family names, OS/2 code pages, else its coverage); CJK families under their native names unless Preferences › Type › Show Font Names in English; the order is recalled from InDesign, not observed |
|
||||
| CJK fonts | Japanese faces (Shippori Mincho first) from the optional craft-fonts build input, in the font menus for users to apply; fallback by language (with fallback fonts on) (Japanese: the craft-fonts faces, then system fonts; Simplified and Traditional Chinese, Korean: system fonts); document fonts: the fonts of a `Document Fonts` folder beside an opened file belong to that document (composition, export, font menus), ahead of installed fonts of the same name, and are forgotten when it closes; Package copies every font file that draws text, fallback fonts included (licence permitting). Font files stay in memory for the session (reopening a document reuses them) and aren't read on the web |
|
||||
| Interface | Preferences › Type › Show CJK Features (`cjkFeatures`; until set, on for a Japanese, Chinese or Korean interface) shows the CJK-only menu items (Type › Tate-Chu-Yoko, Ruby…, Kenten, Warichu); layout, IDML and commands work either way |
|
||||
| Everything else below | missing |
|
||||
|
||||
Known bugs to fix first:
|
||||
|
||||
Reference in New Issue
Block a user