SkPDF:  alloc less memory for strings

    before:
        micros   	bench
        250.98  	WritePDFText	nonrendering
    after:
        micros   	bench
        107.10  	WritePDFText	nonrendering

Also, be slightly more space-efficient in encoding strings.

Also, add a bench.

GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2099463002

Review-Url: https://codereview.chromium.org/2099463002
diff --git a/bench/PDFBench.cpp b/bench/PDFBench.cpp
index 76ee3ca..8f5d2db 100644
--- a/bench/PDFBench.cpp
+++ b/bench/PDFBench.cpp
@@ -208,6 +208,25 @@
     }
 };
 
+struct WritePDFTextBenchmark : public Benchmark {
+    std::unique_ptr<SkWStream> fWStream;
+    WritePDFTextBenchmark() : fWStream(new NullWStream) {}
+    const char* onGetName() override { return "WritePDFText"; }
+    bool isSuitableFor(Backend backend) override {
+        return backend == kNonRendering_Backend;
+    }
+    void onDraw(int loops, SkCanvas*) override {
+        static const char kHello[] = "HELLO SKIA!\n";
+        static const char kBinary[] = "\001\002\003\004\005\006";
+        while (loops-- > 0) {
+            for (int i = 1000; i-- > 0;) {
+                SkPDFUtils::WriteString(fWStream.get(), kHello, strlen(kHello));
+                SkPDFUtils::WriteString(fWStream.get(), kBinary, strlen(kBinary));
+            }
+        }
+    }
+};
+
 }  // namespace
 DEF_BENCH(return new PDFImageBench;)
 DEF_BENCH(return new PDFJpegImageBench;)
@@ -215,3 +234,4 @@
 DEF_BENCH(return new PDFScalarBench;)
 DEF_BENCH(return new PDFShaderBench;)
 DEF_BENCH(return new WStreamWriteTextBenchmark;)
+DEF_BENCH(return new WritePDFTextBenchmark;)
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 5b4ae93..b9e310d 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -1067,30 +1067,32 @@
 //                   discarded.  If true, the upper byte is encoded
 //                   first.  Otherwise, we assert the upper byte is
 //                   zero.
-static SkString format_wide_string(const uint16_t* input,
-                                   size_t len,
-                                   bool wideChars) {
+static void write_wide_string(SkDynamicMemoryWStream* wStream,
+                              const uint16_t* input,
+                              size_t len,
+                              bool wideChars) {
     if (wideChars) {
         SkASSERT(2 * len < 65535);
         static const char gHex[] = "0123456789ABCDEF";
-        SkString result(4 * len + 2);
-        result[0] = '<';
+        wStream->writeText("<");
         for (size_t i = 0; i < len; i++) {
-            result[4 * i + 1] = gHex[(input[i] >> 12) & 0xF];
-            result[4 * i + 2] = gHex[(input[i] >>  8) & 0xF];
-            result[4 * i + 3] = gHex[(input[i] >>  4) & 0xF];
-            result[4 * i + 4] = gHex[(input[i]      ) & 0xF];
+            char result[4];  // Big-endian
+            result[0] = gHex[(input[i] >> 12) & 0xF];
+            result[1] = gHex[(input[i] >> 8) & 0xF];
+            result[2] = gHex[(input[i] >> 4) & 0xF];
+            result[3] = gHex[(input[i]) & 0xF];
+            wStream->write(result, 4);
         }
-        result[4 * len + 1] = '>';
-        return result;
+        wStream->writeText(">");
     } else {
         SkASSERT(len <= 65535);
-        SkString tmp(len);
+        SkAutoMalloc buffer(len);  // Remove every other byte.
+        uint8_t* ptr = (uint8_t*)buffer.get();
         for (size_t i = 0; i < len; i++) {
             SkASSERT(0 == input[i] >> 8);
-            tmp[i] = static_cast<uint8_t>(input[i]);
+            ptr[i] = static_cast<uint8_t>(input[i]);
         }
-        return SkPDFUtils::FormatString(tmp.c_str(), tmp.size());
+        SkPDFUtils::WriteString(wStream, (char*)buffer.get(), len);
     }
 }
 
@@ -1184,10 +1186,9 @@
         fFontGlyphUsage->noteGlyphUsage(
                 font,  glyphIDsCopy.begin() + consumedGlyphCount,
                 availableGlyphs);
-        SkString encodedString =
-                format_wide_string(glyphIDsCopy.begin() + consumedGlyphCount,
-                                   availableGlyphs, font->multiByteGlyphs());
-        content.entry()->fContent.writeText(encodedString.c_str());
+        write_wide_string(&content.entry()->fContent,
+                          glyphIDsCopy.begin() + consumedGlyphCount,
+                          availableGlyphs, font->multiByteGlyphs());
         consumedGlyphCount += availableGlyphs;
         content.entry()->fContent.writeText(" Tj\n");
     }
@@ -1264,9 +1265,8 @@
 
         align_text(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y);
         set_text_transform(x, y, textPaint.getTextSkewX(), &content.entry()->fContent);
-        SkString encodedString =
-                format_wide_string(&encodedValue, 1, font->multiByteGlyphs());
-        content.entry()->fContent.writeText(encodedString.c_str());
+        write_wide_string(&content.entry()->fContent, &encodedValue, 1,
+                          font->multiByteGlyphs());
         content.entry()->fContent.writeText(" Tj\n");
     }
     content.entry()->fContent.writeText("ET\n");
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
index 285da38..19635ab 100644
--- a/src/pdf/SkPDFTypes.cpp
+++ b/src/pdf/SkPDFTypes.cpp
@@ -110,18 +110,6 @@
     }
 }
 
