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( |