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

Commit d3ab692d authored by Raph Levien's avatar Raph Levien
Browse files

Some refactoring of StaticLayout

This patch refactors construction of StaticLayout to use an explicit
Builder object, which is intended to hold state used for constructing
the layout but not needed for merely reading out the results.

Builder objects are allocated from a pool and explicitly recycled,
so there is insignificant additional allocation cost.

This patch has very little impact on performance (it does avoid
allocating a FontMetricsInt object) but opens the way for significant
performance and functionality improvements as more of the Builder
functionality migrates to native code.

Change-Id: I2a576643e573a38b61f895a80d5d92a85c94b6b4
parent f63a665f
Loading
Loading
Loading
Loading
+20 −10
Original line number Diff line number Diff line
@@ -270,22 +270,30 @@ public class DynamicLayout extends Layout
        // generate new layout for affected text

        StaticLayout reflowed;
        StaticLayout.Builder b;

        synchronized (sLock) {
            reflowed = sStaticLayout;
            b = sBuilder;
            sStaticLayout = null;
            sBuilder = null;
        }

        // TODO: make sure reflowed is properly initialized
        if (reflowed == null) {
            reflowed = new StaticLayout(null);
        } else {
            reflowed.prepare();
        }

        reflowed.generate(text, where, where + after,
                getPaint(), getWidth(), getTextDirectionHeuristic(), getSpacingMultiplier(),
                getSpacingAdd(), false,
                true, mEllipsizedWidth, mEllipsizeAt);
            b = StaticLayout.Builder.obtain();
        }

        b.setText(text, where, where + after)
                .setPaint(getPaint())
                .setWidth(getWidth())
                .setTextDir(getTextDirectionHeuristic())
                .setSpacingMult(getSpacingMultiplier())
                .setSpacingAdd(getSpacingAdd())
                .setEllipsizedWidth(mEllipsizedWidth)
                .setEllipsize(mEllipsizeAt);
        reflowed.generate(b, false, true);
        int n = reflowed.getLineCount();

        // If the new layout has a blank line at the end, but it is not
@@ -359,9 +367,10 @@ public class DynamicLayout extends Layout

        updateBlocks(startline, endline - 1, n);

        b.finish();
        synchronized (sLock) {
            sStaticLayout = reflowed;
            reflowed.finish();
            sBuilder = b;
        }
    }

@@ -720,7 +729,8 @@ public class DynamicLayout extends Layout

    private int mTopPadding, mBottomPadding;

    private static StaticLayout sStaticLayout = new StaticLayout(null);
    private static StaticLayout sStaticLayout = null;
    private static StaticLayout.Builder sBuilder = null;

    private static final Object[] sLock = new Object[0];

