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
56 changes: 51 additions & 5 deletions src/officecli/Core/FontMetricsReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,32 @@ namespace OfficeCli.Core;
/// </summary>
internal static class FontMetricsReader
{
// MOD(#12): see docs/cove-desktop-mods.md
// Word documents often specify Windows logical CJK family names such as
// "宋体" / "黑体", while macOS ships the actual font files under stems like
// Songti.ttc / STHeiti.ttc. Metrics lookup must follow the same alias/fallback
// path as the preview CSS, otherwise GetRatio() falls back to 1.0 and the
// rendered line-height becomes much tighter than the browser's real glyph box.
private static readonly Dictionary<string, string[]> s_fontSearchAliases = new(StringComparer.OrdinalIgnoreCase)
{
["宋体"] = ["Songti", "Songti SC", "STSong", "SimSun", "NSimSun"],
["宋体-简"] = ["Songti", "Songti SC", "STSong"],
["宋體-簡"] = ["Songti", "Songti SC", "STSong"],
["SimSun"] = ["Songti", "Songti SC", "STSong", "宋体"],
["NSimSun"] = ["Songti", "Songti SC", "STSong", "宋体"],
["黑体"] = ["STHeiti", "Heiti SC", "Heiti TC", "SimHei"],
["SimHei"] = ["STHeiti", "Heiti SC", "Heiti TC", "黑体"],
["仿宋_GB2312"] = ["STFangsong", "FangSong", "仿宋"],
["仿宋"] = ["STFangsong", "FangSong", "仿宋_GB2312"],
["楷体_GB2312"] = ["STKaiti", "KaiTi", "楷体"],
["楷体"] = ["STKaiti", "KaiTi", "楷体_GB2312"],
["长城小标宋体"] = ["STZhongsong", "Songti", "STSong"],
["Songti SC"] = ["Songti", "STSong", "宋体"],
["STSong"] = ["Songti", "Songti SC", "宋体"],
["Heiti SC"] = ["STHeiti", "黑体"],
["STHeiti"] = ["Heiti SC", "黑体"],
};

/// <summary>
/// Line-height ratio = (usWinAscent + usWinDescent + hheaLineGap) / unitsPerEm.
/// Returns 1.0 if the font file cannot be read.
Expand Down Expand Up @@ -152,24 +178,44 @@ private static uint ReadUInt32BE(BinaryReader r)
dirs.Add("/usr/local/share/fonts");
}

// Normalize: remove spaces, try exact match and lowercase
var normalized = fontFamily.Replace(" ", "");
// Normalize: remove spaces, try exact match and platform fallback aliases.
var candidates = ExpandFontSearchAliases(fontFamily)
.Select(NormalizeFontSearchToken)
.Where(token => !string.IsNullOrWhiteSpace(token))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
foreach (var dir in dirs)
{
if (!Directory.Exists(dir)) continue;
foreach (var file in Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories))
{
var ext = Path.GetExtension(file);
if (ext is not (".ttf" or ".otf" or ".ttc")) continue;
var stem = Path.GetFileNameWithoutExtension(file);
if (stem.Equals(normalized, StringComparison.OrdinalIgnoreCase)
|| stem.Equals(fontFamily, StringComparison.OrdinalIgnoreCase))
var stem = NormalizeFontSearchToken(Path.GetFileNameWithoutExtension(file));
if (candidates.Any(candidate => stem.Equals(candidate, StringComparison.OrdinalIgnoreCase)))
return file;
}
}
return null;
}

private static IEnumerable<string> ExpandFontSearchAliases(string fontFamily)
{
yield return fontFamily;

if (s_fontSearchAliases.TryGetValue(fontFamily, out var aliases))
{
foreach (var alias in aliases)
yield return alias;
}
}

private static string NormalizeFontSearchToken(string value) =>
value
.Replace(" ", "", StringComparison.Ordinal)
.Replace("-", "", StringComparison.Ordinal)
.Replace("_", "", StringComparison.Ordinal);

// ==================== Cached Ratio Lookup ====================

private static readonly Dictionary<string, double> s_ratioCache = new(StringComparer.OrdinalIgnoreCase);
Expand Down
2 changes: 1 addition & 1 deletion src/officecli/Core/SpacingConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal static class SpacingConverter
private const double PointsPerCm = 72.0 / 2.54; // ~28.3465
private const double PointsPerInch = 72.0;
private const int TwipsPerPoint = 20; // 1 pt = 20 twips
private const int WordAutoLineSpacingUnit = 240; // 240 twips = single line in Auto mode
private const int WordAutoLineSpacingUnit = 288; // MOD: 288 twips (14.4pt) better matches WPS/Chinese Word default row height for 16pt fonts // 240 twips = single line in Auto mode

// ────────────────────────────────────────────────────────────────
// spaceBefore / spaceAfter → Word twips
Expand Down
2 changes: 1 addition & 1 deletion src/officecli/Handlers/Word/WordHandler.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1391,7 +1391,7 @@ private static PageMargin EnsureSectPrPageMargin(SectionProperties sectPr)
var existing = sectPr.GetFirstChild<PageMargin>();
if (existing != null) return existing;

var pm = new PageMargin();
var pm = new PageMargin { Top = 1440, Bottom = 1440, Left = 1800, Right = 1800, Header = 851, Footer = 992 };
// Insert after PageSize if present, after SectionType, after last headerRef/footerRef, or prepend
var pageSize = sectPr.GetFirstChild<PageSize>();
if (pageSize != null)
Expand Down
Loading