mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-01 06:06:49 -06:00
Merge PR #1255: TOC separator and style notation
crates/idml/src/tests.rs: kept the PR's TOC tests, dropped the duplicate copy from #1249 crates/engine/src/tests/idml.rs: main's createBookmarks plus the PR's right-indent separator crates/ui-egui/src/dialogs.rs: main's labelled_by fields plus the PR's special-character menu crates/ui-egui/src/i18n/*.rs: kept both added rows
This commit is contained in:
@@ -490,6 +490,7 @@ Newest first.
|
||||
- **2026-10-20** · Drawing a frame or shape shows its width and height in the document's ruler units, to two decimals and on two lines ("W: 6.08 cm", "H: 6.08 cm"), as InDesign does; it was always whole points. No readiness percentage change.
|
||||
- **2026-10-20** · Type tool: a drag that starts in an empty frame draws a new text frame over it, as in InDesign; a click still turns the frame into a text frame. No readiness percentage change.
|
||||
- **2026-10-20** · Measures in centimetres show four decimals, as InDesign's fields do (a 1p gutter reads 0.4233 cm, not 0.423 cm). No readiness percentage change.
|
||||
- **2026-10-18** · TOC separators preserve right-indent tabs through the dialog and IDML, including localized `$ID/^y` values. A separator picker exposes Tab Character, Right Indent Tab, Em Space and En Space. Existing paragraph tab leaders now work with the tutorial's TOC workflow. No readiness percentage change.
|
||||
- **2026-10-21** · Properties › Character and Paragraph: ••• (More Options) expands the section in place, as InDesign's: Character adds vertical and horizontal scale, baseline shift, skew, language and OpenType, and All Caps, Small Caps, Superscript, Subscript, Underline and Strikethrough; Paragraph adds the left, right, first line and last line indents, space before and after, the drop cap's lines and characters, Align to Baseline Grid and Hyphenate. It used to open the Character or Paragraph panel; the language field moved under ••• as in InDesign. Found in the Learn Skills Daily course, chapter 16. No readiness percentage change.
|
||||
- **2026-10-21** · The text Control panel has InDesign's two modes: A (Character Formatting Controls: font and style, size and leading, All Caps/Superscript/Underline and Small Caps/Subscript/Strikethrough, kerning and tracking, vertical and horizontal scale, baseline shift and skew, character style and language) and ¶ (Paragraph Formatting Controls: the nine alignments, left/first line and right/last line indents, space before and after, the drop cap's lines and characters, paragraph style, Hyphenate and Align to Baseline Grid). It used to show a fixed mix of both. Found in the Learn Skills Daily course, chapter 16. No readiness percentage change.
|
||||
- **2026-10-20** · With the Type tool, the Control panel's font size, leading and tracking fields have a spinner and a list of common values, as in InDesign (auto leading in parentheses). No readiness percentage change.
|
||||
|
||||
@@ -39,6 +39,35 @@ pub const FORCED_LINE_BREAK: char = '\u{2028}';
|
||||
pub const ODD_PAGE_BREAK: char = '\u{E011}';
|
||||
pub const EVEN_PAGE_BREAK: char = '\u{E012}';
|
||||
|
||||
/// Encode a table-of-contents separator using InDesign's special-character notation.
|
||||
pub fn toc_separator_notation(text: &str) -> String {
|
||||
text.replace('^', "^^").replace('\t', "^t").replace(RIGHT_INDENT_TAB, "^y").replace('\u{2003}', "^m").replace('\u{2002}', "^>")
|
||||
}
|
||||
|
||||
/// Decode TOC notation, preserving unknown codes and an escaped literal caret.
|
||||
pub fn toc_separator_text(notation: &str) -> String {
|
||||
let mut text = String::new();
|
||||
let mut chars = notation.chars();
|
||||
while let Some(ch) = chars.next() {
|
||||
if ch != '^' {
|
||||
text.push(ch);
|
||||
continue;
|
||||
}
|
||||
match chars.next() {
|
||||
Some('t') => text.push('\t'),
|
||||
Some('y') => text.push(RIGHT_INDENT_TAB),
|
||||
Some('m') => text.push('\u{2003}'),
|
||||
Some('>') => text.push('\u{2002}'),
|
||||
Some('^') | None => text.push('^'),
|
||||
Some(other) => {
|
||||
text.push('^');
|
||||
text.push(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
/// A column, frame or page break character. It ends its paragraph: the story stores it as the
|
||||
/// paragraph's last character, followed by the `\n` separator (also at the end of the story), and
|
||||
/// the next paragraph starts in the next column, frame or page. A break inside a paragraph (from
|
||||
|
||||
@@ -527,7 +527,7 @@ fn table_of_contents_definition_round_trips_through_idml_and_updates() {
|
||||
s.execute(
|
||||
"toc.generate",
|
||||
&json!({"page": page, "title": "Inside", "titleStyle": basic, "createBookmarks": true, "entries": [
|
||||
{"style": "Cover Title", "level": 1, "pageNumber": "before", "separator": " ^ "},
|
||||
{"style": "Cover Title", "level": 1, "pageNumber": "before", "separator": " ^ \u{E006} "},
|
||||
{"style": "Headline", "level": 2, "entryStyle": basic, "pageNumber": "none"},
|
||||
]}),
|
||||
)
|
||||
|
||||
@@ -313,10 +313,12 @@ impl<'r> Importer<'r> {
|
||||
let story = top.iter().find(|e| e.local() == "Story" && e.get("AppliedTOCStyle") == Some(id))?;
|
||||
Some((ts, *self.story_ids.get(story.get("Self")?)?))
|
||||
})?;
|
||||
let known = |r: Option<&str>| r.map(|r| self.para_style_ref(r)).filter(|n| self.styles.para(n).is_some());
|
||||
// Property children are element text: formatting whitespace around a reference is dropped
|
||||
// unless the reference as written names a style.
|
||||
let known = |r: Option<&str>| r.map(|r| self.listed_para_style_ref(r)).filter(|n| self.styles.para(n).is_some());
|
||||
// Character styles the document has; [None] is stored as no style.
|
||||
let known_chars = |r: Option<&str>| {
|
||||
r.map(|r| self.char_style_ref(r)).filter(|n| n != designcraft_doc::NO_CHAR_STYLE && self.styles.char_style(n).is_some())
|
||||
r.map(|r| self.listed_char_style_ref(r)).filter(|n| n != designcraft_doc::NO_CHAR_STYLE && self.styles.char_style(n).is_some())
|
||||
};
|
||||
let entries: Vec<TocEntry> = ts
|
||||
.elements()
|
||||
|
||||
@@ -223,6 +223,11 @@ impl<'r> Importer<'r> {
|
||||
self.para_names.get(r).cloned().unwrap_or_else(|| names::style_name_in(PARA_BUILTINS, &unescape_id(r.trim_start_matches("ParagraphStyle/"))))
|
||||
}
|
||||
|
||||
/// A paragraph style reference written as an element's text: as written when it names a
|
||||
/// style, otherwise without surrounding whitespace (see [`Self::listed_char_style_ref`]).
|
||||
pub(super) fn listed_para_style_ref(&self, r: &str) -> String {
|
||||
if self.para_names.contains_key(r) { self.para_style_ref(r) } else { self.para_style_ref(r.trim()) }
|
||||
}
|
||||
/// A character style reference written as an element's text (nested, line and GREP styles).
|
||||
/// A style name may end in a space, so the reference is used as written when it names a
|
||||
/// style; only otherwise is surrounding whitespace dropped.
|
||||
|
||||
+17
-22
@@ -858,30 +858,25 @@ pub fn digits_in(s: &str) -> Option<designcraft_doc::Digits> {
|
||||
[Digits::Default, Digits::Arabic, Digits::Hindi, Digits::Farsi, Digits::Native].into_iter().find(|d| digits_out(*d) == s)
|
||||
}
|
||||
|
||||
/// A TOC separator in IDML's special-character notation: `^t` is a tab, `^^` a caret.
|
||||
/// A TOC separator in IDML's special-character notation.
|
||||
pub fn toc_separator_out(s: &str) -> String {
|
||||
s.replace('^', "^^").replace('\t', "^t")
|
||||
designcraft_doc::story::toc_separator_notation(s)
|
||||
}
|
||||
/// IDML TOC separator → text (`^t` and `^y`, the right indent tab, are tabs; other codes stay).
|
||||
/// IDML may prefix a separator with its localized-string marker.
|
||||
pub fn toc_separator_in(s: &str) -> String {
|
||||
// InDesign writes its built-in values with the `$ID/` prefix (`$ID/^y`, the right indent tab).
|
||||
let s = s.strip_prefix("$ID/").unwrap_or(s);
|
||||
let mut out = String::new();
|
||||
let mut it = s.chars();
|
||||
while let Some(c) = it.next() {
|
||||
if c != '^' {
|
||||
out.push(c);
|
||||
continue;
|
||||
}
|
||||
match it.next() {
|
||||
Some('t' | 'y') => out.push('\t'),
|
||||
Some('^') => out.push('^'),
|
||||
Some(o) => {
|
||||
out.push('^');
|
||||
out.push(o);
|
||||
}
|
||||
None => out.push('^'),
|
||||
}
|
||||
designcraft_doc::story::toc_separator_text(s.strip_prefix("$ID/").unwrap_or(s))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod toc_separator_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn localized_right_indent_tab_is_not_an_ordinary_tab() {
|
||||
assert_eq!(toc_separator_in("$ID/^y"), "\u{E006}");
|
||||
assert_eq!(toc_separator_in("$ID/ ^y "), " \u{E006} ");
|
||||
assert_eq!(toc_separator_in("^t"), "\t");
|
||||
assert_eq!(toc_separator_out("\u{E006}"), "^y");
|
||||
assert_eq!(toc_separator_in("^^y ^q ^"), "^y ^q ^");
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
+92
-34
@@ -4633,6 +4633,98 @@ fn cell_first_baseline_comes_from_cell_styles_table_regions_and_cells_and_round_
|
||||
assert_eq!(s2.first_baseline, Some(FirstBaseline::CapHeight));
|
||||
}
|
||||
|
||||
/// A TOC style as InDesign writes it: `$ID/`-prefixed separator (`^y`, the right indent tab) and entry style.
|
||||
const TOC_STYLES: &str = r#"<idPkg:Styles xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging">
|
||||
<RootParagraphStyleGroup>
|
||||
<ParagraphStyle Self="ParagraphStyle/Heading 1" Name="Heading 1"/>
|
||||
<ParagraphStyleGroup Self="psg-toc" Name="TOC">
|
||||
<ParagraphStyle Self="ParagraphStyle/TOC%3aTOC body text" Name="TOC:TOC body text" Capitalization="AllCaps"/>
|
||||
<ParagraphStyle Self="ParagraphStyle/TOC%3aTOC Header" Name="TOC:TOC Header"/>
|
||||
</ParagraphStyleGroup>
|
||||
</RootParagraphStyleGroup>
|
||||
<RootCharacterStyleGroup><CharacterStyle Self="CharacterStyle/TOC Page Number" Name="TOC Page Number"/></RootCharacterStyleGroup>
|
||||
<TOCStyle Self="TOCStyle/$ID/DefaultTOCStyleName" TitleStyle="ParagraphStyle/TOC%3aTOC Header" Title="Table of Contents" Name="$ID/DefaultTOCStyleName">
|
||||
<TOCStyleEntry Self="e1" Name="Heading 1" Level="1" PageNumberPosition="AfterEntry" Separator="$ID/^y">
|
||||
<Properties><FormatStyle type="object">ParagraphStyle/$ID/TOC%3aTOC body text</FormatStyle>
|
||||
<PageNumberStyle type="object">CharacterStyle/TOC Page Number</PageNumberStyle>
|
||||
<SeparatorStyle type="string">$ID/[Same Style]</SeparatorStyle></Properties>
|
||||
</TOCStyleEntry>
|
||||
</TOCStyle></idPkg:Styles>"#;
|
||||
|
||||
const TOC_STORY: &str = r#"<idPkg:Story xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging">
|
||||
<Story Self="s1" AppliedTOCStyle="TOCStyle/$ID/DefaultTOCStyleName"><ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/TOC%3aTOC body text"><CharacterStyleRange><Content>Welcome</Content></CharacterStyleRange></ParagraphStyleRange></Story></idPkg:Story>"#;
|
||||
|
||||
#[test]
|
||||
fn toc_style_entries_read_id_prefixed_separators_and_styles() {
|
||||
let d = import_idml(&fixture_with_table_styles(TOC_STYLES, TOC_STORY)).unwrap();
|
||||
let toc = d.toc.as_ref().expect("the story's TOC style is imported");
|
||||
let entry = toc.entries.iter().find(|e| e.style == "Heading 1").unwrap();
|
||||
assert_eq!(entry.separator.as_deref(), Some("\u{E006}"), "right-indent tabs must not become ordinary tab stops");
|
||||
assert_eq!(entry.entry_style.as_deref(), Some("TOC/TOC body text"));
|
||||
assert_eq!(entry.page_number_style.as_deref(), Some("TOC Page Number"));
|
||||
assert_eq!(toc.title_style.as_deref(), Some("TOC/TOC Header"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toc_style_entries_fall_back_on_malformed_and_unsupported_values() {
|
||||
let long = "x".repeat(65);
|
||||
let entries = format!(
|
||||
r#"<TOCStyleEntry Self="e1" Name="Heading 1" Level="abc" PageNumberPosition="Sideways" Separator="$ID/^q">
|
||||
<Properties><FormatStyle type="object">ParagraphStyle/Missing</FormatStyle>
|
||||
<PageNumberStyle type="object">CharacterStyle/$ID/[None]</PageNumberStyle>
|
||||
<SeparatorStyle type="object">CharacterStyle/Missing</SeparatorStyle></Properties>
|
||||
</TOCStyleEntry>
|
||||
<TOCStyleEntry Self="e2" Name="TOC:TOC Header" Level="12" Separator="{long}"/>
|
||||
<TOCStyleEntry Self="e3" Name="Heading 1" Level="2" Separator="^t"/>
|
||||
<TOCStyleEntry Self="e4" Name="" Level="3"/>"#
|
||||
);
|
||||
let start = TOC_STYLES.find("<TOCStyleEntry").unwrap();
|
||||
let end = TOC_STYLES.find("</TOCStyle>").unwrap();
|
||||
let styles = format!("{}{entries}\n{}", &TOC_STYLES[..start], &TOC_STYLES[end..])
|
||||
.replace(r#"TitleStyle="ParagraphStyle/TOC%3aTOC Header""#, r#"TitleStyle="ParagraphStyle/Missing""#);
|
||||
let d = import_idml(&fixture_with_table_styles(&styles, TOC_STORY)).unwrap();
|
||||
d.check().unwrap();
|
||||
let toc = d.toc.as_ref().expect("the TOC style is still imported");
|
||||
assert_eq!(toc.title_style, None, "a title style the document lacks is dropped");
|
||||
assert_eq!(toc.entries.len(), 3, "an entry without a style name is skipped: {:?}", toc.entries);
|
||||
let e1 = &toc.entries[0];
|
||||
assert_eq!(e1.level, 1, "an unreadable level falls back to 1");
|
||||
assert_eq!(e1.page_number, designcraft_doc::TocPageNumber::After, "an unknown page-number position falls back to After");
|
||||
assert_eq!(e1.separator.as_deref(), Some("^q"), "an unknown special-character code is kept as written");
|
||||
assert_eq!(
|
||||
(e1.entry_style.as_deref(), e1.page_number_style.as_deref(), e1.separator_style.as_deref()),
|
||||
(None, None, None),
|
||||
"undefined and [None] styles are no style"
|
||||
);
|
||||
let e2 = &toc.entries[1];
|
||||
assert_eq!(e2.level, 9, "a level past 9 is clamped");
|
||||
assert_eq!(e2.separator, None, "an over-long separator is dropped");
|
||||
assert_eq!((toc.entries[2].level, toc.entries[2].separator.as_deref()), (2, None), "a plain tab is the default separator");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toc_style_references_in_indented_properties_resolve() {
|
||||
// Property children laid out on their own lines: the whitespace around a reference isn't
|
||||
// part of the style name.
|
||||
let styles = TOC_STYLES
|
||||
.replace(
|
||||
">ParagraphStyle/$ID/TOC%3aTOC body text<",
|
||||
">
|
||||
ParagraphStyle/$ID/TOC%3aTOC body text
|
||||
<",
|
||||
)
|
||||
.replace(
|
||||
">CharacterStyle/TOC Page Number<",
|
||||
">
|
||||
CharacterStyle/TOC Page Number
|
||||
<",
|
||||
);
|
||||
let d = import_idml(&fixture_with_table_styles(&styles, TOC_STORY)).unwrap();
|
||||
let entry = d.toc.as_ref().unwrap().entries.iter().find(|e| e.style == "Heading 1").unwrap();
|
||||
assert_eq!(entry.entry_style.as_deref(), Some("TOC/TOC body text"));
|
||||
assert_eq!(entry.page_number_style.as_deref(), Some("TOC Page Number"));
|
||||
}
|
||||
|
||||
/// A graphic placed in a table cell is written into the cell, not dropped.
|
||||
#[test]
|
||||
fn exports_graphic_in_table_cell() {
|
||||
@@ -4827,37 +4919,3 @@ fn epub_split_document_round_trips() {
|
||||
assert_eq!(d.settings.epub_split, want, "{attrs}");
|
||||
assert_eq!(import_idml(&export_idml(&d)).unwrap().settings.epub_split, want, "{attrs}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A TOC style as InDesign writes it: `$ID/`-prefixed separator (`^y`, the right indent tab) and entry style.
|
||||
const TOC_STYLES: &str = r#"<idPkg:Styles xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging">
|
||||
<RootParagraphStyleGroup>
|
||||
<ParagraphStyle Self="ParagraphStyle/Heading 1" Name="Heading 1"/>
|
||||
<ParagraphStyleGroup Self="psg-toc" Name="TOC">
|
||||
<ParagraphStyle Self="ParagraphStyle/TOC%3aTOC body text" Name="TOC:TOC body text" Capitalization="AllCaps"/>
|
||||
<ParagraphStyle Self="ParagraphStyle/TOC%3aTOC Header" Name="TOC:TOC Header"/>
|
||||
</ParagraphStyleGroup>
|
||||
</RootParagraphStyleGroup>
|
||||
<RootCharacterStyleGroup><CharacterStyle Self="CharacterStyle/TOC Page Number" Name="TOC Page Number"/></RootCharacterStyleGroup>
|
||||
<TOCStyle Self="TOCStyle/$ID/DefaultTOCStyleName" TitleStyle="ParagraphStyle/TOC%3aTOC Header" Title="Table of Contents" Name="$ID/DefaultTOCStyleName">
|
||||
<TOCStyleEntry Self="e1" Name="Heading 1" Level="1" PageNumberPosition="AfterEntry" Separator="$ID/^y">
|
||||
<Properties><FormatStyle type="object">ParagraphStyle/$ID/TOC%3aTOC body text</FormatStyle>
|
||||
<PageNumberStyle type="object">CharacterStyle/TOC Page Number</PageNumberStyle>
|
||||
<SeparatorStyle type="string">$ID/[Same Style]</SeparatorStyle></Properties>
|
||||
</TOCStyleEntry>
|
||||
</TOCStyle></idPkg:Styles>"#;
|
||||
|
||||
const TOC_STORY: &str = r#"<idPkg:Story xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging">
|
||||
<Story Self="s1" AppliedTOCStyle="TOCStyle/$ID/DefaultTOCStyleName"><ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/TOC%3aTOC body text"><CharacterStyleRange><Content>Welcome</Content></CharacterStyleRange></ParagraphStyleRange></Story></idPkg:Story>"#;
|
||||
|
||||
#[test]
|
||||
fn toc_style_entries_read_id_prefixed_separators_and_styles() {
|
||||
let d = import_idml(&fixture_with_table_styles(TOC_STYLES, TOC_STORY)).unwrap();
|
||||
let toc = d.toc.as_ref().expect("the story's TOC style is imported");
|
||||
let entry = toc.entries.iter().find(|e| e.style == "Heading 1").unwrap();
|
||||
assert_eq!(entry.separator, None, "`$ID/^y` is the right indent tab, the default separator, not text: {:?}", entry.separator);
|
||||
assert_eq!(entry.entry_style.as_deref(), Some("TOC/TOC body text"));
|
||||
assert_eq!(entry.page_number_style.as_deref(), Some("TOC Page Number"));
|
||||
assert_eq!(toc.title_style.as_deref(), Some("TOC/TOC Header"));
|
||||
}
|
||||
|
||||
@@ -322,6 +322,40 @@ mod color_theme_tests {
|
||||
assert_eq!(parent(h.state(), 3), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toc_right_indent_separator_survives_the_dialog_and_update() {
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.newSample", json!({})).unwrap();
|
||||
open_toc(&mut app);
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert(
|
||||
"included".into(),
|
||||
json!([
|
||||
{"style": "Headline", "level": 1, "separator": " ^y "}
|
||||
]),
|
||||
);
|
||||
confirm(&mut app).unwrap();
|
||||
if app.session.tool_id() == "tocPlace" {
|
||||
app.run("toc.place", json!({"x":36,"y":36})).unwrap();
|
||||
}
|
||||
let separator = format!(" {} ", designcraft_doc::story::RIGHT_INDENT_TAB);
|
||||
let d = &app.session.doc().unwrap().doc;
|
||||
let toc = d.toc.as_ref().unwrap();
|
||||
assert_eq!(toc.entries[0].separator.as_deref(), Some(separator.as_str()));
|
||||
assert!(d.story(toc.story).unwrap().text.contains(&separator));
|
||||
app.run("toc.update", json!({})).unwrap();
|
||||
open_toc(&mut app);
|
||||
assert_eq!(toc_rows(app.ui.dialog.as_ref().unwrap())[0].separator, " ^y ");
|
||||
for text in ["^y", "^t^y", "^m^>", "unknown ^q and trailing ^", "a\t\u{E006}\u{2003}\u{2002}z"] {
|
||||
assert_eq!(toc_separator_typed(&toc_separator_shown(text)), text);
|
||||
}
|
||||
// The picker after a literal caret: the caret stays, and the code is the code.
|
||||
for (field, typed) in [("^", "^\u{E006}"), ("^^", "^\u{E006}"), ("a", "a\u{E006}"), ("^^^", "^^\u{E006}")] {
|
||||
let mut f = field.to_string();
|
||||
toc_separator_append(&mut f, "^y");
|
||||
assert_eq!(toc_separator_typed(&f), typed, "{field:?} → {f:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_theme_cancel_is_read_only_and_confirm_adds_the_preview() {
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
@@ -7571,11 +7605,20 @@ struct TocRow {
|
||||
}
|
||||
|
||||
fn toc_separator_shown(s: &str) -> String {
|
||||
s.replace('\t', "^t").replace('\u{2003}', "^m").replace('\u{2002}', "^>")
|
||||
designcraft_doc::story::toc_separator_notation(s)
|
||||
}
|
||||
|
||||
fn toc_separator_typed(s: &str) -> String {
|
||||
s.replace("^t", "\t").replace("^m", "\u{2003}").replace("^>", "\u{2002}")
|
||||
designcraft_doc::story::toc_separator_text(s)
|
||||
}
|
||||
|
||||
/// Appends the special-character `code` (such as `^y`) to the separator field's notation. A
|
||||
/// trailing unescaped caret is a literal caret: it's escaped first, so it doesn't swallow the code.
|
||||
fn toc_separator_append(field: &mut String, code: &str) {
|
||||
if field.chars().rev().take_while(|&c| c == '^').count() % 2 == 1 {
|
||||
field.push('^');
|
||||
}
|
||||
field.push_str(code);
|
||||
}
|
||||
|
||||
fn toc_rows(d: &Dialog) -> Vec<TocRow> {
|
||||
@@ -7801,7 +7844,19 @@ fn toc_dialog(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
toc_style_combo(ui, "page_number", &char_styles, none, &mut r.page_number_style, name);
|
||||
ui.end_row();
|
||||
let between = crate::rtl::label(ui, tr("Between Entry and Number:")).id;
|
||||
ui.add(egui::TextEdit::singleline(&mut r.separator).desired_width(80.0).align(egui::Align2::LEFT_TOP)).labelled_by(between);
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::TextEdit::singleline(&mut r.separator).desired_width(80.0).align(egui::Align2::LEFT_TOP)).labelled_by(between);
|
||||
ui.menu_button("▸", |ui| {
|
||||
for (label, code) in [("Tab Character", "^t"), ("Right Indent Tab", "^y"), ("Em Space", "^m"), ("En Space", "^>")] {
|
||||
if ui.button(tr(label)).clicked() {
|
||||
toc_separator_append(&mut r.separator, code);
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
})
|
||||
.response
|
||||
.on_hover_text(tr("Insert Special Character"));
|
||||
});
|
||||
let name = crate::rtl::label(ui, tr("Style:")).id;
|
||||
toc_style_combo(ui, "separator", &char_styles, none, &mut r.separator_style, name);
|
||||
ui.end_row();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Original Arabic interface translations, stored in logical Unicode order.
|
||||
pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Fewer Options", "خيارات أقل"),
|
||||
("Tab Character", "محرف جدولة"),
|
||||
("File", "ملف"),
|
||||
("Edit", "تحرير"),
|
||||
("Layout", "تخطيط"),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! the ArtCraft siblings (VectorCraft, PhotoCraft) are kept identical; see ASSETS.md for sources.
|
||||
pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Fewer Options", "Méně možností"),
|
||||
("Tab Character", "Znak tabulátoru"),
|
||||
("File", "Soubor"),
|
||||
("Edit", "Úpravy"),
|
||||
("Layout", "Rozvržení"),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! table is carried over and stays the fallback for keys added there after this table.
|
||||
pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Fewer Options", "Weniger Optionen"),
|
||||
("Tab Character", "Tabulatorzeichen"),
|
||||
("File", "Datei"),
|
||||
("Edit", "Bearbeiten"),
|
||||
("Layout", "Layout"),
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
//! using ordinary Italian desktop-publishing vocabulary.
|
||||
pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Fewer Options", "Meno opzioni"),
|
||||
("Tab Character", "Carattere di tabulazione"),
|
||||
("Right Indent Tab", "Tabulazione rientro destro"),
|
||||
("Em Space", "Spazio em"),
|
||||
("En Space", "Spazio en"),
|
||||
("File", "File"),
|
||||
("Edit", "Modifica"),
|
||||
("Layout", "Layout"),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! shared table are carried over unchanged.
|
||||
pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Fewer Options", "オプションを減らす"),
|
||||
("Tab Character", "タブ文字"),
|
||||
("File", "ファイル"),
|
||||
("Edit", "編集"),
|
||||
("Layout", "レイアウト"),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! the code or the command table use.
|
||||
pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Fewer Options", "ნაკლები პარამეტრი"),
|
||||
("Tab Character", "ტაბულაციის სიმბოლო"),
|
||||
("File", "ფაილი"),
|
||||
("Edit", "რედაქტირება"),
|
||||
("Layout", "განლაგება"),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! (rozkładówka, łam, strona wzorcowa, obszar informacyjny) and the sibling ArtCraft catalogs.
|
||||
pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Fewer Options", "Mniej opcji"),
|
||||
("Tab Character", "Znak tabulacji"),
|
||||
("File", "Plik"),
|
||||
("Edit", "Edycja"),
|
||||
("Layout", "Układ"),
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
//! using ordinary Brazilian Portuguese computing vocabulary.
|
||||
pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Fewer Options", "Menos opções"),
|
||||
("Tab Character", "Caractere de tabulação"),
|
||||
("Right Indent Tab", "Tabulação de recuo à direita"),
|
||||
("Em Space", "Espaço eme"),
|
||||
("En Space", "Espaço ene"),
|
||||
("File", "Arquivo"),
|
||||
("Edit", "Editar"),
|
||||
("Layout", "Layout"),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! category before the number ("страниц: 5"), see `count_label`.
|
||||
pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Fewer Options", "Меньше параметров"),
|
||||
("Tab Character", "Символ табуляции"),
|
||||
("File", "Файл"),
|
||||
("Edit", "Редактирование"),
|
||||
("Can't Open the File", "Не удалось открыть файл"),
|
||||
|
||||
@@ -4749,6 +4749,7 @@ pub(super) const TABLE: &[(&str, [&str; 5])] = &[
|
||||
("OpenType font", ["OpenType-Schrift", "Police OpenType", "Fuente OpenType", "OpenType フォント", "OpenType 字体"]),
|
||||
("Variable font", ["Variable Schrift", "Police variable", "Fuente variable", "バリアブルフォント", "可变字体"]),
|
||||
("Color font", ["Farbschrift", "Police couleur", "Fuente de color", "カラーフォント", "彩色字体"]),
|
||||
("Tab Character", ["Tabulatorzeichen", "Caractère de tabulation", "Carácter de tabulación", "タブ文字", "制表符"]),
|
||||
// Table of Contents.
|
||||
(
|
||||
"Sort Entries in Alphabetical Order",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
//! (VectorCraft, PhotoCraft, WordCraft and DeckCraft); see ASSETS.md for pinned sources.
|
||||
pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Fewer Options", "Менше параметрів"),
|
||||
("Tab Character", "Символ табуляції"),
|
||||
("Right Indent Tab", "Табуляція правого відступу"),
|
||||
("Em Space", "Кругла шпація"),
|
||||
("En Space", "Півкругла шпація"),
|
||||
("File", "Файл"),
|
||||
("Edit", "Редагування"),
|
||||
("Layout", "Макет"),
|
||||
|
||||
@@ -76,6 +76,12 @@ number, separator, sorting and level controls. Toggling visibility preserves the
|
||||
|
||||
## Remaining effort
|
||||
|
||||
The Table of Contents separator field accepts `^y` (Right Indent Tab), with a special-character
|
||||
picker alongside it. Literal carets use `^^`; ordinary tabs, em/en spaces and surrounding spaces
|
||||
survive reopening. IDML preserves right-indent separators, including `$ID/^y`, so updating an
|
||||
imported TOC keeps page numbers at the right edge and inherits paragraph tab leaders. This covers
|
||||
10:54–16:08 of [Michael Bullo's TOC tutorial](https://www.youtube.com/watch?v=m4y2tm6LH_c).
|
||||
|
||||
Applying a paragraph or character style now removes local values equal to the applied style,
|
||||
so text used to capture a style follows later edits to that style. Different local formatting
|
||||
and named character styles stay intact. Paragraph-style application treats selection endpoints
|
||||
|
||||
Reference in New Issue
Block a user