-static void write_string(SkWStream* o, const SkString& s) {
-    o->write(s.c_str(), s.size());
-}
-
-static SkString format_string(const SkString& s) {
-    return SkPDFUtils::FormatString(s.c_str(), s.size());
-}
-
-static SkString format_string(const char* s) {
-    return SkPDFUtils::FormatString(s, strlen(s));
-}
-
 void SkPDFUnion::emitObject(SkWStream* stream,
                             const SkPDFObjNumMap& objNumMap,
                             const SkPDFSubstituteMap& substitutes) const {
@@ -142,14 +130,16 @@
             return;
         case Type::kString:
             SkASSERT(fStaticString);
-            write_string(stream, format_string(fStaticString));
+            SkPDFUtils::WriteString(stream, fStaticString,
+                                    strlen(fStaticString));
             return;
         case Type::kNameSkS:
             stream->writeText("/");
             write_name_escaped(stream, pun(fSkString)->c_str());
             return;
         case Type::kStringSkS:
-            write_string(stream, format_string(*pun(fSkString)));
+            SkPDFUtils::WriteString(stream, pun(fSkString)->c_str(),
+                                    pun(fSkString)->size());
             return;
         case Type::kObjRef:
             stream->writeDecAsText(objNumMap.getObjectNumber(
diff --git a/src/pdf/SkPDFUtils.cpp b/src/pdf/SkPDFUtils.cpp
index f305765..4e1a6d7 100644
--- a/src/pdf/SkPDFUtils.cpp
+++ b/src/pdf/SkPDFUtils.cpp
@@ -370,46 +370,48 @@
     return output - result;
 }
 
-SkString SkPDFUtils::FormatString(const char* cin, size_t len) {
+void SkPDFUtils::WriteString(SkWStream* wStream, const char* cin, size_t len) {
     SkDEBUGCODE(static const size_t kMaxLen = 65535;)
     SkASSERT(len <= kMaxLen);
 
-    // 7-bit clean is a heuristic to decide what string format to use;
-    // a 7-bit clean string should require little escaping.
-    bool sevenBitClean = true;
-    size_t characterCount = 2 + len;
+    size_t extraCharacterCount = 0;
     for (size_t i = 0; i < len; i++) {
         if (cin[i] > '~' || cin[i] < ' ') {
-            sevenBitClean = false;
-            break;
+            extraCharacterCount += 3;
         }
         if (cin[i] == '\\' || cin[i] == '(' || cin[i] == ')') {
-            ++characterCount;
+            ++extraCharacterCount;
         }
     }
-    SkString result;
-    if (sevenBitClean) {
-        result.resize(characterCount);
-        char* str = result.writable_str();
-        *str++ = '(';
+    if (extraCharacterCount <= len) {
+        wStream->writeText("(");
         for (size_t i = 0; i < len; i++) {
-            if (cin[i] == '\\' || cin[i] == '(' || cin[i] == ')') {
-                *str++ = '\\';
+            if (cin[i] > '~' || cin[i] < ' ') {
+                uint8_t c = static_cast<uint8_t>(cin[i]);
+                uint8_t octal[4];
+                octal[0] = '\\';
+                octal[1] = '0' + ( c >> 6        );
+                octal[2] = '0' + ((c >> 3) & 0x07);
+                octal[3] = '0' + ( c       & 0x07);
+                wStream->write(octal, 4);
+            } else {
+                if (cin[i] == '\\' || cin[i] == '(' || cin[i] == ')') {
+                    wStream->writeText("\\");
+                }
+                wStream->write(&cin[i], 1);
             }
-            *str++ = cin[i];
         }
-        *str++ = ')';
+        wStream->writeText(")");
     } else {
-        result.resize(2 * len + 2);
-        char* str = result.writable_str();
-        *str++ = '<';
+        wStream->writeText("<");
         for (size_t i = 0; i < len; i++) {
             uint8_t c = static_cast<uint8_t>(cin[i]);
             static const char gHex[] = "0123456789ABCDEF";
-            *str++ = gHex[(c >> 4) & 0xF];
-            *str++ = gHex[(c     ) & 0xF];
+            char hexValue[2];
+            hexValue[0] = gHex[(c >> 4) & 0xF];
+            hexValue[1] = gHex[ c       & 0xF];
+            wStream->write(hexValue, 2);
         }
-        *str++ = '>';
+        wStream->writeText(">");
     }
-    return result;
 }
diff --git a/src/pdf/SkPDFUtils.h b/src/pdf/SkPDFUtils.h
index 3340089..693d9cd 100644
--- a/src/pdf/SkPDFUtils.h
+++ b/src/pdf/SkPDFUtils.h
@@ -66,7 +66,7 @@
     static size_t FloatToDecimal(float value,
                                  char output[kMaximumFloatDecimalLength]);
     static void AppendScalar(SkScalar value, SkWStream* stream);
-    static SkString FormatString(const char* input, size_t len);
+    static void WriteString(SkWStream* wStream, const char* input, size_t len);
 };
 
 #endif
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index e438bfe..9e2a89e 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -217,9 +217,11 @@
 
     SkString stringComplexInput("\ttest ) string ( foo");
     SkPDFUnion stringComplex = SkPDFUnion::String(stringComplexInput);
-    ASSERT_EMIT_EQ(reporter,
-                   stringComplex,
-                   "<0974657374202920737472696E67202820666F6F>");
+    ASSERT_EMIT_EQ(reporter, stringComplex, "(\\011test \\) string \\( foo)");
+
+    SkString binaryStringInput("\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20");
+    SkPDFUnion binaryString = SkPDFUnion::String(binaryStringInput);
+    ASSERT_EMIT_EQ(reporter, binaryString, "<0102030405060708090A0B0C0D0E0F10>");
 
     SkString nameInput("Test name\twith#tab");
     SkPDFUnion name = SkPDFUnion::Name(nameInput);