Skip to content

Commit 2301d32

Browse files
committed
Software-only implementation of glyph positioning (bug 5443796)
This patch implements glyph positioning in the Skia-based renderer. Note that it depends on a fix for bug 6833339 being in place (correct calculation of advance widths under skew and scale transforms), otherwise there will be regressions. Careful attention was paid to correct results in a wide variety of conditions: alignments, text decorations, scale, skew, etc. Many of these are exercised in the test app attached to bug 6833339. Note that this patch also changes slightly the way that the total advance is calculated - the running is accumulated and passed through to computeRunValues(), so that the x positions of each glyph can be set according to the total advance of all glyphs (in all runs) appearing before (plus, of course, the offset for mark positioning). After committing this patch, text rendering will no longer match between the software and hardware rendering cases. Implementing positioning in the hardware renderer will resolve that, and fully implement bug 5443796. Change-Id: Ie0f7835d48bc120475a19afbfe159aa5304fcaa8
1 parent 1deb589 commit 2301d32

File tree

3 files changed

+111
-23
lines changed

3 files changed

+111
-23
lines changed

core/jni/android/graphics/Canvas.cpp

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,15 +766,64 @@ class SkCanvasGlue {
766766
if (value == NULL) {
767767
return;
768768
}
769-
doDrawGlyphs(canvas, value->getGlyphs(), 0, value->getGlyphsCount(), x, y, flags, paint);
769+
SkPaint::Align align = paint->getTextAlign();
770+
if (align == SkPaint::kCenter_Align) {
771+
x -= 0.5 * value->getTotalAdvance();
772+
} else if (align == SkPaint::kRight_Align) {
773+
x -= value->getTotalAdvance();
774+
}
775+
paint->setTextAlign(SkPaint::kLeft_Align);
776+
doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(), x, y, flags, paint);
777+
doDrawTextDecorations(canvas, x, y, value->getTotalAdvance(), paint);
778+
paint->setTextAlign(align);
779+
}
780+
781+
// Same values used by Skia
782+
#define kStdStrikeThru_Offset (-6.0f / 21.0f)
783+
#define kStdUnderline_Offset (1.0f / 9.0f)
784+
#define kStdUnderline_Thickness (1.0f / 18.0f)
785+
786+
static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat length, SkPaint* paint) {
787+
uint32_t flags = paint->getFlags();
788+
if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
789+
SkScalar left = SkFloatToScalar(x);
790+
SkScalar right = SkFloatToScalar(x + length);
791+
float textSize = paint->getTextSize();
792+
float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
793+
if (flags & SkPaint::kUnderlineText_Flag) {
794+
SkScalar top = SkFloatToScalar(y + textSize * kStdUnderline_Offset
795+
- 0.5f * strokeWidth);
796+
SkScalar bottom = SkFloatToScalar(y + textSize * kStdUnderline_Offset
797+
+ 0.5f * strokeWidth);
798+
canvas->drawRectCoords(left, top, right, bottom, *paint);
799+
}
800+
if (flags & SkPaint::kStrikeThruText_Flag) {
801+
SkScalar top = SkFloatToScalar(y + textSize * kStdStrikeThru_Offset
802+
- 0.5f * strokeWidth);
803+
SkScalar bottom = SkFloatToScalar(y + textSize * kStdStrikeThru_Offset
804+
+ 0.5f * strokeWidth);
805+
canvas->drawRectCoords(left, top, right, bottom, *paint);
806+
}
770807
}
808+
}
771809

772810
static void doDrawGlyphs(SkCanvas* canvas, const jchar* glyphArray, int index, int count,
773811
jfloat x, jfloat y, int flags, SkPaint* paint) {
774812
// Beware: this needs Glyph encoding (already done on the Paint constructor)
775813
canvas->drawText(glyphArray + index * 2, count * 2, x, y, *paint);
776814
}
777815

816+
static void doDrawGlyphsPos(SkCanvas* canvas, const jchar* glyphArray, const jfloat* posArray,
817+
int index, int count, jfloat x, jfloat y, int flags, SkPaint* paint) {
818+
SkPoint* posPtr = new SkPoint[count];
819+
for (int indx = 0; indx < count; indx++) {
820+
posPtr[indx].fX = SkFloatToScalar(x + posArray[indx * 2]);
821+
posPtr[indx].fY = SkFloatToScalar(y + posArray[indx * 2 + 1]);
822+
}
823+
canvas->drawPosText(glyphArray, count << 1, posPtr, *paint);
824+
delete[] posPtr;
825+
}
826+
778827
static void drawTextRun___CIIIIFFIPaint(
779828
JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index,
780829
int count, int contextIndex, int contextCount,

core/jni/android/graphics/TextLayoutCache.cpp

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -308,11 +308,12 @@ TextLayoutValue::TextLayoutValue(size_t contextCount) :
308308
// Give a hint for advances and glyphs vectors size
309309
mAdvances.setCapacity(contextCount);
310310
mGlyphs.setCapacity(contextCount);
311+
mPos.setCapacity(contextCount * 2);
311312
}
312313

