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
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -94,6 +94,7 @@ public class StaticLayout extends Layout {
|
||||
b.mMaxLines = Integer.MAX_VALUE;
|
||||
b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
|
||||
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
|
||||
b.mJustify = false;
|
||||
|
||||
b.mMeasuredText = MeasuredText.obtain();
|
||||
return b;
|
||||
@@ -319,6 +320,17 @@ public class StaticLayout extends Layout {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables paragraph justification. The default value is disabled (false).
|
||||
*
|
||||
* @param justify true for enabling and false for disabling paragraph justification.
|
||||
* @return this builder, useful for chaining.
|
||||
*/
|
||||
public Builder setJustify(boolean justify) {
|
||||
mJustify = justify;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Measurement and break iteration is done in native code. The protocol for using
|
||||
* the native code is as follows.
|
||||
@@ -404,6 +416,7 @@ public class StaticLayout extends Layout {
|
||||
int mHyphenationFrequency;
|
||||
int[] mLeftIndents;
|
||||
int[] mRightIndents;
|
||||
boolean mJustify;
|
||||
|
||||
Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
|
||||
|
||||
@@ -557,6 +570,7 @@ public class StaticLayout extends Layout {
|
||||
|
||||
mLeftIndents = b.mLeftIndents;
|
||||
mRightIndents = b.mRightIndents;
|
||||
setJustify(b.mJustify);
|
||||
|
||||
generate(b, b.mIncludePad, b.mIncludePad);
|
||||
}
|
||||
@@ -676,7 +690,8 @@ public class StaticLayout extends Layout {
|
||||
|
||||
nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
|
||||
firstWidth, firstWidthLineCount, restWidth,
|
||||
variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency);
|
||||
variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
|
||||
b.mJustify);
|
||||
if (mLeftIndents != null || mRightIndents != null) {
|
||||
// TODO(raph) performance: it would be better to do this once per layout rather
|
||||
// than once per paragraph, but that would require a change to the native
|
||||
@@ -1284,7 +1299,8 @@ public class StaticLayout extends Layout {
|
||||
// Set up paragraph text and settings; done as one big method to minimize jni crossings
|
||||
private static native void nSetupParagraph(long nativePtr, char[] text, int length,
|
||||
float firstWidth, int firstWidthLineCount, float restWidth,
|
||||
int[] variableTabStops, int defaultTabStop, int breakStrategy, int hyphenationFrequency);
|
||||
int[] variableTabStops, int defaultTabStop, int breakStrategy, int hyphenationFrequency,
|
||||
boolean isJustified);
|
||||
|
||||
private static native float nAddStyleRun(long nativePtr, long nativePaint,
|
||||
long nativeTypeface, int start, int end, boolean isRtl);
|
||||
|
||||
@@ -54,6 +54,10 @@ class TextLine {
|
||||
private char[] mChars;
|
||||
private boolean mCharsValid;
|
||||
private Spanned mSpanned;
|
||||
|
||||
// Additional width of whitespace for justification. This value is per whitespace, thus
|
||||
// the line width will increase by mAddedWidth x (number of stretchable whitespaces).
|
||||
private float mAddedWidth;
|
||||
private final TextPaint mWorkPaint = new TextPaint();
|
||||
private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
|
||||
new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
|
||||
@@ -177,6 +181,25 @@ class TextLine {
|
||||
}
|
||||
}
|
||||
mTabs = tabStops;
|
||||
mAddedWidth = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Justify the line to the given width.
|
||||
*/
|
||||
void justify(float justifyWidth) {
|
||||
int end = mLen;
|
||||
while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) {
|
||||
end--;
|
||||
}
|
||||
final int spaces = countStretchableSpaces(0, end);
|
||||
if (spaces == 0) {
|
||||
// There are no stretchable spaces, so we can't help the justification by adding any
|
||||
// width.
|
||||
return;
|
||||
}
|
||||
final float width = Math.abs(measure(end, false, null));
|
||||
mAddedWidth = (justifyWidth - width) / spaces;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -595,6 +618,7 @@ class TextLine {
|
||||
|
||||
TextPaint wp = mWorkPaint;
|
||||
wp.set(mPaint);
|
||||
wp.setWordSpacing(mAddedWidth);
|
||||
|
||||
int spanStart = runStart;
|
||||
int spanLimit;
|
||||
@@ -695,6 +719,7 @@ class TextLine {
|
||||
Canvas c, float x, int top, int y, int bottom,
|
||||
FontMetricsInt fmi, boolean needWidth, int offset) {
|
||||
|
||||
wp.setWordSpacing(mAddedWidth);
|
||||
// Get metrics first (even for empty strings or "0" width runs)
|
||||
if (fmi != null) {
|
||||
expandMetricsFromPaint(fmi, wp);
|
||||
@@ -981,5 +1006,34 @@ class TextLine {
|
||||
return TabStops.nextDefaultStop(h, TAB_INCREMENT);
|
||||
}
|
||||
|
||||
private boolean isStretchableWhitespace(int ch) {
|
||||
// TODO: Support other stretchable whitespace. (Bug: 34013491)
|
||||
return ch == 0x0020 || ch == 0x00A0;
|
||||
}
|
||||
|
||||
private int nextStretchableSpace(int start, int end) {
|
||||
for (int i = start; i < end; i++) {
|
||||
final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
|
||||
if (isStretchableWhitespace(c)) return i;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
/* Return the number of spaces in the text line, for the purpose of justification */
|
||||
private int countStretchableSpaces(int start, int end) {
|
||||
int count = 0;
|
||||
for (int i = start; i < end; i = nextStretchableSpace(i + 1, end)) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
|
||||
public static boolean isLineEndSpace(char ch) {
|
||||
return ch == ' ' || ch == '\t' || ch == 0x1680
|
||||
|| (0x2000 <= ch && ch <= 0x200A && ch != 0x2007)
|
||||
|| ch == 0x205F || ch == 0x3000;
|
||||
}
|
||||
|
||||
private static final int TAB_INCREMENT = 20;
|
||||
}
|
||||
|
||||
@@ -596,6 +596,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
|
||||
private int mBreakStrategy;
|
||||
private int mHyphenationFrequency;
|
||||
private boolean mJustify;
|
||||
|
||||
private int mMaximum = Integer.MAX_VALUE;
|
||||
private int mMaxMode = LINES;
|
||||
@@ -769,6 +770,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
String fontFeatureSettings = null;
|
||||
mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
|
||||
mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
|
||||
mJustify = false;
|
||||
|
||||
final Resources.Theme theme = context.getTheme();
|
||||
|
||||
@@ -3297,6 +3299,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
return mHyphenationFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables full justification. The default value is false.
|
||||
*
|
||||
* @see #getJustify()
|
||||
*/
|
||||
public void setJustify(boolean justify) {
|
||||
mJustify = justify;
|
||||
if (mLayout != null) {
|
||||
nullLayouts();
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if currently paragraph justification is enabled.
|
||||
*
|
||||
* @see #setJustify(boolean)
|
||||
*/
|
||||
public boolean getJustify() {
|
||||
return mJustify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets font feature settings. The format is the same as the CSS
|
||||
* font-feature-settings attribute:
|
||||
@@ -7170,6 +7195,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
.setIncludePad(mIncludePad)
|
||||
.setBreakStrategy(mBreakStrategy)
|
||||
.setHyphenationFrequency(mHyphenationFrequency)
|
||||
.setJustify(mJustify)
|
||||
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
|
||||
if (shouldEllipsize) {
|
||||
builder.setEllipsize(mEllipsize)
|
||||
@@ -7211,7 +7237,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
if (mText instanceof Spannable) {
|
||||
result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
|
||||
alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
|
||||
mBreakStrategy, mHyphenationFrequency,
|
||||
mBreakStrategy, mHyphenationFrequency, mJustify,
|
||||
getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
|
||||
} else {
|
||||
if (boring == UNKNOWN_BORING) {
|
||||
@@ -7261,6 +7287,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
.setIncludePad(mIncludePad)
|
||||
.setBreakStrategy(mBreakStrategy)
|
||||
.setHyphenationFrequency(mHyphenationFrequency)
|
||||
.setJustify(mJustify)
|
||||
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
|
||||
if (shouldEllipsize) {
|
||||
builder.setEllipsize(effectiveEllipsize)
|
||||
|
||||
@@ -54,7 +54,8 @@ static JLineBreaksID gLineBreaks_fieldID;
|
||||
// hyphenFrequency)
|
||||
static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length,
|
||||
jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
|
||||
jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency) {
|
||||
jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency,
|
||||
jboolean isJustified) {
|
||||
minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
|
||||
b->resize(length);
|
||||
env->GetCharArrayRegion(text, 0, length, b->buffer());
|
||||
@@ -68,6 +69,7 @@ static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray tex
|
||||
}
|
||||
b->setStrategy(static_cast<minikin::BreakStrategy>(strategy));
|
||||
b->setHyphenationFrequency(static_cast<minikin::HyphenationFrequency>(hyphenFrequency));
|
||||
b->setJustified(isJustified);
|
||||
}
|
||||
|
||||
static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
|
||||
@@ -190,7 +192,7 @@ static const JNINativeMethod gMethods[] = {
|
||||
{"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
|
||||
{"nLoadHyphenator", "(Ljava/nio/ByteBuffer;I)J", (void*) nLoadHyphenator},
|
||||
{"nSetLocale", "(JLjava/lang/String;J)V", (void*) nSetLocale},
|
||||
{"nSetupParagraph", "(J[CIFIF[IIII)V", (void*) nSetupParagraph},
|
||||
{"nSetupParagraph", "(J[CIFIF[IIIIZ)V", (void*) nSetupParagraph},
|
||||
{"nSetIndents", "(J[I)V", (void*) nSetIndents},
|
||||
{"nAddStyleRun", "(JJJIIZ)F", (void*) nAddStyleRun},
|
||||
{"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun},
|
||||
|
||||
Reference in New Issue
Block a user