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