From 6059dc32fe36358175cb81541c91e74a2a7e771a Mon Sep 17 00:00:00 2001 From: halcanary Date: Mon, 15 Aug 2016 11:45:36 -0700 Subject: [PATCH] SkPDF: unify drawText and drawPosText Motivation: a later CL will add drawTextBlob() (after https://crrev.com/2084533004 lands). This CL is designed with that change in mind. Also fewer redundant lines of code. BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2241683005 Review-Url: https://codereview.chromium.org/2241683005 --- src/pdf/SkPDFDevice.cpp | 394 +++++++++++++++------------------------- src/pdf/SkPDFDevice.h | 9 +- src/pdf/SkPDFFont.cpp | 16 +- src/pdf/SkPDFFont.h | 7 +- 4 files changed, 176 insertions(+), 250 deletions(-) diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp index f3ede3524d..fb7bed1805 100644 --- a/src/pdf/SkPDFDevice.cpp +++ b/src/pdf/SkPDFDevice.cpp @@ -76,77 +76,6 @@ static SkPaint calculate_text_paint(const SkPaint& paint) { return result; } -// Stolen from measure_text in SkDraw.cpp and then tweaked. -static void align_text(const SkPaint& paint, - const uint16_t* glyphs, size_t len, - SkScalar* x, SkScalar* y) { - if (paint.getTextAlign() == SkPaint::kLeft_Align) { - return; - } - SkScalar advance = paint.measureText(glyphs, len * sizeof(uint16_t)); - if (paint.getTextAlign() == SkPaint::kCenter_Align) { - advance *= 0.5f; - } - if (paint.isVerticalText()) { - *y -= advance; - } else { - *x -= advance; - } -} - -static int max_glyphid_for_typeface(SkTypeface* typeface) { - SkAutoResolveDefaultTypeface autoResolve(typeface); - typeface = autoResolve.get(); - return typeface->countGlyphs() - 1; -} - -typedef SkAutoSTMalloc<128, uint16_t> SkGlyphStorage; - -static int force_glyph_encoding(const SkPaint& paint, const void* text, - size_t len, SkGlyphStorage* storage, - const uint16_t** glyphIDs) { - // Make sure we have a glyph id encoding. - if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) { - int numGlyphs = paint.textToGlyphs(text, len, nullptr); - storage->reset(numGlyphs); - paint.textToGlyphs(text, len, storage->get()); - *glyphIDs = storage->get(); - return numGlyphs; - } - - // For user supplied glyph ids we need to validate them. - SkASSERT((len & 1) == 0); - int numGlyphs = SkToInt(len / 2); - const uint16_t* input = static_cast(text); - - int maxGlyphID = max_glyphid_for_typeface(paint.getTypeface()); - int validated; - for (validated = 0; validated < numGlyphs; ++validated) { - if (input[validated] > maxGlyphID) { - break; - } - } - if (validated >= numGlyphs) { - *glyphIDs = static_cast(text); - return numGlyphs; - } - - // Silently drop anything out of range. - storage->reset(numGlyphs); - if (validated > 0) { - memcpy(storage->get(), input, validated * sizeof(uint16_t)); - } - - for (int i = validated; i < numGlyphs; ++i) { - storage->get()[i] = input[i]; - if (input[i] > maxGlyphID) { - storage->get()[i] = 0; - } - } - *glyphIDs = storage->get(); - return numGlyphs; -} - static void set_text_transform(SkScalar x, SkScalar y, SkScalar textSkewX, SkWStream* content) { // Flip the text about the x-axis to account for origin swap and include @@ -1027,50 +956,22 @@ void SkPDFDevice::drawImageRect(const SkDraw& draw, SkASSERT(false); } -// Create a PDF string. Maximum length (in bytes) is 65,535. -// @param input A string value. -// @param len The length of the input array. -// @param wideChars True iff the upper byte in each uint16_t is -// significant and should be encoded and not -// discarded. If true, the upper byte is encoded -// first. Otherwise, we assert the upper byte is -// zero. -static void write_wide_string(SkDynamicMemoryWStream* wStream, - const uint16_t* input, - size_t len, - bool wideChars) { - if (wideChars) { - SkASSERT(2 * len < 65535); - wStream->writeText("<"); - for (size_t i = 0; i < len; i++) { - SkPDFUtils::WriteUInt16BE(wStream, input[i]); - } - wStream->writeText(">"); - } else { - SkASSERT(len <= 65535); - SkAutoMalloc buffer(len); // Remove every other byte. - uint8_t* ptr = (uint8_t*)buffer.get(); - for (size_t i = 0; i < len; i++) { - SkASSERT(0 == input[i] >> 8); - ptr[i] = static_cast(input[i]); - } - SkPDFUtils::WriteString(wStream, (char*)buffer.get(), len); - } -} - namespace { class GlyphPositioner { public: GlyphPositioner(SkDynamicMemoryWStream* content, SkScalar textSkewX, - bool wideChars) + bool wideChars, + bool defaultPositioning, + SkPoint origin) : fContent(content) , fCurrentMatrixX(0.0f) , fCurrentMatrixY(0.0f) , fXAdvance(0.0f) , fWideChars(wideChars) - , fInText(false) { - set_text_transform(0.0f, 0.0f, textSkewX, fContent); + , fInText(false) + , fDefaultPositioning(defaultPositioning) { + set_text_transform(origin.x(), origin.y(), textSkewX, fContent); } ~GlyphPositioner() { SkASSERT(!fInText); /* flush first */ } void flush() { @@ -1082,6 +983,7 @@ public: void setWideChars(bool wideChars) { if (fWideChars != wideChars) { SkASSERT(!fInText); + SkASSERT(fWideChars == wideChars); fWideChars = wideChars; } } @@ -1089,17 +991,20 @@ public: SkScalar y, SkScalar advanceWidth, uint16_t glyph) { - SkScalar xPosition = x - fCurrentMatrixX; - SkScalar yPosition = y - fCurrentMatrixY; - if (xPosition != fXAdvance || yPosition != 0) { - this->flush(); - SkPDFUtils::AppendScalar(xPosition, fContent); - fContent->writeText(" "); - SkPDFUtils::AppendScalar(-yPosition, fContent); - fContent->writeText(" Td "); - fCurrentMatrixX = x; - fCurrentMatrixY = y; - fXAdvance = 0; + if (!fDefaultPositioning) { + SkScalar xPosition = x - fCurrentMatrixX; + SkScalar yPosition = y - fCurrentMatrixY; + if (xPosition != fXAdvance || yPosition != 0) { + this->flush(); + SkPDFUtils::AppendScalar(xPosition, fContent); + fContent->writeText(" "); + SkPDFUtils::AppendScalar(-yPosition, fContent); + fContent->writeText(" Td "); + fCurrentMatrixX = x; + fCurrentMatrixY = y; + fXAdvance = 0; + } + fXAdvance += advanceWidth; } if (!fInText) { fContent->writeText("<"); @@ -1111,7 +1016,6 @@ public: SkASSERT(0 == glyph >> 8); SkPDFUtils::WriteUInt8(fContent, static_cast(glyph)); } - fXAdvance += advanceWidth; } private: @@ -1121,6 +1025,7 @@ private: SkScalar fXAdvance; bool fWideChars; bool fInText; + const bool fDefaultPositioning; }; } // namespace @@ -1147,6 +1052,8 @@ static void draw_transparent_text(SkPDFDevice* device, srcPaint.glyphsToUnichars( (const uint16_t*)text, SkToInt(glyphCount), &unichars[0]); transparent.setTextEncoding(SkPaint::kUTF32_TextEncoding); + // TODO(halcanary): deal with case where default typeface + // does not have glyphs for these unicode code points. device->drawText(d, &unichars[0], glyphCount * sizeof(SkUnichar), x, y, transparent); @@ -1163,158 +1070,153 @@ static void draw_transparent_text(SkPDFDevice* device, } } - -void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len, - SkScalar x, SkScalar y, const SkPaint& srcPaint) { - if (!SkPDFFont::CanEmbedTypeface(srcPaint.getTypeface(), fDocument->canon())) { - // https://bug.skia.org/3866 - SkPath path; - srcPaint.getTextPath(text, len, x, y, &path); +void SkPDFDevice::internalDrawText( + const SkDraw& d, const void* sourceText, size_t sourceByteCount, + const SkScalar pos[], SkTextBlob::GlyphPositioning positioning, + SkPoint offset, const SkPaint& srcPaint) { + NOT_IMPLEMENTED(srcPaint.getMaskFilter() != nullptr, false); + if (srcPaint.getMaskFilter() != nullptr) { + // Don't pretend we support drawing MaskFilters, it makes for artifacts + // making text unreadable (e.g. same text twice when using CSS shadows). + return; + } + SkPaint paint = calculate_text_paint(srcPaint); + replace_srcmode_on_opaque_paint(&paint); + if (!paint.getTypeface()) { + paint.setTypeface(SkTypeface::MakeDefault()); + } + SkTypeface* typeface = paint.getTypeface(); + SkASSERT(typeface); + if (!SkPDFFont::CanEmbedTypeface(typeface, fDocument->canon())) { + SkPath path; // https://bug.skia.org/3866 + paint.getTextPath(sourceText, sourceByteCount, + offset.x(), offset.y(), &path); this->drawPath(d, path, srcPaint, &SkMatrix::I(), true); // Draw text transparently to make it copyable/searchable/accessable. - draw_transparent_text(this, d, text, len, x, y, srcPaint); + draw_transparent_text(this, d, sourceText, sourceByteCount, + offset.x(), offset.y(), paint); return; } - SkPaint paint = srcPaint; - replace_srcmode_on_opaque_paint(&paint); + // Always make a copy (1) to validate user-input glyphs and + // (2) because we may modify the glyphs in place (for + // single-byte-glyph-id PDF fonts). + int glyphCount = paint.textToGlyphs(sourceText, sourceByteCount, nullptr); + if (glyphCount <= 0) { return; } + SkAutoSTMalloc<128, SkGlyphID> glyphStorage(SkToSizeT(glyphCount)); + SkGlyphID* glyphs = glyphStorage.get(); + (void)paint.textToGlyphs(sourceText, sourceByteCount, glyphs); + if (paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding) { + // Validate user-input glyphs. + SkGlyphID maxGlyphID = SkToU16(typeface->countGlyphs() - 1); + for (int i = 0; i < glyphCount; ++i) { + glyphs[i] = SkTMin(maxGlyphID, glyphs[i]); + } + } else { + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + } - NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false); - if (paint.getMaskFilter() != nullptr) { - // Don't pretend we support drawing MaskFilters, it makes for artifacts - // making text unreadable (e.g. same text twice when using CSS shadows). - return; + bool defaultPositioning = (positioning == SkTextBlob::kDefault_Positioning); + SkAutoGlyphCache glyphCache(paint, nullptr, nullptr); + + SkPaint::Align alignment = paint.getTextAlign(); + bool verticalText = paint.isVerticalText(); + if (defaultPositioning && alignment != SkPaint::kLeft_Align) { + SkScalar advance{0}; + for (int i = 0; i < glyphCount; ++i) { + advance += glyphCache->getGlyphIDAdvance(glyphs[i]).fAdvanceX; + } + SkScalar m = alignment == SkPaint::kCenter_Align + ? 0.5f * advance : advance; + offset -= verticalText ? SkPoint{0, m} : SkPoint{m, 0}; } - SkPaint textPaint = calculate_text_paint(paint); - ScopedContentEntry content(this, d, textPaint, true); + ScopedContentEntry content(this, d, paint, true); if (!content.entry()) { return; } - - SkGlyphStorage storage(0); - const uint16_t* glyphIDs = nullptr; - int numGlyphs = force_glyph_encoding(paint, text, len, &storage, &glyphIDs); - textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - - align_text(textPaint, glyphIDs, numGlyphs, &x, &y); - content.entry()->fContent.writeText("BT\n"); - set_text_transform(x, y, textPaint.getTextSkewX(), - &content.entry()->fContent); - int consumedGlyphCount = 0; - - SkTDArray glyphIDsCopy(glyphIDs, numGlyphs); - - SkPDFGlyphSetMap* fontGlyphUsage = fDocument->getGlyphUsage(); - - while (numGlyphs > consumedGlyphCount) { - if (!this->updateFont(textPaint, glyphIDs[consumedGlyphCount], content.entry())) { - SkDebugf("SkPDF: Font error."); - content.entry()->fContent.writeText("ET\n%SkPDF: Font error.\n"); - return; - } - SkPDFFont* font = content.entry()->fState.fFont; - - int availableGlyphs = font->glyphsToPDFFontEncoding( - glyphIDsCopy.begin() + consumedGlyphCount, - numGlyphs - consumedGlyphCount); - fontGlyphUsage->noteGlyphUsage( - font, glyphIDsCopy.begin() + consumedGlyphCount, - availableGlyphs); - write_wide_string(&content.entry()->fContent, - glyphIDsCopy.begin() + consumedGlyphCount, - availableGlyphs, font->multiByteGlyphs()); - consumedGlyphCount += availableGlyphs; - content.entry()->fContent.writeText(" Tj\n"); - } - content.entry()->fContent.writeText("ET\n"); -} - -void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len, - const SkScalar pos[], int scalarsPerPos, - const SkPoint& offset, const SkPaint& srcPaint) { - if (!SkPDFFont::CanEmbedTypeface(srcPaint.getTypeface(), fDocument->canon())) { - const SkPoint* positions = reinterpret_cast(pos); - SkAutoTMalloc positionsBuffer; - if (2 != scalarsPerPos) { - int glyphCount = srcPaint.textToGlyphs(text, len, NULL); - positionsBuffer.reset(glyphCount); - for (int i = 0; i < glyphCount; ++i) { - positionsBuffer[i].set(pos[i], 0.0f); - } - positions = &positionsBuffer[0]; - } - SkPath path; - srcPaint.getPosTextPath(text, len, positions, &path); - SkMatrix matrix; - matrix.setTranslate(offset); - this->drawPath(d, path, srcPaint, &matrix, true); - // Draw text transparently to make it copyable/searchable/accessable. - draw_transparent_text( - this, d, text, len, offset.x() + positions[0].x(), - offset.y() + positions[0].y(), srcPaint); - return; - } - - SkPaint paint = srcPaint; - replace_srcmode_on_opaque_paint(&paint); - - NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false); - if (paint.getMaskFilter() != nullptr) { - // Don't pretend we support drawing MaskFilters, it makes for artifacts - // making text unreadable (e.g. same text twice when using CSS shadows). - return; - } - SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos); - SkPaint textPaint = calculate_text_paint(paint); - ScopedContentEntry content(this, d, textPaint, true); - if (!content.entry()) { - return; - } - - SkGlyphStorage storage(0); - const uint16_t* glyphIDs = nullptr; - size_t numGlyphs = force_glyph_encoding(paint, text, len, &storage, &glyphIDs); - textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - SkAutoGlyphCache autoGlyphCache(textPaint, nullptr, nullptr); - - content.entry()->fContent.writeText("BT\n"); - if (!this->updateFont(textPaint, glyphIDs[0], content.entry())) { + SkDynamicMemoryWStream* out = &content.entry()->fContent; + out->writeText("BT\n"); + if (!this->updateFont(paint, glyphs[0], content.entry())) { SkDebugf("SkPDF: Font error."); - content.entry()->fContent.writeText("ET\n%SkPDF: Font error.\n"); + out->writeText("ET\n%SkPDF: Font error.\n"); return; } - GlyphPositioner glyphPositioner(&content.entry()->fContent, - textPaint.getTextSkewX(), - content.entry()->fState.fFont->multiByteGlyphs()); + SkPDFFont* font = content.entry()->fState.fFont; + GlyphPositioner glyphPositioner(out, + paint.getTextSkewX(), + font->multiByteGlyphs(), + defaultPositioning, + offset); SkPDFGlyphSetMap* fontGlyphUsage = fDocument->getGlyphUsage(); - for (size_t i = 0; i < numGlyphs; i++) { - SkPDFFont* font = content.entry()->fState.fFont; - uint16_t encodedValue = glyphIDs[i]; - SkScalar advanceWidth = autoGlyphCache->getGlyphIDAdvance(encodedValue).fAdvanceX; - if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) { - // The current pdf font cannot encode the current glyph. - // Try to get a pdf font which can encode the current glyph. + const SkGlyphID* const glyphsEnd = glyphs + glyphCount; + + while (glyphs < glyphsEnd) { + font = content.entry()->fState.fFont; + int stretch = font->multiByteGlyphs() + ? SkToInt(glyphsEnd - glyphs) + : font->glyphsToPDFFontEncodingCount(glyphs, SkToInt(glyphsEnd - glyphs)); + SkASSERT(glyphs + stretch <= glyphsEnd); + if (stretch < 1) { + SkASSERT(!font->multiByteGlyphs()); + // The current pdf font cannot encode the next glyph. + // Try to get a pdf font which can encode the next glyph. glyphPositioner.flush(); - if (!this->updateFont(textPaint, glyphIDs[i], content.entry())) { + if (!this->updateFont(paint, *glyphs, content.entry())) { SkDebugf("SkPDF: Font error."); - content.entry()->fContent.writeText("ET\n%SkPDF: Font error.\n"); + out->writeText("ET\n%SkPDF: Font error.\n"); return; } font = content.entry()->fState.fFont; glyphPositioner.setWideChars(font->multiByteGlyphs()); - if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) { + // try again + stretch = font->glyphsToPDFFontEncodingCount(glyphs, + SkToInt(glyphsEnd - glyphs)); + if (stretch < 1) { SkDEBUGFAIL("PDF could not encode glyph."); - continue; + glyphPositioner.flush(); + out->writeText("ET\n%SkPDF: Font encoding error.\n"); + return; + } + } + fontGlyphUsage->noteGlyphUsage(font, glyphs, stretch); + if (defaultPositioning) { + (void)font->glyphsToPDFFontEncoding(glyphs, SkToInt(glyphsEnd - glyphs)); + while (stretch-- > 0) { + glyphPositioner.writeGlyph(0, 0, 0, *glyphs); + ++glyphs; + } + } else { + while (stretch-- > 0) { + SkScalar advance = glyphCache->getGlyphIDAdvance(*glyphs).fAdvanceX; + SkScalar x = *pos++; + // evaluate x and y in order! + SkScalar y = SkTextBlob::kFull_Positioning == positioning ? *pos++ : 0; + SkPoint xy{x, y}; + if (alignment != SkPaint::kLeft_Align) { + SkScalar m = alignment == SkPaint::kCenter_Align + ? 0.5f * advance : advance; + xy -= verticalText ? SkPoint{0, m} : SkPoint{m, 0}; + } + (void)font->glyphsToPDFFontEncoding(glyphs, 1); + glyphPositioner.writeGlyph(xy.x(), xy.y(), advance, *glyphs); + ++glyphs; } } - fontGlyphUsage->noteGlyphUsage(font, &encodedValue, 1); - SkScalar x = offset.x() + pos[i * scalarsPerPos]; - SkScalar y = offset.y() + (2 == scalarsPerPos ? pos[i * scalarsPerPos + 1] : 0); - align_text(textPaint, glyphIDs + i, 1, &x, &y); - - glyphPositioner.writeGlyph(x, y, advanceWidth, encodedValue); } - glyphPositioner.flush(); // Must flush before ending text object. - content.entry()->fContent.writeText("ET\n"); + glyphPositioner.flush(); + out->writeText("ET\n"); +} + +void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len, + SkScalar x, SkScalar y, const SkPaint& paint) { + this->internalDrawText(d, text, len, nullptr, SkTextBlob::kDefault_Positioning, + SkPoint{x, y}, paint); +} + +void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len, + const SkScalar pos[], int scalarsPerPos, + const SkPoint& offset, const SkPaint& paint) { + this->internalDrawText(d, text, len, pos, (SkTextBlob::GlyphPositioning)scalarsPerPos, + offset, paint); } void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode, diff --git a/src/pdf/SkPDFDevice.h b/src/pdf/SkPDFDevice.h index f38dfcbc66..364cc61688 100644 --- a/src/pdf/SkPDFDevice.h +++ b/src/pdf/SkPDFDevice.h @@ -16,10 +16,10 @@ #include "SkPaint.h" #include "SkRect.h" #include "SkRefCnt.h" +#include "SkSinglyLinkedList.h" #include "SkStream.h" #include "SkTDArray.h" - -#include "SkSinglyLinkedList.h" +#include "SkTextBlob.h" class SkImageBitmap; class SkPath; @@ -295,6 +295,11 @@ private: bool updateFont(const SkPaint& paint, uint16_t glyphID, ContentEntry* contentEntry); int getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID); + + void internalDrawText(const SkDraw&, const void*, size_t, const SkScalar pos[], + SkTextBlob::GlyphPositioning, SkPoint, const SkPaint&); + + void internalDrawPaint(const SkPaint& paint, ContentEntry* contentEntry); void internalDrawImage(const SkMatrix& origMatrix, diff --git a/src/pdf/SkPDFFont.cpp b/src/pdf/SkPDFFont.cpp index 769771ab23..05dbdc7e55 100644 --- a/src/pdf/SkPDFFont.cpp +++ b/src/pdf/SkPDFFont.cpp @@ -498,7 +498,7 @@ bool SkPDFFont::hasGlyph(uint16_t id) { return (id >= fFirstGlyphID && id <= fLastGlyphID) || id == 0; } -int SkPDFFont::glyphsToPDFFontEncoding(uint16_t* glyphIDs, int numGlyphs) { +int SkPDFFont::glyphsToPDFFontEncoding(SkGlyphID* glyphIDs, int numGlyphs) const { // A font with multibyte glyphs will support all glyph IDs in a single font. if (this->multiByteGlyphs()) { return numGlyphs; @@ -517,6 +517,20 @@ int SkPDFFont::glyphsToPDFFontEncoding(uint16_t* glyphIDs, int numGlyphs) { return numGlyphs; } +int SkPDFFont::glyphsToPDFFontEncodingCount(const SkGlyphID* glyphIDs, + int numGlyphs) const { + if (this->multiByteGlyphs()) { // A font with multibyte glyphs will + return numGlyphs; // support all glyph IDs in a single font. + } + for (int i = 0; i < numGlyphs; i++) { + if (glyphIDs[i] != 0 && + (glyphIDs[i] < fFirstGlyphID || glyphIDs[i] > fLastGlyphID)) { + return i; + } + } + return numGlyphs; +} + // static SkPDFFont* SkPDFFont::GetFontResource(SkPDFCanon* canon, SkTypeface* typeface, diff --git a/src/pdf/SkPDFFont.h b/src/pdf/SkPDFFont.h index acf9bf87ee..c2e34e44e4 100644 --- a/src/pdf/SkPDFFont.h +++ b/src/pdf/SkPDFFont.h @@ -106,7 +106,12 @@ public: * @param numGlyphs The number of input glyphs. * @return Returns the number of glyphs consumed. */ - int glyphsToPDFFontEncoding(uint16_t* glyphIDs, int numGlyphs); + int glyphsToPDFFontEncoding(SkGlyphID* glyphIDs, int numGlyphs) const; + /** + * Like above, but does not modify glyphIDs array. + */ + int glyphsToPDFFontEncodingCount(const SkGlyphID* glyphIDs, + int numGlyphs) const; /** Get the font resource for the passed typeface and glyphID. The * reference count of the object is incremented and it is the caller's