313314
size_t TextLayoutValue::getSize() const {
314315
return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
315-
sizeof(jchar) * mGlyphs.capacity();
316+
sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity();
316317
}
317318

318319
void TextLayoutValue::setElapsedTime(uint32_t time) {
@@ -329,13 +330,9 @@ TextLayoutShaper::TextLayoutShaper() : mShaperItemGlyphArraySize(0) {
329330
mFontRec.klass = &harfbuzzSkiaClass;
330331
mFontRec.userData = 0;
331332

332-
// The values which harfbuzzSkiaClass returns are already scaled to
333-
// pixel units, so we just set all these to one to disable further
334-
// scaling.
335-
mFontRec.x_ppem = 1;
336-
mFontRec.y_ppem = 1;
337-
mFontRec.x_scale = 1;
338-
mFontRec.y_scale = 1;
333+
// Note that the scaling values (x_ and y_ppem, x_ and y_scale) will be set
334+
// below, when the paint transform and em unit of the actual shaping font
335+
// are known.
339336

340337
memset(&mShaperItem, 0, sizeof(mShaperItem));
341338

@@ -360,7 +357,7 @@ void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* pain
360357
size_t start, size_t count, size_t contextCount, int dirFlags) {
361358

362359
computeValues(paint, chars, start, count, contextCount, dirFlags,
363-
&value->mAdvances, &value->mTotalAdvance, &value->mGlyphs);
360+
&value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos);
364361
#if DEBUG_ADVANCES
365362
ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
366363
contextCount, value->mTotalAdvance);
@@ -370,9 +367,9 @@ void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* pain
370367
void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
371368
size_t start, size_t count, size_t contextCount, int dirFlags,
372369
Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
373-
Vector<jchar>* const outGlyphs) {
370+
Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
371+
*outTotalAdvance = 0;
374372
if (!count) {
375-
*outTotalAdvance = 0;
376373
return;
377374
}
378375

@@ -437,6 +434,7 @@ void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
437434
ALOGW("Visual run is not valid");
438435
outGlyphs->clear();
439436
outAdvances->clear();
437+
outPos->clear();
440438
*outTotalAdvance = 0;
441439
isRTL = (paraDir == 1);
442440
useSingleRun = true;
@@ -459,15 +457,13 @@ void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
459457

460458
lengthRun = endRun - startRun;
461459
isRTL = (runDir == UBIDI_RTL);
462-
jfloat runTotalAdvance = 0;
463460
#if DEBUG_GLYPHS
464461
ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
465462
i, startRun, lengthRun, isRTL);
466463
#endif
467464
computeRunValues(paint, chars + startRun, lengthRun, isRTL,
468-
outAdvances, &runTotalAdvance, outGlyphs);
465+
outAdvances, outTotalAdvance, outGlyphs, outPos);
469466

470-
*outTotalAdvance += runTotalAdvance;
471467
}
472468
}
473469
} else {
@@ -490,7 +486,7 @@ void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
490486
"-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
491487
#endif
492488
computeRunValues(paint, chars + start, count, isRTL,
493-
outAdvances, outTotalAdvance, outGlyphs);
489+
outAdvances, outTotalAdvance, outGlyphs, outPos);
494490
}
495491