+16 −10
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package android.text;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
@@ -67,8 +66,7 @@ class MeasuredText {
    }

    static MeasuredText recycle(MeasuredText mt) {
        mt.mText = null;
        if (mt.mLen < 1000) {
        mt.finish();
        synchronized(sLock) {
            for (int i = 0; i < sCached.length; ++i) {
                if (sCached[i] == null) {
@@ -78,10 +76,18 @@ class MeasuredText {
                }
            }
        }
        }
        return null;
    }

    void finish() {
        mText = null;
        if (mLen > 1000) {
            mWidths = null;
            mChars = null;
            mLevels = null;
        }
    }

    void setPos(int pos) {
        mPos = pos - mTextStart;
    }
+177 −32
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package android.text;

import android.graphics.Bitmap;
import android.graphics.Paint;
import android.text.style.LeadingMarginSpan;
import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
@@ -44,6 +43,153 @@ public class StaticLayout extends Layout {

    static final String TAG = "StaticLayout";

    /**
     * Builder for static layouts. It would be better if this were a public
     * API (as it would offer much greater flexibility for adding new options)
     * but for the time being it's just internal.
     *
     * @hide
     */
    public final static class Builder {
        static Builder obtain() {
            Builder b = null;
            synchronized (sLock) {
                for (int i = 0; i < sCached.length; i++) {
                    if (sCached[i] != null) {
                        b = sCached[i];
                        sCached[i] = null;
                        break;
                    }
                }
            }
            if (b == null) {
                b = new Builder();
            }

            // set default initial values
            b.mWidth = 0;
            b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
            b.mSpacingMult = 1.0f;
            b.mSpacingAdd = 0.0f;
            b.mIncludePad = true;
            b.mEllipsizedWidth = 0;
            b.mEllipsize = null;
            b.mMaxLines = Integer.MAX_VALUE;

            b.mMeasuredText = MeasuredText.obtain();
            return b;
        }

        static void recycle(Builder b) {
            b.mPaint = null;
            b.mText = null;
            MeasuredText.recycle(b.mMeasuredText);
            synchronized (sLock) {
                for (int i = 0; i < sCached.length; i++) {
                    if (sCached[i] == null) {
                        sCached[i] = b;
                        break;
                    }
                }
            }
        }

        // release any expensive state
        /* package */ void finish() {
            mMeasuredText.finish();
        }

        public Builder setText(CharSequence source) {
            return setText(source, 0, source.length());
        }

        public Builder setText(CharSequence source, int start, int end) {
            mText = source;
            mStart = start;
            mEnd = end;
            return this;
        }

        public Builder setPaint(TextPaint paint) {
            mPaint = paint;
            return this;
        }

        public Builder setWidth(int width) {
            mWidth = width;
            if (mEllipsize == null) {
                mEllipsizedWidth = width;
            }
            return this;
        }

        public Builder setTextDir(TextDirectionHeuristic textDir) {
            mTextDir = textDir;
            return this;
        }

        // TODO: combine the following, as they're almost always set together?
        public Builder setSpacingMult(float spacingMult) {
            mSpacingMult = spacingMult;
            return this;
        }

        public Builder setSpacingAdd(float spacingAdd) {
            mSpacingAdd = spacingAdd;
            return this;
        }

        public Builder setIncludePad(boolean includePad) {
            mIncludePad = includePad;
            return this;
        }

        // TODO: combine the following?
        public Builder setEllipsizedWidth(int ellipsizedWidth) {
            mEllipsizedWidth = ellipsizedWidth;
            return this;
        }

        public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
            mEllipsize = ellipsize;
            return this;
        }

        public Builder setMaxLines(int maxLines) {
            mMaxLines = maxLines;
            return this;
        }

        public StaticLayout build() {
            // TODO: can optimize based on whether ellipsis is needed
            StaticLayout result = new StaticLayout(mText);
            result.initFromBuilder(this);
            recycle(this);
            return result;
        }

        CharSequence mText;
        int mStart;
        int mEnd;
        TextPaint mPaint;
        int mWidth;
        TextDirectionHeuristic mTextDir;
        float mSpacingMult;
        float mSpacingAdd;
        boolean mIncludePad;
        int mEllipsizedWidth;
        TextUtils.TruncateAt mEllipsize;
        int mMaxLines;

        Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();

        // This will go away and be subsumed by native builder code
        MeasuredText mMeasuredText;

        private static final Object sLock = new Object();
        private static final Builder[] sCached = new Builder[3];
    }

    public StaticLayout(CharSequence source, TextPaint paint,
                        int width,
                        Alignment align, float spacingmult, float spacingadd,
@@ -111,6 +257,17 @@ public class StaticLayout extends Layout {
                    : new Ellipsizer(source),
              paint, outerwidth, align, textDir, spacingmult, spacingadd);

        Builder b = Builder.obtain();
        b.setText(source, bufstart, bufend)
            .setPaint(paint)
            .setWidth(outerwidth)
            .setTextDir(textDir)
            .setSpacingMult(spacingmult)
            .setSpacingAdd(spacingadd)
            .setIncludePad(includepad)
            .setEllipsizedWidth(ellipsizedWidth)
            .setEllipsize(ellipsize)
            .setMaxLines(maxLines);
        /*
         * This is annoying, but we can't refer to the layout until
         * superclass construction is finished, and the superclass
@@ -137,14 +294,9 @@ public class StaticLayout extends Layout {
        mLines = new int[mLineDirections.length];
        mMaximumVisibleLineCount = maxLines;

        mMeasured = MeasuredText.obtain();

        generate(source, bufstart, bufend, paint, outerwidth, textDir, spacingmult,
                 spacingadd, includepad, includepad, ellipsizedWidth,
                 ellipsize);
        initFromBuilder(b);

        mMeasured = MeasuredText.recycle(mMeasured);
        mFontMetricsInt = null;
        Builder.recycle(b);
    }

    /* package */ StaticLayout(CharSequence text) {
@@ -153,16 +305,23 @@ public class StaticLayout extends Layout {
        mColumns = COLUMNS_ELLIPSIZE;
        mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
        mLines = new int[mLineDirections.length];
        // FIXME This is never recycled
        mMeasured = MeasuredText.obtain();
    }

    /* package */ void generate(CharSequence source, int bufStart, int bufEnd,
                        TextPaint paint, int outerWidth,
                        TextDirectionHeuristic textDir, float spacingmult,
                        float spacingadd, boolean includepad,
                        boolean trackpad, float ellipsizedWidth,
                        TextUtils.TruncateAt ellipsize) {
    private void initFromBuilder(Builder b) {
        generate(b, b.mIncludePad, b.mIncludePad);
    }

    /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
        CharSequence source = b.mText;
        int bufStart = b.mStart;
        int bufEnd = b.mEnd;
        TextPaint paint = b.mPaint;
        int outerWidth = b.mWidth;
        TextDirectionHeuristic textDir = b.mTextDir;
        float spacingmult = b.mSpacingMult;
        float spacingadd = b.mSpacingAdd;
        float ellipsizedWidth = b.mEllipsizedWidth;
        TextUtils.TruncateAt ellipsize = b.mEllipsize;
        LineBreaks lineBreaks = new LineBreaks();
        // store span end locations
        int[] spanEndCache = new int[4];
@@ -176,10 +335,10 @@ public class StaticLayout extends Layout {
        int v = 0;
        boolean needMultiply = (spacingmult != 1 || spacingadd != 0);

        Paint.FontMetricsInt fm = mFontMetricsInt;
        Paint.FontMetricsInt fm = b.mFontMetricsInt;
        int[] chooseHtv = null;

        MeasuredText measured = mMeasured;
        MeasuredText measured = b.mMeasuredText;

        Spanned spanned = null;
        if (source instanceof Spanned)
@@ -747,14 +906,6 @@ public class StaticLayout extends Layout {
        return mEllipsizedWidth;
    }

    void prepare() {
        mMeasured = MeasuredText.obtain();
    }
    
    void finish() {
        mMeasured = MeasuredText.recycle(mMeasured);
    }

    // populates LineBreaks and returns the number of breaks found
    //
    // the arrays inside the LineBreaks objects are passed in as well
@@ -794,12 +945,6 @@ public class StaticLayout extends Layout {

    private static final double EXTRA_ROUNDING = 0.5;

    /*
     * This is reused across calls to generate()
     */
    private MeasuredText mMeasured;
    private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();

    // This is used to return three arrays from a single JNI call when
    // performing line breaking
    /*package*/ static class LineBreaks {