diff options
| -rw-r--r-- | core/java/android/text/TextLine.java | 215 | ||||
| -rw-r--r-- | core/java/android/text/TextPaint.java | 21 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Paint.java | 64 |
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 |