Get underline position and thickness from the font

Previously, font underline position and thickness were fixed in Android.
Although a custom font may have specified a different value, such a value
would never be used. Now we use the values from the font if they are
provided and fall back to the old default values only if needed.

Bug: 62353930

Test: adb shell am instrument -w -e package android.graphics com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
Test: adb shell am instrument -w -e package android.text com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
Test: cts-tradefed run cts-dev --module CtsTextTestCases
Test: Manual
Change-Id: I6bf21000dd69a2780c894b231638bc0c122e41f4
This commit is contained in:
Roozbeh Pournader
2017-06-06 18:30:29 -07:00
parent eed27590ba
commit ca8a04a366
8 changed files with 256 additions and 19 deletions

View File

@@ -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);
}
}

View File

@@ -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<Paint*>(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<Paint*>(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<Paint*>(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}
};

View File

@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.9">
<GlyphOrder>
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="a"/>
</GlyphOrder>
<head>
<tableVersion value="1.0"/>
<fontRevision value="1.0"/>
<checkSumAdjustment value="0"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Fri Mar 17 07:26:00 2017"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="7"/>
<fontDirectionHint value="2"/>
<glyphDataFormat value="0"/>
</head>
<hhea>
<tableVersion value="0x00010000"/>
<ascent value="1000"/>
<descent value="-200"/>
<lineGap value="0"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
</hhea>
<maxp>
<tableVersion value="0x10000"/>
<maxZones value="0"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="0"/>
</maxp>
<OS_2>
<version value="3"/>
<xAvgCharWidth value="594"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00001000"/>
<ySubscriptXSize value="650"/>
<ySubscriptYSize value="600"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="75"/>
<ySuperscriptXSize value="650"/>
<ySuperscriptYSize value="600"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="350"/>
<yStrikeoutSize value="50"/>
<yStrikeoutPosition value="300"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="5"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="UKWN"/>
<fsSelection value="00000000 01000000"/>
<sTypoAscender value="800"/>
<sTypoDescender value="-200"/>
<sTypoLineGap value="200"/>
<usWinAscent value="1000"/>
<usWinDescent value="200"/>
<ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
<sxHeight value="500"/>
<sCapHeight value="700"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="0"/>
</OS_2>
<hmtx>
<mtx name=".notdef" width="500" lsb="93"/>
<mtx name="a" width="500" lsb="93"/>
</hmtx>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="3" platEncID="10" language="0">
<map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
</cmap_format_4>
</cmap>
<loca>
<!-- The 'loca' table will be calculated by the compiler -->
</loca>
<glyf>
<TTGlyph name=".notdef"/><!-- contains no outline data -->
<TTGlyph name="a"/><!-- contains no outline data -->
</glyf>
<name>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
Sample Font
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
Sample Font
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
SampleFont-Regular
</namerecord>
</name>
<post>
<formatType value="3.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="-200"/>
<underlineThickness value="300"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
</post>
</ttFont>

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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)