| Index: src/pdf/SkPDFDevice.cpp
|
| diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
|
| index 9635f54367d27d01eac749d5d86988c850625896..e9fc05039b929b4165a16034f3c3654cb79b726f 100644
|
| --- a/src/pdf/SkPDFDevice.cpp
|
| +++ b/src/pdf/SkPDFDevice.cpp
|
| @@ -7,6 +7,7 @@
|
|
|
| #include "SkPDFDevice.h"
|
|
|
| +#include "SkAdvancedTypefaceMetrics.h"
|
| #include "SkAnnotationKeys.h"
|
| #include "SkBitmapDevice.h"
|
| #include "SkBitmapKey.h"
|
| @@ -37,6 +38,7 @@
|
| #include "SkTemplates.h"
|
| #include "SkTextBlobRunIterator.h"
|
| #include "SkTextFormatParams.h"
|
| +#include "SkUtils.h"
|
| #include "SkXfermodeInterpretation.h"
|
|
|
| #define DPI_FOR_RASTER_SCALE_ONE 72
|
| @@ -85,9 +87,7 @@ SkPDFDevice::GraphicStateEntry::GraphicStateEntry()
|
| , fTextScaleX(SK_Scalar1)
|
| , fTextFill(SkPaint::kFill_Style)
|
| , fShaderIndex(-1)
|
| - , fGraphicStateIndex(-1)
|
| - , fFont(nullptr)
|
| - , fTextSize(SK_ScalarNaN) {
|
| + , fGraphicStateIndex(-1) {
|
| fMatrix.reset();
|
| }
|
|
|
| @@ -861,21 +861,10 @@ public:
|
| bool defaultPositioning,
|
| SkPoint origin)
|
| : fContent(content)
|
| - , fCurrentMatrixOrigin{0.0f, 0.0f}
|
| - , fXAdvance(0.0f)
|
| + , fCurrentMatrixOrigin(origin)
|
| + , fTextSkewX(textSkewX)
|
| , fWideChars(wideChars)
|
| - , fInText(false)
|
| - , fDefaultPositioning(defaultPositioning) {
|
| - // Flip the text about the x-axis to account for origin swap and include
|
| - // the passed parameters.
|
| - fContent->writeText("1 0 ");
|
| - SkPDFUtils::AppendScalar(0 - textSkewX, fContent);
|
| - fContent->writeText(" -1 ");
|
| - SkPDFUtils::AppendScalar(origin.x(), fContent);
|
| - fContent->writeText(" ");
|
| - SkPDFUtils::AppendScalar(origin.y(), fContent);
|
| - fContent->writeText(" Tm\n");
|
| - }
|
| + , fDefaultPositioning(defaultPositioning) {}
|
| ~GlyphPositioner() { this->flush(); }
|
| void flush() {
|
| if (fInText) {
|
| @@ -883,16 +872,22 @@ public:
|
| fInText = false;
|
| }
|
| }
|
| - void setWideChars(bool wideChars) {
|
| - if (fWideChars != wideChars) {
|
| - SkASSERT(!fInText);
|
| - SkASSERT(fWideChars == wideChars);
|
| - fWideChars = wideChars;
|
| - }
|
| - }
|
| void writeGlyph(SkPoint xy,
|
| SkScalar advanceWidth,
|
| uint16_t glyph) {
|
| + if (!fInitialized) {
|
| + // Flip the text about the x-axis to account for origin swap and include
|
| + // the passed parameters.
|
| + fContent->writeText("1 0 ");
|
| + SkPDFUtils::AppendScalar(-fTextSkewX, fContent);
|
| + fContent->writeText(" -1 ");
|
| + SkPDFUtils::AppendScalar(fCurrentMatrixOrigin.x(), fContent);
|
| + fContent->writeText(" ");
|
| + SkPDFUtils::AppendScalar(fCurrentMatrixOrigin.y(), fContent);
|
| + fContent->writeText(" Tm\n");
|
| + fCurrentMatrixOrigin.set(0.0f, 0.0f);
|
| + fInitialized = true;
|
| + }
|
| if (!fDefaultPositioning) {
|
| SkPoint position = xy - fCurrentMatrixOrigin;
|
| if (position != SkPoint{fXAdvance, 0}) {
|
| @@ -921,11 +916,114 @@ public:
|
| private:
|
| SkDynamicMemoryWStream* fContent;
|
| SkPoint fCurrentMatrixOrigin;
|
| - SkScalar fXAdvance;
|
| + SkScalar fXAdvance = 0.0f;
|
| + SkScalar fTextSkewX;
|
| bool fWideChars;
|
| - bool fInText;
|
| + bool fInText = false;
|
| + bool fInitialized = false;
|
| const bool fDefaultPositioning;
|
| };
|
| +
|
| +class Clusterator {
|
| +public:
|
| + Clusterator(size_t glyphCount)
|
| + : fClusters(nullptr)
|
| + , fGlyphCount(SkToU32(glyphCount))
|
| + , fTextByteLength(0)
|
| + , fUtf8Text(nullptr) {}
|
| + Clusterator(const uint32_t* clusters,
|
| + size_t glyphCount,
|
| + uint32_t textByteLength,
|
| + const char* utf8Text)
|
| + : fClusters(clusters)
|
| + , fGlyphCount(SkToU32(glyphCount))
|
| + , fTextByteLength(textByteLength)
|
| + , fUtf8Text(utf8Text) {}
|
| + struct Cluster {
|
| + const char* fUtf8Text;
|
| + uint32_t fTextByteLength;
|
| + uint32_t fGlyphIndex;
|
| + uint32_t fGlyphCount;
|
| + explicit operator bool() const { return fGlyphCount != 0; }
|
| + };
|
| + Cluster next() {
|
| + if (!fUtf8Text || !fClusters) {
|
| + // These glyphs have no text. Treat as one "cluster".
|
| + uint32_t glyphCount = fGlyphCount;
|
| + fGlyphCount = 0;
|
| + return Cluster{nullptr, 0, 0, glyphCount};
|
| + }
|
| + if (fGlyphCount == 0 || fTextByteLength == 0) {
|
| + return Cluster{nullptr, 0, 0, 0}; // empty
|
| + }
|
| + uint32_t cluster = fClusters[0];
|
| + if (cluster >= fTextByteLength) {
|
| + return Cluster{nullptr, 0, 0, 0}; // bad input.
|
| + }
|
| + uint32_t glyphsInCluster = 1;
|
| + while (fClusters[glyphsInCluster] == cluster &&
|
| + glyphsInCluster < fGlyphCount) {
|
| + ++glyphsInCluster;
|
| + }
|
| + SkASSERT(glyphsInCluster <= fGlyphCount);
|
| + uint32_t textLength = 0;
|
| + if (glyphsInCluster == fGlyphCount) {
|
| + // consumes rest of glyphs and rest of text
|
| + if (kInvalidCluster == fPreviousCluster) { // LTR text or single cluster
|
| + textLength = fTextByteLength - cluster;
|
| + } else { // RTL text; last cluster.
|
| + SkASSERT(fPreviousCluster < fTextByteLength);
|
| + if (fPreviousCluster <= cluster) { // bad input.
|
| + return Cluster{nullptr, 0, 0, 0};
|
| + }
|
| + textLength = fPreviousCluster - cluster;
|
| + }
|
| + fGlyphCount = 0;
|
| + return Cluster{fUtf8Text + cluster,
|
| + textLength,
|
| + fGlyphIndex,
|
| + glyphsInCluster};
|
| + }
|
| + uint32_t nextCluster = fClusters[glyphsInCluster];
|
| + if (nextCluster >= fTextByteLength) {
|
| + return Cluster{nullptr, 0, 0, 0}; // bad input.
|
| + }
|
| + if (nextCluster > cluster) { // LTR text
|
| + if (kInvalidCluster != fPreviousCluster) {
|
| + return Cluster{nullptr, 0, 0, 0}; // bad input.
|
| + }
|
| + textLength = nextCluster - cluster;
|
| + } else { // RTL text
|
| + SkASSERT(nextCluster < cluster);
|
| + if (kInvalidCluster == fPreviousCluster) { // first cluster
|
| + textLength = fTextByteLength - cluster;
|
| + } else { // later cluster
|
| + if (fPreviousCluster <= cluster) {
|
| + return Cluster{nullptr, 0, 0, 0}; // bad input.
|
| + }
|
| + textLength = fPreviousCluster - cluster;
|
| + }
|
| + fPreviousCluster = cluster;
|
| + }
|
| + uint32_t glyphIndex = fGlyphIndex;
|
| + fGlyphCount -= glyphsInCluster;
|
| + fGlyphIndex += glyphsInCluster;
|
| + fClusters += glyphsInCluster;
|
| + return Cluster{fUtf8Text + cluster,
|
| + textLength,
|
| + glyphIndex,
|
| + glyphsInCluster};
|
| + }
|
| +
|
| +private:
|
| + static constexpr uint32_t kInvalidCluster = 0xFFFFFFFF;
|
| + const uint32_t* fClusters;
|
| + uint32_t fGlyphIndex = 0;
|
| + uint32_t fGlyphCount;
|
| + uint32_t fPreviousCluster = kInvalidCluster;
|
| + uint32_t fTextByteLength;
|
| + const char* fUtf8Text;
|
| +};
|
| } // namespace
|
|
|
| static void draw_transparent_text(SkPDFDevice* device,
|
| @@ -969,6 +1067,33 @@ static void draw_transparent_text(SkPDFDevice* device,
|
| }
|
| }
|
|
|
| +static SkUnichar map_glyph(const SkAdvancedTypefaceMetrics& metrics, SkGlyphID glyph) {
|
| + return SkToInt(glyph) < metrics.fGlyphToUnicode.count()
|
| + ? metrics.fGlyphToUnicode[SkToInt(glyph)]
|
| + : -1; // represent unmapped value
|
| +}
|
| +
|
| +// TODO: move into SkPDFUtils.h
|
| +static void write_utf16be(SkDynamicMemoryWStream* wStream, SkUnichar utf32) {
|
| + SkGlyphID utf16[2] = {0, 0};
|
| + size_t len = SkUTF16_FromUnichar(utf32, utf16);
|
| + SkASSERT(len == 1 || len == 2);
|
| + SkPDFUtils::WriteUInt16BE(wStream, utf16[0]);
|
| + if (len == 2) {
|
| + SkPDFUtils::WriteUInt16BE(wStream, utf16[1]);
|
| + }
|
| +}
|
| +
|
| +static void update_font(SkWStream* wStream, int fontIndex, SkScalar textSize) {
|
| + wStream->writeText("/");
|
| + char prefix = SkPDFResourceDict::GetResourceTypePrefix(SkPDFResourceDict::kFont_ResourceType);
|
| + wStream->write(&prefix, 1);
|
| + wStream->writeDecAsText(fontIndex);
|
| + wStream->writeText(" ");
|
| + SkPDFUtils::AppendScalar(textSize, wStream);
|
| + wStream->writeText(" Tf\n");
|
| +}
|
| +
|
| void SkPDFDevice::internalDrawText(
|
| const SkDraw& d, const void* sourceText, size_t sourceByteCount,
|
| const SkScalar pos[], SkTextBlob::GlyphPositioning positioning,
|
| @@ -988,19 +1113,6 @@ void SkPDFDevice::internalDrawText(
|
| // https://bug.skia.org/5665
|
| return;
|
| }
|
| - // TODO(halcanary): implement /ActualText with these values.
|
| - (void)clusters;
|
| - (void)textByteLength;
|
| - (void)utf8Text;
|
| - if (textByteLength > 0) {
|
| - SkASSERT(clusters);
|
| - SkASSERT(utf8Text);
|
| - SkASSERT(srcPaint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
|
| - } else {
|
| - SkASSERT(nullptr == clusters);
|
| - SkASSERT(nullptr == utf8Text);
|
| - }
|
| -
|
| SkPaint paint = calculate_text_paint(srcPaint);
|
| replace_srcmode_on_opaque_paint(&paint);
|
| if (!paint.getTypeface()) {
|
| @@ -1018,7 +1130,6 @@ void SkPDFDevice::internalDrawText(
|
| return;
|
| }
|
| // TODO(halcanary): use metrics->fGlyphToUnicode to check Unicode mapping.
|
| - const SkGlyphID maxGlyphID = metrics->fLastGlyphID;
|
| if (!SkPDFFont::CanEmbedTypeface(typeface, fDocument->canon())) {
|
| SkPath path; // https://bug.skia.org/3866
|
| paint.getTextPath(sourceText, sourceByteCount,
|
| @@ -1045,6 +1156,17 @@ void SkPDFDevice::internalDrawText(
|
| paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
|
| }
|
|
|
| + Clusterator clusterator(glyphCount);
|
| + if (textByteLength > 0) {
|
| + SkASSERT(clusters);
|
| + SkASSERT(utf8Text);
|
| + SkASSERT(srcPaint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
|
| + clusterator = Clusterator(clusters, glyphCount, textByteLength, utf8Text);
|
| + } else {
|
| + SkASSERT(nullptr == clusters);
|
| + SkASSERT(nullptr == utf8Text);
|
| + }
|
| +
|
| bool defaultPositioning = (positioning == SkTextBlob::kDefault_Positioning);
|
| paint.setHinting(SkPaint::kNo_Hinting);
|
| SkAutoGlyphCache glyphCache(paint, nullptr, nullptr);
|
| @@ -1067,74 +1189,96 @@ void SkPDFDevice::internalDrawText(
|
| SkDynamicMemoryWStream* out = &content.entry()->fContent;
|
| SkScalar textSize = paint.getTextSize();
|
|
|
| - int index = 0;
|
| - while (glyphs[index] > maxGlyphID) { // Invalid glyphID for this font.
|
| - ++index; // Skip this glyphID
|
| - if (index == glyphCount) {
|
| - return; // all glyphIDs were bad.
|
| - }
|
| - }
|
| -
|
| out->writeText("BT\n");
|
| SK_AT_SCOPE_EXIT(out->writeText("ET\n"));
|
|
|
| - SkPDFFont* font = this->updateFont(
|
| - typeface, textSize, glyphs[index], content.entry());
|
| - SkASSERT(font); // All preconditions for SkPDFFont::GetFontResource are met.
|
| - if (!font) { return; }
|
| -
|
| + const SkGlyphID maxGlyphID = metrics->fLastGlyphID;
|
| + bool multiByte = SkPDFFont::IsMultiByte(SkPDFFont::FontType(*metrics));
|
| GlyphPositioner glyphPositioner(out,
|
| paint.getTextSkewX(),
|
| - font->multiByteGlyphs(),
|
| + multiByte,
|
| defaultPositioning,
|
| offset);
|
| -
|
| - while (index < glyphCount) {
|
| - int stretch = font->countStretch(&glyphs[index], glyphCount - index, maxGlyphID);
|
| - SkASSERT(index + stretch <= glyphCount);
|
| - if (stretch < 1) {
|
| - // The current pdf font cannot encode the next glyph.
|
| - // Try to get a pdf font which can encode the next glyph.
|
| - glyphPositioner.flush();
|
| - // first, validate the next glyph
|
| - while (glyphs[index] > maxGlyphID) {
|
| - ++index; // Skip this glyphID
|
| - if (index == glyphCount) {
|
| - return; // all remainng glyphIDs were bad.
|
| + SkPDFFont* font = nullptr;
|
| + while (Clusterator::Cluster c = clusterator.next()) {
|
| + int glyphIndex = c.fGlyphIndex;
|
| + int glyphLimit = glyphIndex + c.fGlyphCount;
|
| +
|
| + bool actualText = false;
|
| + SK_AT_SCOPE_EXIT2(if (actualText) { glyphPositioner.flush(); out->writeText("EMC\n"); } );
|
| + if (c.fUtf8Text) { // real cluster
|
| + // Check if `/ActualText` needed.
|
| + const char* textPtr = c.fUtf8Text;
|
| + // TODO(halcanary): validate utf8 input.
|
| + SkUnichar unichar = SkUTF8_NextUnichar(&textPtr);
|
| + const char* textEnd = c.fUtf8Text + c.fTextByteLength;
|
| + if (textPtr < textEnd || // more characters left
|
| + glyphLimit > glyphIndex + 1 || // toUnicode wouldn't work
|
| + unichar != map_glyph(*metrics, glyphs[glyphIndex])) { // alternate map for single unichar
|
| + // actual text
|
| + // TODO: consider not hex-endoding string.
|
| + glyphPositioner.flush();
|
| + out->writeText("/Span<</ActualText <");
|
| + write_utf16be(out, 0xFEFF); // U+FEFF = BYTE ORDER MARK
|
| + // the BOM marks this text as utf16be, not latin.
|
| + write_utf16be(out, unichar); // first char
|
| + while (textPtr < textEnd) {
|
| + unichar = SkUTF8_NextUnichar(&textPtr);
|
| + write_utf16be(out, unichar);
|
| }
|
| + out->writeText("> >> BDC\n"); // begin marked-content sequence
|
| + // with an associated property list.
|
| + actualText = true;
|
| }
|
| - SkASSERT(index < glyphCount);
|
| - font = this->updateFont(typeface, textSize, glyphs[index], content.entry());
|
| - SkASSERT(font); // preconditions for SkPDFFont::GetFontResource met.
|
| - if (!font) { return; }
|
| - glyphPositioner.setWideChars(font->multiByteGlyphs());
|
| - // Get stretch for this new font.
|
| - stretch = font->countStretch(&glyphs[index], glyphCount - index, maxGlyphID);
|
| - if (stretch < 1) {
|
| - SkDEBUGFAIL("PDF could not encode glyph.");
|
| - return;
|
| - }
|
| }
|
| - while (stretch-- > 0) {
|
| - SkGlyphID gid = glyphs[index];
|
| - if (gid <= maxGlyphID) {
|
| - font->noteGlyphUsage(gid);
|
| - SkGlyphID encodedGlyph = font->glyphToPDFFontEncoding(gid);
|
| - if (defaultPositioning) {
|
| - glyphPositioner.writeGlyph(SkPoint{0, 0}, 0, encodedGlyph);
|
| - } else {
|
| - SkScalar advance = glyphCache->getGlyphIDAdvance(gid).fAdvanceX;
|
| - SkPoint 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);
|
| + while (glyphs[glyphIndex] > maxGlyphID && glyphIndex < glyphLimit) { // Invalid glyphID for this font.
|
| + ++glyphIndex; // Skip this glyphID
|
| + }
|
| + while (glyphIndex < glyphLimit) {
|
| + if (!font || // not yet specified font
|
| + !font->hasGlyph(glyphs[glyphIndex])) { // Need to switch font.
|
| + int fontIndex = this->getFontResourceIndex(typeface, glyphs[glyphIndex]);
|
| + SkASSERT(fontIndex >= 0);
|
| + if (fontIndex < 0) {
|
| + goto clusterLoopEnd;
|
| + }
|
| + glyphPositioner.flush();
|
| + //static void update_font(SkWStream* wStream, int fontIndex, SkScalar textSize) {
|
| + update_font(out, fontIndex, textSize);
|
| + font = fFontResources[fontIndex];
|
| + SkASSERT(font); // All preconditions for SkPDFFont::GetFontResource are met.
|
| + if (!font) {
|
| + goto clusterLoopEnd;
|
| + }
|
| + }
|
| + int stretch = font->countStretch(&glyphs[glyphIndex], glyphLimit - glyphIndex, maxGlyphID);
|
| + SkASSERT(glyphIndex + stretch <= glyphLimit);
|
| + SkASSERT(stretch >= 1); // we just got the font for glyphs[glyphIndex],
|
| + if (stretch < 1) { // so we are guaranteed to get at least 1.
|
| + goto clusterLoopEnd;
|
| + }
|
| + while (stretch-- > 0) {
|
| + SkGlyphID gid = glyphs[glyphIndex];
|
| + if (gid <= maxGlyphID) {
|
| + font->noteGlyphUsage(gid);
|
| + SkGlyphID encodedGlyph = font->glyphToPDFFontEncoding(gid);
|
| + if (defaultPositioning) {
|
| + glyphPositioner.writeGlyph(SkPoint{0, 0}, 0, encodedGlyph);
|
| + } else {
|
| + SkScalar advance = glyphCache->getGlyphIDAdvance(gid).fAdvanceX;
|
| + SkPoint xy = SkTextBlob::kFull_Positioning == positioning
|
| + ? SkPoint{pos[2 * glyphIndex], pos[2 * glyphIndex + 1]}
|
| + : SkPoint{pos[glyphIndex], 0};
|
| + if (alignment != SkPaint::kLeft_Align) {
|
| + xy.offset(alignmentFactor * advance, 0);
|
| + }
|
| + glyphPositioner.writeGlyph(xy, advance, encodedGlyph);
|
| }
|
| - glyphPositioner.writeGlyph(xy, advance, encodedGlyph);
|
| }
|
| + ++glyphIndex;
|
| }
|
| - ++index;
|
| }
|
| + clusterLoopEnd: (void)0;
|
| }
|
| }
|
|
|
| @@ -1837,29 +1981,6 @@ int SkPDFDevice::addXObjectResource(SkPDFObject* xObject) {
|
| return result;
|
| }
|
|
|
| -SkPDFFont* SkPDFDevice::updateFont(SkTypeface* typeface,
|
| - SkScalar textSize,
|
| - uint16_t glyphID,
|
| - SkPDFDevice::ContentEntry* contentEntry) {
|
| - if (contentEntry->fState.fFont == nullptr ||
|
| - contentEntry->fState.fTextSize != textSize ||
|
| - !contentEntry->fState.fFont->hasGlyph(glyphID)) {
|
| - int fontIndex = getFontResourceIndex(typeface, glyphID);
|
| - if (fontIndex < 0) {
|
| - SkDebugf("SkPDF: Font error.");
|
| - return nullptr;
|
| - }
|
| - contentEntry->fContent.writeText("/");
|
| - contentEntry->fContent.writeText(SkPDFResourceDict::getResourceName(
|
| - SkPDFResourceDict::kFont_ResourceType,
|
| - fontIndex).c_str());
|
| - contentEntry->fContent.writeText(" ");
|
| - SkPDFUtils::AppendScalar(textSize, &contentEntry->fContent);
|
| - contentEntry->fContent.writeText(" Tf\n");
|
| - contentEntry->fState.fFont = fFontResources[fontIndex];
|
| - }
|
| - return contentEntry->fState.fFont;
|
| -}
|
|
|
| int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) {
|
| sk_sp<SkPDFFont> newFont(
|
|
|