diff options
| author | Seigo Nonaka <nona@google.com> | 2016-11-28 16:24:14 +0900 |
|---|---|---|
| committer | Seigo Nonaka <nona@google.com> | 2017-01-05 18:14:15 +0900 |
| commit | 09da71a6dcfe07e0efdc35933322fba16091f555 (patch) | |
| tree | 23e2b4c7de31883d6c156a8678a5605a7dc93eb4 /core/java/android/text/Layout.java | |
| parent | ab156196f88bf7adac9cbf2779c049fc414be890 (diff) | |
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
Diffstat (limited to 'core/java/android/text/Layout.java')
| -rw-r--r-- | core/java/android/text/Layout.java | 135 |
1 files changed, 123 insertions, 12 deletions
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 3cb81b0a250f..2fc12d30ffd8 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -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; |
