SkPDF: Join Positioned Text
When N sequential positioned glyphs differ in positions by exactly the
advances of the first (N-1) glyphs, join the glyphs into a string
rather than changing the text matrix between each glyph draw.
Decreases PDF output size by about ~1.4%. Potentially more on
text-heavy pages.
A single-typeface PDF of an 27kB ASCII document shaped with harfbuzz:
before: 187743 Bytes
after: 65513 Bytes
difference: -65.1%
Before:
BT
/F0 13 Tf
1 0 0 -1 143.5 61 Tm
<0029> Tj
1 0 0 -1 150.634765 61 Tm
<004C> Tj
1 0 0 -1 154.602050 61 Tm
<0055> Tj
1 0 0 -1 160.245117 61 Tm
<0048> Tj
1 0 0 -1 167.925781 61 Tm
<004B> Tj
1 0 0 -1 176.469726 61 Tm
<0052> Tj
1 0 0 -1 184.518554 61 Tm
<0056> Tj
1 0 0 -1 190.980468 61 Tm
<0048> Tj
ET
After:
BT
/F0 13 Tf
1 0 0 -1 0 0 Tm
143.5 -61 Td <0029004C0055> Tj
16.7451171 0 Td <0048004B005200560048> Tj
ET
Also: update the Text matrix with the `Td` operator, instead of
overwriting it with the the `Tm` operator. In the worst case, when
every glyph is positioned differently than it's advance, this still
makes the command stream smaller:
Before:
...
1 0 0 -1 58.328125 660 Tm <0055> Tj
1 0 0 -1 61.609375 660 Tm <004C> Tj
1 0 0 -1 63.828125 660 Tm <0056> Tj
...
After:
...
3.140625 0 Td <0055> Tj
3.28125 0 Td <004C> Tj
2.21875 0 Td <0056> Tj
...
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2150393002
Review-Url: https://codereview.chromium.org/2150393002
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 8e417b1..8e76c44 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -1079,6 +1079,72 @@
}
}
+namespace {
+class GlyphPositioner {
+public:
+ GlyphPositioner(SkDynamicMemoryWStream* content,
+ SkScalar textSkewX,
+ bool wideChars)
+ : fContent(content)
+ , fCurrentMatrixX(0.0f)
+ , fCurrentMatrixY(0.0f)
+ , fXAdvance(0.0f)
+ , fWideChars(wideChars)
+ , fInText(false) {
+ set_text_transform(0.0f, 0.0f, textSkewX, fContent);
+ }
+ ~GlyphPositioner() { SkASSERT(!fInText); /* flush first */ }
+ void flush() {
+ if (fInText) {
+ fContent->writeText("> Tj\n");
+ fInText = false;
+ }
+ }
+ void setWideChars(bool wideChars) {
+ if (fWideChars != wideChars) {
+ SkASSERT(!fInText);
+ fWideChars = wideChars;
+ }
+ }
+ void writeGlyph(SkScalar x,
+ 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 (!fInText) {
+ fContent->writeText("<");
+ fInText = true;
+ }
+ if (fWideChars) {
+ SkPDFUtils::WriteUInt16BE(fContent, glyph);
+ } else {
+ SkASSERT(0 == glyph >> 8);
+ SkPDFUtils::WriteUInt8(fContent, static_cast<uint8_t>(glyph));
+ }
+ fXAdvance += advanceWidth;
+ }
+
+private:
+ SkDynamicMemoryWStream* fContent;
+ SkScalar fCurrentMatrixX;
+ SkScalar fCurrentMatrixY;
+ SkScalar fXAdvance;
+ bool fWideChars;
+ bool fInText;
+};
+} // namespace
+
static void draw_transparent_text(SkPDFDevice* device,
const SkDraw& d,
const void* text, size_t len,
@@ -1230,6 +1296,9 @@
SkPaint::GlyphCacheProc glyphCacheProc = textPaint.getGlyphCacheProc(true);
content.entry()->fContent.writeText("BT\n");
this->updateFont(textPaint, glyphIDs[0], content.entry());
+ GlyphPositioner glyphPositioner(&content.entry()->fContent,
+ textPaint.getTextSkewX(),
+ content.entry()->fState.fFont->multiByteGlyphs());
SkPDFGlyphSetMap* fontGlyphUsage = fDocument->getGlyphUsage();
for (size_t i = 0; i < numGlyphs; i++) {
SkPDFFont* font = content.entry()->fState.fFont;
@@ -1237,8 +1306,10 @@
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.
+ glyphPositioner.flush();
this->updateFont(textPaint, glyphIDs[i], content.entry());
font = content.entry()->fState.fFont;
+ glyphPositioner.setWideChars(font->multiByteGlyphs());
if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) {
SkDEBUGFAIL("PDF could not encode glyph.");
continue;
@@ -1248,13 +1319,12 @@
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(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y);
- set_text_transform(x, y, textPaint.getTextSkewX(), &content.entry()->fContent);
- write_wide_string(&content.entry()->fContent, &encodedValue, 1,
- font->multiByteGlyphs());
- content.entry()->fContent.writeText(" Tj\n");
+
+ SkScalar advanceWidth = textPaint.measureText(&encodedValue, sizeof(uint16_t));
+ glyphPositioner.writeGlyph(x, y, advanceWidth, encodedValue);
}
+ glyphPositioner.flush(); // Must flush before ending text object.
content.entry()->fContent.writeText("ET\n");
}
diff --git a/src/pdf/SkPDFUtils.h b/src/pdf/SkPDFUtils.h
index 124e199..3ddd3d0 100644
--- a/src/pdf/SkPDFUtils.h
+++ b/src/pdf/SkPDFUtils.h
@@ -76,6 +76,13 @@
result[3] = gHex[0xF & (value )];
wStream->write(result, 4);
}
+inline void WriteUInt8(SkDynamicMemoryWStream* wStream, uint8_t value) {
+ static const char gHex[] = "0123456789ABCDEF";
+ char result[2];
+ result[0] = gHex[value >> 4 ];
+ result[1] = gHex[0xF & value];
+ wStream->write(result, 2);
+}
} // namespace SkPDFUtils