Skip to content

Commit 130391e

Browse files
kev1n77bitfun-ai
andcommitted
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>
1 parent b2ad0c1 commit 130391e

2 files changed

Lines changed: 36 additions & 4 deletions

File tree

‎src/web-ui/src/app/components/NavPanel/NavPanel.scss‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,10 +1391,14 @@ $_section-header-height: 22px;
13911391
> svg {
13921392
flex-shrink: 0;
13931393
// A line box is split by the font's ascent and descent, not by the glyphs
1394-
// drawn in it, so centring the mark on that box leaves its ink about a pixel
1395-
// above the text's own ink centre. One pixel, measured in the browser, puts
1396-
// the two on one optical centre.
1397-
transform: translateY(1px);
1394+
// drawn in it, so the mark was once pushed down a pixel to follow the text.
1395+
// Measured against the face this row actually renders (HarmonyOS Sans:
1396+
// 92.8%/24.4% against a cap ink of 74.5%/1.5%), centring the mark on that box
1397+
// already leaves it 0.03em below a caps label's ink centre and 0.05em below a
1398+
// Chinese one, and a device name is one of those two. The nudge therefore read
1399+
// as the mark sitting low. A mark whose own mass is off-centre still states its
1400+
// correction in `deviceSystemMarks`, which moves the drawn mass rather than the
1401+
// box.
13981402
}
13991403

14001404
&:hover,

‎src/web-ui/src/font-profiles/harmony-bundled.css‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,34 @@
1313
font-style: normal;
1414
font-weight: 400 700;
1515
font-display: swap;
16+
/*
17+
* The zh-CN profile puts this face in front of all body text, so it is the face
18+
* Chinese copy is centred by. HarmonyOS Sans splits its line box as 92.8%
19+
* ascent / 24.4% descent, but its ideographs draw from 84.5% above the baseline
20+
* to 7.5% below it, which leaves the box about 0.043em more room under the
21+
* glyphs than over them: a selected line reads as an uneven band and message
22+
* text sits high in its bubble. Splitting the same 117.2% as 97.1% / 20.1%
23+
* moves the shared baseline down by exactly that 0.043em, before the glyphs are
24+
* painted and without touching any box the product lays out.
25+
*
26+
* Keeping the sum is what contains the blast radius. `line-height: normal` and
27+
* `1lh` both resolve to it, and the product sets an explicit numeric leading on
28+
* every other text it lays out, so a line box only follows the split where the
29+
* leading is intrinsic. Chromium on Windows rounds each metric to whole pixels,
30+
* which at 13px, 14px and 20px turns the split into a one pixel `normal` box
31+
* change and a half pixel baseline move; the design system's text overflow
32+
* primitive is the only consumer of that intrinsic leading, and its block
33+
* padding and centred grid absorb the pixel.
34+
*
35+
* The descent is deliberately tighter than the deepest Latin descender (24.5%):
36+
* one reaches 0.044em past the content box, which is the same block padding.
37+
*
38+
* The Latin-first base face above keeps its own metrics: Latin ink brackets that
39+
* split from both sides (lowercase 52%, sentence case 53%, cap labels 73%), so
40+
* shifting it would trade one class of text for the other.
41+
*/
42+
ascent-override: 97.1%;
43+
descent-override: 20.1%;
1644
}
1745

1846
@font-face {

0 commit comments

Comments
 (0)