Loading core/java/android/text/TextLine.java +58 −45 Original line number Original line Diff line number Diff line Loading @@ -72,8 +72,8 @@ class TextLine { private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet = private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet = new SpanSet<ReplacementSpan>(ReplacementSpan.class); new SpanSet<ReplacementSpan>(ReplacementSpan.class); private final UnderlineInfo mUnderlineInfo = new UnderlineInfo(); private final DecorationInfo mDecorationInfo = new DecorationInfo(); private final ArrayList<UnderlineInfo> mUnderlines = new ArrayList(); private final ArrayList<DecorationInfo> mDecorations = new ArrayList(); private static final TextLine[] sCached = new TextLine[3]; private static final TextLine[] sCached = new TextLine[3]; Loading Loading @@ -704,9 +704,9 @@ class TextLine { fmi.leading = Math.max(fmi.leading, previousLeading); fmi.leading = Math.max(fmi.leading, previousLeading); } } private static void drawUnderline(TextPaint wp, Canvas c, int color, float thickness, private static void drawStroke(TextPaint wp, Canvas c, int color, float position, float xleft, float xright, float baseline) { float thickness, float xleft, float xright, float baseline) { final float underlineTop = baseline + wp.baselineShift + wp.getUnderlinePosition(); final float strokeTop = baseline + wp.baselineShift + position; final int previousColor = wp.getColor(); final int previousColor = wp.getColor(); final Paint.Style previousStyle = wp.getStyle(); final Paint.Style previousStyle = wp.getStyle(); Loading @@ -716,7 +716,7 @@ class TextLine { wp.setAntiAlias(true); wp.setAntiAlias(true); wp.setColor(color); wp.setColor(color); c.drawRect(xleft, underlineTop, xright, underlineTop + thickness, wp); c.drawRect(xleft, strokeTop, xright, strokeTop + thickness, wp); wp.setStyle(previousStyle); wp.setStyle(previousStyle); wp.setColor(previousColor); wp.setColor(previousColor); Loading Loading @@ -750,7 +750,7 @@ class TextLine { * @param fmi receives metrics information, can be null * @param fmi receives metrics information, can be null * @param needWidth true if the width of the run is needed * @param needWidth true if the width of the run is needed * @param offset the offset for the purpose of measuring * @param offset the offset for the purpose of measuring * @param underlines the list of locations and paremeters for drawing underlines * @param decorations the list of locations and paremeters for drawing decorations * @return the signed width of the run based on the run direction; only * @return the signed width of the run based on the run direction; only * valid if needWidth is true * valid if needWidth is true */ */ Loading @@ -758,7 +758,7 @@ class TextLine { int contextStart, int contextEnd, boolean runIsRtl, int contextStart, int contextEnd, boolean runIsRtl, Canvas c, float x, int top, int y, int bottom, 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) { @Nullable ArrayList<DecorationInfo> decorations) { wp.setWordSpacing(mAddedWidth); wp.setWordSpacing(mAddedWidth); // Get metrics first (even for empty strings or "0" width runs) // Get metrics first (even for empty strings or "0" width runs) Loading @@ -773,8 +773,8 @@ class TextLine { float totalWidth = 0; float totalWidth = 0; final int numUnderlines = underlines == null ? 0 : underlines.size(); final int numDecorations = decorations == null ? 0 : decorations.size(); if (needWidth || (c != null && (wp.bgColor != 0 || numUnderlines != 0 || runIsRtl))) { if (needWidth || (c != null && (wp.bgColor != 0 || numDecorations != 0 || runIsRtl))) { totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); } } Loading @@ -800,37 +800,44 @@ class TextLine { wp.setColor(previousColor); wp.setColor(previousColor); } } if (numUnderlines != 0) { if (numDecorations != 0) { for (int i = 0; i < numUnderlines; i++) { for (int i = 0; i < numDecorations; i++) { final UnderlineInfo info = underlines.get(i); final DecorationInfo info = decorations.get(i); final int underlineStart = Math.max(info.start, start); final int decorationStart = Math.max(info.start, start); final int underlineEnd = Math.min(info.end, offset); final int decorationEnd = Math.min(info.end, offset); float underlineStartAdvance = getRunAdvance( float decorationStartAdvance = getRunAdvance( wp, start, end, contextStart, contextEnd, runIsRtl, underlineStart); wp, start, end, contextStart, contextEnd, runIsRtl, decorationStart); float underlineEndAdvance = getRunAdvance( float decorationEndAdvance = getRunAdvance( wp, start, end, contextStart, contextEnd, runIsRtl, underlineEnd); wp, start, end, contextStart, contextEnd, runIsRtl, decorationEnd); final float underlineXLeft, underlineXRight; final float decorationXLeft, decorationXRight; if (runIsRtl) { if (runIsRtl) { underlineXLeft = rightX - underlineEndAdvance; decorationXLeft = rightX - decorationEndAdvance; underlineXRight = rightX - underlineStartAdvance; decorationXRight = rightX - decorationStartAdvance; } else { } else { underlineXLeft = leftX + underlineStartAdvance; decorationXLeft = leftX + decorationStartAdvance; underlineXRight = leftX + underlineEndAdvance; decorationXRight = leftX + decorationEndAdvance; } } // Theoretically, there could be cases where both Paint's and TextPaint's // Theoretically, there could be cases where both Paint's and TextPaint's // setUnderLineText() are called. For backward compatibility, we need to draw // setUnderLineText() are called. For backward compatibility, we need to draw // both underlines, the one with custom color first. // both underlines, the one with custom color first. if (info.underlineColor != 0) { if (info.underlineColor != 0) { drawUnderline(wp, c, info.underlineColor, info.underlineThickness, drawStroke(wp, c, info.underlineColor, wp.getUnderlinePosition(), underlineXLeft, underlineXRight, y); info.underlineThickness, decorationXLeft, decorationXRight, y); } } if (info.isUnderlineText) { if (info.isUnderlineText) { final float thickness = final float thickness = Math.max(((Paint) wp).getUnderlineThickness(), 1.0f); Math.max(((Paint) wp).getUnderlineThickness(), 1.0f); drawUnderline(wp, c, wp.getColor(), thickness, drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness, underlineXLeft, underlineXRight, y); decorationXLeft, decorationXRight, y); } if (info.isStrikeThruText) { final float thickness = Math.max(((Paint) wp).getStrikeThruThickness(), 1.0f); drawStroke(wp, c, wp.getColor(), wp.getStrikeThruPosition(), thickness, decorationXLeft, decorationXRight, y); } } } } } } Loading Loading @@ -919,20 +926,22 @@ class TextLine { return result; return result; } } private static final class UnderlineInfo { private static final class DecorationInfo { public boolean isStrikeThruText; public boolean isUnderlineText; public boolean isUnderlineText; public int underlineColor; public int underlineColor; public float underlineThickness; public float underlineThickness; public int start = -1; public int start = -1; public int end = -1; public int end = -1; public boolean hasUnderline() { public boolean hasDecoration() { return isUnderlineText || underlineColor != 0; return isStrikeThruText || isUnderlineText || underlineColor != 0; } } // Copies the info, but not the start and end range. // Copies the info, but not the start and end range. public UnderlineInfo copyInfo() { public DecorationInfo copyInfo() { final UnderlineInfo copy = new UnderlineInfo(); final DecorationInfo copy = new DecorationInfo(); copy.isStrikeThruText = isStrikeThruText; copy.isUnderlineText = isUnderlineText; copy.isUnderlineText = isUnderlineText; copy.underlineColor = underlineColor; copy.underlineColor = underlineColor; copy.underlineThickness = underlineThickness; copy.underlineThickness = underlineThickness; Loading @@ -940,7 +949,11 @@ class TextLine { } } } } private void extractUnderlineInfo(@NonNull TextPaint paint, @NonNull UnderlineInfo info) { private void extractDecorationInfo(@NonNull TextPaint paint, @NonNull DecorationInfo info) { info.isStrikeThruText = paint.isStrikeThruText(); if (info.isStrikeThruText) { paint.setStrikeThruText(false); } info.isUnderlineText = paint.isUnderlineText(); info.isUnderlineText = paint.isUnderlineText(); if (info.isUnderlineText) { if (info.isUnderlineText) { paint.setUnderlineText(false); paint.setUnderlineText(false); Loading Loading @@ -1047,8 +1060,8 @@ class TextLine { activePaint.set(mPaint); activePaint.set(mPaint); int activeStart = i; int activeStart = i; int activeEnd = mlimit; int activeEnd = mlimit; final UnderlineInfo underlineInfo = mUnderlineInfo; final DecorationInfo decorationInfo = mDecorationInfo; mUnderlines.clear(); mDecorations.clear(); for (int j = i, jnext; j < mlimit; j = jnext) { for (int j = i, jnext; j < mlimit; j = jnext) { jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) - jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) - mStart; mStart; Loading @@ -1064,7 +1077,7 @@ class TextLine { span.updateDrawState(wp); span.updateDrawState(wp); } } extractUnderlineInfo(wp, underlineInfo); extractDecorationInfo(wp, decorationInfo); if (j == i) { if (j == i) { // First chunk of text. We can't handle it yet, since we may need to merge it // First chunk of text. We can't handle it yet, since we may need to merge it Loading @@ -1079,24 +1092,24 @@ class TextLine { activeStart, activeEnd, mPaint.getHyphenEdit())); activeStart, activeEnd, mPaint.getHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mUnderlines); Math.min(activeEnd, mlimit), mDecorations); activeStart = j; activeStart = j; activePaint.set(wp); activePaint.set(wp); mUnderlines.clear(); mDecorations.clear(); } else { } else { // The present TextPaint is substantially equal to the last TextPaint except // 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 // perhaps for decorations. 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 // include the present chunk, which we always do anyway. We don't need to save // wp to activePaint, since they are already equal. // wp to activePaint, since they are already equal. } } activeEnd = jnext; activeEnd = jnext; if (underlineInfo.hasUnderline()) { if (decorationInfo.hasDecoration()) { final UnderlineInfo copy = underlineInfo.copyInfo(); final DecorationInfo copy = decorationInfo.copyInfo(); copy.start = j; copy.start = j; copy.end = jnext; copy.end = jnext; mUnderlines.add(copy); mDecorations.add(copy); } } } } // Handle the final piece of text. // Handle the final piece of text. Loading @@ -1104,7 +1117,7 @@ class TextLine { activeStart, activeEnd, mPaint.getHyphenEdit())); activeStart, activeEnd, mPaint.getHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mUnderlines); Math.min(activeEnd, mlimit), mDecorations); } } return x - originalX; return x - originalX; Loading core/jni/android/graphics/Paint.cpp +12 −0 Original line number Original line Diff line number Diff line Loading @@ -985,6 +985,16 @@ namespace PaintGlue { } } } } static jfloat getStrikeThruPosition(jlong paintHandle, jlong typefaceHandle) { const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); return SkScalarToFloat(Paint::kStdStrikeThru_Top * textSize); } static jfloat getStrikeThruThickness(jlong paintHandle, jlong typefaceHandle) { const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize); } static void setShadowLayer(jlong paintHandle, jfloat radius, static void setShadowLayer(jlong paintHandle, jfloat radius, jfloat dx, jfloat dy, jint color) { jfloat dx, jfloat dy, jint color) { Paint* paint = reinterpret_cast<Paint*>(paintHandle); Paint* paint = reinterpret_cast<Paint*>(paintHandle); Loading Loading @@ -1098,6 +1108,8 @@ static const JNINativeMethod methods[] = { {"nDescent","(JJ)F", (void*) PaintGlue::descent}, {"nDescent","(JJ)F", (void*) PaintGlue::descent}, {"nGetUnderlinePosition","(JJ)F", (void*) PaintGlue::getUnderlinePosition}, {"nGetUnderlinePosition","(JJ)F", (void*) PaintGlue::getUnderlinePosition}, {"nGetUnderlineThickness","(JJ)F", (void*) PaintGlue::getUnderlineThickness}, {"nGetUnderlineThickness","(JJ)F", (void*) PaintGlue::getUnderlineThickness}, {"nGetStrikeThruPosition","(JJ)F", (void*) PaintGlue::getStrikeThruPosition}, {"nGetStrikeThruThickness","(JJ)F", (void*) PaintGlue::getStrikeThruThickness}, {"nSetShadowLayer", "(JFFFI)V", (void*)PaintGlue::setShadowLayer}, {"nSetShadowLayer", "(JFFFI)V", (void*)PaintGlue::setShadowLayer}, {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer} {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer} }; }; Loading graphics/java/android/graphics/Paint.java +21 −0 Original line number Original line Diff line number Diff line Loading @@ -851,6 +851,23 @@ public class Paint { return (getFlags() & STRIKE_THRU_TEXT_FLAG) != 0; return (getFlags() & STRIKE_THRU_TEXT_FLAG) != 0; } } /** * Distance from top of the strike-through line to the baseline. Negative values mean above the * baseline. This method returns where the strike-through line should be drawn independent of if * the strikeThruText bit is set at the moment. * @hide */ public float getStrikeThruPosition() { return nGetStrikeThruPosition(mNativePaint, mNativeTypeface); } /** * @hide */ public float getStrikeThruThickness() { return nGetStrikeThruThickness(mNativePaint, mNativeTypeface); } /** /** * Helper for setFlags(), setting or clearing the STRIKE_THRU_TEXT_FLAG bit * Helper for setFlags(), setting or clearing the STRIKE_THRU_TEXT_FLAG bit * * Loading Loading @@ -2997,5 +3014,9 @@ public class Paint { @CriticalNative @CriticalNative private static native float nGetUnderlineThickness(long paintPtr, long typefacePtr); private static native float nGetUnderlineThickness(long paintPtr, long typefacePtr); @CriticalNative @CriticalNative private static native float nGetStrikeThruPosition(long paintPtr, long typefacePtr); @CriticalNative private static native float nGetStrikeThruThickness(long paintPtr, long typefacePtr); @CriticalNative private static native void nSetTextSize(long paintPtr, float textSize); private static native void nSetTextSize(long paintPtr, float textSize); } } libs/hwui/hwui/Canvas.cpp +12 −9 Original line number Original line Diff line number Diff line Loading @@ -35,6 +35,13 @@ Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::Rende return new uirenderer::RecordingCanvas(width, height); return new uirenderer::RecordingCanvas(width, height); } } static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness, const SkPaint& paint, Canvas* canvas) { const SkScalar strokeWidth = fmax(thickness, 1.0f); const SkScalar bottom = top + strokeWidth; canvas->drawRect(left, top, right, bottom, paint); } void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) { void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) { uint32_t flags; uint32_t flags; SkDrawFilter* drawFilter = getDrawFilter(); SkDrawFilter* drawFilter = getDrawFilter(); Loading @@ -46,7 +53,6 @@ void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& flags = paint.getFlags(); flags = paint.getFlags(); } } if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) { if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) { const SkScalar left = x; const SkScalar left = x; const SkScalar right = x + length; const SkScalar right = x + length; if (flags & SkPaint::kUnderlineText_ReserveFlag) { if (flags & SkPaint::kUnderlineText_ReserveFlag) { Loading @@ -60,18 +66,15 @@ void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& if (!metrics.hasUnderlineThickness(&thickness)) { if (!metrics.hasUnderlineThickness(&thickness)) { thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness; thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness; } } const float strokeWidth = fmax(thickness, 1.0f); const SkScalar top = y + position; const SkScalar top = y + position; const SkScalar bottom = top + strokeWidth; drawStroke(left, right, top, thickness, paint, this); drawRect(left, top, right, bottom, paint); } } if (flags & SkPaint::kStrikeThruText_ReserveFlag) { if (flags & SkPaint::kStrikeThruText_ReserveFlag) { const float textSize = paint.getTextSize(); const float textSize = paint.getTextSize(); const float position = textSize * Paint::kStdStrikeThru_Offset; const float position = textSize * Paint::kStdStrikeThru_Top; const float strokeWidth = fmax(textSize * Paint::kStdUnderline_Thickness, 1.0f); const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; const SkScalar top = y + position - 0.5f * strokeWidth; const SkScalar top = y + position; const SkScalar bottom = y + position + 0.5f * strokeWidth; drawStroke(left, right, top, thickness, paint, this); drawRect(left, top, right, bottom, paint); } } } } } } Loading libs/hwui/hwui/Paint.h +4 −0 Original line number Original line Diff line number Diff line Loading @@ -37,6 +37,10 @@ public: constexpr static float kStdUnderline_Top = constexpr static float kStdUnderline_Top = kStdUnderline_Offset - 0.5f * kStdUnderline_Thickness; kStdUnderline_Offset - 0.5f * kStdUnderline_Thickness; constexpr static float kStdStrikeThru_Thickness = kStdUnderline_Thickness; constexpr static float kStdStrikeThru_Top = kStdStrikeThru_Offset - 0.5f * kStdStrikeThru_Thickness; Paint(); Paint(); Paint(const Paint& paint); Paint(const Paint& paint); Paint(const SkPaint& paint); // NOLINT(implicit) Paint(const SkPaint& paint); // NOLINT(implicit) Loading Loading
core/java/android/text/TextLine.java +58 −45 Original line number Original line Diff line number Diff line Loading @@ -72,8 +72,8 @@ class TextLine { private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet = private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet = new SpanSet<ReplacementSpan>(ReplacementSpan.class); new SpanSet<ReplacementSpan>(ReplacementSpan.class); private final UnderlineInfo mUnderlineInfo = new UnderlineInfo(); private final DecorationInfo mDecorationInfo = new DecorationInfo(); private final ArrayList<UnderlineInfo> mUnderlines = new ArrayList(); private final ArrayList<DecorationInfo> mDecorations = new ArrayList(); private static final TextLine[] sCached = new TextLine[3]; private static final TextLine[] sCached = new TextLine[3]; Loading Loading @@ -704,9 +704,9 @@ class TextLine { fmi.leading = Math.max(fmi.leading, previousLeading); fmi.leading = Math.max(fmi.leading, previousLeading); } } private static void drawUnderline(TextPaint wp, Canvas c, int color, float thickness, private static void drawStroke(TextPaint wp, Canvas c, int color, float position, float xleft, float xright, float baseline) { float thickness, float xleft, float xright, float baseline) { final float underlineTop = baseline + wp.baselineShift + wp.getUnderlinePosition(); final float strokeTop = baseline + wp.baselineShift + position; final int previousColor = wp.getColor(); final int previousColor = wp.getColor(); final Paint.Style previousStyle = wp.getStyle(); final Paint.Style previousStyle = wp.getStyle(); Loading @@ -716,7 +716,7 @@ class TextLine { wp.setAntiAlias(true); wp.setAntiAlias(true); wp.setColor(color); wp.setColor(color); c.drawRect(xleft, underlineTop, xright, underlineTop + thickness, wp); c.drawRect(xleft, strokeTop, xright, strokeTop + thickness, wp); wp.setStyle(previousStyle); wp.setStyle(previousStyle); wp.setColor(previousColor); wp.setColor(previousColor); Loading Loading @@ -750,7 +750,7 @@ class TextLine { * @param fmi receives metrics information, can be null * @param fmi receives metrics information, can be null * @param needWidth true if the width of the run is needed * @param needWidth true if the width of the run is needed * @param offset the offset for the purpose of measuring * @param offset the offset for the purpose of measuring * @param underlines the list of locations and paremeters for drawing underlines * @param decorations the list of locations and paremeters for drawing decorations * @return the signed width of the run based on the run direction; only * @return the signed width of the run based on the run direction; only * valid if needWidth is true * valid if needWidth is true */ */ Loading @@ -758,7 +758,7 @@ class TextLine { int contextStart, int contextEnd, boolean runIsRtl, int contextStart, int contextEnd, boolean runIsRtl, Canvas c, float x, int top, int y, int bottom, 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) { @Nullable ArrayList<DecorationInfo> decorations) { wp.setWordSpacing(mAddedWidth); wp.setWordSpacing(mAddedWidth); // Get metrics first (even for empty strings or "0" width runs) // Get metrics first (even for empty strings or "0" width runs) Loading @@ -773,8 +773,8 @@ class TextLine { float totalWidth = 0; float totalWidth = 0; final int numUnderlines = underlines == null ? 0 : underlines.size(); final int numDecorations = decorations == null ? 0 : decorations.size(); if (needWidth || (c != null && (wp.bgColor != 0 || numUnderlines != 0 || runIsRtl))) { if (needWidth || (c != null && (wp.bgColor != 0 || numDecorations != 0 || runIsRtl))) { totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); } } Loading @@ -800,37 +800,44 @@ class TextLine { wp.setColor(previousColor); wp.setColor(previousColor); } } if (numUnderlines != 0) { if (numDecorations != 0) { for (int i = 0; i < numUnderlines; i++) { for (int i = 0; i < numDecorations; i++) { final UnderlineInfo info = underlines.get(i); final DecorationInfo info = decorations.get(i); final int underlineStart = Math.max(info.start, start); final int decorationStart = Math.max(info.start, start); final int underlineEnd = Math.min(info.end, offset); final int decorationEnd = Math.min(info.end, offset); float underlineStartAdvance = getRunAdvance( float decorationStartAdvance = getRunAdvance( wp, start, end, contextStart, contextEnd, runIsRtl, underlineStart); wp, start, end, contextStart, contextEnd, runIsRtl, decorationStart); float underlineEndAdvance = getRunAdvance( float decorationEndAdvance = getRunAdvance( wp, start, end, contextStart, contextEnd, runIsRtl, underlineEnd); wp, start, end, contextStart, contextEnd, runIsRtl, decorationEnd); final float underlineXLeft, underlineXRight; final float decorationXLeft, decorationXRight; if (runIsRtl) { if (runIsRtl) { underlineXLeft = rightX - underlineEndAdvance; decorationXLeft = rightX - decorationEndAdvance; underlineXRight = rightX - underlineStartAdvance; decorationXRight = rightX - decorationStartAdvance; } else { } else { underlineXLeft = leftX + underlineStartAdvance; decorationXLeft = leftX + decorationStartAdvance; underlineXRight = leftX + underlineEndAdvance; decorationXRight = leftX + decorationEndAdvance; } } // Theoretically, there could be cases where both Paint's and TextPaint's // Theoretically, there could be cases where both Paint's and TextPaint's // setUnderLineText() are called. For backward compatibility, we need to draw // setUnderLineText() are called. For backward compatibility, we need to draw // both underlines, the one with custom color first. // both underlines, the one with custom color first. if (info.underlineColor != 0) { if (info.underlineColor != 0) { drawUnderline(wp, c, info.underlineColor, info.underlineThickness, drawStroke(wp, c, info.underlineColor, wp.getUnderlinePosition(), underlineXLeft, underlineXRight, y); info.underlineThickness, decorationXLeft, decorationXRight, y); } } if (info.isUnderlineText) { if (info.isUnderlineText) { final float thickness = final float thickness = Math.max(((Paint) wp).getUnderlineThickness(), 1.0f); Math.max(((Paint) wp).getUnderlineThickness(), 1.0f); drawUnderline(wp, c, wp.getColor(), thickness, drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness, underlineXLeft, underlineXRight, y); decorationXLeft, decorationXRight, y); } if (info.isStrikeThruText) { final float thickness = Math.max(((Paint) wp).getStrikeThruThickness(), 1.0f); drawStroke(wp, c, wp.getColor(), wp.getStrikeThruPosition(), thickness, decorationXLeft, decorationXRight, y); } } } } } } Loading Loading @@ -919,20 +926,22 @@ class TextLine { return result; return result; } } private static final class UnderlineInfo { private static final class DecorationInfo { public boolean isStrikeThruText; public boolean isUnderlineText; public boolean isUnderlineText; public int underlineColor; public int underlineColor; public float underlineThickness; public float underlineThickness; public int start = -1; public int start = -1; public int end = -1; public int end = -1; public boolean hasUnderline() { public boolean hasDecoration() { return isUnderlineText || underlineColor != 0; return isStrikeThruText || isUnderlineText || underlineColor != 0; } } // Copies the info, but not the start and end range. // Copies the info, but not the start and end range. public UnderlineInfo copyInfo() { public DecorationInfo copyInfo() { final UnderlineInfo copy = new UnderlineInfo(); final DecorationInfo copy = new DecorationInfo(); copy.isStrikeThruText = isStrikeThruText; copy.isUnderlineText = isUnderlineText; copy.isUnderlineText = isUnderlineText; copy.underlineColor = underlineColor; copy.underlineColor = underlineColor; copy.underlineThickness = underlineThickness; copy.underlineThickness = underlineThickness; Loading @@ -940,7 +949,11 @@ class TextLine { } } } } private void extractUnderlineInfo(@NonNull TextPaint paint, @NonNull UnderlineInfo info) { private void extractDecorationInfo(@NonNull TextPaint paint, @NonNull DecorationInfo info) { info.isStrikeThruText = paint.isStrikeThruText(); if (info.isStrikeThruText) { paint.setStrikeThruText(false); } info.isUnderlineText = paint.isUnderlineText(); info.isUnderlineText = paint.isUnderlineText(); if (info.isUnderlineText) { if (info.isUnderlineText) { paint.setUnderlineText(false); paint.setUnderlineText(false); Loading Loading @@ -1047,8 +1060,8 @@ class TextLine { activePaint.set(mPaint); activePaint.set(mPaint); int activeStart = i; int activeStart = i; int activeEnd = mlimit; int activeEnd = mlimit; final UnderlineInfo underlineInfo = mUnderlineInfo; final DecorationInfo decorationInfo = mDecorationInfo; mUnderlines.clear(); mDecorations.clear(); for (int j = i, jnext; j < mlimit; j = jnext) { for (int j = i, jnext; j < mlimit; j = jnext) { jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) - jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) - mStart; mStart; Loading @@ -1064,7 +1077,7 @@ class TextLine { span.updateDrawState(wp); span.updateDrawState(wp); } } extractUnderlineInfo(wp, underlineInfo); extractDecorationInfo(wp, decorationInfo); if (j == i) { if (j == i) { // First chunk of text. We can't handle it yet, since we may need to merge it // First chunk of text. We can't handle it yet, since we may need to merge it Loading @@ -1079,24 +1092,24 @@ class TextLine { activeStart, activeEnd, mPaint.getHyphenEdit())); activeStart, activeEnd, mPaint.getHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mUnderlines); Math.min(activeEnd, mlimit), mDecorations); activeStart = j; activeStart = j; activePaint.set(wp); activePaint.set(wp); mUnderlines.clear(); mDecorations.clear(); } else { } else { // The present TextPaint is substantially equal to the last TextPaint except // 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 // perhaps for decorations. 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 // include the present chunk, which we always do anyway. We don't need to save // wp to activePaint, since they are already equal. // wp to activePaint, since they are already equal. } } activeEnd = jnext; activeEnd = jnext; if (underlineInfo.hasUnderline()) { if (decorationInfo.hasDecoration()) { final UnderlineInfo copy = underlineInfo.copyInfo(); final DecorationInfo copy = decorationInfo.copyInfo(); copy.start = j; copy.start = j; copy.end = jnext; copy.end = jnext; mUnderlines.add(copy); mDecorations.add(copy); } } } } // Handle the final piece of text. // Handle the final piece of text. Loading @@ -1104,7 +1117,7 @@ class TextLine { activeStart, activeEnd, mPaint.getHyphenEdit())); activeStart, activeEnd, mPaint.getHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mUnderlines); Math.min(activeEnd, mlimit), mDecorations); } } return x - originalX; return x - originalX; Loading
core/jni/android/graphics/Paint.cpp +12 −0 Original line number Original line Diff line number Diff line Loading @@ -985,6 +985,16 @@ namespace PaintGlue { } } } } static jfloat getStrikeThruPosition(jlong paintHandle, jlong typefaceHandle) { const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); return SkScalarToFloat(Paint::kStdStrikeThru_Top * textSize); } static jfloat getStrikeThruThickness(jlong paintHandle, jlong typefaceHandle) { const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize); } static void setShadowLayer(jlong paintHandle, jfloat radius, static void setShadowLayer(jlong paintHandle, jfloat radius, jfloat dx, jfloat dy, jint color) { jfloat dx, jfloat dy, jint color) { Paint* paint = reinterpret_cast<Paint*>(paintHandle); Paint* paint = reinterpret_cast<Paint*>(paintHandle); Loading Loading @@ -1098,6 +1108,8 @@ static const JNINativeMethod methods[] = { {"nDescent","(JJ)F", (void*) PaintGlue::descent}, {"nDescent","(JJ)F", (void*) PaintGlue::descent}, {"nGetUnderlinePosition","(JJ)F", (void*) PaintGlue::getUnderlinePosition}, {"nGetUnderlinePosition","(JJ)F", (void*) PaintGlue::getUnderlinePosition}, {"nGetUnderlineThickness","(JJ)F", (void*) PaintGlue::getUnderlineThickness}, {"nGetUnderlineThickness","(JJ)F", (void*) PaintGlue::getUnderlineThickness}, {"nGetStrikeThruPosition","(JJ)F", (void*) PaintGlue::getStrikeThruPosition}, {"nGetStrikeThruThickness","(JJ)F", (void*) PaintGlue::getStrikeThruThickness}, {"nSetShadowLayer", "(JFFFI)V", (void*)PaintGlue::setShadowLayer}, {"nSetShadowLayer", "(JFFFI)V", (void*)PaintGlue::setShadowLayer}, {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer} {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer} }; }; Loading
graphics/java/android/graphics/Paint.java +21 −0 Original line number Original line Diff line number Diff line Loading @@ -851,6 +851,23 @@ public class Paint { return (getFlags() & STRIKE_THRU_TEXT_FLAG) != 0; return (getFlags() & STRIKE_THRU_TEXT_FLAG) != 0; } } /** * Distance from top of the strike-through line to the baseline. Negative values mean above the * baseline. This method returns where the strike-through line should be drawn independent of if * the strikeThruText bit is set at the moment. * @hide */ public float getStrikeThruPosition() { return nGetStrikeThruPosition(mNativePaint, mNativeTypeface); } /** * @hide */ public float getStrikeThruThickness() { return nGetStrikeThruThickness(mNativePaint, mNativeTypeface); } /** /** * Helper for setFlags(), setting or clearing the STRIKE_THRU_TEXT_FLAG bit * Helper for setFlags(), setting or clearing the STRIKE_THRU_TEXT_FLAG bit * * Loading Loading @@ -2997,5 +3014,9 @@ public class Paint { @CriticalNative @CriticalNative private static native float nGetUnderlineThickness(long paintPtr, long typefacePtr); private static native float nGetUnderlineThickness(long paintPtr, long typefacePtr); @CriticalNative @CriticalNative private static native float nGetStrikeThruPosition(long paintPtr, long typefacePtr); @CriticalNative private static native float nGetStrikeThruThickness(long paintPtr, long typefacePtr); @CriticalNative private static native void nSetTextSize(long paintPtr, float textSize); private static native void nSetTextSize(long paintPtr, float textSize); } }
libs/hwui/hwui/Canvas.cpp +12 −9 Original line number Original line Diff line number Diff line Loading @@ -35,6 +35,13 @@ Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::Rende return new uirenderer::RecordingCanvas(width, height); return new uirenderer::RecordingCanvas(width, height); } } static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness, const SkPaint& paint, Canvas* canvas) { const SkScalar strokeWidth = fmax(thickness, 1.0f); const SkScalar bottom = top + strokeWidth; canvas->drawRect(left, top, right, bottom, paint); } void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) { void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) { uint32_t flags; uint32_t flags; SkDrawFilter* drawFilter = getDrawFilter(); SkDrawFilter* drawFilter = getDrawFilter(); Loading @@ -46,7 +53,6 @@ void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& flags = paint.getFlags(); flags = paint.getFlags(); } } if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) { if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) { const SkScalar left = x; const SkScalar left = x; const SkScalar right = x + length; const SkScalar right = x + length; if (flags & SkPaint::kUnderlineText_ReserveFlag) { if (flags & SkPaint::kUnderlineText_ReserveFlag) { Loading @@ -60,18 +66,15 @@ void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& if (!metrics.hasUnderlineThickness(&thickness)) { if (!metrics.hasUnderlineThickness(&thickness)) { thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness; thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness; } } const float strokeWidth = fmax(thickness, 1.0f); const SkScalar top = y + position; const SkScalar top = y + position; const SkScalar bottom = top + strokeWidth; drawStroke(left, right, top, thickness, paint, this); drawRect(left, top, right, bottom, paint); } } if (flags & SkPaint::kStrikeThruText_ReserveFlag) { if (flags & SkPaint::kStrikeThruText_ReserveFlag) { const float textSize = paint.getTextSize(); const float textSize = paint.getTextSize(); const float position = textSize * Paint::kStdStrikeThru_Offset; const float position = textSize * Paint::kStdStrikeThru_Top; const float strokeWidth = fmax(textSize * Paint::kStdUnderline_Thickness, 1.0f); const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; const SkScalar top = y + position - 0.5f * strokeWidth; const SkScalar top = y + position; const SkScalar bottom = y + position + 0.5f * strokeWidth; drawStroke(left, right, top, thickness, paint, this); drawRect(left, top, right, bottom, paint); } } } } } } Loading
libs/hwui/hwui/Paint.h +4 −0 Original line number Original line Diff line number Diff line Loading @@ -37,6 +37,10 @@ public: constexpr static float kStdUnderline_Top = constexpr static float kStdUnderline_Top = kStdUnderline_Offset - 0.5f * kStdUnderline_Thickness; kStdUnderline_Offset - 0.5f * kStdUnderline_Thickness; constexpr static float kStdStrikeThru_Thickness = kStdUnderline_Thickness; constexpr static float kStdStrikeThru_Top = kStdStrikeThru_Offset - 0.5f * kStdStrikeThru_Thickness; Paint(); Paint(); Paint(const Paint& paint); Paint(const Paint& paint); Paint(const SkPaint& paint); // NOLINT(implicit) Paint(const SkPaint& paint); // NOLINT(implicit) Loading