diff --git a/CHANGELOG.md b/CHANGELOG.md index cd505abba..5ebb9e23c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,24 @@ Open cycle — bug-fix / housekeeping. Entries land here as they merge. the per-measurement path). **Measured line metrics are unchanged.** No public API or behaviour change. +- **Auto-size font fitting binary-searches the size grid.** A paragraph with + `autoSize(...)` resolved its font size by scanning every step from max down to + min, re-measuring the line at each candidate (up to ~50 measurements). Line width + is linear in font size, so the fit is monotonic — the search now binary-searches + the grid for the same boundary in ~log2(n) measurements instead of n. **Output is + byte-identical** — it returns the same grid size the linear scan did (covered by + the existing auto-size integration and snapshot tests). No public API or behaviour + change. + +### Deprecations + +- **`Font.adjustFontSizeToFit(...)` is deprecated.** The engine-internal + `Font#adjustFontSizeToFit` (and its `PdfFont` / `WordFont` implementations) is + unused and incorrect — the only real implementation re-measured with the + unchanged style, so it always returned the minimum size. Canonical auto-size is + resolved by the layout compiler. The method is kept for binary compatibility and + scheduled for removal in the next major. + ### Tests / tooling - **Benchmark regression gate and measurement probe (benchmarks module, not part diff --git a/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java b/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java index 0da49991a..7155f88f7 100644 --- a/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java +++ b/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java @@ -585,15 +585,30 @@ private static DocumentTextStyle resolveAutoSizeTextStyle(ParagraphNode node, double minSize = autoSize.minSize(); double step = Math.max(0.1, autoSize.step()); - // Single-line text: shrink the font size until the longest logical line - // measures inside the available inner width, otherwise fall back to the - // smallest configured size. - for (double size = maxSize; size >= minSize - 1e-6; size -= step) { - DocumentTextStyle candidate = baseStyle.withSize(size); + // Single-line text: pick the largest grid size (maxSize, maxSize-step, …, + // down to >= minSize) whose longest logical line measures inside the + // available inner width, otherwise fall back to the smallest configured + // size. The fit predicate is monotonic in size (line width is linear in + // size), so binary-search the grid for the boundary instead of measuring + // at every step — the same size the linear scan returned, in ~log2(n) + // measurements rather than n. + int maxStepCount = (int) Math.floor((maxSize - minSize + 1e-6) / step); + int lo = 0; + int hi = maxStepCount; + int fitStep = -1; + while (lo <= hi) { + int mid = (lo + hi) >>> 1; + DocumentTextStyle candidate = baseStyle.withSize(maxSize - mid * step); if (paragraphFitsSingleLine(node, candidate, innerWidth, measurement)) { - return candidate; + fitStep = mid; // fits — try a larger size (fewer steps down) + hi = mid - 1; + } else { + lo = mid + 1; // too wide — need a smaller size (more steps) } } + if (fitStep >= 0) { + return baseStyle.withSize(maxSize - fitStep * step); + } return baseStyle.withSize(minSize); } diff --git a/src/main/java/com/demcha/compose/engine/font/Font.java b/src/main/java/com/demcha/compose/engine/font/Font.java index d6e87b8d3..a64a214df 100644 --- a/src/main/java/com/demcha/compose/engine/font/Font.java +++ b/src/main/java/com/demcha/compose/engine/font/Font.java @@ -46,7 +46,20 @@ default T fontType(TextDecoration textDecoration) { double scale(double size); - public TextStyle adjustFontSizeToFit(String text, TextStyle style, double availableWidth); + /** + * @param text the text to fit + * @param style the starting style + * @param availableWidth the width to fit within + * @return a re-sized style + * @deprecated Unused and incorrect: the only real implementation re-measures + * with the unchanged {@code style}, so the loop never converges and the + * result is always the minimum size. Canonical auto-size is resolved by the + * layout compiler ({@code TextFlowSupport.resolveAutoSizeTextStyle}); this + * method has no callers and is kept only for binary compatibility, + * scheduled for removal in the next major. + */ + @Deprecated + TextStyle adjustFontSizeToFit(String text, TextStyle style, double availableWidth); ContentSize getTightBounds(String text, TextStyle style); } diff --git a/src/main/java/com/demcha/compose/engine/render/pdf/PdfFont.java b/src/main/java/com/demcha/compose/engine/render/pdf/PdfFont.java index f84eb24b3..09386ce4d 100644 --- a/src/main/java/com/demcha/compose/engine/render/pdf/PdfFont.java +++ b/src/main/java/com/demcha/compose/engine/render/pdf/PdfFont.java @@ -29,7 +29,18 @@ public PdfFont(PDFont defaultFont, PDFont bold, PDFont italic, PDFont boldItalic } - // Adjust font size automatically based on the width of the text and available space + /** + * @param text the text to fit + * @param style the starting style + * @param availableWidth the width to fit within + * @return a re-sized style + * @deprecated Unused and incorrect — it re-measures with the unchanged + * {@code style}, so {@code textWidth} never shrinks and the loop runs to the + * minimum size regardless of fit. Canonical auto-size is handled by the + * layout compiler; kept only for binary compatibility. + */ + @Deprecated + @Override public TextStyle adjustFontSizeToFit(String text, TextStyle style, double availableWidth) { double textWidth = getTextWidth(style, text); @@ -39,7 +50,6 @@ public TextStyle adjustFontSizeToFit(String text, TextStyle style, double availa newSize--; // Reduce size textWidth = getTextWidth(style, text); // Recalculate text width } - PDFont pdFont = fontType(style.decoration()); return new TextStyle(style.fontName(), newSize, style.decoration(), style.color()); } diff --git a/src/main/java/com/demcha/compose/engine/render/word/WordFont.java b/src/main/java/com/demcha/compose/engine/render/word/WordFont.java index db35a7b9f..2e3e93941 100644 --- a/src/main/java/com/demcha/compose/engine/render/word/WordFont.java +++ b/src/main/java/com/demcha/compose/engine/render/word/WordFont.java @@ -83,11 +83,14 @@ public double scale(double size) { } /** - * @param text - * @param style - * @param availableWidth - * @return + * @param text the text to fit + * @param style the starting style + * @param availableWidth the width to fit within + * @return {@code null} — never implemented + * @deprecated Unused; never implemented (returns {@code null}). Kept only for + * binary compatibility. See {@code Font#adjustFontSizeToFit}. */ + @Deprecated @Override public TextStyle adjustFontSizeToFit(String text, TextStyle style, double availableWidth) { return null;