Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(112)

Unified Diff: src/pdf/SkPDFDevice.cpp

Issue 2330503002: work in progress (Closed)
Patch Set: Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/pdf/SkPDFDevice.h ('k') | src/pdf/SkPDFFont.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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(
« no previous file with comments | « src/pdf/SkPDFDevice.h ('k') | src/pdf/SkPDFFont.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698