summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/text/TextLine.java215
-rw-r--r--core/java/android/text/TextPaint.java21
-rw-r--r--graphics/java/android/graphics/Paint.java64
3 files changed, 255 insertions, 45 deletions
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 756e9a0e2f2a..97e281894395 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -16,6 +16,8 @@
package android.text;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
@@ -28,6 +30,8 @@ import android.util.Log;
import com.android.internal.util.ArrayUtils;
+import java.util.ArrayList;
+
/**
* Represents a line of styled text, for measuring in visual order and
* for rendering.
@@ -58,7 +62,9 @@ class TextLine {
// 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 TextPaint mActivePaint = new TextPaint();
private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
@@ -66,6 +72,9 @@ class TextLine {
private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
new SpanSet<ReplacementSpan>(ReplacementSpan.class);
+ private final UnderlineInfo mUnderlineInfo = new UnderlineInfo();
+ private final ArrayList<UnderlineInfo> mUnderlines = new ArrayList();
+
private static final TextLine[] sCached = new TextLine[3];
/**
@@ -695,6 +704,37 @@ class TextLine {
fmi.leading = Math.max(fmi.leading, previousLeading);
}
+ private static void drawUnderline(TextPaint wp, Canvas c, int color, float thickness,
+ float xleft, float xright, int baseline) {
+ // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
+ final float underlineTop = baseline + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();
+
+ final int previousColor = wp.getColor();
+ final Paint.Style previousStyle = wp.getStyle();
+ final boolean previousAntiAlias = wp.isAntiAlias();
+
+ wp.setStyle(Paint.Style.FILL);
+ wp.setAntiAlias(true);
+
+ wp.setColor(color);
+ c.drawRect(xleft, underlineTop, xright, underlineTop + thickness, wp);
+
+ wp.setStyle(previousStyle);
+ wp.setColor(previousColor);
+ wp.setAntiAlias(previousAntiAlias);
+ }
+
+ private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd,
+ boolean runIsRtl, int offset) {
+ if (mCharsValid) {
+ return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset);
+ } else {
+ final int delta = mStart;
+ return wp.getRunAdvance(mText, delta + start, delta + end,
+ delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
+ }
+ }
+
/**
* Utility function for measuring and rendering text. The text must
* not include a tab.
@@ -711,13 +751,15 @@ class TextLine {
* @param fmi receives metrics information, can be null
* @param needWidth true if the width of the run is needed
* @param offset the offset for the purpose of measuring
+ * @param underlines the list of locations and paremeters for drawing underlines
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
private float handleText(TextPaint wp, int start, int end,
int contextStart, int contextEnd, boolean runIsRtl,
Canvas c, float x, int top, int y, int bottom,
- FontMetricsInt fmi, boolean needWidth, int offset) {
+ FontMetricsInt fmi, boolean needWidth, int offset,
+ @Nullable ArrayList<UnderlineInfo> underlines) {
wp.setWordSpacing(mAddedWidth);
// Get metrics first (even for empty strings or "0" width runs)
@@ -725,28 +767,26 @@ class TextLine {
expandMetricsFromPaint(fmi, wp);
}
- int runLen = end - start;
// No need to do anything if the run width is "0"
- if (runLen == 0) {
+ if (end == start) {
return 0f;
}
- float ret = 0;
+ float totalWidth = 0;
- if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
- if (mCharsValid) {
- ret = wp.getRunAdvance(mChars, start, end, contextStart, contextEnd,
- runIsRtl, offset);
- } else {
- int delta = mStart;
- ret = wp.getRunAdvance(mText, delta + start, delta + end,
- delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
- }
+ final int numUnderlines = underlines == null ? 0 : underlines.size();
+ if (needWidth || (c != null && (wp.bgColor != 0 || numUnderlines != 0 || runIsRtl))) {
+ totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset);
}
if (c != null) {
+ final float leftX, rightX;
if (runIsRtl) {
- x -= ret;
+ leftX = x - totalWidth;
+ rightX = x;
+ } else {
+ leftX = x;
+ rightX = x + totalWidth;
}
if (wp.bgColor != 0) {
@@ -755,36 +795,52 @@ class TextLine {
wp.setColor(wp.bgColor);
wp.setStyle(Paint.Style.FILL);
- c.drawRect(x, top, x + ret, bottom, wp);
+ c.drawRect(leftX, top, rightX, bottom, wp);
wp.setStyle(previousStyle);
wp.setColor(previousColor);
}
- if (wp.underlineColor != 0) {
- // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
- float underlineTop = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();
-
- int previousColor = wp.getColor();
- Paint.Style previousStyle = wp.getStyle();
- boolean previousAntiAlias = wp.isAntiAlias();
-
- wp.setStyle(Paint.Style.FILL);
- wp.setAntiAlias(true);
-
- wp.setColor(wp.underlineColor);
- c.drawRect(x, underlineTop, x + ret, underlineTop + wp.underlineThickness, wp);
+ if (numUnderlines != 0) {
+ // kStdUnderline_Thickness = 1/18, defined in SkTextFormatParams.h
+ final float defaultThickness = (1.0f / 18.0f) * wp.getTextSize();
+ for (int i = 0; i < numUnderlines; i++) {
+ final UnderlineInfo info = underlines.get(i);
+
+ final int underlineStart = Math.max(info.start, start);
+ final int underlineEnd = Math.min(info.end, offset);
+ float underlineStartAdvance = getRunAdvance(
+ wp, start, end, contextStart, contextEnd, runIsRtl, underlineStart);
+ float underlineEndAdvance = getRunAdvance(
+ wp, start, end, contextStart, contextEnd, runIsRtl, underlineEnd);
+ final float underlineXLeft, underlineXRight;
+ if (runIsRtl) {
+ underlineXLeft = rightX - underlineEndAdvance;
+ underlineXRight = rightX - underlineStartAdvance;
+ } else {
+ underlineXLeft = leftX + underlineStartAdvance;
+ underlineXRight = leftX + underlineEndAdvance;
+ }
- wp.setStyle(previousStyle);
- wp.setColor(previousColor);
- wp.setAntiAlias(previousAntiAlias);
+ // Theoretically, there could be cases where both Paint's and TextPaint's
+ // setUnderLineText() are called. For backward compatibility, we need to draw
+ // both underlines, the one with custom color first.
+ if (info.underlineColor != 0) {
+ drawUnderline(wp, c, wp.underlineColor, wp.underlineThickness,
+ underlineXLeft, underlineXRight, y);
+ }
+ if (info.isUnderlineText) {
+ drawUnderline(wp, c, wp.getColor(), defaultThickness,
+ underlineXLeft, underlineXRight, y);
+ }
+ }
}
drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
- x, y + wp.baselineShift);
+ leftX, y + wp.baselineShift);
}
- return runIsRtl ? -ret : ret;
+ return runIsRtl ? -totalWidth : totalWidth;
}
/**
@@ -864,6 +920,37 @@ class TextLine {
return result;
}
+ private static final class UnderlineInfo {
+ public boolean isUnderlineText;
+ public int underlineColor;
+ public float underlineThickness;
+ public int start = -1;
+ public int end = -1;
+
+ public boolean hasUnderline() {
+ return isUnderlineText || underlineColor != 0;
+ }
+
+ // Copies the info, but not the start and end range.
+ public UnderlineInfo copyInfo() {
+ final UnderlineInfo copy = new UnderlineInfo();
+ copy.isUnderlineText = isUnderlineText;
+ copy.underlineColor = underlineColor;
+ copy.underlineThickness = underlineThickness;
+ return copy;
+ }
+ }
+
+ private void extractUnderlineInfo(@NonNull TextPaint paint, @NonNull UnderlineInfo info) {
+ info.isUnderlineText = paint.isUnderlineText();
+ if (info.isUnderlineText) {
+ paint.setUnderlineText(false);
+ }
+ info.underlineColor = paint.underlineColor;
+ info.underlineThickness = paint.underlineThickness;
+ paint.setUnderlineText(0, 0.0f);
+ }
+
/**
* Utility function for handling a unidirectional run. The run must not
* contain tabs but can contain styles.
@@ -894,7 +981,7 @@ class TextLine {
// Case of an empty line, make sure we update fmi according to mPaint
if (start == measureLimit) {
- TextPaint wp = mWorkPaint;
+ final TextPaint wp = mWorkPaint;
wp.set(mPaint);
if (fmi != null) {
expandMetricsFromPaint(fmi, wp);
@@ -903,11 +990,11 @@ class TextLine {
}
if (mSpanned == null) {
- TextPaint wp = mWorkPaint;
+ final TextPaint wp = mWorkPaint;
wp.set(mPaint);
wp.setHyphenEdit(adjustHyphenEdit(start, limit, wp.getHyphenEdit()));
return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top,
- y, bottom, fmi, needWidth, measureLimit);
+ y, bottom, fmi, needWidth, measureLimit, null);
}
mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
@@ -920,7 +1007,7 @@ class TextLine {
// for the run bounds.
final float originalX = x;
for (int i = start, inext; i < measureLimit; i = inext) {
- TextPaint wp = mWorkPaint;
+ final TextPaint wp = mWorkPaint;
wp.set(mPaint);
inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
@@ -934,7 +1021,7 @@ class TextLine {
// empty by construction. This special case in getSpans() explains the >= & <= tests
if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
(mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
- MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
+ final MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
if (span instanceof ReplacementSpan) {
replacement = (ReplacementSpan)span;
} else {
@@ -950,26 +1037,68 @@ class TextLine {
continue;
}
+ final TextPaint activePaint = mActivePaint;
+ activePaint.set(mPaint);
+ int activeStart = i;
+ int activeEnd = mlimit;
+ final UnderlineInfo underlineInfo = mUnderlineInfo;
+ mUnderlines.clear();
for (int j = i, jnext; j < mlimit; j = jnext) {
jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) -
mStart;
- int offset = Math.min(jnext, mlimit);
+ final int offset = Math.min(jnext, mlimit);
wp.set(mPaint);
for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
// Intentionally using >= and <= as explained above
if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) ||
(mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
- CharacterStyle span = mCharacterStyleSpanSet.spans[k];
+ final CharacterStyle span = mCharacterStyleSpanSet.spans[k];
span.updateDrawState(wp);
}
- wp.setHyphenEdit(adjustHyphenEdit(j, jnext, wp.getHyphenEdit()));
+ extractUnderlineInfo(wp, underlineInfo);
+
+ if (j == i) {
+ // First chunk of text. We can't handle it yet, since we may need to merge it
+ // with the next chunk. So we just save the TextPaint for future comparisons
+ // and use.
+ activePaint.set(wp);
+ } else if (!wp.hasEqualAttributes(activePaint)) {
+ // The style of the present chunk of text is substantially different from the
+ // style of the previous chunk. We need to handle the active piece of text
+ // and restart with the present chunk.
+ activePaint.setHyphenEdit(adjustHyphenEdit(
+ activeStart, activeEnd, mPaint.getHyphenEdit()));
+ x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
+ top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
+ Math.min(activeEnd, mlimit), mUnderlines);
+
+ activeStart = j;
+ activePaint.set(wp);
+ mUnderlines.clear();
+ } else {
+ // The present TextPaint is substantially equal to the last TextPaint except
+ // perhaps for underlines. We just need to expand the active piece of text to
+ // include the present chunk, which we always do anyway. We don't need to save
+ // wp to activePaint, since they are already equal.
+ }
- x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
- top, y, bottom, fmi, needWidth || jnext < measureLimit, offset);
+ activeEnd = jnext;
+ if (underlineInfo.hasUnderline()) {
+ final UnderlineInfo copy = underlineInfo.copyInfo();
+ copy.start = j;
+ copy.end = jnext;
+ mUnderlines.add(copy);
+ }
}
+ // Handle the final piece of text.
+ activePaint.setHyphenEdit(adjustHyphenEdit(
+ activeStart, activeEnd, mPaint.getHyphenEdit()));
+ x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
+ top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
+ Math.min(activeEnd, mlimit), mUnderlines);
}
return x - originalX;
diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java
index 4f8cff0fd7c2..1b062cf8adcf 100644
--- a/core/java/android/text/TextPaint.java
+++ b/core/java/android/text/TextPaint.java
@@ -17,6 +17,7 @@
package android.text;
import android.annotation.ColorInt;
+import android.annotation.NonNull;
import android.graphics.Paint;
/**
@@ -40,7 +41,7 @@ public class TextPaint extends Paint {
@ColorInt
public int underlineColor = 0;
/**
- * Defined as a multiplier of the default underline thickness. Use 1.0f for default thickness.
+ * Thickness of the underline, in pixels.
* @hide
*/
public float underlineThickness;
@@ -74,6 +75,24 @@ public class TextPaint extends Paint {
}
/**
+ * Returns true if all attributes, including the attributes inherited from Paint, are equal.
+ *
+ * The caller is expected to have checked the trivial cases, like the pointers being equal,
+ * the objects having different classes, or the parameter being null.
+ * @hide
+ */
+ public boolean hasEqualAttributes(@NonNull TextPaint other) {
+ return bgColor == other.bgColor
+ && baselineShift == other.baselineShift
+ && linkColor == other.linkColor
+ && drawableState == other.drawableState
+ && density == other.density
+ && underlineColor == other.underlineColor
+ && underlineThickness == other.underlineThickness
+ && super.hasEqualAttributes((Paint) other);
+ }
+
+ /**
* Defines a custom underline for this Paint.
* @param color underline solid color
* @param thickness underline thickness
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index e3b47405e0d4..eb33ca857095 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -80,6 +80,11 @@ public class Paint {
private String mFontFeatureSettings;
private String mFontVariationSettings;
+ private float mShadowLayerRadius;
+ private float mShadowLayerDx;
+ private float mShadowLayerDy;
+ private int mShadowLayerColor;
+
private static final Object sCacheLock = new Object();
/**
@@ -530,6 +535,11 @@ public class Paint {
setElegantTextHeight(false);
mFontFeatureSettings = null;
mFontVariationSettings = null;
+
+ mShadowLayerRadius = 0.0f;
+ mShadowLayerDx = 0.0f;
+ mShadowLayerDy = 0.0f;
+ mShadowLayerColor = 0;
}
/**
@@ -567,6 +577,54 @@ public class Paint {
mLocales = paint.mLocales;
mFontFeatureSettings = paint.mFontFeatureSettings;
mFontVariationSettings = paint.mFontVariationSettings;
+
+ mShadowLayerRadius = paint.mShadowLayerRadius;
+ mShadowLayerDx = paint.mShadowLayerDx;
+ mShadowLayerDy = paint.mShadowLayerDy;
+ mShadowLayerColor = paint.mShadowLayerColor;
+ }
+
+ /**
+ * Returns true if all attributes are equal.
+ *
+ * The caller is expected to have checked the trivial cases, like the pointers being equal,
+ * the objects having different classes, or the parameter being null.
+ * @hide
+ */
+ public boolean hasEqualAttributes(@NonNull Paint other) {
+ return mColorFilter == other.mColorFilter
+ && mMaskFilter == other.mMaskFilter
+ && mPathEffect == other.mPathEffect
+ && mShader == other.mShader
+ && mTypeface == other.mTypeface
+ && mXfermode == other.mXfermode
+ && mHasCompatScaling == other.mHasCompatScaling
+ && mCompatScaling == other.mCompatScaling
+ && mInvCompatScaling == other.mInvCompatScaling
+ && mBidiFlags == other.mBidiFlags
+ && mLocales.equals(other.mLocales)
+ && TextUtils.equals(mFontFeatureSettings, other.mFontFeatureSettings)
+ && TextUtils.equals(mFontVariationSettings, other.mFontVariationSettings)
+ && mShadowLayerRadius == other.mShadowLayerRadius
+ && mShadowLayerDx == other.mShadowLayerDx
+ && mShadowLayerDy == other.mShadowLayerDy
+ && mShadowLayerColor == other.mShadowLayerColor
+ && getFlags() == other.getFlags()
+ && getHinting() == other.getHinting()
+ && getStyle() == other.getStyle()
+ && getColor() == other.getColor()
+ && getStrokeWidth() == other.getStrokeWidth()
+ && getStrokeMiter() == other.getStrokeMiter()
+ && getStrokeCap() == other.getStrokeCap()
+ && getStrokeJoin() == other.getStrokeJoin()
+ && getTextAlign() == other.getTextAlign()
+ && isElegantTextHeight() == other.isElegantTextHeight()
+ && getTextSize() == other.getTextSize()
+ && getTextScaleX() == other.getTextScaleX()
+ && getTextSkewX() == other.getTextSkewX()
+ && getLetterSpacing() == other.getLetterSpacing()
+ && getWordSpacing() == other.getWordSpacing()
+ && getHyphenEdit() == other.getHyphenEdit();
}
/** @hide */
@@ -1236,6 +1294,10 @@ public class Paint {
* opaque, or the alpha from the shadow color if not.
*/
public void setShadowLayer(float radius, float dx, float dy, int shadowColor) {
+ mShadowLayerRadius = radius;
+ mShadowLayerDx = dx;
+ mShadowLayerDy = dy;
+ mShadowLayerColor = shadowColor;
nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
}
@@ -2796,7 +2858,7 @@ public class Paint {
private static native int nGetFontMetricsInt(long paintPtr,
long typefacePtr, FontMetricsInt fmi);
-
+
// ---------------- @CriticalNative ------------------------
@CriticalNative