SkPDF: only draw text with SkglyphRuns

Change-Id: I24e79c73a9c65a5d6a974bf52b0d0aee21be07db
Reviewed-on: https://skia-review.googlesource.com/142695
Commit-Queue: Hal Canary <halcanary@google.com>
Reviewed-by: Herb Derby <herb@google.com>
This commit is contained in:
Hal Canary 2018-07-23 10:50:49 -04:00 committed by Skia Commit-Bot
parent 5e6cd2affe
commit 98caedd213
5 changed files with 97 additions and 238 deletions

View File

@ -7,6 +7,7 @@
#include "SkClusterator.h"
#include "SkGlyphRun.h"
#include "SkTo.h"
#include "SkUtils.h"
@ -24,90 +25,18 @@ static bool is_reversed(const uint32_t* clusters, uint32_t count) {
return true;
}
SkClusterator::SkClusterator(const void* sourceText,
size_t sourceByteCount,
const SkPaint& paint,
const uint32_t* clusters,
uint32_t utf8TextByteLength,
const char* utf8Text) {
if (SkPaint::kGlyphID_TextEncoding == paint.getTextEncoding()) {
fGlyphs = reinterpret_cast<const SkGlyphID*>(sourceText);
fClusters = clusters;
fUtf8Text = utf8Text;
fGlyphCount = sourceByteCount / sizeof(SkGlyphID);
fTextByteLength = utf8TextByteLength;
if (fClusters) {
SkASSERT(fUtf8Text && fTextByteLength > 0 && fGlyphCount > 0);
fReversedChars = is_reversed(fClusters, fGlyphCount);
} else {
SkASSERT(!fUtf8Text && fTextByteLength == 0);
}
return;
}
// If Skia is given text (not glyphs), then our fallback primitive shaping will
// produce a simple 1-1 cluster mapping.
fGlyphCount = SkToU32(paint.textToGlyphs(sourceText, sourceByteCount, nullptr));
fGlyphStorage.resize(fGlyphCount);
(void)paint.textToGlyphs(sourceText, sourceByteCount, fGlyphStorage.data());
fGlyphs = fGlyphStorage.data();
fClusterStorage.resize(fGlyphCount);
fClusters = fClusterStorage.data();
switch (paint.getTextEncoding()) {
case SkPaint::kUTF8_TextEncoding:
{
fUtf8Text = reinterpret_cast<const char*>(sourceText);
fTextByteLength = SkToU32(sourceByteCount);
const char* txtPtr = fUtf8Text;
for (uint32_t i = 0; i < fGlyphCount; ++i) {
fClusterStorage[i] = SkToU32(txtPtr - fUtf8Text);
txtPtr += SkUTF8_LeadByteToCount(*(const unsigned char*)txtPtr);
SkASSERT(txtPtr <= fUtf8Text + sourceByteCount);
}
SkASSERT(txtPtr == fUtf8Text + sourceByteCount);
return;
}
case SkPaint::kUTF16_TextEncoding:
{
const uint16_t* utf16ptr = reinterpret_cast<const uint16_t*>(sourceText);
int utf16count = SkToInt(sourceByteCount / sizeof(uint16_t));
fTextByteLength = SkToU32(SkUTF16_ToUTF8(utf16ptr, utf16count));
fUtf8textStorage.resize(fTextByteLength);
fUtf8Text = fUtf8textStorage.data();
char* txtPtr = fUtf8textStorage.data();
uint32_t clusterIndex = 0;
while (utf16ptr < (const uint16_t*)sourceText + utf16count) {
fClusterStorage[clusterIndex++] = SkToU32(txtPtr - fUtf8Text);
SkUnichar uni = SkUTF16_NextUnichar(&utf16ptr);
txtPtr += SkUTF8_FromUnichar(uni, txtPtr);
}
SkASSERT(clusterIndex == fGlyphCount);
SkASSERT(txtPtr == fUtf8textStorage.data() + fTextByteLength);
SkASSERT(utf16ptr == (const uint16_t*)sourceText + utf16count);
return;
}
case SkPaint::kUTF32_TextEncoding:
{
const SkUnichar* utf32 = reinterpret_cast<const SkUnichar*>(sourceText);
uint32_t utf32count = SkToU32(sourceByteCount / sizeof(SkUnichar));
SkASSERT(fGlyphCount == utf32count);
fTextByteLength = 0;
for (uint32_t i = 0; i < utf32count; ++i) {
fTextByteLength += SkToU32(SkUTF8_FromUnichar(utf32[i]));
}
fUtf8textStorage.resize(SkToSizeT(fTextByteLength));
fUtf8Text = fUtf8textStorage.data();
char* txtPtr = fUtf8textStorage.data();
for (uint32_t i = 0; i < utf32count; ++i) {
fClusterStorage[i] = SkToU32(txtPtr - fUtf8Text);
txtPtr += SkUTF8_FromUnichar(utf32[i], txtPtr);
}
return;
}
default:
SkDEBUGFAIL("");
break;
SkClusterator::SkClusterator(const SkGlyphRun& run)
: fClusters(run.clusters().data())
, fUtf8Text(run.text().data())
, fGlyphCount(SkToU32(run.shuntGlyphsIDs().size()))
, fTextByteLength(SkToU32(run.text().size()))
{
SkASSERT(SkPaint::kGlyphID_TextEncoding == run.paint().getTextEncoding());
if (fClusters) {
SkASSERT(fUtf8Text && fTextByteLength > 0 && fGlyphCount > 0);
fReversedChars = is_reversed(fClusters, fGlyphCount);
} else {
SkASSERT(!fUtf8Text && fTextByteLength == 0);
}
}
@ -134,4 +63,3 @@ SkClusterator::Cluster SkClusterator::next() {
uint32_t clusterLen = clusterEnd - cluster;
return Cluster{fUtf8Text + cluster, clusterLen, clusterGlyphIndex, clusterGlyphCount};
}

View File

@ -8,21 +8,15 @@
#define SkClusterator_DEFINED
#include <vector>
#include <cstdint>
#include "SkTypes.h"
#include "SkPaint.h"
class SkGlyphRun;
/** Given the m-to-n glyph-to-character mapping data (as returned by
harfbuzz), iterate over the clusters. */
class SkClusterator {
public:
SkClusterator(const void* sourceText,
size_t sourceByteCount,
const SkPaint& paint,
const uint32_t* clusters,
uint32_t utf8TextByteLength,
const char* utf8Text);
const SkGlyphID* glyphs() const { return fGlyphs; }
SkClusterator(const SkGlyphRun& run);
uint32_t glyphCount() const { return fGlyphCount; }
bool reversedChars() const { return fReversedChars; }
struct Cluster {
@ -37,15 +31,10 @@ public:
&& fGlyphIndex == o.fGlyphIndex
&& fGlyphCount == o.fGlyphCount;
}
};
Cluster next();
private:
std::vector<SkGlyphID> fGlyphStorage;
std::vector<char> fUtf8textStorage;
std::vector<uint32_t> fClusterStorage;
const SkGlyphID* fGlyphs;
const uint32_t* fClusters;
const char* fUtf8Text;
uint32_t fGlyphCount;

View File

@ -1033,13 +1033,11 @@ public:
GlyphPositioner(SkDynamicMemoryWStream* content,
SkScalar textSkewX,
bool wideChars,
bool defaultPositioning,
SkPoint origin)
: fContent(content)
, fCurrentMatrixOrigin(origin)
, fTextSkewX(textSkewX)
, fWideChars(wideChars)
, fDefaultPositioning(defaultPositioning) {
, fWideChars(wideChars) {
}
~GlyphPositioner() { this->flush(); }
void flush() {
@ -1064,19 +1062,17 @@ public:
fCurrentMatrixOrigin.set(0.0f, 0.0f);
fInitialized = true;
}
if (!fDefaultPositioning) {
SkPoint position = xy - fCurrentMatrixOrigin;
if (position != SkPoint{fXAdvance, 0}) {
this->flush();
SkPDFUtils::AppendScalar(position.x() - position.y() * fTextSkewX, fContent);
fContent->writeText(" ");
SkPDFUtils::AppendScalar(-position.y(), fContent);
fContent->writeText(" Td ");
fCurrentMatrixOrigin = xy;
fXAdvance = 0;
}
fXAdvance += advanceWidth;
SkPoint position = xy - fCurrentMatrixOrigin;
if (position != SkPoint{fXAdvance, 0}) {
this->flush();
SkPDFUtils::AppendScalar(position.x() - position.y() * fTextSkewX, fContent);
fContent->writeText(" ");
SkPDFUtils::AppendScalar(-position.y(), fContent);
fContent->writeText(" Td ");
fCurrentMatrixOrigin = xy;
fXAdvance = 0;
}
fXAdvance += advanceWidth;
if (!fInText) {
fContent->writeText("<");
fInText = true;
@ -1097,7 +1093,6 @@ private:
bool fWideChars;
bool fInText = false;
bool fInitialized = false;
const bool fDefaultPositioning;
};
} // namespace
@ -1115,30 +1110,15 @@ static void update_font(SkWStream* wStream, int fontIndex, SkScalar textSize) {
wStream->writeText(" Tf\n");
}
static SkPath draw_text_as_path(const void* sourceText, size_t sourceByteCount,
const SkScalar pos[], SkTextBlob::GlyphPositioning positioning,
SkPoint offset, const SkPaint& srcPaint) {
static void draw_glyph_run_as_path(SkPDFDevice* dev, const SkGlyphRun& glyphRun, SkPoint offset) {
SkPath path;
int glyphCount;
SkAutoTMalloc<SkPoint> tmpPoints;
switch (positioning) {
case SkTextBlob::kDefault_Positioning:
srcPaint.getTextPath(sourceText, sourceByteCount, offset.x(), offset.y(), &path);
break;
case SkTextBlob::kHorizontal_Positioning:
glyphCount = srcPaint.countText(sourceText, sourceByteCount);
tmpPoints.realloc(glyphCount);
for (int i = 0; i < glyphCount; ++i) {
tmpPoints[i] = {pos[i] + offset.x(), offset.y()};
}
srcPaint.getPosTextPath(sourceText, sourceByteCount, tmpPoints.get(), &path);
break;
case SkTextBlob::kFull_Positioning:
srcPaint.getPosTextPath(sourceText, sourceByteCount, (const SkPoint*)pos, &path);
path.offset(offset.x(), offset.y());
break;
}
return path;
SkASSERT(glyphRun.paint().getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
glyphRun.paint().getPosTextPath(glyphRun.shuntGlyphsIDs().data(),
glyphRun.shuntGlyphsIDs().size() * sizeof(SkGlyphID),
glyphRun.positions().data(),
&path);
path.offset(offset.x(), offset.y());
dev->drawPath(path, glyphRun.paint(), nullptr, true);
}
static bool has_outline_glyph(SkGlyphID gid, SkGlyphCache* cache) {
@ -1205,36 +1185,25 @@ static sk_sp<SkImage> image_from_mask(const SkMask& mask) {
}
}
void SkPDFDevice::internalDrawText(
const void* sourceText, size_t sourceByteCount,
const SkScalar pos[], SkTextBlob::GlyphPositioning positioning,
SkPoint offset, const SkPaint& srcPaint, const uint32_t* clusters,
uint32_t textByteLength, const char* utf8Text) {
if (0 == sourceByteCount || !sourceText || srcPaint.getTextSize() <= 0) {
void SkPDFDevice::internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offset) {
const SkGlyphID* glyphs = glyphRun.shuntGlyphsIDs().data();
uint32_t glyphCount = SkToU32(glyphRun.shuntGlyphsIDs().size());
const SkPaint& srcPaint = glyphRun.paint();
if (!glyphCount || !glyphs || srcPaint.getTextSize() <= 0 || this->hasEmptyClip()) {
return;
}
if (this->hasEmptyClip()) {
return;
}
NOT_IMPLEMENTED(srcPaint.isVerticalText(), false);
if (srcPaint.isVerticalText()) {
// Don't pretend we support drawing vertical text. It is not
// clear to me how to switch to "vertical writing" mode in PDF.
// Currently neither Chromium or Android set this flag.
// https://bug.skia.org/5665
}
if (srcPaint.getPathEffect()
|| srcPaint.getMaskFilter()
|| SkPaint::kFill_Style != srcPaint.getStyle()) {
|| srcPaint.getMaskFilter()
|| srcPaint.isVerticalText()
|| SkPaint::kFill_Style != srcPaint.getStyle()) {
// Stroked Text doesn't work well with Type3 fonts.
SkPath path = draw_text_as_path(sourceText, sourceByteCount, pos,
positioning, offset, srcPaint);
this->drawPath(path, srcPaint, nullptr, true);
return;
return draw_glyph_run_as_path(this, glyphRun, offset);
}
SkPaint paint = calculate_text_paint(srcPaint);
remove_color_filter(&paint);
replace_srcmode_on_opaque_paint(&paint);
paint.setHinting(SkPaint::kNo_Hinting);
if (!paint.getTypeface()) {
paint.setTypeface(SkTypeface::MakeDefault());
}
@ -1243,25 +1212,14 @@ void SkPDFDevice::internalDrawText(
SkDebugf("SkPDF: SkTypeface::MakeDefault() returned nullptr.\n");
return;
}
const SkAdvancedTypefaceMetrics* metrics =
SkPDFFont::GetMetrics(typeface, fDocument->canon());
const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, fDocument->canon());
if (!metrics) {
return;
}
const std::vector<SkUnichar>& glyphToUnicode = SkPDFFont::GetUnicodeMap(
typeface, fDocument->canon());
SkClusterator clusterator(sourceText, sourceByteCount, paint,
clusters, textByteLength, utf8Text);
const SkGlyphID* glyphs = clusterator.glyphs();
uint32_t glyphCount = clusterator.glyphCount();
if (glyphCount == 0) {
return;
}
bool defaultPositioning = (positioning == SkTextBlob::kDefault_Positioning);
paint.setHinting(SkPaint::kNo_Hinting);
SkClusterator clusterator(glyphRun);
int emSize;
auto glyphCache = SkPDFFont::MakeVectorCache(typeface, &emSize);
@ -1273,23 +1231,13 @@ void SkPDFDevice::internalDrawText(
SkScalar textScaleY = textSize / emSize;
SkScalar textScaleX = advanceScale + paint.getTextSkewX() * textScaleY;
SkPaint::Align alignment = paint.getTextAlign();
float alignmentFactor = SkPaint::kLeft_Align == alignment ? 0.0f :
SkPaint::kCenter_Align == alignment ? -0.5f :
/* SkPaint::kRight_Align */ -1.0f;
if (defaultPositioning && alignment != SkPaint::kLeft_Align) {
SkScalar advance = 0;
for (uint32_t i = 0; i < glyphCount; ++i) {
advance += advanceScale * glyphCache->getGlyphIDAdvance(glyphs[i]).fAdvanceX;
}
offset.offset(alignmentFactor * advance, 0);
}
SkASSERT(paint.getTextAlign() == SkPaint::kLeft_Align);
SkRect clipStackBounds = this->cs().bounds(this->bounds());
struct PositionedGlyph {
SkPoint fPos;
SkGlyphID fGlyph;
};
SkTArray<PositionedGlyph> fMissingGlyphs;
SkTArray<PositionedGlyph> missingGlyphs;
{
ScopedContentEntry content(this, paint, true);
if (!content.entry()) {
@ -1310,7 +1258,6 @@ void SkPDFDevice::internalDrawText(
GlyphPositioner glyphPositioner(out,
paint.getTextSkewX(),
multiByteGlyphs,
defaultPositioning,
offset);
SkPDFFont* font = nullptr;
@ -1374,45 +1321,33 @@ void SkPDFDevice::internalDrawText(
}
SkASSERT(font->multiByteGlyphs() == multiByteGlyphs);
}
SkPoint xy = {0, 0};
SkScalar advance = advanceScale * glyphCache->getGlyphIDAdvance(gid).fAdvanceX;
if (!defaultPositioning) {
xy = SkTextBlob::kFull_Positioning == positioning
? SkPoint{pos[2 * index], pos[2 * index + 1]}
: SkPoint{pos[index], 0};
if (alignment != SkPaint::kLeft_Align) {
xy.offset(alignmentFactor * advance, 0);
}
// Do a glyph-by-glyph bounds-reject if positions are absolute.
SkRect glyphBounds = get_glyph_bounds_device_space(
gid, glyphCache.get(), textScaleX, textScaleY,
xy + offset, this->ctm());
if (glyphBounds.isEmpty()) {
if (!contains(clipStackBounds, {glyphBounds.x(), glyphBounds.y()})) {
continue;
}
} else {
if (!clipStackBounds.intersects(glyphBounds)) {
continue; // reject glyphs as out of bounds
}
}
if (!has_outline_glyph(gid, glyphCache.get())) {
fMissingGlyphs.push_back({xy + offset, gid});
SkPoint xy = glyphRun.positions()[index];
// Do a glyph-by-glyph bounds-reject if positions are absolute.
SkRect glyphBounds = get_glyph_bounds_device_space(
gid, glyphCache.get(), textScaleX, textScaleY,
xy + offset, this->ctm());
if (glyphBounds.isEmpty()) {
if (!contains(clipStackBounds, {glyphBounds.x(), glyphBounds.y()})) {
continue;
}
} else {
if (!has_outline_glyph(gid, glyphCache.get())) {
fMissingGlyphs.push_back({offset, gid});
if (!clipStackBounds.intersects(glyphBounds)) {
continue; // reject glyphs as out of bounds
}
offset += SkPoint{advance, 0};
}
if (!has_outline_glyph(gid, glyphCache.get())) {
missingGlyphs.push_back({xy + offset, gid});
}
font->noteGlyphUsage(gid);
SkGlyphID encodedGlyph = multiByteGlyphs ? gid : font->glyphToPDFFontEncoding(gid);
SkScalar advance = advanceScale * glyphCache->getGlyphIDAdvance(gid).fAdvanceX;
glyphPositioner.writeGlyph(xy, advance, encodedGlyph);
}
}
}
if (fMissingGlyphs.count() > 0) {
if (missingGlyphs.count() > 0) {
// Fall back on images.
SkPaint scaledGlyphCachePaint;
scaledGlyphCachePaint.setTextSize(paint.getTextSize());
@ -1422,7 +1357,7 @@ void SkPDFDevice::internalDrawText(
auto scaledGlyphCache = SkStrikeCache::FindOrCreateStrikeExclusive(scaledGlyphCachePaint);
SkTHashMap<SkPDFCanon::BitmapGlyphKey, SkPDFCanon::BitmapGlyph>* map =
&this->getCanon()->fBitmapGlyphImages;
for (PositionedGlyph positionedGlyph : fMissingGlyphs) {
for (PositionedGlyph positionedGlyph : missingGlyphs) {
SkPDFCanon::BitmapGlyphKey key = {typeface->uniqueID(),
paint.getTextSize(),
paint.getTextScaleX(),
@ -1451,20 +1386,9 @@ void SkPDFDevice::internalDrawText(
}
}
void SkPDFDevice::drawPosText(const void* text, size_t len,
const SkScalar pos[], int scalarsPerPos,
const SkPoint& offset, const SkPaint& paint) {
this->internalDrawText(text, len, pos, (SkTextBlob::GlyphPositioning)scalarsPerPos,
offset, paint, nullptr, 0, nullptr);
}
void SkPDFDevice::drawGlyphRunList(SkGlyphRunList* glyphRunList) {
for (SkGlyphRunListIterator it(glyphRunList); !it.done(); it.next()) {
SkPaint runPaint;
it.applyFontToPaint(&runPaint);
this->internalDrawText(it.glyphs(), sizeof(SkGlyphID) * it.glyphCount(),
it.pos(), it.positioning(), glyphRunList->origin(), runPaint,
it.clusters(), it.textSize(), it.text());
for (const SkGlyphRun& glyphRun : *glyphRunList) {
this->internalDrawGlyphRun(glyphRun, glyphRunList->origin());
}
}

View File

@ -97,7 +97,7 @@ public:
SkCanvas::SrcRectConstraint) override;
void drawPosText(const void* text, size_t len,
const SkScalar pos[], int scalarsPerPos,
const SkPoint& offset, const SkPaint&) override;
const SkPoint& offset, const SkPaint&) override { SkASSERT(false); }
void drawGlyphRunList(SkGlyphRunList* glyphRunList) override;
void drawVertices(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
const SkPaint&) override;
@ -242,9 +242,7 @@ private:
int getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID);
void internalDrawText( const void*, size_t, const SkScalar pos[],
SkTextBlob::GlyphPositioning, SkPoint, const SkPaint&,
const uint32_t*, uint32_t, const char*);
void internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offset);
void internalDrawPaint(const SkPaint& paint, ContentEntry* contentEntry);

View File

@ -16,6 +16,7 @@
#include "SkData.h"
#include "SkDeflate.h"
#include "SkDocument.h"
#include "SkGlyphRun.h"
#include "SkImageEncoder.h"
#include "SkImageFilterPriv.h"
#include "SkMakeUnique.h"
@ -488,14 +489,30 @@ DEF_TEST(SkPDF_Primitives_Color, reporter) {
}
}
static SkGlyphRun make_run(size_t len, const SkGlyphID* glyphs, const SkPoint* pos,
SkPaint paint, const uint32_t* clusters,
size_t utf8TextByteLength, const char* utf8Text) {
return SkGlyphRun(std::move(paint),
SkSpan<const uint16_t>{}, // No dense indices for now.
SkSpan<const SkPoint>{pos, len},
SkSpan<const SkGlyphID>{glyphs, len},
SkSpan<const SkGlyphID>{},
SkSpan<const char>{utf8Text, utf8TextByteLength},
SkSpan<const uint32_t>{clusters, len});
}
DEF_TEST(SkPDF_Clusterator, reporter) {
SkPaint paint;
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
{
const uint32_t clusters[11] = { 3, 2, 2, 1, 0, 4, 4, 7, 6, 6, 5 };
const SkGlyphID glyphs[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
constexpr unsigned len = 11;
const uint32_t clusters[len] = { 3, 2, 2, 1, 0, 4, 4, 7, 6, 6, 5 };
const SkGlyphID glyphs[len] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
const SkPoint pos[len] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},
{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}};
const char text[] = "abcdefgh";
SkClusterator clusterator(glyphs, sizeof(glyphs), paint, clusters, strlen(text), text);
SkGlyphRun run = make_run(len, glyphs, pos, paint, clusters, strlen(text), text);
SkClusterator clusterator(run);
SkClusterator::Cluster expectations[] = {
{&text[3], 1, 0, 1},
{&text[2], 1, 1, 2},
@ -512,10 +529,13 @@ DEF_TEST(SkPDF_Clusterator, reporter) {
}
}
{
const uint32_t clusters[5] = { 0, 1, 4, 5, 6 };
const SkGlyphID glyphs[5] = { 43, 167, 79, 79, 82, };
constexpr unsigned len = 5;
const uint32_t clusters[len] = { 0, 1, 4, 5, 6 };
const SkGlyphID glyphs[len] = { 43, 167, 79, 79, 82, };
const SkPoint pos[len] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}};
const char text[] = "Ha\xCC\x8A" "llo";
SkClusterator clusterator(glyphs, sizeof(glyphs), paint, clusters, strlen(text), text);
SkGlyphRun run = make_run(len, glyphs, pos, paint, clusters, strlen(text), text);
SkClusterator clusterator(run);
SkClusterator::Cluster expectations[] = {
{&text[0], 1, 0, 1},
{&text[1], 3, 1, 1},