Paragraph: Asian Typography settings, with kinsoku and mid-word Latin wrapping in layout (#269)
Paragraphs gain the Asian Typography flags (line-breaking rules, wrapping Latin text in the middle of a word, hanging punctuation, compressing punctuation at the start of a line, and automatic space between Asian and Latin text and between Asian text and numbers). They inherit through styles, round-trip through .docx pPr, are set by the new para.asianTypography command and appear on a new Asian Typography tab of the Paragraph dialog. Layout applies the kinsoku lists (no line starts with closing East Asian punctuation or ends with opening punctuation), breaks between any East Asian characters when kinsoku is off, and breaks Latin words anywhere when word wrap is off. Text without East Asian characters breaks as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7b8c38fa62
commit
147a055468
|
|
@ -622,13 +622,34 @@ pub struct ParaProps {
|
|||
/// Drop cap: lines to drop (0 = none) — applies to the first character(s).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub drop_cap: Option<u8>,
|
||||
/// Asian typography (`w:kinsoku`): East Asian line-breaking rules — no line starts with
|
||||
/// closing punctuation such as 、。」 or ends with opening punctuation such as 「(.
|
||||
/// Unset = on.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub kinsoku: Option<bool>,
|
||||
/// `w:wordWrap`: Latin words wrap whole; `false` lets them break at any character (Word's
|
||||
/// "Allow Latin text to wrap in the middle of a word"). Unset = on.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub word_wrap: Option<bool>,
|
||||
/// `w:overflowPunct`: punctuation may hang past the line end. Unset = on.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub overflow_punct: Option<bool>,
|
||||
/// `w:topLinePunct`: compress punctuation at the start of a line. Unset = off.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub top_line_punct: Option<bool>,
|
||||
/// `w:autoSpaceDE`: automatic space between Asian and Latin text. Unset = on.
|
||||
#[serde(rename = "autoSpaceDE", skip_serializing_if = "Option::is_none")]
|
||||
pub auto_space_de: Option<bool>,
|
||||
/// `w:autoSpaceDN`: automatic space between Asian text and numbers. Unset = on.
|
||||
#[serde(rename = "autoSpaceDN", skip_serializing_if = "Option::is_none")]
|
||||
pub auto_space_dn: Option<bool>,
|
||||
}
|
||||
|
||||
impl ParaProps {
|
||||
pub fn overlay(&mut self, patch: &ParaProps) {
|
||||
overlay_fields!(self, patch; style, align, indent_left, indent_right, indent_first, space_before, space_after, line_spacing,
|
||||
contextual_spacing, keep_next, keep_lines, page_break_before, widow_control, outline_level, numbering, tabs, shading, borders,
|
||||
suppress_hyphens, suppress_line_numbers, bidi, drop_cap);
|
||||
suppress_hyphens, suppress_line_numbers, bidi, drop_cap, kinsoku, word_wrap, overflow_punct, top_line_punct, auto_space_de, auto_space_dn);
|
||||
}
|
||||
pub fn overlaid(mut self, patch: &ParaProps) -> ParaProps {
|
||||
self.overlay(patch);
|
||||
|
|
|
|||
|
|
@ -141,6 +141,14 @@ pub struct ResolvedPara {
|
|||
pub bidi: bool,
|
||||
pub drop_cap: u8,
|
||||
pub suppress_hyphens: bool,
|
||||
/// Asian typography (see `ParaProps`): East Asian line-breaking rules.
|
||||
pub kinsoku: bool,
|
||||
/// Latin words wrap whole (`false`: they may break at any character).
|
||||
pub word_wrap: bool,
|
||||
pub overflow_punct: bool,
|
||||
pub top_line_punct: bool,
|
||||
pub auto_space_de: bool,
|
||||
pub auto_space_dn: bool,
|
||||
}
|
||||
|
||||
const MAX_INDENT: f32 = 1584.0; // 22"
|
||||
|
|
@ -186,6 +194,12 @@ impl ResolvedPara {
|
|||
bidi: p.bidi.unwrap_or(false),
|
||||
drop_cap: p.drop_cap.unwrap_or(0).min(10),
|
||||
suppress_hyphens: p.suppress_hyphens.unwrap_or(false),
|
||||
kinsoku: p.kinsoku.unwrap_or(true),
|
||||
word_wrap: p.word_wrap.unwrap_or(true),
|
||||
overflow_punct: p.overflow_punct.unwrap_or(true),
|
||||
top_line_punct: p.top_line_punct.unwrap_or(false),
|
||||
auto_space_de: p.auto_space_de.unwrap_or(true),
|
||||
auto_space_dn: p.auto_space_dn.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,6 +142,12 @@ impl PropCtx {
|
|||
"w:suppressAutoHyphens" => p.suppress_hyphens = Some(on_off(k)),
|
||||
"w:suppressLineNumbers" => p.suppress_line_numbers = Some(on_off(k)),
|
||||
"w:bidi" => p.bidi = Some(on_off(k)),
|
||||
"w:kinsoku" => p.kinsoku = Some(on_off(k)),
|
||||
"w:wordWrap" => p.word_wrap = Some(on_off(k)),
|
||||
"w:overflowPunct" => p.overflow_punct = Some(on_off(k)),
|
||||
"w:topLinePunct" => p.top_line_punct = Some(on_off(k)),
|
||||
"w:autoSpaceDE" => p.auto_space_de = Some(on_off(k)),
|
||||
"w:autoSpaceDN" => p.auto_space_dn = Some(on_off(k)),
|
||||
"w:framePr" => {
|
||||
if matches!(k.attr("w:dropCap"), Some("drop") | Some("margin")) {
|
||||
p.drop_cap = Some(k.attr("w:lines").and_then(u32_of).unwrap_or(3).clamp(1, 10) as u8);
|
||||
|
|
|
|||
|
|
@ -230,6 +230,12 @@ pub fn ppr_inner(w: &mut W, p: &ParaProps, framed: bool) {
|
|||
w.close("w:tabs");
|
||||
}
|
||||
toggle(w, "w:suppressAutoHyphens", p.suppress_hyphens);
|
||||
toggle(w, "w:kinsoku", p.kinsoku);
|
||||
toggle(w, "w:wordWrap", p.word_wrap);
|
||||
toggle(w, "w:overflowPunct", p.overflow_punct);
|
||||
toggle(w, "w:topLinePunct", p.top_line_punct);
|
||||
toggle(w, "w:autoSpaceDE", p.auto_space_de);
|
||||
toggle(w, "w:autoSpaceDN", p.auto_space_dn);
|
||||
toggle(w, "w:bidi", p.bidi);
|
||||
if p.space_before.is_some() || p.space_after.is_some() || p.line_spacing.is_some() {
|
||||
let before = p.space_before.map(|v| twips(v.max(0.0)));
|
||||
|
|
|
|||
|
|
@ -136,11 +136,18 @@ fn every_para_prop_round_trips() {
|
|||
suppress_line_numbers: Some(true),
|
||||
bidi: Some(false),
|
||||
drop_cap: None,
|
||||
kinsoku: Some(false),
|
||||
word_wrap: Some(false),
|
||||
overflow_punct: Some(false),
|
||||
top_line_punct: Some(true),
|
||||
auto_space_de: Some(false),
|
||||
auto_space_dn: Some(true),
|
||||
};
|
||||
let variants = [
|
||||
ParaProps { line_spacing: Some(LineSpacing::AtLeast(14.0)), indent_first: Some(24.0), align: Some(Align::Center), ..Default::default() },
|
||||
ParaProps { line_spacing: Some(LineSpacing::Exactly(20.0)), align: Some(Align::Right), ..Default::default() },
|
||||
ParaProps { align: Some(Align::Distribute), ..Default::default() },
|
||||
ParaProps { kinsoku: Some(true), word_wrap: Some(true), auto_space_dn: Some(false), ..Default::default() },
|
||||
ParaProps::default(),
|
||||
];
|
||||
let mut ps = vec![Paragraph::with_text("main", CharProps::default())];
|
||||
|
|
|
|||
|
|
@ -129,6 +129,9 @@ pub fn specs() -> Vec<CommandSpec> {
|
|||
CommandSpec::new("para.widowControl", "Widow/Orphan Control", "Home › Paragraph › Line and Page Breaks", |s, v| {
|
||||
tog(s, v, |p| p.widow_control, |p, b| p.widow_control = Some(b))
|
||||
}),
|
||||
CommandSpec::new("para.asianTypography", "Asian Typography", "Home › Paragraph › Asian Typography", asian_typography).params(
|
||||
r#"{"kinsoku"?: bool (East Asian line-breaking rules), "wordWrap"?: bool (false: Latin words may break at any character), "overflowPunct"?: bool (hanging punctuation), "topLinePunct"?: bool (compress punctuation at the start of a line), "autoSpaceDE"?: bool (space between Asian and Latin text), "autoSpaceDN"?: bool (space between Asian text and numbers)} — flags left out stay as they are; returns the flags at the caret"#,
|
||||
),
|
||||
CommandSpec::new("para.tabs", "Tabs", "Home › Paragraph › Paragraph", |s, v| {
|
||||
let tabs: Vec<TabStop> = serde_json::from_value(v.get("tabs").cloned().unwrap_or(Value::Null)).map_err(|e| CmdError::Params(e.to_string()))?;
|
||||
if tabs.len() > 64 {
|
||||
|
|
@ -240,6 +243,42 @@ fn tog(s: &mut Session, v: &Value, get: fn(&ParaProps) -> Option<bool>, set: fn(
|
|||
fmt(s, &|p| set(p, on))
|
||||
}
|
||||
|
||||
/// Asian Typography (the Paragraph dialog's tab): sets the given flags on every selected
|
||||
/// paragraph and returns the resolved flags at the caret.
|
||||
fn asian_typography(s: &mut Session, v: &Value) -> CmdResult {
|
||||
let flag = |k: &str| p::bool(v, k);
|
||||
let (kinsoku, word_wrap, overflow, top_line, de, dn) =
|
||||
(flag("kinsoku"), flag("wordWrap"), flag("overflowPunct"), flag("topLinePunct"), flag("autoSpaceDE"), flag("autoSpaceDN"));
|
||||
let mut out = if [kinsoku, word_wrap, overflow, top_line, de, dn].iter().any(Option::is_some) {
|
||||
fmt(s, &|p| {
|
||||
let set = |field: &mut Option<bool>, val: Option<bool>| {
|
||||
if val.is_some() {
|
||||
*field = val;
|
||||
}
|
||||
};
|
||||
set(&mut p.kinsoku, kinsoku);
|
||||
set(&mut p.word_wrap, word_wrap);
|
||||
set(&mut p.overflow_punct, overflow);
|
||||
set(&mut p.top_line_punct, top_line);
|
||||
set(&mut p.auto_space_de, de);
|
||||
set(&mut p.auto_space_dn, dn);
|
||||
})?
|
||||
} else {
|
||||
sel_result(s)?
|
||||
};
|
||||
let r = cur(s);
|
||||
if let Some(o) = out.as_object_mut() {
|
||||
o.insert(
|
||||
"asianTypography".into(),
|
||||
json!({
|
||||
"kinsoku": r.kinsoku, "wordWrap": r.word_wrap, "overflowPunct": r.overflow_punct,
|
||||
"topLinePunct": r.top_line_punct, "autoSpaceDE": r.auto_space_de, "autoSpaceDN": r.auto_space_dn,
|
||||
}),
|
||||
);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn indent(s: &mut Session, _: &Value) -> CmdResult {
|
||||
let (a, b) = s.sel.ordered();
|
||||
let paths = s.doc.paths_between(&a, &b);
|
||||
|
|
|
|||
|
|
@ -1652,3 +1652,24 @@ fn timestamps_come_from_the_clock() {
|
|||
// Fixed-width ISO strings sort by time.
|
||||
assert!(date >= cmd::iso_from_unix_secs(before) && date <= cmd::iso_from_unix_secs(cmd::now_unix()), "{date}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asian_typography_sets_flags_on_selected_paragraphs_and_undoes() {
|
||||
let mut s = s();
|
||||
run(&mut s, "document.setText", json!({"text": "一\n二\n三"}));
|
||||
run(&mut s, "select.all", json!({}));
|
||||
let r = run(&mut s, "para.asianTypography", json!({"kinsoku": false, "wordWrap": false, "topLinePunct": true}));
|
||||
assert_eq!(r["asianTypography"]["kinsoku"], false);
|
||||
assert_eq!(r["asianTypography"]["autoSpaceDE"], true, "a flag left out keeps its default");
|
||||
for p in s.doc.body.iter().filter_map(|b| b.as_para()) {
|
||||
assert_eq!(
|
||||
(p.props.kinsoku, p.props.word_wrap, p.props.top_line_punct, p.props.overflow_punct),
|
||||
(Some(false), Some(false), Some(true), None)
|
||||
);
|
||||
}
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
assert!(s.doc.body.iter().filter_map(|b| b.as_para()).all(|p| p.props.kinsoku.is_none() && p.props.word_wrap.is_none()));
|
||||
// Without flags it only reports them.
|
||||
let r = run(&mut s, "para.asianTypography", json!({}));
|
||||
assert_eq!(r["asianTypography"]["kinsoku"], true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
//! Asian typography line-breaking rules (the paragraph's `kinsoku` and `word_wrap` flags), applied
|
||||
//! on top of the Unicode line-break opportunities (UAX #14).
|
||||
//!
|
||||
//! - Kinsoku on (the default): no line starts with closing East Asian punctuation (、。,)」…)
|
||||
//! and none ends with opening punctuation ((「【…). UAX #14 already forbids most of these
|
||||
//! breaks; the lists make sure of it for the standard Japanese/Chinese sets.
|
||||
//! - Kinsoku off: a line may break between any two East Asian characters, punctuation included.
|
||||
//! - Word wrap off: Latin words may break at any character.
|
||||
//!
|
||||
//! Only East Asian characters are in the lists, and the extra breaks need letters on both sides,
|
||||
//! so text with no East Asian characters breaks exactly as before unless word wrap is off.
|
||||
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
/// East Asian characters a line may not start with: closing brackets and quotes, the
|
||||
/// ideographic comma and full stop, fullwidth and halfwidth punctuation, the middle dot,
|
||||
/// iteration and sound marks.
|
||||
const NO_START: &str = "、。,.・:;?!゛゜ヽヾゝゞ々〻〉》」』】〕〗〙〛〞〟)]}⦆。」、・゙゚%¢";
|
||||
|
||||
/// East Asian characters a line may not end with: opening brackets and quotes, currency signs.
|
||||
const NO_END: &str = "〈《「『【〔〖〘〚〝([{⦅「$£¥";
|
||||
|
||||
/// Whether `c` is East Asian text: CJK ideographs, kana, bopomofo, CJK punctuation, and
|
||||
/// fullwidth/halfwidth forms (not spaces, not combining sound marks).
|
||||
pub fn is_east_asian(c: char) -> bool {
|
||||
matches!(c as u32,
|
||||
0x2E80..=0x2FFF // radicals, ideographic description
|
||||
| 0x3001..=0x303F // CJK symbols and punctuation (not the ideographic space)
|
||||
| 0x3040..=0x3098 // hiragana
|
||||
| 0x309B..=0x30FF // sound marks, katakana
|
||||
| 0x3100..=0x312F // bopomofo
|
||||
| 0x3190..=0x31FF // kanbun, CJK strokes, katakana extensions
|
||||
| 0x3400..=0x4DBF // CJK extension A
|
||||
| 0x4E00..=0x9FFF // CJK unified ideographs
|
||||
| 0xF900..=0xFAFF // compatibility ideographs
|
||||
| 0xFE30..=0xFE4F // CJK compatibility forms
|
||||
| 0xFF01..=0xFFEF // fullwidth and halfwidth forms
|
||||
| 0x20000..=0x3FFFF) // supplementary ideographs
|
||||
}
|
||||
|
||||
/// Adjust the break opportunities `opps` (byte offsets into `text`: a line may start there) for
|
||||
/// the paragraph's kinsoku and word-wrap settings. The result is sorted and has no duplicates.
|
||||
pub fn apply(text: &str, opps: &mut Vec<usize>, kinsoku: bool, word_wrap: bool) {
|
||||
let next_char = |i: usize| text.get(i..).and_then(|t| t.chars().next());
|
||||
let prev_char = |i: usize| text.get(..i).and_then(|t| t.chars().next_back());
|
||||
if kinsoku {
|
||||
opps.retain(|&i| !next_char(i).is_some_and(|c| NO_START.contains(c)) && !prev_char(i).is_some_and(|c| NO_END.contains(c)));
|
||||
} else {
|
||||
// Between any two East Asian characters.
|
||||
for (i, c) in text.char_indices().skip(1) {
|
||||
if is_east_asian(c) && prev_char(i).is_some_and(is_east_asian) {
|
||||
opps.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !word_wrap {
|
||||
// Between any two letters or digits that are not East Asian (whole graphemes, so accents
|
||||
// stay with their letters).
|
||||
let latin = |g: &str| g.chars().next().is_some_and(|c| c.is_alphanumeric() && !is_east_asian(c));
|
||||
let mut prev: Option<&str> = None;
|
||||
for (i, g) in text.grapheme_indices(true) {
|
||||
if prev.is_some_and(latin) && latin(g) {
|
||||
opps.push(i);
|
||||
}
|
||||
prev = Some(g);
|
||||
}
|
||||
}
|
||||
opps.sort_unstable();
|
||||
opps.dedup();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::apply;
|
||||
|
||||
/// Where a line may start, as the text from there to the next opportunity.
|
||||
fn breaks(text: &str, kinsoku: bool, word_wrap: bool) -> Vec<String> {
|
||||
let mut opps: Vec<usize> = unicode_linebreak::linebreaks(text).map(|(i, _)| i).filter(|&i| i < text.len()).collect();
|
||||
apply(text, &mut opps, kinsoku, word_wrap);
|
||||
opps.iter().map(|&i| text.get(i..).and_then(|t| t.chars().next()).map(String::from).unwrap_or_default()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kinsoku_and_word_wrap_break_opportunities() {
|
||||
let ja = "日本語、「漢字」。テスト";
|
||||
let on = breaks(ja, true, true);
|
||||
for c in ["、", "」", "。", "漢"] {
|
||||
assert!(!on.iter().any(|s| s == c), "kinsoku: no line starts with {c}: {on:?}");
|
||||
}
|
||||
assert!(on.iter().any(|s| s == "「"), "a line may start at an opening bracket: {on:?}");
|
||||
assert!(on.iter().any(|s| s == "テ"), "{on:?}");
|
||||
let off = breaks(ja, false, true);
|
||||
for c in ["、", "」", "。", "漢"] {
|
||||
assert!(off.iter().any(|s| s == c), "kinsoku off: a line may start with {c}: {off:?}");
|
||||
}
|
||||
// Latin text: unchanged by kinsoku, broken anywhere without word wrap.
|
||||
let en = "Hello wonderful (world), 50%.";
|
||||
assert_eq!(breaks(en, true, true), breaks(en, false, true));
|
||||
assert_eq!(breaks(en, true, true), ["w", "(", "5"]);
|
||||
let any = breaks("Hello café", true, false);
|
||||
assert_eq!(any, ["e", "l", "l", "o", "c", "a", "f", "é"]);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
pub mod display;
|
||||
pub mod fields;
|
||||
pub mod hit;
|
||||
pub mod kinsoku;
|
||||
pub mod math;
|
||||
pub mod para;
|
||||
mod table;
|
||||
|
|
|
|||
|
|
@ -803,19 +803,19 @@ pub fn layout_para(p: &Paragraph, env: &ParaEnv) -> ParaLayout {
|
|||
let to_para = |i: usize| -> Option<usize> {
|
||||
if left.is_empty() { Some(i) } else { ends.binary_search_by_key(&i, |e| e.0).ok().and_then(|k| ends.get(k)).map(|e| e.1) }
|
||||
};
|
||||
let mut opps = std::collections::HashSet::new();
|
||||
let mut found = Vec::new();
|
||||
for (i, o) in unicode_linebreak::linebreaks(&text) {
|
||||
// Word keeps "and/or" and web addresses whole: no break right after a slash (a word
|
||||
// too long for the line still breaks anywhere).
|
||||
let after_slash =
|
||||
text.get(..i).is_some_and(|t| t.ends_with('/')) && text.get(i..).and_then(|t| t.chars().next()).is_some_and(char::is_alphanumeric);
|
||||
if (o == unicode_linebreak::BreakOpportunity::Allowed || i < text.len())
|
||||
&& !after_slash
|
||||
&& let Some(end) = to_para(i)
|
||||
{
|
||||
opps.insert(end);
|
||||
if (o == unicode_linebreak::BreakOpportunity::Allowed || i < text.len()) && !after_slash {
|
||||
found.push(i);
|
||||
}
|
||||
}
|
||||
// Asian typography: kinsoku and breaking Latin words anywhere.
|
||||
crate::kinsoku::apply(&text, &mut found, rp.kinsoku, rp.word_wrap);
|
||||
let opps: std::collections::HashSet<usize> = found.into_iter().filter_map(to_para).collect();
|
||||
for c in &mut b.clusters {
|
||||
c.break_after = opps.contains(&c.end) || matches!(c.kind, ClKind::Object(_) | ClKind::Tab);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ pub enum Dialog {
|
|||
keep_lines: bool,
|
||||
page_break: bool,
|
||||
widow: bool,
|
||||
/// Page: 0 Indents and Spacing, 1 Line and Page Breaks, 2 Asian Typography.
|
||||
tab: u8,
|
||||
/// Asian Typography as the model has it (`para.asianTypography`): kinsoku, word wrap,
|
||||
/// hanging punctuation, compress punctuation at line start, space between Asian and
|
||||
/// Latin text, space between Asian text and numbers.
|
||||
asian: [bool; 6],
|
||||
/// `asian` when the dialog opened: OK sets only the flags that changed.
|
||||
#[serde(skip)]
|
||||
asian_was: [bool; 6],
|
||||
},
|
||||
Find {
|
||||
query: String,
|
||||
|
|
@ -163,7 +172,7 @@ impl Dialog {
|
|||
color: s("color"),
|
||||
spacing: 0.0,
|
||||
},
|
||||
"paragraph" => {
|
||||
"paragraph" | "asianTypography" => {
|
||||
let rp = app.session.doc.para_at(&app.session.sel.focus).map(|p| app.session.doc.styles.resolve_para(&p.props));
|
||||
let rp = rp?;
|
||||
let line = match rp.line_spacing {
|
||||
|
|
@ -184,6 +193,9 @@ impl Dialog {
|
|||
keep_lines: rp.keep_lines,
|
||||
page_break: rp.page_break_before,
|
||||
widow: rp.widow_control,
|
||||
tab: if name == "asianTypography" { 2 } else { 0 },
|
||||
asian: [rp.kinsoku, rp.word_wrap, rp.overflow_punct, rp.top_line_punct, rp.auto_space_de, rp.auto_space_dn],
|
||||
asian_was: [rp.kinsoku, rp.word_wrap, rp.overflow_punct, rp.top_line_punct, rp.auto_space_de, rp.auto_space_dn],
|
||||
}
|
||||
}
|
||||
"find" | "replace" => Dialog::Find {
|
||||
|
|
@ -426,48 +438,94 @@ fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
|||
}
|
||||
ok || cancel
|
||||
}
|
||||
Dialog::Paragraph { rtl, align, left, right, first, before, after, line, keep_next, keep_lines, page_break, widow } => {
|
||||
ui.label(egui::RichText::new(tl!("General")).font(semibold(12.5)));
|
||||
Dialog::Paragraph {
|
||||
rtl,
|
||||
align,
|
||||
left,
|
||||
right,
|
||||
first,
|
||||
before,
|
||||
after,
|
||||
line,
|
||||
keep_next,
|
||||
keep_lines,
|
||||
page_break,
|
||||
widow,
|
||||
tab,
|
||||
asian,
|
||||
asian_was,
|
||||
} => {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Direction:"));
|
||||
ui.radio_value(rtl, true, tl!("Right-to-left"));
|
||||
ui.radio_value(rtl, false, tl!("Left-to-right"));
|
||||
});
|
||||
egui::ComboBox::from_label(tl!("Alignment")).selected_text(align.clone()).show_ui(ui, |ui| {
|
||||
for a in ["left", "center", "right", "justify"] {
|
||||
ui.selectable_value(align, a.to_string(), a);
|
||||
for (k, name) in ["Indents and Spacing", "Line and Page Breaks", "Asian Typography"].into_iter().enumerate() {
|
||||
if ui.selectable_label(*tab as usize == k, tl!(name)).clicked() {
|
||||
*tab = k as u8;
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.label(egui::RichText::new(tl!("Indentation")).font(semibold(12.5)));
|
||||
egui::Grid::new("ind").num_columns(4).show(ui, |ui| {
|
||||
ui.label(tl!("Left:"));
|
||||
ui.add(egui::DragValue::new(left).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.label(tl!("Right:"));
|
||||
ui.add(egui::DragValue::new(right).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.end_row();
|
||||
ui.label(tl!("First line:"));
|
||||
ui.add(egui::DragValue::new(first).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.end_row();
|
||||
});
|
||||
ui.label(egui::RichText::new(tl!("Spacing")).font(semibold(12.5)));
|
||||
egui::Grid::new("sp").num_columns(4).show(ui, |ui| {
|
||||
ui.label(tl!("Before:"));
|
||||
ui.add(egui::DragValue::new(before).speed(1.0).range(0.0..=1584.0).suffix(" pt"));
|
||||
ui.label(tl!("Line spacing:"));
|
||||
ui.add(egui::DragValue::new(line).speed(0.05).range(0.5..=5.0));
|
||||
ui.end_row();
|
||||
ui.label(tl!("After:"));
|
||||
ui.add(egui::DragValue::new(after).speed(1.0).range(0.0..=1584.0).suffix(" pt"));
|
||||
ui.end_row();
|
||||
});
|
||||
ui.label(egui::RichText::new(tl!("Line and Page Breaks")).font(semibold(12.5)));
|
||||
ui.checkbox(widow, tl!("Widow/Orphan control"));
|
||||
ui.checkbox(keep_next, tl!("Keep with next"));
|
||||
ui.checkbox(keep_lines, tl!("Keep lines together"));
|
||||
ui.checkbox(page_break, tl!("Page break before"));
|
||||
ui.separator();
|
||||
match *tab {
|
||||
0 => {
|
||||
ui.label(egui::RichText::new(tl!("General")).font(semibold(12.5)));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Direction:"));
|
||||
ui.radio_value(rtl, true, tl!("Right-to-left"));
|
||||
ui.radio_value(rtl, false, tl!("Left-to-right"));
|
||||
});
|
||||
egui::ComboBox::from_label(tl!("Alignment")).selected_text(align.clone()).show_ui(ui, |ui| {
|
||||
for a in ["left", "center", "right", "justify"] {
|
||||
ui.selectable_value(align, a.to_string(), a);
|
||||
}
|
||||
});
|
||||
ui.label(egui::RichText::new(tl!("Indentation")).font(semibold(12.5)));
|
||||
egui::Grid::new("ind").num_columns(4).show(ui, |ui| {
|
||||
ui.label(tl!("Left:"));
|
||||
ui.add(egui::DragValue::new(left).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.label(tl!("Right:"));
|
||||
ui.add(egui::DragValue::new(right).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.end_row();
|
||||
ui.label(tl!("First line:"));
|
||||
ui.add(egui::DragValue::new(first).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.end_row();
|
||||
});
|
||||
ui.label(egui::RichText::new(tl!("Spacing")).font(semibold(12.5)));
|
||||
egui::Grid::new("sp").num_columns(4).show(ui, |ui| {
|
||||
ui.label(tl!("Before:"));
|
||||
ui.add(egui::DragValue::new(before).speed(1.0).range(0.0..=1584.0).suffix(" pt"));
|
||||
ui.label(tl!("Line spacing:"));
|
||||
ui.add(egui::DragValue::new(line).speed(0.05).range(0.5..=5.0));
|
||||
ui.end_row();
|
||||
ui.label(tl!("After:"));
|
||||
ui.add(egui::DragValue::new(after).speed(1.0).range(0.0..=1584.0).suffix(" pt"));
|
||||
ui.end_row();
|
||||
});
|
||||
}
|
||||
1 => {
|
||||
ui.label(egui::RichText::new(tl!("Line and Page Breaks")).font(semibold(12.5)));
|
||||
ui.checkbox(widow, tl!("Widow/Orphan control"));
|
||||
ui.checkbox(keep_next, tl!("Keep with next"));
|
||||
ui.checkbox(keep_lines, tl!("Keep lines together"));
|
||||
ui.checkbox(page_break, tl!("Page break before"));
|
||||
}
|
||||
_ => {
|
||||
let [kinsoku, word_wrap, overflow, top_line, de, dn] = asian;
|
||||
ui.label(egui::RichText::new(tl!("Line breaking")).font(semibold(12.5)));
|
||||
ui.checkbox(kinsoku, tl!("Apply Asian line-breaking rules (kinsoku)"));
|
||||
// The model's flag is "wrap whole words"; the box offers the opposite.
|
||||
let mut mid_word = !*word_wrap;
|
||||
if ui.checkbox(&mut mid_word, tl!("Allow Latin text to wrap in the middle of a word")).changed() {
|
||||
*word_wrap = !mid_word;
|
||||
}
|
||||
ui.checkbox(overflow, tl!("Allow hanging punctuation"));
|
||||
ui.label(egui::RichText::new(tl!("Character Spacing")).font(semibold(12.5)));
|
||||
ui.checkbox(top_line, tl!("Compress punctuation at the start of a line"));
|
||||
ui.checkbox(de, tl!("Add space between Asian and Latin text"));
|
||||
ui.checkbox(dn, tl!("Add space between Asian text and numbers"));
|
||||
}
|
||||
}
|
||||
let (ok, cancel) = buttons(ui, tl!("OK"));
|
||||
if ok {
|
||||
apply_paragraph(app, *rtl, align, *left, *right, *first, *before, *after, *line, [*keep_next, *keep_lines, *page_break, *widow]);
|
||||
apply_asian(app, *asian, *asian_was);
|
||||
}
|
||||
ok || cancel
|
||||
}
|
||||
|
|
@ -858,6 +916,17 @@ fn apply_paragraph(
|
|||
);
|
||||
}
|
||||
|
||||
/// The Paragraph dialog's Asian Typography: sets the flags that changed since it opened, so an
|
||||
/// untouched tab adds no direct formatting.
|
||||
fn apply_asian(app: &mut WordApp, asian: [bool; 6], was: [bool; 6]) {
|
||||
let keys = ["kinsoku", "wordWrap", "overflowPunct", "topLinePunct", "autoSpaceDE", "autoSpaceDN"];
|
||||
let changed: serde_json::Map<String, Value> =
|
||||
keys.iter().zip(asian.iter().zip(was)).filter(|(_, (now, was))| **now != *was).map(|(k, (now, _))| (k.to_string(), json!(now))).collect();
|
||||
if !changed.is_empty() {
|
||||
let _ = app.run("para.asianTypography", Value::Object(changed));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -876,7 +945,7 @@ mod tests {
|
|||
|
||||
/// Open the Paragraph dialog and press OK without changing anything.
|
||||
fn ok_unchanged(app: &mut WordApp) {
|
||||
let Some(Dialog::Paragraph { rtl, align, left, right, first, before, after, line, keep_next, keep_lines, page_break, widow }) =
|
||||
let Some(Dialog::Paragraph { rtl, align, left, right, first, before, after, line, keep_next, keep_lines, page_break, widow, .. }) =
|
||||
Dialog::open("paragraph", app)
|
||||
else {
|
||||
panic!("no paragraph dialog")
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
Accessibility Accesibilidad
|
||||
Add Añadir
|
||||
Add Space Before Paragraph Añadir espacio antes del párrafo
|
||||
Add space between Asian and Latin text Añadir espacio entre texto asiático y latino
|
||||
Add space between Asian text and numbers Añadir espacio entre texto asiático y números
|
||||
Add Text Añadir texto
|
||||
Add a Table Añadir una tabla
|
||||
Add a comment Añadir un comentario
|
||||
|
|
@ -45,13 +47,17 @@
|
|||
All Caps Mayúsculas
|
||||
All Markup Todas las marcas
|
||||
All caps Mayúsculas
|
||||
Allow hanging punctuation Permitir puntuación colgante
|
||||
Allow Latin text to wrap in the middle of a word Permitir que el texto latino se divida en mitad de una palabra
|
||||
Alt Text Texto alternativo
|
||||
Apply Asian line-breaking rules (kinsoku) Aplicar las reglas asiáticas de división de líneas (kinsoku)
|
||||
Apply Character Style Aplicar estilo de carácter
|
||||
Apply Style Aplicar estilo
|
||||
Arrange Organizar
|
||||
Arrange\nAll Organizar\ntodo
|
||||
Arrow Flecha
|
||||
Artistic Effects Efectos artísticos
|
||||
Asian Typography Tipografía asiática
|
||||
Author Autor
|
||||
AutoCorrect Options Opciones de autocorrección
|
||||
AutoFit Ajustar automáticamente
|
||||
|
|
@ -140,6 +146,7 @@
|
|||
Compact Compacto
|
||||
Compare Comparar
|
||||
Compress Pictures Comprimir imágenes
|
||||
Compress punctuation at the start of a line Comprimir la puntuación al inicio de la línea
|
||||
Continuous Continuo
|
||||
Convert Convertir
|
||||
Convert Text to Table Convertir texto en tabla
|
||||
|
|
@ -311,6 +318,7 @@
|
|||
Indent Sangría
|
||||
Indentation Sangría
|
||||
Indents Sangrías
|
||||
Indents and Spacing Sangría y espaciado
|
||||
Index Índice
|
||||
Info Información
|
||||
Ink to\nMath Tinta a\nfórmula
|
||||
|
|
@ -364,6 +372,7 @@
|
|||
Letter Carta
|
||||
Line Línea
|
||||
Line Break Salto de línea
|
||||
Line breaking División de líneas
|
||||
Line End Fin de línea
|
||||
Line Numbers Números de línea
|
||||
Line Spacing Interlineado
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
|
|
@ -13,6 +13,8 @@
|
|||
Add a comment コメントを追加
|
||||
Add a Table 表を追加
|
||||
Add Space Before Paragraph 段落前に間隔を追加
|
||||
Add space between Asian and Latin text アジア文字と欧文の間隔を空ける
|
||||
Add space between Asian text and numbers アジア文字と数字の間隔を空ける
|
||||
Add Text テキストの追加
|
||||
Add to Dictionary 辞書に追加
|
||||
Add to Style Gallery スタイル ギャラリーに追加
|
||||
|
|
@ -31,7 +33,10 @@
|
|||
All Caps すべて大文字
|
||||
All caps すべて大文字
|
||||
All Markup すべての変更履歴
|
||||
Allow hanging punctuation 句読点のぶら下げを行う
|
||||
Allow Latin text to wrap in the middle of a word 欧文を単語の途中で折り返す
|
||||
Alt Text 代替テキスト
|
||||
Apply Asian line-breaking rules (kinsoku) アジア文字の禁則処理を行う
|
||||
Apply Character Style 文字スタイルを適用
|
||||
Apply Style スタイルを適用
|
||||
Apply Styles スタイルを選んで適用
|
||||
|
|
@ -39,6 +44,7 @@
|
|||
Arrange\nAll 整列
|
||||
Arrow 矢印
|
||||
Artistic Effects アート効果
|
||||
Asian Typography アジア文字の組版
|
||||
Author 作成者
|
||||
AutoCorrect Options オートコレクトのオプション
|
||||
AutoFit 自動調整
|
||||
|
|
@ -52,7 +58,10 @@
|
|||
AutoSave 自動保存
|
||||
AutoSave documents you have saved in WordCraft WordCraft で保存した文書を自動保存する
|
||||
AutoSave failed: {error}. Save the document to turn AutoSave back on. 自動保存できませんでした: {error}。文書を保存し直すと、自動保存が再開します。
|
||||
Compress punctuation at the start of a line 行頭の約物を詰める
|
||||
Dark page (white text on black) ダーク ページ (黒地に白い文字)
|
||||
Indents and Spacing インデントと間隔
|
||||
Line breaking 行の分割
|
||||
More templates → その他のテンプレート →
|
||||
No templates match your search. 検索に一致するテンプレートはありません。
|
||||
Search for templates テンプレートを検索
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 3.
|
|
|
@ -24,6 +24,8 @@
|
|||
Add a comment Adicionar um comentário
|
||||
Add a Table Adicionar uma tabela
|
||||
Add Space Before Paragraph Adicionar espaço antes do parágrafo
|
||||
Add space between Asian and Latin text Adicionar espaço entre texto asiático e latino
|
||||
Add space between Asian text and numbers Adicionar espaço entre texto asiático e números
|
||||
Add Text Adicionar texto
|
||||
Add to Dictionary Adicionar ao dicionário
|
||||
Add to Style Gallery Adicionar à galeria de estilos
|
||||
|
|
@ -42,13 +44,17 @@
|
|||
All Caps Tudo em maiúsculas
|
||||
All caps Tudo em maiúsculas
|
||||
All Markup Todas as marcações
|
||||
Allow hanging punctuation Permitir pontuação pendente
|
||||
Allow Latin text to wrap in the middle of a word Permitir que o texto latino quebre no meio de uma palavra
|
||||
Alt Text Texto alternativo
|
||||
Apply Asian line-breaking rules (kinsoku) Aplicar as regras asiáticas de quebra de linha (kinsoku)
|
||||
Apply Character Style Aplicar estilo de caractere
|
||||
Apply Style Aplicar estilo
|
||||
Arrange Organizar
|
||||
Arrange\nAll Organizar\ntudo
|
||||
Arrow Seta
|
||||
Artistic Effects Efeitos artísticos
|
||||
Asian Typography Tipografia asiática
|
||||
Author Autor
|
||||
AutoCorrect Options Opções de autocorreção
|
||||
AutoFit Autoajuste
|
||||
|
|
@ -137,6 +143,7 @@
|
|||
Compact Compacto
|
||||
Compare Comparar
|
||||
Compress Pictures Compactar imagens
|
||||
Compress punctuation at the start of a line Comprimir a pontuação no início da linha
|
||||
Continuous Contínua
|
||||
Convert Converter
|
||||
Convert Text to Table Converter texto em tabela
|
||||
|
|
@ -179,6 +186,8 @@
|
|||
Delete Style Excluir estilo
|
||||
Delete Table Excluir tabela
|
||||
Design Design
|
||||
Indents and Spacing Recuos e espaçamento
|
||||
Line breaking Quebra de linhas
|
||||
More templates → Mais modelos →
|
||||
No templates match your search. Nenhum modelo corresponde à pesquisa.
|
||||
Search for templates Pesquisar modelos
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
|
|
@ -24,6 +24,8 @@
|
|||
Add a comment Додати коментар
|
||||
Add a Table Додати таблицю
|
||||
Add Space Before Paragraph Додати інтервал перед абзацом
|
||||
Add space between Asian and Latin text Додавати інтервал між азійським і латинським текстом
|
||||
Add space between Asian text and numbers Додавати інтервал між азійським текстом і числами
|
||||
Add Text Додати текст
|
||||
Add to Dictionary Додати до словника
|
||||
Add to Style Gallery Додати до колекції стилів
|
||||
|
|
@ -42,13 +44,17 @@
|
|||
All Caps Усі великі літери
|
||||
All caps Усі великі літери
|
||||
All Markup Усі позначки
|
||||
Allow hanging punctuation Дозволити висячу пунктуацію
|
||||
Allow Latin text to wrap in the middle of a word Дозволити розрив латинського тексту посеред слова
|
||||
Alt Text Альтернативний текст
|
||||
Apply Asian line-breaking rules (kinsoku) Застосовувати азійські правила розриву рядків (кінсоку)
|
||||
Apply Character Style Застосувати стиль символів
|
||||
Apply Style Застосувати стиль
|
||||
Arrange Упорядкувати
|
||||
Arrange\nAll Упорядкувати\nвсі
|
||||
Arrow Стрілка
|
||||
Artistic Effects Художні ефекти
|
||||
Asian Typography Азійська типографіка
|
||||
Author Автор
|
||||
AutoCorrect Options Параметри автовиправлення
|
||||
AutoFit Добір розміру
|
||||
|
|
@ -137,6 +143,7 @@
|
|||
Compact Компактний
|
||||
Compare Порівняти
|
||||
Compress Pictures Стиснути зображення
|
||||
Compress punctuation at the start of a line Стискати розділові знаки на початку рядка
|
||||
Continuous Безперервний
|
||||
Convert Перетворити
|
||||
Convert Text to Table Перетворити текст на таблицю
|
||||
|
|
@ -179,6 +186,8 @@
|
|||
Delete Style Видалити стиль
|
||||
Delete Table Видалити таблицю
|
||||
Design Дизайн
|
||||
Indents and Spacing Відступи й інтервали
|
||||
Line breaking Розрив рядків
|
||||
More templates → Більше шаблонів →
|
||||
No templates match your search. Немає шаблонів, що відповідають пошуку.
|
||||
Search for templates Пошук шаблонів
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 31.
|
|
|
@ -24,6 +24,8 @@
|
|||
Add a comment 添加批注
|
||||
Add a Table 添加表格
|
||||
Add Space Before Paragraph 在段前添加间距
|
||||
Add space between Asian and Latin text 在亚洲文字与西文之间添加间距
|
||||
Add space between Asian text and numbers 在亚洲文字与数字之间添加间距
|
||||
Add Text 添加文字
|
||||
Add to Dictionary 添加到词典
|
||||
Add to Style Gallery 添加到样式库
|
||||
|
|
@ -42,7 +44,10 @@
|
|||
All Caps 全部大写
|
||||
All caps 全部大写
|
||||
All Markup 所有标记
|
||||
Allow hanging punctuation 允许标点溢出边界
|
||||
Allow Latin text to wrap in the middle of a word 允许西文在单词中间换行
|
||||
Alt Text 替代文字
|
||||
Apply Asian line-breaking rules (kinsoku) 应用亚洲文字的断行规则(禁则)
|
||||
Apply Character Style 应用字符样式
|
||||
Apply Style 应用样式
|
||||
Apply Styles 选择并应用样式
|
||||
|
|
@ -50,6 +55,7 @@
|
|||
Arrange\nAll 全部重排
|
||||
Arrow 箭头
|
||||
Artistic Effects 艺术效果
|
||||
Asian Typography 亚洲版式
|
||||
Author 作者
|
||||
AutoCorrect Options 自动更正选项
|
||||
AutoFit 自动调整
|
||||
|
|
@ -63,7 +69,10 @@
|
|||
AutoSave 自动保存
|
||||
AutoSave documents you have saved in WordCraft 自动保存在 WordCraft 中保存过的文档
|
||||
AutoSave failed: {error}. Save the document to turn AutoSave back on. 自动保存失败:{error}。重新保存文档后将恢复自动保存。
|
||||
Compress punctuation at the start of a line 压缩行首标点
|
||||
Dark page (white text on black) 深色页面(黑底白字)
|
||||
Indents and Spacing 缩进和间距
|
||||
Line breaking 断行
|
||||
More templates → 更多模板 →
|
||||
No templates match your search. 没有与搜索匹配的模板。
|
||||
Search for templates 搜索模板
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
|
|
@ -14,6 +14,8 @@
|
|||
Add a comment 新增註解
|
||||
Add a Table 新增表格
|
||||
Add Space Before Paragraph 在段落前新增間距
|
||||
Add space between Asian and Latin text 在亞洲文字與西文之間加入間距
|
||||
Add space between Asian text and numbers 在亞洲文字與數字之間加入間距
|
||||
Add Text 新增文字
|
||||
Add to Dictionary 新增至字典
|
||||
Add to Style Gallery 新增至樣式庫
|
||||
|
|
@ -32,7 +34,10 @@
|
|||
All Caps 全部大寫
|
||||
All caps 全部大寫
|
||||
All Markup 所有標記
|
||||
Allow hanging punctuation 允許標點溢出邊界
|
||||
Allow Latin text to wrap in the middle of a word 允許西文在單字中間換行
|
||||
Alt Text 替代文字
|
||||
Apply Asian line-breaking rules (kinsoku) 套用亞洲文字的斷行規則(禁則)
|
||||
Apply Character Style 套用字元樣式
|
||||
Apply Style 套用樣式
|
||||
Apply Styles 選擇並套用樣式
|
||||
|
|
@ -40,6 +45,7 @@
|
|||
Arrange\nAll 全部排列
|
||||
Arrow 箭號
|
||||
Artistic Effects 美術效果
|
||||
Asian Typography 亞洲版式
|
||||
Author 作者
|
||||
AutoCorrect Options 自動校正選項
|
||||
AutoFit 自動調整
|
||||
|
|
@ -53,7 +59,10 @@
|
|||
AutoSave 自動儲存
|
||||
AutoSave documents you have saved in WordCraft 自動儲存在 WordCraft 中儲存過的文件
|
||||
AutoSave failed: {error}. Save the document to turn AutoSave back on. 自動儲存失敗:{error}。重新儲存文件後將恢復自動儲存。
|
||||
Compress punctuation at the start of a line 壓縮行首標點
|
||||
Dark page (white text on black) 深色頁面(黑底白字)
|
||||
Indents and Spacing 縮排和間距
|
||||
Line breaking 斷行
|
||||
More templates → 更多範本 →
|
||||
No templates match your search. 沒有符合搜尋的範本。
|
||||
Search for templates 搜尋範本
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 4.
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
Generated by `cargo xtask parity` (`wordcraft-cli parity --markdown`): the word-processor feature catalog (`crates/engine/src/catalog.rs`) compared with the live command registry.
|
||||
|
||||
**378 of 428 catalog features have commands (88.3%).**
|
||||
**379 of 428 catalog features have commands (88.6%).**
|
||||
|
||||
| Tab | Live | Total |
|
||||
|---|---|---|
|
||||
| File | 20 | 20 |
|
||||
| Quick Access | 4 | 4 |
|
||||
| Home | 92 | 101 |
|
||||
| Home | 93 | 101 |
|
||||
| Insert | 42 | 53 |
|
||||
| Draw | 0 | 11 |
|
||||
| Design | 10 | 10 |
|
||||
|
|
@ -34,7 +34,6 @@ Generated by `cargo xtask parity` (`wordcraft-cli parity --markdown`): the word-
|
|||
- Clipboard › Clipboard Pane (`edit.clipboardPane`)
|
||||
- Font › Phonetic Guide (`format.phonetic`)
|
||||
- Font › Enclose Characters (`format.enclose`)
|
||||
- Paragraph › Asian Typography (`para.asianTypography`)
|
||||
- Styles › Style Inspector (`styles.inspector`)
|
||||
- Styles › Manage Styles (`styles.manage`)
|
||||
- Editing › Advanced Find (`edit.advancedFind`)
|
||||
|
|
|
|||
Loading…
Reference in New Issue