Skip to content

Commit 18770ad

Browse files
committed
Fix browser-like symbol runs in long words
Broaden the no-space punctuation preprocessing into a word-internal symbol pass so keyboard and Unicode symbols stay with surrounding long-word text while browser-break symbols stay breakable. Add a compact Chrome/Safari symbol-run oracle and refresh accuracy/benchmark dashboards. Refs #169
1 parent 9cf49de commit 18770ad

14 files changed

Lines changed: 666 additions & 361 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
### Fixed
1111

1212
- `{ wordBreak: 'keep-all' }` now handles no-space mixed Latin, numeric, and CJK text more like browsers.
13-
- No-space punctuation chains now stay together for non-ASCII word-like text too, instead of only ASCII words.
13+
- No-space punctuation and symbol chains now stay together for non-ASCII word-like text too, instead of only ASCII words (#169).
1414
- Opening punctuation such as `¡`, `¿`, German low quotes, and `` now stays with the following word instead of dangling at line end (#165).
1515
- Numeric prefix/postfix symbols like `$`, `%`, ``, `+`, ``, and `°` now stay attached to adjacent text the way browser line breaking does (#105).
1616
- Soft-hyphen breaks now stay at the soft-hyphen insertion point instead of pulling post-hyphen graphemes onto the broken line (#162).

DEVELOPMENT.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ bun install
3434
- `bun run benchmark-check:safari`
3535
- `bun run pre-wrap-check` — compact batched browser oracle for `{ whiteSpace: 'pre-wrap' }`
3636
- `bun run keep-all-check` — compact batched browser oracle for `{ wordBreak: 'keep-all' }`, including mixed-script no-space canaries
37+
- `bun run symbol-check` — compact batched Chrome + Safari oracle for no-space symbol runs inside long words
3738
- `bun run letter-spacing-check` — compact batched browser oracle for `{ letterSpacing }`, using one posted-report probe per browser and covering narrow wraps, combining marks, bidi, CJK, emoji, digits, RTL punctuation, `pre-wrap`, and soft hyphens
3839
- `bun run letter-spacing-snapshot` — refresh `accuracy/letter-spacing.json` from the Chrome + Safari compact `{ letterSpacing }` oracle
3940
- `bun run probe-check` — smaller browser probe/diagnostic entrypoint

accuracy/chrome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76825,5 +76825,5 @@
7682576825
"diff": 0
7682676826
}
7682776827
],
76828-
"requestId": "1778455134064-dzaeth"
76828+
"requestId": "1778458451443-udrw2b"
7682976829
}

accuracy/firefox.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76825,5 +76825,5 @@
7682576825
"diff": 0
7682676826
}
7682776827
],
76828-
"requestId": "1778455145984-0ujr0y"
76828+
"requestId": "1778458462178-8bdtvw"
7682976829
}

accuracy/safari.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76825,5 +76825,5 @@
7682576825
"diff": 0
7682676826
}
7682776827
],
76828-
"requestId": "1778455139711-8jiaqg"
76828+
"requestId": "1778458457473-esv0s5"
7682976829
}

benchmarks/chrome.json

