From a0124f7a674c9be6241366db3004aded89eb3ea2 Mon Sep 17 00:00:00 2001 From: ImpulseB23 Date: Sun, 5 Apr 2026 17:07:33 +0200 Subject: [PATCH 1/2] fix: currency symbols stick to adjacent numbers during line breaking --- src/analysis.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/analysis.ts b/src/analysis.ts index 8819fad2..e517e553 100644 --- a/src/analysis.ts +++ b/src/analysis.ts @@ -97,6 +97,7 @@ export function setAnalysisLocale(locale?: string): void { const arabicScriptRe = /\p{Script=Arabic}/u const combiningMarkRe = /\p{M}/u +const currencySymbolRe = /\p{Sc}/u const decimalDigitRe = /\p{Nd}/u function containsArabicScript(text: string): boolean { @@ -270,7 +271,7 @@ function isLeftStickyPunctuationSegment(segment: string): boolean { if (isEscapedQuoteClusterSegment(segment)) return true let sawPunctuation = false for (const ch of segment) { - if (leftStickyPunctuation.has(ch)) { + if (leftStickyPunctuation.has(ch) || currencySymbolRe.test(ch)) { sawPunctuation = true continue } @@ -290,7 +291,7 @@ function isCJKLineStartProhibitedSegment(segment: string): boolean { function isForwardStickyClusterSegment(segment: string): boolean { if (isEscapedQuoteClusterSegment(segment)) return true for (const ch of segment) { - if (!kinsokuEnd.has(ch) && !forwardStickyGlue.has(ch) && !combiningMarkRe.test(ch)) return false + if (!kinsokuEnd.has(ch) && !forwardStickyGlue.has(ch) && !combiningMarkRe.test(ch) && !currencySymbolRe.test(ch)) return false } return segment.length > 0 } From 8d9dc99f692913d5f09a29886f03a0c1914a14e9 Mon Sep 17 00:00:00 2001 From: ImpulseB23 Date: Sun, 5 Apr 2026 17:21:41 +0200 Subject: [PATCH 2/2] test: add regression tests for currency symbol segmentation --- src/layout.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/layout.test.ts b/src/layout.test.ts index d9b83327..6416a51b 100644 --- a/src/layout.test.ts +++ b/src/layout.test.ts @@ -465,6 +465,16 @@ describe('prepare invariants', () => { expect(prepared.segments).toEqual(['say', ' ', String.raw`\"hello\"`, ' ', 'there']) }) + test('keeps prefix currency symbols attached to the following number', () => { + const prepared = prepareWithSegments('costs $500 today', FONT) + expect(prepared.segments).toEqual(['costs', ' ', '$500', ' ', 'today']) + }) + + test('keeps postfix currency symbols attached to the preceding number', () => { + const prepared = prepareWithSegments('price 500€ each', FONT) + expect(prepared.segments).toEqual(['price', ' ', '500€', ' ', 'each']) + }) + test('keeps URL-like runs together as one breakable segment', () => { const prepared = prepareWithSegments('see https://example.com/reports/q3?lang=ar&mode=full now', FONT) expect(prepared.segments).toEqual([