Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 87b1547c authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Compute hyphenated word pieces in MeasuredText

Bug: 67504091
Test: bit CtsTextTestCases:*
Change-Id: Id1ff5abbf6d433a2d8baad364133fca98d1fe450
parent 4fe35999
Loading
Loading
Loading
Loading
+25 −10
Original line number Diff line number Diff line
@@ -190,8 +190,11 @@ public class StaticLayoutPerfTest {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        while (state.keepRunning()) {
            state.pauseTiming();
            final MeasuredText text = MeasuredText.build(
                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
            final MeasuredText text = new MeasuredText.Builder(
                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
                    .build();
            state.resumeTiming();

            StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -206,8 +209,11 @@ public class StaticLayoutPerfTest {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        while (state.keepRunning()) {
            state.pauseTiming();
            final MeasuredText text = MeasuredText.build(
                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
            final MeasuredText text = new MeasuredText.Builder(
                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                    .build();
            state.resumeTiming();

            StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -222,8 +228,11 @@ public class StaticLayoutPerfTest {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        while (state.keepRunning()) {
            state.pauseTiming();
            final MeasuredText text = MeasuredText.build(
                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
            final MeasuredText text = new MeasuredText.Builder(
                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
                    .build();
            state.resumeTiming();

            StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -238,8 +247,11 @@ public class StaticLayoutPerfTest {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        while (state.keepRunning()) {
            state.pauseTiming();
            final MeasuredText text = MeasuredText.build(
                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
            final MeasuredText text = new MeasuredText.Builder(
                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                    .build();
            state.resumeTiming();

            StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -254,8 +266,11 @@ public class StaticLayoutPerfTest {
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        while (state.keepRunning()) {
            state.pauseTiming();
            final MeasuredText text = MeasuredText.build(
                    generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR);
            final MeasuredText text = new MeasuredText.Builder(
                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
                    .build();
            state.resumeTiming();

            StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
+11 −2
Original line number Diff line number Diff line
@@ -42295,10 +42295,10 @@ package android.text {
  }
  public class MeasuredText implements android.text.Spanned {
    method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic);
    method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int);
    method public char charAt(int);
    method public int getBreakStrategy();
    method public int getEnd();
    method public int getHyphenationFrequency();
    method public android.text.TextPaint getPaint();
    method public int getParagraphCount();
    method public int getParagraphEnd(int);
@@ -42315,6 +42315,15 @@ package android.text {
    method public java.lang.CharSequence subSequence(int, int);
  }
  public static final class MeasuredText.Builder {
    ctor public MeasuredText.Builder(java.lang.CharSequence, android.text.TextPaint);
    method public android.text.MeasuredText build();
    method public android.text.MeasuredText.Builder setBreakStrategy(int);
    method public android.text.MeasuredText.Builder setHyphenationFrequency(int);
    method public android.text.MeasuredText.Builder setRange(int, int);
    method public android.text.MeasuredText.Builder setTextDirection(android.text.TextDirectionHeuristic);
  }
  public abstract interface NoCopySpan {
  }
+7 −3
Original line number Diff line number Diff line
@@ -356,6 +356,7 @@ public class MeasuredParagraph {
            @IntRange(from = 0) int start,
            @IntRange(from = 0) int end,
            @NonNull TextDirectionHeuristic textDir,
            boolean computeHyphenation,
            @Nullable MeasuredParagraph recycle) {
        final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
        mt.resetAndAnalyzeBidi(text, start, end, textDir);
@@ -365,7 +366,8 @@ public class MeasuredParagraph {
            long nativeBuilderPtr = nInitBuilder();
            try {
                mt.bindNativeObject(
                        nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
                        nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
                              computeHyphenation));
            } finally {
                nFreeBuilder(nativeBuilderPtr);
            }
@@ -394,7 +396,8 @@ public class MeasuredParagraph {
                    mt.mSpanEndCache.append(spanEnd);
                }
            }
            mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
            mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
                      computeHyphenation));
        } finally {
            nFreeBuilder(nativeBuilderPtr);
        }
@@ -668,7 +671,8 @@ public class MeasuredParagraph {
                                                  @FloatRange(from = 0) float width);

    private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr,
                                                 @NonNull char[] text);
                                                 @NonNull char[] text,
                                                 boolean computeHyphenation);

    private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);

+145 −52
Original line number Diff line number Diff line
@@ -52,70 +52,147 @@ public class MeasuredText implements Spanned {
    // The sorted paragraph end offsets.
    private final @NonNull int[] mParagraphBreakPoints;

    // The break strategy for this measured text.
    private final @Layout.BreakStrategy int mBreakStrategy;

    // The hyphenation frequency for this measured text.
    private final @Layout.HyphenationFrequency int mHyphenationFrequency;

    /**
     * Build MeasuredText from the text.
     * A Builder for MeasuredText
     */
    public static final class Builder {
        // Mandatory parameters.
        private final @NonNull CharSequence mText;
        private final @NonNull TextPaint mPaint;

        // Members to be updated by setters.
        private @IntRange(from = 0) int mStart;
        private @IntRange(from = 0) int mEnd;
        private TextDirectionHeuristic mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
        private @Layout.BreakStrategy int mBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
        private @Layout.HyphenationFrequency int mHyphenationFrequency =
                Layout.HYPHENATION_FREQUENCY_NORMAL;


        /**
         * Builder constructor
         *
         * @param text The text to be measured.
         * @param paint The paint to be used for drawing.
     * @param textDir The text direction.
     * @return The measured text.
         */
    public static @NonNull MeasuredText build(@NonNull CharSequence text,
                                              @NonNull TextPaint paint,
                                              @NonNull TextDirectionHeuristic textDir) {
        return MeasuredText.build(text, paint, textDir, 0, text.length());
        public Builder(@NonNull CharSequence text, @NonNull TextPaint paint) {
            Preconditions.checkNotNull(text);
            Preconditions.checkNotNull(paint);

            mText = text;
            mPaint = paint;
            mStart = 0;
            mEnd = text.length();
        }

        /**
     * Build MeasuredText from the specific range of the text..
         * Set the range of measuring target.
         *
     * @param text The text to be measured.
     * @param paint The paint to be used for drawing.
     * @param textDir The text direction.
     * @param start The inclusive start offset of the text.
     * @param end The exclusive start offset of the text.
     * @return The measured text.
         * @param start The measuring target start offset in the text.
         * @param end The measuring target end offset in the text.
         */
    public static @NonNull MeasuredText build(@NonNull CharSequence text,
                                              @NonNull TextPaint paint,
                                              @NonNull TextDirectionHeuristic textDir,
                                              @IntRange(from = 0) int start,
        public @NonNull Builder setRange(@IntRange(from = 0) int start,
                                         @IntRange(from = 0) int end) {
        Preconditions.checkNotNull(text);
        Preconditions.checkNotNull(paint);
            Preconditions.checkArgumentInRange(start, 0, mText.length(), "start");
            Preconditions.checkArgumentInRange(end, 0, mText.length(), "end");
            Preconditions.checkArgument(start <= end, "The range is reversed.");

            mStart = start;
            mEnd = end;
            return this;
        }

        /**
         * Set the text direction heuristic
         *
         * The default value is {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
         *
         * @param textDir The text direction heuristic for resolving bidi behavior.
         * @return this builder, useful for chaining.
         */
        public @NonNull Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
            Preconditions.checkNotNull(textDir);
        Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
        Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
            mTextDir = textDir;
            return this;
        }

        /**
         * Set the break strategy
         *
         * The default value is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}.
         *
         * @param breakStrategy The break strategy.
         * @return this builder, useful for chaining.
         */
        public @NonNull Builder setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
            mBreakStrategy = breakStrategy;
            return this;
        }

        /**
         * Set the hyphenation frequency
         *
         * The default value is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
         *
         * @param hyphenationFrequency The hyphenation frequency.
         * @return this builder, useful for chaining.
         */
        public @NonNull Builder setHyphenationFrequency(
                @Layout.HyphenationFrequency int hyphenationFrequency) {
            mHyphenationFrequency = hyphenationFrequency;
            return this;
        }

        /**
         * Build the measured text
         *
         * @return the measured text.
         */
        public @NonNull MeasuredText build() {
            final boolean needHyphenation = mBreakStrategy != Layout.BREAK_STRATEGY_SIMPLE
                    && mHyphenationFrequency != Layout.HYPHENATION_FREQUENCY_NONE;

            final IntArray paragraphEnds = new IntArray();
            final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();

            int paraEnd = 0;
        for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
            paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
            for (int paraStart = mStart; paraStart < mEnd; paraStart = paraEnd) {
                paraEnd = TextUtils.indexOf(mText, LINE_FEED, paraStart, mEnd);
                if (paraEnd < 0) {
                // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
                paraEnd = end;
                    // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
                    // end.
                    paraEnd = mEnd;
                } else {
                    paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
                }

                paragraphEnds.add(paraEnd);
                measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
                    paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
                        mPaint, mText, paraStart, paraEnd, mTextDir, needHyphenation,
                        null /* no recycle */));
            }

        return new MeasuredText(text, start, end, paint, textDir,
                                measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]),
            return new MeasuredText(mText, mStart, mEnd, mPaint, mTextDir, mBreakStrategy,
                                    mHyphenationFrequency, measuredTexts.toArray(
                                            new MeasuredParagraph[measuredTexts.size()]),
                                    paragraphEnds.toArray());
        }
    };

    // Use MeasuredText.build instead.
    // Use MeasuredText.Builder instead.
    private MeasuredText(@NonNull CharSequence text,
                         @IntRange(from = 0) int start,
                         @IntRange(from = 0) int end,
                         @NonNull TextPaint paint,
                         @NonNull TextDirectionHeuristic textDir,
                         @Layout.BreakStrategy int breakStrategy,
                         @Layout.HyphenationFrequency int frequency,
                         @NonNull MeasuredParagraph[] measuredTexts,
                         @NonNull int[] paragraphBreakPoints) {
        mText = text;
@@ -125,6 +202,8 @@ public class MeasuredText implements Spanned {
        mMeasuredParagraphs = measuredTexts;
        mParagraphBreakPoints = paragraphBreakPoints;
        mTextDir = textDir;
        mBreakStrategy = breakStrategy;
        mHyphenationFrequency = frequency;
    }

    /**
@@ -190,6 +269,20 @@ public class MeasuredText implements Spanned {
        return mMeasuredParagraphs[paraIndex];
    }

    /**
     * Returns the break strategy for this text.
     */
    public @Layout.BreakStrategy int getBreakStrategy() {
        return mBreakStrategy;
    }

    /**
     * Returns the hyphenation frequency for this text.
     */
    public @Layout.HyphenationFrequency int getHyphenationFrequency() {
        return mHyphenationFrequency;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Spanned overrides
    //
+24 −10
Original line number Diff line number Diff line
@@ -653,27 +653,41 @@ public class StaticLayout extends Layout {

        MeasuredText measured = null;
        final Spanned spanned;
        final boolean canUseMeasuredText;
        if (source instanceof MeasuredText) {
            measured = (MeasuredText) source;

            final CharSequence original = measured.getText();
            spanned = (original instanceof Spanned) ? (Spanned) original : null;

            if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) {
                // The buffer position has changed. Re-measure here.
                measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd);
                canUseMeasuredText = false;
            } else if (b.mBreakStrategy != measured.getBreakStrategy()
                    || b.mHyphenationFrequency != measured.getHyphenationFrequency()) {
                // The computed hyphenation pieces may not be able to used. Re-measure it.
                canUseMeasuredText = false;
            } else {
                // We can use measured information.
                canUseMeasuredText = true;
            }
        } else {
            canUseMeasuredText = false;
        }

                // Overwrite with the one when emeasured.
        if (!canUseMeasuredText) {
            measured = new MeasuredText.Builder(source, paint)
                    .setRange(bufStart, bufEnd)
                    .setTextDirection(textDir)
                    .setBreakStrategy(b.mBreakStrategy)
                    .setHyphenationFrequency(b.mHyphenationFrequency)
                    .build();
            spanned = (source instanceof Spanned) ? (Spanned) source : null;
        } else {
            final CharSequence original = measured.getText();
            spanned = (original instanceof Spanned) ? (Spanned) original : null;
            // Overwrite with the one when measured.
            // TODO: Give an option for developer not to overwrite and measure again here?
            textDir = measured.getTextDir();
            paint = measured.getPaint();
        }
        } else {
            measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd);
            spanned = (source instanceof Spanned) ? (Spanned) source : null;
        }

        try {
            for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) {
Loading