Lines changed: 87 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33
"results": [
44
{
55
"label": "Our library: prepare()",
6-
"ms": 17.75,
6+
"ms": 17.050000071525574,
77
"desc": "One cold 500-text measurement batch"
88
},
99
{
1010
"label": "Our library: layout()",
11-
"ms": 0.14424999952316286,
11+
"ms": 0.1440000003576279,
1212
"desc": "Normalized hot-path throughput per 500-text batch"
1313
},
1414
{
1515
"label": "DOM batch",
16-
"ms": 3.9000000953674316,
16+
"ms": 3.8000000715255737,
1717
"desc": "Single 400→300px batch resize: write all, then read all"
1818
},
1919
{
2020
"label": "DOM interleaved",
21-
"ms": 44.59999990463257,
21+
"ms": 43.25000011920929,
2222
"desc": "Single 400→300px batch resize: write + read per div"
2323
}
2424
],
@@ -30,7 +30,7 @@
3030
},
3131
{
3232
"label": "Our library: layoutWithLines()",
33-
"ms": 0.0550000011920929,
33+
"ms": 0.05250000059604645,
3434
"desc": "60-text shared-corpus batch across widths 180/220/260px; materializes text lines"
3535
},
3636
{
@@ -57,7 +57,7 @@
5757
},
5858
{
5959
"label": "Our library: materializeRichInlineLineRange()",
60-
"ms": 0.0449999988079071,
60+
"ms": 0.04375,
6161
"desc": "36 rich-text inline flow shared-corpus texts across widths 180/220/260px; range walker plus per-line materialization"
6262
},
6363
{
@@ -69,44 +69,44 @@
6969
"richPreWrapResults": [
7070
{
7171
"label": "Our library: measureLineStats()",
72-
"ms": 0.3074999988079071,
72+
"ms": 0.3024999976158142,
7373
"desc": "12 generated pre-wrap texts with 320 hard-break chunks across widths 220/260/320px; stats only"
7474
},
7575
{
7676
"label": "Our library: layoutWithLines()",
77-
"ms": 0.42000000476837157,
77+
"ms": 0.4149999976158142,
7878
"desc": "12 generated pre-wrap texts with 320 hard-break chunks across widths 220/260/320px; materializes text lines"
7979
},
8080
{
8181
"label": "Our library: walkLineRanges()",
82-
"ms": 0.24499999284744262,
82+
"ms": 0.2399999976158142,
8383
"desc": "12 generated pre-wrap texts with 320 hard-break chunks across widths 220/260/320px; line ranges only, no line text strings"
8484
},
8585
{
8686
"label": "Our library: layoutNextLine()",
87-
"ms": 0.9724999964237213,
87+
"ms": 0.9475000023841857,
8888
"desc": "12 generated pre-wrap texts with 320 hard-break chunks across widths 220/260/320px; streaming line-by-line layout"
8989
}
9090
],
9191
"richLongResults": [
9292
{
9393
"label": "Our library: measureLineStats()",
94-
"ms": 1.6787499994039536,
94+
"ms": 1.6599999994039536,
9595
"desc": "8 Arabic long-form texts across widths 240/300/360px; stats only"
9696
},
9797
{
9898
"label": "Our library: layoutWithLines()",
99-
"ms": 5.534999996423721,
99+
"ms": 5.548750001192093,
100100
"desc": "8 Arabic long-form texts across widths 240/300/360px; materializes text lines"
101101
},
102102
{
103103
"label": "Our library: walkLineRanges()",
104-
"ms": 2.23125,
104+
"ms": 2.2125,
105105
"desc": "8 Arabic long-form texts across widths 240/300/360px; line ranges only, no line text strings"
106106
},
107107
{
108108
"label": "Our library: layoutNextLine()",
109-
"ms": 5.313749998807907,
109+
"ms": 5.21875,
110110
"desc": "8 Arabic long-form texts across widths 240/300/360px; streaming line-by-line layout"
111111
}
112112
],
@@ -122,9 +122,9 @@
122122
"width": 300,
123123
"lineCount": 193,
124124
"analysisMs": 2.5999999046325684,
125-
"measureMs": 4.299999713897705,
126-
"prepareMs": 6.800000190734863,
127-
"layoutMs": 0.02649999976158142
125+
"measureMs": 4.099999904632568,
126+
"prepareMs": 6.700000047683716,
127+
"layoutMs": 0.025499999523162842
128128
},
129129
{
130130
"id": "ja-rashomon",
@@ -136,10 +136,10 @@
136136
"breakableSegments": 1,
137137
"width": 300,
138138
"lineCount": 380,
139-
"analysisMs": 4.900000095367432,
140-
"measureMs": 8.5,
141-
"prepareMs": 13.600000143051147,
142-
"layoutMs": 0.049000000953674315
139+
"analysisMs": 5.199999809265137,
140+
"measureMs": 8.200000286102295,
141+
"prepareMs": 13.599999904632568,
142+
"layoutMs": 0.04850000023841858
143143
},
144144
{
145145
"id": "ko-unsu-joh-eun-nal",
@@ -151,10 +151,10 @@
151151
"breakableSegments": 0,
152152
"width": 300,
153153
"lineCount": 428,
154-
"analysisMs": 3.4000000953674316,
155-
"measureMs": 8.499999761581421,
156-
"prepareMs": 12.200000047683716,
157-
"layoutMs": 0.07850000023841858
154+
"analysisMs": 3,
155+
"measureMs": 9,
156+
"prepareMs": 11.799999952316284,
157+
"layoutMs": 0.07699999928474427
158158
},
159159
{
160160
"id": "ko-sonagi",
@@ -166,25 +166,25 @@
166166
"breakableSegments": 2,
167167
"width": 300,
168168
"lineCount": 332,
169-
"analysisMs": 2.5999999046325684,
170-
"measureMs": 6.599999904632568,
171-
"prepareMs": 9.099999904632568,
172-
"layoutMs": 0.05950000047683716
169+
"analysisMs": 2.299999952316284,
170+
"measureMs": 6.299999952316284,
171+
"prepareMs": 8.800000190734863,
172+
"layoutMs": 0.05850000023841858
173173
},
174174
{
175175
"id": "zh-zhufu",
176176
"label": "Chinese prose",
177177
"font": "20px \"Songti SC\", \"PingFang SC\", \"Noto Serif CJK SC\", serif",
178178
"chars": 9428,
179-
"analysisSegments": 5433,
180-
"segments": 7949,
181-
"breakableSegments": 4,
179+
"analysisSegments": 5428,
180+
"segments": 7944,
181+
"breakableSegments": 2,
182182
"width": 300,
183183
"lineCount": 626,
184-
"analysisMs": 7.900000095367432,
185-
"measureMs": 14.399999856948853,
186-
"prepareMs": 22.100000143051147,
187-
"layoutMs": 0.08
184+
"analysisMs": 8,
185+
"measureMs": 14.400000095367432,
186+
"prepareMs": 22.200000047683716,
187+
"layoutMs": 0.07800000071525574
188188
},
189189
{
190190
"id": "zh-guxiang",
@@ -196,85 +196,85 @@
196196
"breakableSegments": 14,
197197
"width": 300,
198198
"lineCount": 375,
199-
"analysisMs": 4.700000047683716,
200-
"measureMs": 8.900000095367432,
201-
"prepareMs": 13.799999952316284,
202-
"layoutMs": 0.04850000023841858
199+
"analysisMs": 4.799999952316284,
200+
"measureMs": 7.8999998569488525,
201+
"prepareMs": 12.700000047683716,
202+
"layoutMs": 0.04700000047683716
203203
},
204204
{
205205
"id": "th-nithan-vetal-story-1",
206206
"label": "Thai prose",
207207
"font": "20px \"Thonburi\", \"Noto Sans Thai\", sans-serif",
208208
"chars": 34033,
209-
"analysisSegments": 10281,
210-
"segments": 10281,
209+
"analysisSegments": 10279,
210+
"segments": 10279,
211211
"breakableSegments": 8087,
212212
"width": 300,
213213
"lineCount": 1024,
214-
"analysisMs": 10.399999856948853,
215-
"measureMs": 7.699999809265137,
216-
"prepareMs": 18.299999952316284,
217-
"layoutMs": 0.08950000047683716
214+
"analysisMs": 10.899999856948853,
215+
"measureMs": 7.3999998569488525,
216+
"prepareMs": 18.799999952316284,
217+
"layoutMs": 0.08649999976158142
218218
},
219219
{
220220
"id": "my-cunning-heron-teacher",
221221
"label": "Myanmar prose",
222222
"font": "20px \"Myanmar MN\", \"Myanmar Sangam MN\", \"Noto Sans Myanmar\", serif",
223223
"chars": 2707,
224-
"analysisSegments": 797,
225-
"segments": 797,
226-
"breakableSegments": 422,
224+
"analysisSegments": 786,
225+
"segments": 786,
226+
"breakableSegments": 420,
227227
"width": 300,
228228
"lineCount": 81,
229229
"analysisMs": 0.7999999523162842,
230-
"measureMs": 0.9999997615814209,
231-
"prepareMs": 1.7999999523162842,
230+
"measureMs": 0.7000002861022949,
231+
"prepareMs": 1.6000001430511475,
232232
"layoutMs": 0.0070000004768371584
233233
},
234234
{
235235
"id": "my-bad-deeds-return-to-you-teacher",
236236
"label": "Myanmar prose (story 2)",
237237
"font": "20px \"Myanmar MN\", \"Myanmar Sangam MN\", \"Noto Sans Myanmar\", serif",
238238
"chars": 1666,
239-
"analysisSegments": 498,
240-
"segments": 498,
241-
"breakableSegments": 274,
239+
"analysisSegments": 491,
240+
"segments": 491,
241+
"breakableSegments": 271,
242242
"width": 300,
243243
"lineCount": 54,
244244
"analysisMs": 0.5,
245-
"measureMs": 0.6000001430511475,
246-
"prepareMs": 1.1999998092651367,
247-
"layoutMs": 0.004500000476837158
245+
"measureMs": 0.5999999046325684,
246+
"prepareMs": 1,
247+
"layoutMs": 0.004499999284744263
248248
},
249249
{
250250
"id": "ur-chughd",
251251
"label": "Urdu prose",
252252
"font": "20px \"Noto Nastaliq Urdu\", \"DecoType Nastaleeq Urdu UI\", \"Geeza Pro\", serif",
253253
"chars": 13391,
254-
"analysisSegments": 6051,
255-
"segments": 6051,
256-
"breakableSegments": 2922,
254+
"analysisSegments": 5841,
255+
"segments": 5841,
256+
"breakableSegments": 2916,
257257
"width": 300,
258-
"lineCount": 351,
259-
"analysisMs": 3.700000047683716,
260-
"measureMs": 4.1000001430511475,
261-
"prepareMs": 8,
262-
"layoutMs": 0.04350000023841858
258+
"lineCount": 352,
259+
"analysisMs": 3.4000000953674316,
260+
"measureMs": 4.299999952316284,
261+
"prepareMs": 7.799999952316284,
262+
"layoutMs": 0.04
263263
},
264264
{
265265
"id": "km-prachum-reuang-preng-khmer-volume-7-stories-1-10",
266266
"label": "Khmer prose",
267267
"font": "20px \"Khmer Sangam MN\", \"Khmer MN\", \"Noto Sans Khmer\", serif",
268268
"chars": 24240,
269-
"analysisSegments": 11109,
270-
"segments": 11109,
271-
"breakableSegments": 4010,
269+
"analysisSegments": 11107,
270+
"segments": 11107,
271+
"breakableSegments": 4011,
272272
"width": 300,
273273
"lineCount": 591,
274-
"analysisMs": 8.599999904632568,
275-
"measureMs": 5,
276-
"prepareMs": 13.799999952316284,
277-
"layoutMs": 0.08600000023841858
274+
"analysisMs": 6.900000095367432,
275+
"measureMs": 5.6000001430511475,
276+
"prepareMs": 12.5,
277+
"layoutMs": 0.08400000095367431
278278
},
279279
{
280280
"id": "hi-eidgah",
@@ -286,10 +286,10 @@
286286
"breakableSegments": 4089,
287287
"width": 300,
288288
"lineCount": 653,
289-
"analysisMs": 6.099999904632568,
290-
"measureMs": 8.000000238418579,
291-
"prepareMs": 14.599999904632568,
292-
"layoutMs": 0.07049999952316284
289+
"analysisMs": 5.1000001430511475,
290+
"measureMs": 7.999999761581421,
291+
"prepareMs": 13.200000047683716,
292+
"layoutMs": 0.06949999928474426
293293
},
294294
{
295295
"id": "synthetic-long-breakable-runs",
@@ -301,25 +301,25 @@
301301
"breakableSegments": 1540,
302302
"width": 300,
303303
"lineCount": 2860,
304-
"analysisMs": 8.599999904632568,
305-
"measureMs": 15.5,
306-
"prepareMs": 24,
307-
"layoutMs": 0.39799999952316284
304+
"analysisMs": 8.400000095367432,
305+
"measureMs": 14.900000095367432,
306+
"prepareMs": 23.200000047683716,
307+
"layoutMs": 0.3960000002384186
308308
},
309309
{
310310
"id": "ar-risalat-al-ghufran-part-1",
311311
"label": "Arabic prose",
312312
"font": "20px \"Geeza Pro\", \"Noto Naskh Arabic\", \"Arial\", serif",
313313
"chars": 106857,
314-
"analysisSegments": 37603,
315-
"segments": 37603,
316-
"breakableSegments": 18745,
314+
"analysisSegments": 37601,
315+
"segments": 37601,
316+
"breakableSegments": 18743,
317317
"width": 300,
318318
"lineCount": 2643,
319-
"analysisMs": 29.100000143051147,
320-
"measureMs": 45,
321-
"prepareMs": 73.10000014305115,
322-
"layoutMs": 0.27700000047683715
319+
"analysisMs": 23.59999990463257,
320+
"measureMs": 45.39999985694885,
321+
"prepareMs": 69.39999985694885,
322+
"layoutMs": 0.2689999997615814
323323
}
324324
]
325325
}

0 commit comments

Comments
 (0)