From 130391e88933bc44a9d72569ad066043f3b45bbd Mon Sep 17 00:00:00 2001 From: kev1n77 Date: Mon, 28 Sep 2026 17:13:57 +0800 Subject: [PATCH] fix(web-ui): center CJK text and the device mark on glyph ink A line box is split by a font's ascent and descent, not by the glyphs drawn in it. HarmonyOS Sans declares 92.8% / 24.4%, while its ideographs draw from 84.5% above the baseline to 7.5% below it, so a Chinese line box kept about 0.043em more room under its glyphs than over them: a selected line read as an uneven band (1.13px more space below than above at 14px) and message text sat high in its bubble. Split the same 117.2% as 97.1% / 20.1% on the SC face only, which moves the shared baseline down by exactly that 0.043em before the glyphs are painted. The sum is what `line-height: normal` and `1lh` resolve to, and the product sets an explicit numeric leading on every other text it lays out, so no laid out box changes size. The Latin-first base face, the code face, and the zh-TW system stack keep their own metrics. The nav panel footer pushed its device mark down a pixel to follow that text, but the marks that reach this row state their own `opticalShift` as 0, so the nudge left the mark 1.31px below the label's ink centre instead of 0.31px. Remove it and record the measurement in the comment. Measured at 8x device pixels: the selected-line gap difference drops from 1.13px to 0.13px, and the device mark from 1.31px to 0.31px below its label. Verified with the NavPanel suite (34 files, 204 tests), the web font profile guard (7 tests), and the typography token audit (15 tests plus a zero-violation audit run). Co-authored-by: OpenBitFun <318544290+bitfun-ai@users.noreply.github.com> --- .../src/app/components/NavPanel/NavPanel.scss | 12 +++++--- .../src/font-profiles/harmony-bundled.css | 28 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/web-ui/src/app/components/NavPanel/NavPanel.scss b/src/web-ui/src/app/components/NavPanel/NavPanel.scss index e5f59c8b76..f38d9a2490 100644 --- a/src/web-ui/src/app/components/NavPanel/NavPanel.scss +++ b/src/web-ui/src/app/components/NavPanel/NavPanel.scss @@ -1391,10 +1391,14 @@ $_section-header-height: 22px; > svg { flex-shrink: 0; // A line box is split by the font's ascent and descent, not by the glyphs - // drawn in it, so centring the mark on that box leaves its ink about a pixel - // above the text's own ink centre. One pixel, measured in the browser, puts - // the two on one optical centre. - transform: translateY(1px); + // drawn in it, so the mark was once pushed down a pixel to follow the text. + // Measured against the face this row actually renders (HarmonyOS Sans: + // 92.8%/24.4% against a cap ink of 74.5%/1.5%), centring the mark on that box + // already leaves it 0.03em below a caps label's ink centre and 0.05em below a + // Chinese one, and a device name is one of those two. The nudge therefore read + // as the mark sitting low. A mark whose own mass is off-centre still states its + // correction in `deviceSystemMarks`, which moves the drawn mass rather than the + // box. } &:hover, diff --git a/src/web-ui/src/font-profiles/harmony-bundled.css b/src/web-ui/src/font-profiles/harmony-bundled.css index 98a63a3cb3..fc451cea9b 100644 --- a/src/web-ui/src/font-profiles/harmony-bundled.css +++ b/src/web-ui/src/font-profiles/harmony-bundled.css @@ -13,6 +13,34 @@ font-style: normal; font-weight: 400 700; font-display: swap; + /* + * The zh-CN profile puts this face in front of all body text, so it is the face + * Chinese copy is centred by. HarmonyOS Sans splits its line box as 92.8% + * ascent / 24.4% descent, but its ideographs draw from 84.5% above the baseline + * to 7.5% below it, which leaves the box about 0.043em more room under the + * glyphs than over them: a selected line reads as an uneven band and message + * text sits high in its bubble. Splitting the same 117.2% as 97.1% / 20.1% + * moves the shared baseline down by exactly that 0.043em, before the glyphs are + * painted and without touching any box the product lays out. + * + * Keeping the sum is what contains the blast radius. `line-height: normal` and + * `1lh` both resolve to it, and the product sets an explicit numeric leading on + * every other text it lays out, so a line box only follows the split where the + * leading is intrinsic. Chromium on Windows rounds each metric to whole pixels, + * which at 13px, 14px and 20px turns the split into a one pixel `normal` box + * change and a half pixel baseline move; the design system's text overflow + * primitive is the only consumer of that intrinsic leading, and its block + * padding and centred grid absorb the pixel. + * + * The descent is deliberately tighter than the deepest Latin descender (24.5%): + * one reaches 0.044em past the content box, which is the same block padding. + * + * The Latin-first base face above keeps its own metrics: Latin ink brackets that + * split from both sides (lowercase 52%, sentence case 53%, cap labels 73%), so + * shifting it would trade one class of text for the other. + */ + ascent-override: 97.1%; + descent-override: 20.1%; } @font-face {