496492
#if DEBUG_GLYPHS
@@ -512,10 +508,9 @@ static void logGlyphs(HB_ShaperItem shaperItem) {
512508
void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars,
513509
size_t count, bool isRTL,
514510
Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
515-
Vector<jchar>* const outGlyphs) {
511+
Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
516512
if (!count) {
517513
// We cannot shape an empty run.
518-
*outTotalAdvance = 0;
519514
return;
520515
}
521516

@@ -615,7 +610,8 @@ void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars
615610

616611
// Define shaping paint properties
617612
mShapingPaint.setTextSize(paint->getTextSize());
618-
mShapingPaint.setTextSkewX(paint->getTextSkewX());
613+
float skewX = paint->getTextSkewX();
614+
mShapingPaint.setTextSkewX(skewX);
619615
mShapingPaint.setTextScaleX(paint->getTextScaleX());
620616
mShapingPaint.setFlags(paint->getFlags());
621617
mShapingPaint.setHinting(paint->getHinting());
@@ -624,7 +620,7 @@ void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars
624620
// into the shaperItem
625621
ssize_t indexFontRun = isRTL ? mShaperItem.stringLength - 1 : 0;
626622
unsigned numCodePoints = 0;
627-
jfloat totalAdvance = 0;
623+
jfloat totalAdvance = *outTotalAdvance;
628624
while ((isRTL) ?
629625
hb_utf16_script_run_prev(&numCodePoints, &mShaperItem.item, mShaperItem.string,
630626
mShaperItem.stringLength, &indexFontRun):
@@ -692,7 +688,6 @@ void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars
692688
currentAdvance = HBFixedToFloat(mShaperItem.advances[i]);
693689
totalFontRunAdvance += currentAdvance;
694690
}
695-
totalAdvance += totalFontRunAdvance;
696691

697692
#if DEBUG_ADVANCES
698693
ALOGD("Returned advances");
@@ -717,6 +712,30 @@ void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars
717712
outGlyphs->add(glyph);
718713
}
719714
}
715+
716+
// Get glyph positions (and reverse them in place if RTL)
717+
if (outPos) {
718+
size_t countGlyphs = mShaperItem.num_glyphs;
719+
jfloat x = totalAdvance;
720+
for (size_t i = 0; i < countGlyphs; i++) {
721+
size_t index = (!isRTL) ? i : countGlyphs - 1 - i;
722+
float xo = HBFixedToFloat(mShaperItem.offsets[index].x);
723+
float yo = HBFixedToFloat(mShaperItem.offsets[index].y);
724+
// Apply skewX component of transform to position offsets. Note
725+
// that scale has already been applied through x_ and y_scale
726+
// set in the mFontRec.
727+
outPos->add(x + xo + yo * skewX);
728+
outPos->add(yo);
729+
#ifdef DEBUG_GLYPHS
730+
ALOGD(" -- hb adv[%d] = %f, log_cluster[%d] = %d",
731+
index, HBFixedToFloat(mShaperItem.advances[index]),
732+
index, mShaperItem.log_clusters[index]);
733+
#endif
734+
x += HBFixedToFloat(mShaperItem.advances[index]);
735+
}
736+
}
737+
738+
totalAdvance += totalFontRunAdvance;
720739
}
721740

722741
*outTotalAdvance = totalAdvance;
@@ -807,6 +826,19 @@ size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint, bool isRTL) {
807826
mShapingPaint.setTypeface(typeface);
808827
mShaperItem.face = getCachedHBFace(typeface);
809828

829+
int textSize = paint->getTextSize();
830+
float scaleX = paint->getTextScaleX();
831+
mFontRec.x_ppem = floor(scaleX * textSize + 0.5);
832+
mFontRec.y_ppem = textSize;
833+
uint32_t unitsPerEm = SkFontHost::GetUnitsPerEm(typeface->uniqueID());
834+
// x_ and y_scale are the conversion factors from font design space
835+
// (unitsPerEm) to 1/64th of device pixels in 16.16 format.
836+
const int kDevicePixelFraction = 64;
837+
const int kMultiplyFor16Dot16 = 1 << 16;
838+
float emScale = kDevicePixelFraction * kMultiplyFor16Dot16 / (float)unitsPerEm;
839+
mFontRec.x_scale = emScale * scaleX * textSize;
840+
mFontRec.y_scale = emScale * textSize;
841+
810842
#if DEBUG_GLYPHS
811843
ALOGD("Run typeface = %p, uniqueID = %d, hb_face = %p",
812844
typeface, typeface->uniqueID(), mShaperItem.face);

core/jni/android/graphics/TextLayoutCache.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ class TextLayoutValue : public RefBase {
130130
inline jfloat getTotalAdvance() const { return mTotalAdvance; }
131131
inline const jchar* getGlyphs() const { return mGlyphs.array(); }
132132
inline size_t getGlyphsCount() const { return mGlyphs.size(); }
133+
inline const jfloat* getPos() const { return mPos.array(); }
134+
inline size_t getPosCount() const { return mPos.size(); }
133135

134136
/**
135137
* Advances vector
@@ -146,6 +148,11 @@ class TextLayoutValue : public RefBase {
146148
*/
147149
Vector<jchar> mGlyphs;
148150

151+
/**
152+
* Pos vector (2 * i is x pos, 2 * i + 1 is y pos, same as drawPosText)
153+
*/
154+
Vector<jfloat> mPos;
155+
149156
/**
150157
* Get the size of the Cache entry
151158
*/
@@ -224,12 +231,12 @@ class TextLayoutShaper {
224231
void computeValues(const SkPaint* paint, const UChar* chars,
225232
size_t start, size_t count, size_t contextCount, int dirFlags,
226233
Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
227-
Vector<jchar>* const outGlyphs);
234+
Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos);
228235

229236
void computeRunValues(const SkPaint* paint, const UChar* chars,
230237
size_t count, bool isRTL,
231238
Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
232-
Vector<jchar>* const outGlyphs);
239+
Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos);
233240

234241
SkTypeface* getCachedTypeface(SkTypeface** typeface, HB_Script script, SkTypeface::Style style);
235242
HB_Face getCachedHBFace(SkTypeface* typeface);

0 commit comments

Comments
 (0)