55import com .demcha .compose .engine .components .content .text .TextStyle ;
66import com .demcha .compose .engine .components .geometry .ContentSize ;
77import com .demcha .compose .engine .font .Font ;
8- import com .demcha .compose .engine .render . pdf . PdfFont ;
8+ import com .demcha .compose .engine .font . FontLineMetrics ;
99
1010import java .util .HashMap ;
1111import java .util .Map ;
1616/**
1717 * Default measurement system backed by a document font library and a concrete
1818 * font implementation class supplied by the backend runtime.
19+ *
20+ * <p>Line metrics resolve polymorphically through the {@link Font} contract
21+ * ({@link Font#lineMetrics(TextStyle)} plus {@link Font#measurementCacheKey(TextStyle)}),
22+ * so every backend font — not only the PDF font — gets first-class metrics and
23+ * can opt into the process-wide cache without this shared class being modified
24+ * or special-cased per backend.</p>
1925 */
2026public final class FontLibraryTextMeasurementSystem implements TextMeasurementSystem {
2127 private static final int GLOBAL_LINE_METRICS_CACHE_LIMIT = 50_000 ;
2228 private static final int SESSION_TEXT_WIDTH_CACHE_LIMIT = 10_000 ;
23- private static final ConcurrentMap <GlobalPdfStyleKey , LineMetrics > GLOBAL_PDF_LINE_METRICS_CACHE = new ConcurrentHashMap <>();
29+ private static final ConcurrentMap <GlobalStyleKey , LineMetrics > GLOBAL_LINE_METRICS_CACHE = new ConcurrentHashMap <>();
2430
2531 private final FontLibrary fonts ;
2632 private final Class <? extends Font <?>> fontClass ;
2733 private final Map <TextStyle , Font <?>> fontCache = new HashMap <>();
2834 private final Map <TextStyle , LineMetrics > lineMetricsCache = new HashMap <>();
2935 private final Map <TextStyle , Map <String , Double >> textWidthCache = new HashMap <>();
30- private final Map <TextStyle , GlobalPdfStyleKey > globalPdfStyleKeyCache = new HashMap <>();
36+ private final Map <TextStyle , GlobalStyleKey > globalStyleKeyCache = new HashMap <>();
3137
3238 public FontLibraryTextMeasurementSystem (FontLibrary fonts , Class <? extends Font <?>> fontClass ) {
3339 this .fonts = Objects .requireNonNull (fonts , "fonts" );
@@ -69,20 +75,24 @@ public LineMetrics lineMetrics(TextStyle style) {
6975
7076 private LineMetrics resolveLineMetrics (TextStyle style ) {
7177 Font <?> font = resolveFont (style );
72- if (font instanceof PdfFont pdfFont ) {
73- GlobalPdfStyleKey cacheKey = globalPdfStyleKey (pdfFont , style );
74- LineMetrics cached = GLOBAL_PDF_LINE_METRICS_CACHE .get (cacheKey );
75- if (cached != null ) {
76- return cached ;
77- }
78- var metrics = pdfFont .verticalMetrics (style );
79- LineMetrics resolved = new LineMetrics (metrics .ascent (), metrics .descent (), metrics .leading ());
80- cacheGlobalLineMetrics (cacheKey , resolved );
81- return resolved ;
78+ String cacheKey = font .measurementCacheKey (style );
79+ if (cacheKey == null ) {
80+ // Backend opted out of the process-wide cache; the per-session
81+ // lineMetricsCache (via lineMetrics(...)) still memoizes per style.
82+ return toLineMetrics (font .lineMetrics (style ));
8283 }
84+ GlobalStyleKey key = globalStyleKey (style , cacheKey );
85+ LineMetrics cached = GLOBAL_LINE_METRICS_CACHE .get (key );
86+ if (cached != null ) {
87+ return cached ;
88+ }
89+ LineMetrics resolved = toLineMetrics (font .lineMetrics (style ));
90+ cacheGlobalLineMetrics (key , resolved );
91+ return resolved ;
92+ }
8393
84- double lineHeight = Math . max ( 0.0 , font . getLineHeight ( style ));
85- return new LineMetrics (lineHeight , 0.0 , 0.0 );
94+ private static LineMetrics toLineMetrics ( FontLineMetrics metrics ) {
95+ return new LineMetrics (metrics . ascent (), metrics . descent (), metrics . leading () );
8696 }
8797
8898 private double resolveTextWidth (Font <?> font , TextStyle style , String text ) {
@@ -95,20 +105,25 @@ private Font<?> resolveFont(TextStyle style) {
95105 .orElseThrow (() -> new IllegalStateException ("Font not found for style: " + key .fontName ())));
96106 }
97107
98- private GlobalPdfStyleKey globalPdfStyleKey (PdfFont font , TextStyle style ) {
99- return globalPdfStyleKeyCache .computeIfAbsent (style , key -> GlobalPdfStyleKey .from (font , key ));
108+ private GlobalStyleKey globalStyleKey (TextStyle style , String cacheKey ) {
109+ // Namespace the process-wide cache by backend font type: distinct backends
110+ // may return the same measurementCacheKey (e.g. both key on "Helvetica")
111+ // for different metrics, so without fontClass they would collide in the
112+ // shared static cache.
113+ return globalStyleKeyCache .computeIfAbsent (style ,
114+ key -> new GlobalStyleKey (fontClass .getName (), cacheKey , key .size (), key .decoration ()));
100115 }
101116
102- private static void cacheGlobalLineMetrics (GlobalPdfStyleKey key , LineMetrics metrics ) {
117+ private static void cacheGlobalLineMetrics (GlobalStyleKey key , LineMetrics metrics ) {
103118 // Safety cap on the process-wide line-metrics cache. Distinct styles are
104119 // few in real use (a handful of font/size/decoration combos); this only
105120 // guards a pathological style explosion. Stop inserting once full instead
106121 // of clear()-ing: the old full flush wiped every hot entry under
107122 // concurrent rendering (a thundering-herd recompute), so keeping the
108123 // existing entries is strictly better. This runs on a cache miss only,
109124 // never on the per-measurement get() path.
110- if (GLOBAL_PDF_LINE_METRICS_CACHE .size () < GLOBAL_LINE_METRICS_CACHE_LIMIT ) {
111- GLOBAL_PDF_LINE_METRICS_CACHE .putIfAbsent (key , metrics );
125+ if (GLOBAL_LINE_METRICS_CACHE .size () < GLOBAL_LINE_METRICS_CACHE_LIMIT ) {
126+ GLOBAL_LINE_METRICS_CACHE .putIfAbsent (key , metrics );
112127 }
113128 }
114129
@@ -117,7 +132,7 @@ public void clearCaches() {
117132 fontCache .clear ();
118133 lineMetricsCache .clear ();
119134 textWidthCache .clear ();
120- globalPdfStyleKeyCache .clear ();
135+ globalStyleKeyCache .clear ();
121136 }
122137
123138 int sessionTextWidthCacheSize () {
@@ -128,10 +143,7 @@ int sessionTextWidthCacheSize() {
128143 return total ;
129144 }
130145
131- private record GlobalPdfStyleKey (String fontKey , double size , TextDecoration decoration ) {
132- private static GlobalPdfStyleKey from (PdfFont font , TextStyle style ) {
133- return new GlobalPdfStyleKey (font .measurementCacheKey (style ), style .size (), style .decoration ());
134- }
146+ private record GlobalStyleKey (String fontType , String fontKey , double size , TextDecoration decoration ) {
135147 }
136148
137149}
0 commit comments