diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index e4ed62a1ead9e..d4de62fa919a1 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -827,7 +827,9 @@ class TextLine { underlineXLeft, underlineXRight, y); } if (info.isUnderlineText) { - drawUnderline(wp, c, wp.getColor(), ((Paint) wp).getUnderlineThickness(), + final float thickness = + Math.max(((Paint) wp).getUnderlineThickness(), 1.0f); + drawUnderline(wp, c, wp.getColor(), thickness, underlineXLeft, underlineXRight, y); } } diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index fa25a8f0c596a..b93697398020b 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -961,6 +961,30 @@ namespace PaintGlue { return SkScalarToFloat(metrics.fDescent); } + static jfloat getUnderlinePosition(jlong paintHandle, jlong typefaceHandle) { + Paint::FontMetrics metrics; + getMetricsInternal(paintHandle, typefaceHandle, &metrics); + SkScalar position; + if (metrics.hasUnderlinePosition(&position)) { + return SkScalarToFloat(position); + } else { + const SkScalar textSize = reinterpret_cast(paintHandle)->getTextSize(); + return SkScalarToFloat(Paint::kStdUnderline_Top * textSize); + } + } + + static jfloat getUnderlineThickness(jlong paintHandle, jlong typefaceHandle) { + Paint::FontMetrics metrics; + getMetricsInternal(paintHandle, typefaceHandle, &metrics); + SkScalar thickness; + if (metrics.hasUnderlineThickness(&thickness)) { + return SkScalarToFloat(thickness); + } else { + const SkScalar textSize = reinterpret_cast(paintHandle)->getTextSize(); + return SkScalarToFloat(Paint::kStdUnderline_Thickness * textSize); + } + } + static void setShadowLayer(jlong paintHandle, jfloat radius, jfloat dx, jfloat dy, jint color) { Paint* paint = reinterpret_cast(paintHandle); @@ -1072,6 +1096,8 @@ static const JNINativeMethod methods[] = { {"nSetHyphenEdit", "(JI)V", (void*) PaintGlue::setHyphenEdit}, {"nAscent","(JJ)F", (void*) PaintGlue::ascent}, {"nDescent","(JJ)F", (void*) PaintGlue::descent}, + {"nGetUnderlinePosition","(JJ)F", (void*) PaintGlue::getUnderlinePosition}, + {"nGetUnderlineThickness","(JJ)F", (void*) PaintGlue::getUnderlineThickness}, {"nSetShadowLayer", "(JFFFI)V", (void*)PaintGlue::setShadowLayer}, {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer} }; diff --git a/core/tests/coretests/assets/fonts/underlineTestFont.ttf b/core/tests/coretests/assets/fonts/underlineTestFont.ttf new file mode 100644 index 0000000000000..a46a38e0b397b Binary files /dev/null and b/core/tests/coretests/assets/fonts/underlineTestFont.ttf differ diff --git a/core/tests/coretests/assets/fonts/underlineTestFont.ttx b/core/tests/coretests/assets/fonts/underlineTestFont.ttx new file mode 100644 index 0000000000000..d7376b06703ee --- /dev/null +++ b/core/tests/coretests/assets/fonts/underlineTestFont.ttx @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sample Font + + + Regular + + + Sample Font + + + SampleFont-Regular + + + + + + + + + + + + + + + + diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java index 5811ca0fd2660..b55bbd39a4b80 100644 --- a/core/tests/coretests/src/android/graphics/PaintTest.java +++ b/core/tests/coretests/src/android/graphics/PaintTest.java @@ -16,6 +16,8 @@ package android.graphics; +import static org.junit.Assert.assertNotEquals; + import android.graphics.Paint; import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.SmallTest; @@ -365,4 +367,31 @@ public class PaintTest extends InstrumentationTestCase { p.setWordSpacing(-2.0f); assertEquals(-2.0f, p.getWordSpacing()); } + + public void testGetUnderlinePositionAndThickness() { + final Typeface fontTypeface = Typeface.createFromAsset( + getInstrumentation().getContext().getAssets(), "fonts/underlineTestFont.ttf"); + final Paint p = new Paint(); + final int textSize = 100; + p.setTextSize(textSize); + + final float origPosition = p.getUnderlinePosition(); + final float origThickness = p.getUnderlineThickness(); + + p.setTypeface(fontTypeface); + assertNotEquals(origPosition, p.getUnderlinePosition()); + assertNotEquals(origThickness, p.getUnderlineThickness()); + + // -200 (underlinePosition in 'post' table, negative means below the baseline) + // ÷ 1000 (unitsPerEm in 'head' table) + // × 100 (text size) + // × -1 (negated, since we consider below the baseline positive) + // = 20 + assertEquals(20.0f, p.getUnderlinePosition(), 0.5f); + // 300 (underlineThickness in 'post' table) + // ÷ 1000 (unitsPerEm in 'head' table) + // × 100 (text size) + // = 30 + assertEquals(30.0f, p.getUnderlineThickness(), 0.5f); + } } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index e1eb69a27e33d..1fd56974783e1 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -822,18 +822,14 @@ public class Paint { * @hide */ public float getUnderlinePosition() { - // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h - // TODO: replace with position from post and MVAR tables (b/62353930). - return (1.0f / 9.0f) * getTextSize(); + return nGetUnderlinePosition(mNativePaint, mNativeTypeface); } /** * @hide */ public float getUnderlineThickness() { - // kStdUnderline_Thickness = 1/18, defined in SkTextFormatParams.h - // TODO: replace with thickness from post and MVAR tables (b/62353930). - return (1.0f / 18.0f) * getTextSize(); + return nGetUnderlineThickness(mNativePaint, mNativeTypeface); } /** @@ -2997,5 +2993,9 @@ public class Paint { @CriticalNative private static native float nDescent(long paintPtr, long typefacePtr); @CriticalNative + private static native float nGetUnderlinePosition(long paintPtr, long typefacePtr); + @CriticalNative + private static native float nGetUnderlineThickness(long paintPtr, long typefacePtr); + @CriticalNative private static native void nSetTextSize(long paintPtr, float textSize); } diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 679cb5021e27e..4507c5018c262 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -46,23 +46,31 @@ void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& flags = paint.getFlags(); } if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) { - // Same values used by Skia - const float kStdStrikeThru_Offset = (-6.0f / 21.0f); - const float kStdUnderline_Offset = (1.0f / 9.0f); - const float kStdUnderline_Thickness = (1.0f / 18.0f); - SkScalar left = x; - SkScalar right = x + length; - float textSize = paint.getTextSize(); - float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f); + const SkScalar left = x; + const SkScalar right = x + length; if (flags & SkPaint::kUnderlineText_ReserveFlag) { - SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth; - SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth; + Paint::FontMetrics metrics; + paint.getFontMetrics(&metrics); + SkScalar position; + if (!metrics.hasUnderlinePosition(&position)) { + position = paint.getTextSize() * Paint::kStdUnderline_Top; + } + SkScalar thickness; + if (!metrics.hasUnderlineThickness(&thickness)) { + thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness; + } + const float strokeWidth = fmax(thickness, 1.0f); + const SkScalar top = y + position; + const SkScalar bottom = top + strokeWidth; drawRect(left, top, right, bottom, paint); } if (flags & SkPaint::kStrikeThruText_ReserveFlag) { - SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth; - SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth; + const float textSize = paint.getTextSize(); + const float position = textSize * Paint::kStdStrikeThru_Offset; + const float strokeWidth = fmax(textSize * Paint::kStdUnderline_Thickness, 1.0f); + const SkScalar top = y + position - 0.5f * strokeWidth; + const SkScalar bottom = y + position + 0.5f * strokeWidth; drawRect(left, top, right, bottom, paint); } } diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index c9b5f0031a7b2..4305025272b2f 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -28,6 +28,15 @@ namespace android { class ANDROID_API Paint : public SkPaint { public: + // Default values for underlined and strikethrough text, + // as defined by Skia in SkTextFormatParams.h. + constexpr static float kStdStrikeThru_Offset = (-6.0f / 21.0f); + constexpr static float kStdUnderline_Offset = (1.0f / 9.0f); + constexpr static float kStdUnderline_Thickness = (1.0f / 18.0f); + + constexpr static float kStdUnderline_Top = + kStdUnderline_Offset - 0.5f * kStdUnderline_Thickness; + Paint(); Paint(const Paint& paint); Paint(const SkPaint& paint); // NOLINT(implicit)