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

Commit 09da71a6 authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Introduce full justification.

Adds a new get/setJustify API to TextView and StaticLayout.Builder for
justification, and fully justifies text when it's enabled.

This is based on a patch by Raph Levien (raph@google.com).

Bug: 31707212
Test: Manually done and CTS will introduced with
      I0f3bbf39d60a66b71b30e1351f7c741208f05dce passes.
Change-Id: Icbfab2faa11a6a0b52e6f0a77a9c9b5ef6e191da
parent ab156196
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -39434,6 +39434,7 @@ package android.text {
    method public android.text.StaticLayout.Builder setHyphenationFrequency(int);
    method public android.text.StaticLayout.Builder setIncludePad(boolean);
    method public android.text.StaticLayout.Builder setIndents(int[], int[]);
    method public android.text.StaticLayout.Builder setJustify(boolean);
    method public android.text.StaticLayout.Builder setLineSpacing(float, float);
    method public android.text.StaticLayout.Builder setMaxLines(int);
    method public android.text.StaticLayout.Builder setText(java.lang.CharSequence);
@@ -48958,6 +48959,7 @@ package android.widget {
    method public boolean getIncludeFontPadding();
    method public android.os.Bundle getInputExtras(boolean);
    method public int getInputType();
    method public boolean getJustify();
    method public final android.text.method.KeyListener getKeyListener();
    method public final android.text.Layout getLayout();
    method public float getLetterSpacing();
@@ -49066,6 +49068,7 @@ package android.widget {
    method public void setIncludeFontPadding(boolean);
    method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
    method public void setInputType(int);
    method public void setJustify(boolean);
    method public void setKeyListener(android.text.method.KeyListener);
    method public void setLetterSpacing(float);
    method public void setLineSpacing(float, float);
+3 −0
Original line number Diff line number Diff line
@@ -42644,6 +42644,7 @@ package android.text {
    method public android.text.StaticLayout.Builder setHyphenationFrequency(int);
    method public android.text.StaticLayout.Builder setIncludePad(boolean);
    method public android.text.StaticLayout.Builder setIndents(int[], int[]);
    method public android.text.StaticLayout.Builder setJustify(boolean);
    method public android.text.StaticLayout.Builder setLineSpacing(float, float);
    method public android.text.StaticLayout.Builder setMaxLines(int);
    method public android.text.StaticLayout.Builder setText(java.lang.CharSequence);
@@ -52528,6 +52529,7 @@ package android.widget {
    method public boolean getIncludeFontPadding();
    method public android.os.Bundle getInputExtras(boolean);
    method public int getInputType();
    method public boolean getJustify();
    method public final android.text.method.KeyListener getKeyListener();
    method public final android.text.Layout getLayout();
    method public float getLetterSpacing();
@@ -52636,6 +52638,7 @@ package android.widget {
    method public void setIncludeFontPadding(boolean);
    method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
    method public void setInputType(int);
    method public void setJustify(boolean);
    method public void setKeyListener(android.text.method.KeyListener);
    method public void setLetterSpacing(float);
    method public void setLineSpacing(float, float);
+3 −0
Original line number Diff line number Diff line
@@ -39554,6 +39554,7 @@ package android.text {
    method public android.text.StaticLayout.Builder setHyphenationFrequency(int);
    method public android.text.StaticLayout.Builder setIncludePad(boolean);
    method public android.text.StaticLayout.Builder setIndents(int[], int[]);
    method public android.text.StaticLayout.Builder setJustify(boolean);
    method public android.text.StaticLayout.Builder setLineSpacing(float, float);
    method public android.text.StaticLayout.Builder setMaxLines(int);
    method public android.text.StaticLayout.Builder setText(java.lang.CharSequence);
@@ -49260,6 +49261,7 @@ package android.widget {
    method public boolean getIncludeFontPadding();
    method public android.os.Bundle getInputExtras(boolean);
    method public int getInputType();
    method public boolean getJustify();
    method public final android.text.method.KeyListener getKeyListener();
    method public final android.text.Layout getLayout();
    method public float getLetterSpacing();
@@ -49368,6 +49370,7 @@ package android.widget {
    method public void setIncludeFontPadding(boolean);
    method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
    method public void setInputType(int);
    method public void setJustify(boolean);
    method public void setKeyListener(android.text.method.KeyListener);
    method public void setLetterSpacing(float);
    method public void setLineSpacing(float, float);
+7 −3
Original line number Diff line number Diff line
@@ -85,7 +85,7 @@ public class DynamicLayout extends Layout
        this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
                spacingmult, spacingadd, includepad,
                StaticLayout.BREAK_STRATEGY_SIMPLE, StaticLayout.HYPHENATION_FREQUENCY_NONE,
                ellipsize, ellipsizedWidth);
                false /* justify */, ellipsize, ellipsizedWidth);
    }

    /**
@@ -102,7 +102,8 @@ public class DynamicLayout extends Layout
                         int width, Alignment align, TextDirectionHeuristic textDir,
                         float spacingmult, float spacingadd,
                         boolean includepad, int breakStrategy, int hyphenationFrequency,
                         TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
                         boolean justify, TextUtils.TruncateAt ellipsize,
                         int ellipsizedWidth) {
        super((ellipsize == null)
                ? display
                : (display instanceof Spanned)
@@ -127,6 +128,7 @@ public class DynamicLayout extends Layout

        mIncludePad = includepad;
        mBreakStrategy = breakStrategy;
        mJustify = justify;
        mHyphenationFrequency = hyphenationFrequency;

        /*
@@ -300,7 +302,8 @@ public class DynamicLayout extends Layout
                .setEllipsizedWidth(mEllipsizedWidth)
                .setEllipsize(mEllipsizeAt)
                .setBreakStrategy(mBreakStrategy)
                .setHyphenationFrequency(mHyphenationFrequency);
                .setHyphenationFrequency(mHyphenationFrequency)
                .setJustify(mJustify);
        reflowed.generate(b, false, true);
        int n = reflowed.getLineCount();
        // If the new layout has a blank line at the end, but it is not
@@ -808,6 +811,7 @@ public class DynamicLayout extends Layout
    private TextUtils.TruncateAt mEllipsizeAt;
    private int mBreakStrategy;
    private int mHyphenationFrequency;
    private boolean mJustify;

    private PackedIntVector mInts;
    private PackedObjectVector<Directions> mObjects;
+123 −12
Original line number Diff line number Diff line
@@ -218,6 +218,11 @@ public abstract class Layout {
        mTextDir = textDir;
    }

    /** @hide */
    protected void setJustify(boolean justify) {
        mJustify = justify;
    }

    /**
     * Replace constructor properties of this Layout with new ones.  Be careful.
     */
@@ -266,6 +271,99 @@ public abstract class Layout {
        drawText(canvas, firstLine, lastLine);
    }

    private boolean isJustificationRequired(int lineNum) {
        if (!mJustify) return false;
        final int lineEnd = getLineEnd(lineNum);
        return lineEnd < mText.length() && mText.charAt(lineEnd - 1) != '\n';
    }

    private float getJustifyWidth(int lineNum) {
        Alignment paraAlign = mAlignment;
        TabStops tabStops = null;
        boolean tabStopsIsInitialized = false;

        int left = 0;
        int right = mWidth;

        final int dir = getParagraphDirection(lineNum);

        ParagraphStyle[] spans = NO_PARA_SPANS;
        if (mSpannedText) {
            Spanned sp = (Spanned) mText;
            final int start = getLineStart(lineNum);

            final boolean isFirstParaLine = (start == 0 || mText.charAt(start - 1) == '\n');

            if (isFirstParaLine) {
                final int spanEnd = sp.nextSpanTransition(start, mText.length(),
                        ParagraphStyle.class);
                spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);

                for (int n = spans.length - 1; n >= 0; n--) {
                    if (spans[n] instanceof AlignmentSpan) {
                        paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
                        break;
                    }
                }
            }

            final int length = spans.length;
            boolean useFirstLineMargin = isFirstParaLine;
            for (int n = 0; n < length; n++) {
                if (spans[n] instanceof LeadingMarginSpan2) {
                    int count = ((LeadingMarginSpan2) spans[n]).getLeadingMarginLineCount();
                    int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
                    if (lineNum < startLine + count) {
                        useFirstLineMargin = true;
                        break;
                    }
                }
            }
            for (int n = 0; n < length; n++) {
                if (spans[n] instanceof LeadingMarginSpan) {
                    LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
                    if (dir == DIR_RIGHT_TO_LEFT) {
                        right -= margin.getLeadingMargin(useFirstLineMargin);
                    } else {
                        left += margin.getLeadingMargin(useFirstLineMargin);
                    }
                }
            }
        }

        if (getLineContainsTab(lineNum)) {
            tabStops = new TabStops(TAB_INCREMENT, spans);
        }

        final Alignment align;
        if (paraAlign == Alignment.ALIGN_LEFT) {
            align = (dir == DIR_LEFT_TO_RIGHT) ?  Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
        } else if (paraAlign == Alignment.ALIGN_RIGHT) {
            align = (dir == DIR_LEFT_TO_RIGHT) ?  Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
        } else {
            align = paraAlign;
        }

        final int indentWidth;
        if (align == Alignment.ALIGN_NORMAL) {
            if (dir == DIR_LEFT_TO_RIGHT) {
                indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
            } else {
                indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
            }
        } else if (align == Alignment.ALIGN_OPPOSITE) {
            if (dir == DIR_LEFT_TO_RIGHT) {
                indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
            } else {
                indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
            }
        } else { // Alignment.ALIGN_CENTER
            indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
        }

        return right - left - indentWidth;
    }

    /**
     * @hide
     */
@@ -274,7 +372,7 @@ public abstract class Layout {
        int previousLineEnd = getLineStart(firstLine);
        ParagraphStyle[] spans = NO_PARA_SPANS;
        int spanEnd = 0;
        TextPaint paint = mPaint;
        final TextPaint paint = mPaint;
        CharSequence buf = mText;

        Alignment paraAlign = mAlignment;
@@ -288,6 +386,7 @@ public abstract class Layout {
        for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) {
            int start = previousLineEnd;
            previousLineEnd = getLineStart(lineNum + 1);
            final boolean justify = isJustificationRequired(lineNum);
            int end = getLineVisibleEnd(lineNum, start, previousLineEnd);

            int ltop = previousLineBottom;
@@ -386,34 +485,42 @@ public abstract class Layout {
            }

            int x;
            final int indentWidth;
            if (align == Alignment.ALIGN_NORMAL) {
                if (dir == DIR_LEFT_TO_RIGHT) {
                    x = left + getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
                    indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
                    x = left + indentWidth;
                } else {
                    x = right + getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
                    indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
                    x = right - indentWidth;
                }
            } else {
                int max = (int)getLineExtent(lineNum, tabStops, false);
                if (align == Alignment.ALIGN_OPPOSITE) {
                    if (dir == DIR_LEFT_TO_RIGHT) {
                        x = right - max + getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
                        indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
                        x = right - max - indentWidth;
                    } else {
                        x = left - max + getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
                        indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
                        x = left - max + indentWidth;
                    }
                } else { // Alignment.ALIGN_CENTER
                    indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
                    max = max & ~1;
                    x = ((right + left - max) >> 1) +
                            getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
                    x = ((right + left - max) >> 1) + indentWidth;
                }
            }

            paint.setHyphenEdit(getHyphen(lineNum));
            Directions directions = getLineDirections(lineNum);
            if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTab) {
            if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTab && !justify) {
                // XXX: assumes there's nothing additional to be done
                canvas.drawText(buf, start, end, x, lbaseline, paint);
            } else {
                tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops);
                if (justify) {
                    tl.justify(right - left - indentWidth);
                }
                tl.draw(canvas, x, ltop, lbaseline, lbottom);
            }
            paint.setHyphenEdit(0);
@@ -1094,6 +1201,9 @@ public abstract class Layout {
        TextLine tl = TextLine.obtain();
        mPaint.setHyphenEdit(getHyphen(line));
        tl.set(mPaint, mText, start, end, dir, directions, hasTabs, tabStops);
        if (isJustificationRequired(line)) {
            tl.justify(getJustifyWidth(line));
        }
        float width = tl.metrics(null);
        mPaint.setHyphenEdit(0);
        TextLine.recycle(tl);
@@ -1118,6 +1228,9 @@ public abstract class Layout {
        TextLine tl = TextLine.obtain();
        mPaint.setHyphenEdit(getHyphen(line));
        tl.set(mPaint, mText, start, end, dir, directions, hasTabs, tabStops);
        if (isJustificationRequired(line)) {
            tl.justify(getJustifyWidth(line));
        }
        float width = tl.metrics(null);
        mPaint.setHyphenEdit(0);
        TextLine.recycle(tl);
@@ -1303,10 +1416,7 @@ public abstract class Layout {
                return end - 1;
            }

            // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
            if (!(ch == ' ' || ch == '\t' || ch == 0x1680 ||
                    (0x2000 <= ch && ch <= 0x200A && ch != 0x2007) ||
                    ch == 0x205F || ch == 0x3000)) {
            if (!TextLine.isLineEndSpace(ch)) {
                break;
            }

@@ -2086,6 +2196,7 @@ public abstract class Layout {
    private boolean mSpannedText;
    private TextDirectionHeuristic mTextDir;
    private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
    private boolean mJustify;

    public static final int DIR_LEFT_TO_RIGHT = 1;
    public static final int DIR_RIGHT_TO_LEFT = -1;
Loading