`LineBreakMeasurer.nextLayout()` calls `nextOffset()` internally to calculate 
the layout limit. When this happens, a `GlyphVector` is created and the layout 
engine is invoked to shape the text. The `GlyphVector` is cached in the 
relevant `ExtendedTextSourceLabel` component.

`LineBreakMeasurer.nextLayout()` then calls `TextMeasurer.getLayout()` which 
eventually asks that same `ExtendedTextSourceLabel` component for a subset 
component. This triggers the creation of a fresh `ExtendedTextSourceLabel` 
without the cached `GlyphVector`.

However, this fresh `ExtendedTextSourceLabel` is not necessary if the subset 
requested perfectly matches the already-existing `ExtendedTextSourceLabel` 
component. This happens when the text is short enough that no line break is 
needed.

I think we should change `ExtendedTextSourceLabel.getSubset()` to return `this` 
if the requested subset is identical to the existing instance. This will allow 
us to use the existing cached `GlyphVector`, and the call to 
`LineBreakMeasurer.nextLayout()` will trigger text shaping once, rather than 
twice.

In local testing, the test program below ran in ~1250 ms before this 
optimization, and ran in ~960 ms after the change (a 23% reduction in run time).

The following three existing test classes provide good regression test coverage 
for this change:
- test/jdk/java/awt/font/LineBreakMeasurer/LineBreakWithTrackingAuto
- test/jdk/java/awt/font/LineBreakMeasurer/TestLineBreakWithFontSub
- test/jdk/java/awt/font/LineBreakMeasurer/FRCTest


public class LineBreakMeasurerPerfTest {

    public static void main(String[] args) {

        float advance = 0;
        long start = System.currentTimeMillis();
        AttributedString string = new AttributedString("This is a test.");
        FontRenderContext frc = new FontRenderContext(new AffineTransform(), 
true, true);

        for (int i = 0; i < 100_000; i++) {
            LineBreakMeasurer measurer = new 
LineBreakMeasurer(string.getIterator(), frc);
            TextLayout layout = measurer.nextLayout(999); // large enough to 
not require break
            advance = Math.max(advance, layout.getAdvance());
        }

        long end = System.currentTimeMillis();
        System.out.println((end - start) + " ms elapsed (advance: " + advance + 
")");
    }

}

-------------

Commit messages:
 - Avoid double text shaping in some scenarios

Changes: https://git.openjdk.org/jdk/pull/25193/files
  Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=25193&range=00
  Issue: https://bugs.openjdk.org/browse/JDK-8356814
  Stats: 8 lines in 1 file changed: 7 ins; 0 del; 1 mod
  Patch: https://git.openjdk.org/jdk/pull/25193.diff
  Fetch: git fetch https://git.openjdk.org/jdk.git pull/25193/head:pull/25193

PR: https://git.openjdk.org/jdk/pull/25193

Reply via email to