Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/officecli/CommandBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1709,6 +1709,7 @@ internal static string FormatUnsupported(IEnumerable<string> unsupported, string
"fill", "src", "path", "title", "name", "style", "caps", "smallcaps",
"lineSpacing", "listStyle", "start", "level", "cols", "rows",
"gridspan", "vmerge", "nowrap", "padding", "margin",
"typography.preset",
"orientation", "pageWidth", "pageHeight",
"x", "y", "cx", "cy", "rotation", "opacity",
"border.color", "border.width", "border.style",
Expand Down
52 changes: 52 additions & 0 deletions src/officecli/Handlers/Word/WordHandler.Helpers.Style.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,56 @@ private static bool IsNormalStyle(string styleName)
return styleName is "Normal" or "正文" or "Body Text" or "Body" or "a"
|| styleName.StartsWith("Normal");
}

/// <summary>
/// Apply the <c>typography.preset=zh-body</c> compound preset to a
/// paragraph: set an East Asian body face (SimSun/宋体) on every run and on
/// the paragraph mark so CJK text stops falling back to an unknown default,
/// and set the paragraph rhythm to a CJK-friendly single-spaced body
/// (line rule atLeast, single-line, no space after). Run-level — lives here
/// because it is easiest to read next to the East-asian helpers, and it is
/// invoked from <see cref="WordHandler.Set.Element"/> where the paragraph is
/// in hand.
/// </summary>
private static void ApplyTypography_Body_Zh(
Paragraph para, ParagraphProperties pProps, List<string>? warnings)
{
const string eaFont = "SimSun";
// Runs keep their existing explicit western face; only the East Asia slot
// is pinned to a Chinese body face so latin tokens stay on the doc's font
// while CJK glyphs resolve deterministically.
foreach (var run in para.Elements<Run>())
{
var rProps = run.RunProperties ?? run.PrependChild(new RunProperties());
var rf = rProps.GetFirstChild<RunFonts>();
if (rf == null)
{
rf = new RunFonts();
// CT_RPr schema order: rFonts must come first (before b/i/sz…).
rProps.PrependChild(rf);
}
rf.EastAsia = eaFont;
}
// Paragraph-mark rPr needs the eastAsia face too, or the line rhythm
// cursor can still fall back.
var markRPr = pProps.ParagraphMarkRunProperties;
if (markRPr != null)
{
var mrF = markRPr.GetFirstChild<RunFonts>();
if (mrF == null)
{
mrF = new RunFonts();
markRPr.PrependChild(mrF);
}
mrF.EastAsia = eaFont;
}

// Rhythm: CJK body conventionally lineRule=atLeast single (so a line is
// never compressed below one full ascent of the letters), spaceAfter 0.
var spacing = pProps.SpacingBetweenLines ?? (pProps.SpacingBetweenLines = new SpacingBetweenLines());
spacing.Line = "240"; // single
spacing.LineRule = LineSpacingRuleValues.AtLeast; // never compress
spacing.After = "0";
warnings?.Add($"zh-body preset: eastAsia font set to '{eaFont}' on all runs of this paragraph");
}
}
29 changes: 29 additions & 0 deletions src/officecli/Handlers/Word/WordHandler.Set.Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1736,6 +1736,35 @@ private List<string> SetElementParagraph(Paragraph para, Dictionary<string, stri
ApplyParagraphLevelProperty(pProps, "hangingindent", value, LastSetWarnings);
break;
}
case "typography.preset":
{
// Named Chinese-typography presets. These are COMPOUND:
// they need both the paragraph mark (eastAsia font, spacing)
// and the paragraph's own runs (eastAsia font), so they live
// at the Set.Element level (where `para` is in hand) rather
// than ApplyParagraphLevelProperty. Values:
// zh-body — 宋体/SimSun eastAsia on runs + mark, line
// rule atLeast 单倍, spaceAfter 0
// zh-first-indent— firstLineChars=200 (精确 2 字符,不随字号漂移)
switch (value.Trim().ToLowerInvariant())
{
case "zh-body" or "zh_body":
ApplyTypography_Body_Zh(para, pProps, LastSetWarnings);
break;
case "zh-first-indent" or "zh_first_indent":
var indZi = pProps.Indentation ?? (pProps.Indentation = new Indentation());
// 200 = 2 characters, per OOXML w:ind/@w:firstLineChars
// (100 = 1 char). Clears a rival hard w:firstLine so
// the char-relative rule is the only indent source.
indZi.FirstLineChars = 200;
indZi.FirstLine = null;
break;
default:
throw new ArgumentException(
$"Unknown typography preset '{value}'. Supported: zh-body, zh-first-indent.");
}
break;
}
default:
// Generic dotted "element.attr=value" fallback first.
// Probe pPr (where most paragraph attrs live: ind.*,
Expand Down