SkPDF: Refactor SkPDFObject heiararchy.

Flatten and use a tagged union type

        Δmemory ~= -2.4% ± 0.5%
        Δtime   ~= -1.2% ± 0.2%

BUG=skia:3585

Review URL: https://codereview.chromium.org/1069103003
diff --git a/src/doc/SkDocument_PDF.cpp b/src/doc/SkDocument_PDF.cpp
index e812cd5..1e2148b 100644
--- a/src/doc/SkDocument_PDF.cpp
+++ b/src/doc/SkDocument_PDF.cpp
@@ -94,10 +94,6 @@
     // one child.
     static const int kNodeSize = 8;
 
-    SkAutoTUnref<SkPDFName> kidsName(new SkPDFName("Kids"));
-    SkAutoTUnref<SkPDFName> countName(new SkPDFName("Count"));
-    SkAutoTUnref<SkPDFName> parentName(new SkPDFName("Parent"));
-
     // curNodes takes a reference to its items, which it passes to pageTree.
     SkTDArray<SkPDFDict*> curNodes;
     curNodes.setReserve(pages.count());
@@ -126,7 +122,7 @@
 
             int count = 0;
             for (; i < curNodes.count() && count < kNodeSize; i++, count++) {
-                curNodes[i]->insert(parentName.get(), newNodeRef.get());
+                curNodes[i]->insert("Parent", newNodeRef.get());
                 kids->append(new SkPDFObjRef(curNodes[i]))->unref();
 
                 // TODO(vandebo): put the objects in strict access order.
@@ -149,8 +145,8 @@
             if (i == curNodes.count()) {
                 pageCount = ((pages.count() - 1) % treeCapacity) + 1;
             }
-            newNode->insert(countName.get(), new SkPDFInt(pageCount))->unref();
-            newNode->insert(kidsName.get(), kids.get());
+            newNode->insert("Count", new SkPDFInt(pageCount))->unref();
+            newNode->insert("Kids", kids.get());
             nextRoundNodes.push(newNode);  // Transfer reference.
         }
 
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index b08bb17..40a4576 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -1299,15 +1299,14 @@
 
 SkPDFArray* SkPDFDevice::copyMediaBox() const {
     // should this be a singleton?
-    SkAutoTUnref<SkPDFInt> zero(SkNEW_ARGS(SkPDFInt, (0)));
 
-    SkPDFArray* mediaBox = SkNEW(SkPDFArray);
+    SkAutoTUnref<SkPDFArray> mediaBox(SkNEW(SkPDFArray));
     mediaBox->reserve(4);
-    mediaBox->append(zero.get());
-    mediaBox->append(zero.get());
+    mediaBox->appendInt(0);
+    mediaBox->appendInt(0);
     mediaBox->appendInt(fPageSize.fWidth);
     mediaBox->appendInt(fPageSize.fHeight);
-    return mediaBox;
+    return mediaBox.detach();
 }
 
 SkStreamAsset* SkPDFDevice::content() const {
@@ -1505,7 +1504,7 @@
                  urlData->size() - 1);
     SkAutoTUnref<SkPDFDict> action(SkNEW_ARGS(SkPDFDict, ("Action")));
     action->insertName("S", "URI");
-    action->insert("URI", SkNEW_ARGS(SkPDFString, (url)))->unref();
+    action->insertString("URI", url);
     annotation->insert("A", action.get());
 }
 
@@ -1514,7 +1513,7 @@
     SkAutoTUnref<SkPDFDict> annotation(createLinkAnnotation(r, matrix));
     SkString name(static_cast<const char *>(nameData->data()),
                   nameData->size() - 1);
-    annotation->insert("Dest", SkNEW_ARGS(SkPDFName, (name)))->unref();
+    annotation->insertName("Dest", name);
 }
 
 struct NamedDestination {
@@ -1522,9 +1521,7 @@
     SkPoint point;
 
     NamedDestination(const SkData* nameData, const SkPoint& point)
-        : nameData(nameData), point(point) {
-        nameData->ref();
-    }
+        : nameData(SkRef(nameData)), point(point) {}
 
     ~NamedDestination() {
         nameData->unref();
@@ -1547,13 +1544,13 @@
         NamedDestination* dest = fNamedDestinations[i];
         SkAutoTUnref<SkPDFArray> pdfDest(SkNEW(SkPDFArray));
         pdfDest->reserve(5);
-        pdfDest->append(SkNEW_ARGS(SkPDFObjRef, (page)))->unref();
+        pdfDest->appendObjRef(SkRef(page));
         pdfDest->appendName("XYZ");
         pdfDest->appendScalar(dest->point.x());
         pdfDest->appendScalar(dest->point.y());
         pdfDest->appendInt(0);  // Leave zoom unchanged
-        dict->insert(static_cast<const char *>(dest->nameData->data()),
-                     pdfDest);
+        SkString name(static_cast<const char*>(dest->nameData->data()));
+        dict->insertObject(name, pdfDest.detach());
     }
 }
 
diff --git a/src/pdf/SkPDFFont.cpp b/src/pdf/SkPDFFont.cpp
index 1a396c7..c9de907 100644
--- a/src/pdf/SkPDFFont.cpp
+++ b/src/pdf/SkPDFFont.cpp
@@ -1390,10 +1390,8 @@
         SkAutoTDelete<SkMemoryStream> glyphStream(new SkMemoryStream());
         glyphStream->setData(content.copyToData())->unref();
 
-        SkAutoTUnref<SkPDFStream> glyphDescription(
-            new SkPDFStream(glyphStream.get()));
-        charProcs->insert(characterName.c_str(),
-                          new SkPDFObjRef(glyphDescription.get()))->unref();
+        charProcs->insertObjRef(characterName,
+                                new SkPDFStream(glyphStream.get()));
     }
 
     insert("FontBBox", makeFontBBox(bbox, 1000))->unref();
diff --git a/src/pdf/SkPDFResourceDict.cpp b/src/pdf/SkPDFResourceDict.cpp
index de5c910..1f5bcea 100644
--- a/src/pdf/SkPDFResourceDict.cpp
+++ b/src/pdf/SkPDFResourceDict.cpp
@@ -46,7 +46,7 @@
 static const char* get_resource_type_name(
         SkPDFResourceDict::SkPDFResourceType type) {
     SkASSERT(type >= 0);
-    SkASSERT(type < SkPDFResourceDict::kResourceTypeCount);
+    SkASSERT(type < SK_ARRAY_COUNT(resource_type_names));
 
     return resource_type_names[type];
 }
@@ -67,13 +67,10 @@
     }
     SkAutoTUnref<SkPDFDict> resources(SkNEW(SkPDFDict));
     for (int i = 0; i < resourceList.count(); i++) {
-        SkString keyString = SkPDFResourceDict::getResourceName(type, i);
-        SkAutoTUnref<SkPDFName> keyName(SkNEW_ARGS(SkPDFName, (keyString)));
-        SkAutoTUnref<SkPDFObjRef> ref(
-                SkNEW_ARGS(SkPDFObjRef, (resourceList[i])));
-        resources->insert(keyName, ref);
+        resources->insertObjRef(SkPDFResourceDict::getResourceName(type, i),
+                                SkRef(resourceList[i]));
     }
-    dst->insert(get_resource_type_name(type), resources);
+    dst->insertObject(get_resource_type_name(type), resources.detach());
 }
 
 SkPDFDict* SkPDFResourceDict::Create(
@@ -90,7 +87,7 @@
     for (size_t i = 0; i < SK_ARRAY_COUNT(kProcs); i++) {
         procSets->appendName(kProcs[i]);
     }
-    dict->insert("ProcSets", procSets);
+    dict->insertObject("ProcSets", procSets.detach());
 
     if (gStateResources) {
         add_subdict(*gStateResources, kExtGState_ResourceType, dict);
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
index cd130cf..510b98b 100644
--- a/src/pdf/SkPDFTypes.cpp
+++ b/src/pdf/SkPDFTypes.cpp
@@ -6,7 +6,6 @@
  * found in the LICENSE file.
  */
 
-
 #include "SkPDFTypes.h"
 #include "SkStream.h"
 
@@ -18,62 +17,271 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SkPDFObjRef::SkPDFObjRef(SkPDFObject* obj) : fObj(obj) {
-    SkSafeRef(obj);
+SkString* pun(char* x) { return reinterpret_cast<SkString*>(x); }
+const SkString* pun(const char* x) {
+    return reinterpret_cast<const SkString*>(x);
 }
 
-SkPDFObjRef::~SkPDFObjRef() {}
+SkPDFUnion::SkPDFUnion(Type t) : fType(t) {}
 
-void SkPDFObjRef::emitObject(SkWStream* stream,
-                             const SkPDFObjNumMap& objNumMap,
-                             const SkPDFSubstituteMap& substitutes) {
-    SkPDFObject* obj = substitutes.getSubstitute(fObj);
-    stream->writeDecAsText(objNumMap.getObjectNumber(obj));
-    stream->writeText(" 0 R");  // Generation number is always 0.
-}
-
-void SkPDFObjRef::addResources(SkPDFObjNumMap* catalog,
-                               const SkPDFSubstituteMap& substitutes) const {
-    SkPDFObject* obj = substitutes.getSubstitute(fObj);
-    SkASSERT(obj);
-    if (catalog->addObject(obj)) {
-        obj->addResources(catalog, substitutes);
+SkPDFUnion::~SkPDFUnion() {
+    switch (fType) {
+        case Type::kNameSkS:
+        case Type::kStringSkS:
+            pun(fSkString)->~SkString();
+            return;
+        case Type::kObjRef:
+        case Type::kObject:
+            SkSafeUnref(fObject);
+            return;
+        default:
+            return;
     }
 }
 
-////////////////////////////////////////////////////////////////////////////////
+SkPDFUnion& SkPDFUnion::operator=(SkPDFUnion&& other) {
+    if (this != &other) {
+        this->~SkPDFUnion();
+        SkNEW_PLACEMENT_ARGS(this, SkPDFUnion, (other.move()));
+    }
+    return *this;
+}
 
-SkPDFInt::SkPDFInt(int32_t value) : fValue(value) {}
-SkPDFInt::~SkPDFInt() {}
+SkPDFUnion::SkPDFUnion(SkPDFUnion&& other) {
+    SkASSERT(this != &other);
+    memcpy(this, &other, sizeof(*this));
+    other.fType = Type::kDestroyed;
+}
 
-void SkPDFInt::emitObject(SkWStream* stream,
-                          const SkPDFObjNumMap&,
-                          const SkPDFSubstituteMap&) {
-    stream->writeDecAsText(fValue);
+#if 0
+SkPDFUnion SkPDFUnion::copy() const {
+    SkPDFUnion u(fType);
+    memcpy(&u, this, sizeof(u));
+    switch (fType) {
+        case Type::kNameSkS:
+        case Type::kStringSkS:
+            SkNEW_PLACEMENT_ARGS(pun(u.fSkString), SkString,
+                                 (*pun(fSkString)));
+            return u.move();
+        case Type::kObjRef:
+        case Type::kObject:
+            SkRef(u.fObject);
+            return u.move();
+        default:
+            return u.move();
+    }
+}
+SkPDFUnion& SkPDFUnion::operator=(const SkPDFUnion& other) {
+    return *this = other.copy();
+}
+SkPDFUnion::SkPDFUnion(const SkPDFUnion& other) {
+    *this = other.copy();
+}
+#endif
+
+bool SkPDFUnion::isName() const {
+    return Type::kName == fType || Type::kNameSkS == fType;
+}
+
+#ifdef SK_DEBUG
+// Most names need no escaping.  Such names are handled as static
+// const strings.
+bool is_valid_name(const char* n) {
+    static const char kControlChars[] = "/%()<>[]{}";
+    while (*n) {
+        if (*n < '!' || *n > '~' || strchr(kControlChars, *n)) {
+            return false;
+        }
+        ++n;
+    }
+    return true;
+}
+#endif  // SK_DEBUG
+
+// Given an arbitrary string, convert it to a valid name.
+static SkString escape_name(const char* name, size_t len) {
+    static const char kToEscape[] = "#/%()<>[]{}";
+    int count = 0;
+    const char* const end = &name[len];
+    for (const char* n = name; n != end; ++n) {
+        if (*n < '!' || *n > '~' || strchr(kToEscape, *n)) {
+            count += 2;
+        }
+        ++count;
+    }
+    SkString result(count);
+    char* s = result.writable_str();
+    static const char kHex[] = "0123456789ABCDEF";
+    for (const char* n = name; n != end; ++n) {
+        if (*n < '!' || *n > '~' || strchr(kToEscape, *n)) {
+            *s++ = '#';
+            *s++ = kHex[(*n >> 4) & 0xF];
+            *s++ = kHex[*n & 0xF];
+        } else {
+            *s++ = *n;
+        }
+    }
+    SkASSERT(&result.writable_str()[count] == s);  // don't over-write
+    return result;                                 // allocated space
+}
+
+static SkString escape_name(const SkString& name) {
+    return escape_name(name.c_str(), name.size());
+}
+
+static void write_string(SkWStream* o, const SkString& s) {
+    o->write(s.c_str(), s.size());
+}
+
+static SkString format_string(const SkString& s) {
+    return SkPDFString::FormatString(s.c_str(), s.size());
+}
+
+static SkString format_string(const char* s) {
+    return SkPDFString::FormatString(s, strlen(s));
+}
+
+void SkPDFUnion::emitObject(SkWStream* stream,
+                            const SkPDFObjNumMap& objNumMap,
+                            const SkPDFSubstituteMap& substitutes) const {
+    switch (fType) {
+        case Type::kInt:
+            stream->writeDecAsText(fIntValue);
+            return;
+        case Type::kBool:
+            stream->writeText(fBoolValue ? "true" : "false");
+            return;
+        case Type::kScalar:
+            SkPDFScalar::Append(fScalarValue, stream);
+            return;
+        case Type::kName:
+            stream->writeText("/");
+            SkASSERT(is_valid_name(fStaticString));
+            stream->writeText(fStaticString);
+            return;
+        case Type::kString:
+            SkASSERT(fStaticString);
+            write_string(stream, format_string(fStaticString));
+            return;
+        case Type::kNameSkS:
+            stream->writeText("/");
+            write_string(stream, escape_name(*pun(fSkString)));
+            return;
+        case Type::kStringSkS:
+            write_string(stream, format_string(*pun(fSkString)));
+            return;
+        case Type::kObjRef:
+            stream->writeDecAsText(objNumMap.getObjectNumber(
+                    substitutes.getSubstitute(fObject)));
+            stream->writeText(" 0 R");  // Generation number is always 0.
+            return;
+        case Type::kObject:
+            fObject->emitObject(stream, objNumMap, substitutes);
+            return;
+        default:
+            SkDEBUGFAIL("SkPDFUnion::emitObject with bad type");
+    }
+}
+
+void SkPDFUnion::addResources(SkPDFObjNumMap* objNumMap,
+                              const SkPDFSubstituteMap& substituteMap) const {
+    switch (fType) {
+        case Type::kInt:
+        case Type::kBool:
+        case Type::kScalar:
+        case Type::kName:
+        case Type::kString:
+        case Type::kNameSkS:
+        case Type::kStringSkS:
+            return;  // These have no resources.
+        case Type::kObjRef: {
+            SkPDFObject* obj = substituteMap.getSubstitute(fObject);
+            if (objNumMap->addObject(obj)) {
+                obj->addResources(objNumMap, substituteMap);
+            }
+            return;
+        }
+        case Type::kObject:
+            fObject->addResources(objNumMap, substituteMap);
+            return;
+        default:
+            SkDEBUGFAIL("SkPDFUnion::addResources with bad type");
+    }
+}
+
+SkPDFUnion SkPDFUnion::Int(int32_t value) {
+    SkPDFUnion u(Type::kInt);
+    u.fIntValue = value;
+    return u.move();
+}
+
+SkPDFUnion SkPDFUnion::Bool(bool value) {
+    SkPDFUnion u(Type::kBool);
+    u.fBoolValue = value;
+    return u.move();
+}
+
+SkPDFUnion SkPDFUnion::Scalar(SkScalar value) {
+    SkPDFUnion u(Type::kScalar);
+    u.fScalarValue = value;
+    return u.move();
+}
+
+SkPDFUnion SkPDFUnion::Name(const char* value) {
+    SkPDFUnion u(Type::kName);
+    SkASSERT(value);
+    SkASSERT(is_valid_name(value));
+    u.fStaticString = value;
+    return u.move();
+}
+
+SkPDFUnion SkPDFUnion::String(const char* value) {
+    SkPDFUnion u(Type::kString);
+    SkASSERT(value);
+    u.fStaticString = value;
+    return u.move();
+}
+
+SkPDFUnion SkPDFUnion::Name(const SkString& s) {
+    SkPDFUnion u(Type::kNameSkS);
+    SkNEW_PLACEMENT_ARGS(pun(u.fSkString), SkString, (s));
+    return u.move();
+}
+
+SkPDFUnion SkPDFUnion::String(const SkString& s) {
+    SkPDFUnion u(Type::kStringSkS);
+    SkNEW_PLACEMENT_ARGS(pun(u.fSkString), SkString, (s));
+    return u.move();
+}
+
+SkPDFUnion SkPDFUnion::ObjRef(SkPDFObject* ptr) {
+    SkPDFUnion u(Type::kObjRef);
+    SkASSERT(ptr);
+    u.fObject = ptr;
+    return u.move();
+}
+
+SkPDFUnion SkPDFUnion::Object(SkPDFObject* ptr) {
+    SkPDFUnion u(Type::kObject);
+    SkASSERT(ptr);
+    u.fObject = ptr;
+    return u.move();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SkPDFBool::SkPDFBool(bool value) : fValue(value) {}
-SkPDFBool::~SkPDFBool() {}
-
-void SkPDFBool::emitObject(SkWStream* stream,
-                          const SkPDFObjNumMap&,
-                          const SkPDFSubstituteMap&) {
-    stream->writeText(fValue ? "true" : "false");
+void SkPDFAtom::emitObject(SkWStream* stream,
+                           const SkPDFObjNumMap& objNumMap,
+                           const SkPDFSubstituteMap& substitutes) {
+    fValue.emitObject(stream, objNumMap, substitutes);
+}
+void SkPDFAtom::addResources(SkPDFObjNumMap* map,
+                             const SkPDFSubstituteMap& substitutes) const {
+    fValue.addResources(map, substitutes);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SkPDFScalar::SkPDFScalar(SkScalar value) : fValue(value) {}
-SkPDFScalar::~SkPDFScalar() {}
-
-void SkPDFScalar::emitObject(SkWStream* stream,
-                             const SkPDFObjNumMap&,
-                             const SkPDFSubstituteMap&) {
-    SkPDFScalar::Append(fValue, stream);
-}
-
 // static
 void SkPDFScalar::Append(SkScalar value, SkWStream* stream) {
     // The range of reals in PDF/A is the same as SkFixed: +/- 32,767 and
@@ -125,23 +333,8 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SkPDFString::SkPDFString(const char value[])
-    : fValue(FormatString(value, strlen(value))) {
-}
-
-SkPDFString::SkPDFString(const SkString& value)
-    : fValue(FormatString(value.c_str(), value.size())) {
-}
-
-SkPDFString::~SkPDFString() {}
-
-void SkPDFString::emitObject(SkWStream* stream,
-                             const SkPDFObjNumMap&,
-                             const SkPDFSubstituteMap&) {
-    stream->write(fValue.c_str(), fValue.size());
-}
-
 // static
+
 SkString SkPDFString::FormatString(const char* cin, size_t len) {
     SkASSERT(len <= kMaxLen);
 
@@ -187,55 +380,25 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SkPDFName::SkPDFName(const char name[]) : fValue(FormatName(SkString(name))) {}
-SkPDFName::SkPDFName(const SkString& name) : fValue(FormatName(name)) {}
-SkPDFName::~SkPDFName() {}
-
-bool SkPDFName::operator==(const SkPDFName& b) const {
-    return fValue == b.fValue;
-}
-
-void SkPDFName::emitObject(SkWStream* stream,
-                             const SkPDFObjNumMap&,
-                             const SkPDFSubstituteMap&) {
-    stream->write(fValue.c_str(), fValue.size());
-}
-
-// static
-SkString SkPDFName::FormatName(const SkString& input) {
-    SkASSERT(input.size() <= kMaxLen);
-    // TODO(vandebo) If more escaping is needed, improve the linear scan.
-    static const char escaped[] = "#/%()<>[]{}";
-
-    SkString result("/");
-    for (size_t i = 0; i < input.size(); i++) {
-        if (input[i] & 0x80 || input[i] < '!' || strchr(escaped, input[i])) {
-            result.append("#");
-            // Mask with 0xFF to avoid sign extension. i.e. #FFFFFF81
-            result.appendHex(input[i] & 0xFF, 2);
-        } else {
-            result.append(input.c_str() + i, 1);
-        }
-    }
-
-    return result;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
 SkPDFArray::SkPDFArray() {}
 SkPDFArray::~SkPDFArray() {
-    fValue.unrefAll();
+    for (SkPDFUnion& value : fValues) {
+        value.~SkPDFUnion();
+    }
+    fValues.reset();
 }
 
+int SkPDFArray::size() const { return fValues.count(); }
+
+void SkPDFArray::reserve(int length) { fValues.setReserve(length); }
+
 void SkPDFArray::emitObject(SkWStream* stream,
                              const SkPDFObjNumMap& objNumMap,
                              const SkPDFSubstituteMap& substitutes) {
     stream->writeText("[");
-    for (int i = 0; i < fValue.count(); i++) {
-        SkASSERT(substitutes.getSubstitute(fValue[i]) == fValue[i]);
-        fValue[i]->emitObject(stream, objNumMap, substitutes);
-        if (i + 1 < fValue.count()) {
+    for (int i = 0; i < fValues.count(); i++) {
+        fValues[i].emitObject(stream, objNumMap, substitutes);
+        if (i + 1 < fValues.count()) {
             stream->writeText(" ");
         }
     }
@@ -244,68 +407,74 @@
 
 void SkPDFArray::addResources(SkPDFObjNumMap* catalog,
                               const SkPDFSubstituteMap& substitutes) const {
-    for (int i = 0; i < fValue.count(); i++) {
-        SkASSERT(substitutes.getSubstitute(fValue[i]) == fValue[i]);
-        fValue[i]->addResources(catalog, substitutes);
+    for (const SkPDFUnion& value : fValues) {
+        value.addResources(catalog, substitutes);
     }
 }
 
-void SkPDFArray::reserve(int length) {
-    SkASSERT(length <= kMaxLen);
-    fValue.setReserve(length);
+void SkPDFArray::append(SkPDFUnion&& value) {
+    SkNEW_PLACEMENT_ARGS(fValues.append(), SkPDFUnion, (value.move()));
 }
 
 SkPDFObject* SkPDFArray::append(SkPDFObject* value) {
-    SkASSERT(fValue.count() < kMaxLen);
-    value->ref();
-    fValue.push(value);
+    // DEPRECATED
+    this->append(SkPDFUnion::Object(SkRef(value)));
     return value;
 }
 
 void SkPDFArray::appendInt(int32_t value) {
-    SkASSERT(fValue.count() < kMaxLen);
-    fValue.push(new SkPDFInt(value));
+    this->append(SkPDFUnion::Int(value));
+}
+
+void SkPDFArray::appendBool(bool value) {
+    this->append(SkPDFUnion::Bool(value));
 }
 
 void SkPDFArray::appendScalar(SkScalar value) {
-    SkASSERT(fValue.count() < kMaxLen);
-    fValue.push(new SkPDFScalar(value));
+    this->append(SkPDFUnion::Scalar(value));
 }
 
 void SkPDFArray::appendName(const char name[]) {
-    SkASSERT(fValue.count() < kMaxLen);
-    fValue.push(new SkPDFName(name));
+    this->append(SkPDFUnion::Name(SkString(name)));
+}
+
+void SkPDFArray::appendName(const SkString& name) {
+    this->append(SkPDFUnion::Name(name));
+}
+
+void SkPDFArray::appendString(const SkString& value) {
+    this->append(SkPDFUnion::String(value));
+}
+
+void SkPDFArray::appendString(const char value[]) {
+    this->append(SkPDFUnion::String(value));
+}
+
+void SkPDFArray::appendObject(SkPDFObject* value) {
+    this->append(SkPDFUnion::Object(value));
+}
+
+void SkPDFArray::appendObjRef(SkPDFObject* value) {
+    this->append(SkPDFUnion::ObjRef(value));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 SkPDFDict::SkPDFDict() {}
 
-SkPDFDict::SkPDFDict(const char type[]) {
-    insertName("Type", type);
-}
+SkPDFDict::~SkPDFDict() { this->clear(); }
 
-SkPDFDict::~SkPDFDict() {
-    clear();
-}
-
-int SkPDFDict::size() const {
-    return fValue.count();
-}
+SkPDFDict::SkPDFDict(const char type[]) { this->insertName("Type", type); }
 
 void SkPDFDict::emitObject(SkWStream* stream,
                            const SkPDFObjNumMap& objNumMap,
                            const SkPDFSubstituteMap& substitutes) {
     stream->writeText("<<");
-    for (int i = 0; i < fValue.count(); i++) {
-        SkASSERT(fValue[i].key);
-        SkASSERT(fValue[i].value);
-        SkASSERT(substitutes.getSubstitute(fValue[i].key) == fValue[i].key);
-        SkASSERT(substitutes.getSubstitute(fValue[i].value) == fValue[i].value);
-        fValue[i].key->emitObject(stream, objNumMap, substitutes);
+    for (int i = 0; i < fRecords.count(); i++) {
+        fRecords[i].fKey.emitObject(stream, objNumMap, substitutes);
         stream->writeText(" ");
-        fValue[i].value->emitObject(stream, objNumMap, substitutes);
-        if (i + 1 < fValue.count()) {
+        fRecords[i].fValue.emitObject(stream, objNumMap, substitutes);
+        if (i + 1 < fRecords.count()) {
             stream->writeText("\n");
         }
     }
@@ -314,72 +483,75 @@
 
 void SkPDFDict::addResources(SkPDFObjNumMap* catalog,
                              const SkPDFSubstituteMap& substitutes) const {
-    for (int i = 0; i < fValue.count(); i++) {
-        SkASSERT(fValue[i].key);
-        SkASSERT(fValue[i].value);
-        fValue[i].key->addResources(catalog, substitutes);
-        SkASSERT(substitutes.getSubstitute(fValue[i].value) == fValue[i].value);
-        fValue[i].value->addResources(catalog, substitutes);
+    for (int i = 0; i < fRecords.count(); i++) {
+        fRecords[i].fKey.addResources(catalog, substitutes);
+        fRecords[i].fValue.addResources(catalog, substitutes);
     }
 }
 
-SkPDFObject*  SkPDFDict::append(SkPDFName* key, SkPDFObject* value) {
-    SkASSERT(key);
-    SkASSERT(value);
-    *(fValue.append()) = Rec(key, value);
+void SkPDFDict::set(SkPDFUnion&& name, SkPDFUnion&& value) {
+    Record* rec = fRecords.append();
+    SkASSERT(name.isName());
+    SkNEW_PLACEMENT_ARGS(&rec->fKey, SkPDFUnion, (name.move()));
+    SkNEW_PLACEMENT_ARGS(&rec->fValue, SkPDFUnion, (value.move()));
+}
+
+int SkPDFDict::size() const { return fRecords.count(); }
+
+void SkPDFDict::insertObjRef(const char key[], SkPDFObject* value) {
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::ObjRef(value));
+}
+void SkPDFDict::insertObjRef(const SkString& key, SkPDFObject* value) {
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::ObjRef(value));
+}
+
+void SkPDFDict::insertObject(const char key[], SkPDFObject* value) {
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::Object(value));
+}
+void SkPDFDict::insertObject(const SkString& key, SkPDFObject* value) {
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::Object(value));
+}
+
+// DEPRECATED
+SkPDFObject* SkPDFDict::insert(const char key[], SkPDFObject* value) {
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::Object(SkRef(value)));
     return value;
 }
 
-SkPDFObject* SkPDFDict::insert(SkPDFName* key, SkPDFObject* value) {
-    return this->append(SkRef(key), SkRef(value));
-}
-
-SkPDFObject* SkPDFDict::insert(const char key[], SkPDFObject* value) {
-    return this->append(new SkPDFName(key), SkRef(value));
-}
-
 void SkPDFDict::insertInt(const char key[], int32_t value) {
-    (void)this->append(new SkPDFName(key), new SkPDFInt(value));
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::Int(value));
+}
+
+void SkPDFDict::insertInt(const char key[], size_t value) {
+    this->insertInt(key, SkToS32(value));
 }
 
 void SkPDFDict::insertScalar(const char key[], SkScalar value) {
-    (void)this->append(new SkPDFName(key), new SkPDFScalar(value));
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::Scalar(value));
 }
 
 void SkPDFDict::insertName(const char key[], const char name[]) {
-    (void)this->append(new SkPDFName(key), new SkPDFName(name));
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::Name(name));
+}
+
+void SkPDFDict::insertName(const char key[], const SkString& name) {
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::Name(name));
+}
+
+void SkPDFDict::insertString(const char key[], const char value[]) {
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::String(value));
+}
+
+void SkPDFDict::insertString(const char key[], const SkString& value) {
+    this->set(SkPDFUnion::Name(key), SkPDFUnion::String(value));
 }
 
 void SkPDFDict::clear() {
-    for (int i = 0; i < fValue.count(); i++) {
-        SkASSERT(fValue[i].key);
-        SkASSERT(fValue[i].value);
-        fValue[i].key->unref();
-        fValue[i].value->unref();
+    for (Record& rec : fRecords) {
+        rec.fKey.~SkPDFUnion();
+        rec.fValue.~SkPDFUnion();
     }
-    fValue.reset();
-}
-
-void SkPDFDict::remove(const char key[]) {
-    SkASSERT(key);
-    SkPDFName name(key);
-    for (int i = 0; i < fValue.count(); i++) {
-        SkASSERT(fValue[i].key);
-        if (*(fValue[i].key) == name) {
-            fValue[i].key->unref();
-            SkASSERT(fValue[i].value);
-            fValue[i].value->unref();
-            fValue.removeShuffle(i);
-            return;
-        }
-    }
-}
-
-void SkPDFDict::mergeFrom(const SkPDFDict& other) {
-    for (int i = 0; i < other.fValue.count(); i++) {
-        *(fValue.append()) =
-                Rec(SkRef(other.fValue[i].key), SkRef(other.fValue[i].value));
-    }
+    fRecords.reset();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/pdf/SkPDFTypes.h b/src/pdf/SkPDFTypes.h
index 76e1911..f258e38 100644
--- a/src/pdf/SkPDFTypes.h
+++ b/src/pdf/SkPDFTypes.h
@@ -1,4 +1,3 @@
-
 /*
  * Copyright 2010 The Android Open Source Project
  *
@@ -10,7 +9,6 @@
 #ifndef SkPDFTypes_DEFINED
 #define SkPDFTypes_DEFINED
 
-#include "SkPDFTypes.h"
 #include "SkRefCnt.h"
 #include "SkScalar.h"
 #include "SkString.h"
@@ -28,10 +26,11 @@
     A PDF Object is the base class for primitive elements in a PDF file.  A
     common subtype is used to ease the use of indirect object references,
     which are common in the PDF format.
+
 */
 class SkPDFObject : public SkRefCnt {
 public:
-    SK_DECLARE_INST_COUNT(SkPDFObject)
+    SK_DECLARE_INST_COUNT(SkPDFObject);
 
     /** Subclasses must implement this method to print the object to the
      *  PDF file.
@@ -55,170 +54,191 @@
     typedef SkRefCnt INHERITED;
 };
 
-/** \class SkPDFObjRef
+////////////////////////////////////////////////////////////////////////////////
 
-    An indirect reference to a PDF object.
-*/
-class SkPDFObjRef : public SkPDFObject {
+/**
+   A SkPDFUnion is a non-virtualized implementation of the
+   non-compound, non-specialized PDF Object types: Name, String,
+   Number, Boolean.
+ */
+class SkPDFUnion {
 public:
-    SK_DECLARE_INST_COUNT(SkPDFObjRef)
+    // u.move() is analogous to std::move(u). It returns an rvalue.
+    SkPDFUnion move() { return static_cast<SkPDFUnion&&>(*this); }
+    // Move contstructor and assignemnt operator destroy the argument
+    // and steal their references (if needed).
+    SkPDFUnion(SkPDFUnion&& other);
+    SkPDFUnion& operator=(SkPDFUnion&& other);
 
-    /** Create a reference to an existing SkPDFObject.
-     *  @param obj The object to reference.
-     */
-    explicit SkPDFObjRef(SkPDFObject* obj);
-    virtual ~SkPDFObjRef();
+    ~SkPDFUnion();
 
-    // The SkPDFObject interface.
-    virtual void emitObject(SkWStream* stream,
-                            const SkPDFObjNumMap& objNumMap,
-                            const SkPDFSubstituteMap& substitutes) override;
-    virtual void addResources(SkPDFObjNumMap*,
-                              const SkPDFSubstituteMap&) const override;
+    /** The following nine functions are the standard way of creating
+        SkPDFUnion objects. */
+
+    static SkPDFUnion Int(int32_t);
+
+    static SkPDFUnion Bool(bool);
+
+    static SkPDFUnion Scalar(SkScalar);
+
+    /** These two functions do NOT take ownership of ptr, and do NOT
+        copy the string.  Suitable for passing in static const
+        strings. For example:
+          SkPDFUnion n = SkPDFUnion::Name("Length");
+          SkPDFUnion u = SkPDFUnion::String("Identity"); */
+
+    /** SkPDFUnion::Name(const char*) assumes that the passed string
+        is already a valid name (that is: it has no control or
+        whitespace characters).  This will not copy the name. */
+    static SkPDFUnion Name(const char*);
+
+    /** SkPDFUnion::String will encode the passed string.  This will
+        not copy the name. */
+    static SkPDFUnion String(const char*);
+
+    /** SkPDFUnion::Name(const SkString&) does not assume that the
+        passed string is already a valid name and it will escape the
+        string. */
+    static SkPDFUnion Name(const SkString&);
+
+    /** SkPDFUnion::String will encode the passed string. */
+    static SkPDFUnion String(const SkString&);
+
+    /** This function DOES take ownership of the object. E.g.
+          SkAutoTUnref<SkPDFDict> dict(new SkPDFDict);
+          dict->insert(.....);
+          SkPDFUnion u = SkPDFUnion::Object(dict.detach()) */
+    static SkPDFUnion Object(SkPDFObject*);
+
+    /** This function DOES take ownership of the object. E.g.
+          SkAutoTUnref<SkPDFBitmap> image(
+                 SkPDFBitmap::Create(fCanon, bitmap));
+          SkPDFUnion u = SkPDFUnion::ObjRef(image.detach()) */
+    static SkPDFUnion ObjRef(SkPDFObject*);
+
+    /** These two non-virtual methods mirror SkPDFObject's
+        corresponding virtuals. */
+    void emitObject(SkWStream*,
+                    const SkPDFObjNumMap&,
+                    const SkPDFSubstituteMap&) const;
+    void addResources(SkPDFObjNumMap*, const SkPDFSubstituteMap&) const;
+
+    bool isName() const;
 
 private:
-    SkAutoTUnref<SkPDFObject> fObj;
+    union {
+        int32_t fIntValue;
+        bool fBoolValue;
+        SkScalar fScalarValue;
+        const char* fStaticString;
+        char fSkString[sizeof(SkString)];
+        SkPDFObject* fObject;
+    };
+    enum class Type : char {
+        /** It is an error to call emitObject() or addResources() on an
+            kDestroyed object. */
+        kDestroyed = 0,
+        kInt,
+        kBool,
+        kScalar,
+        kName,
+        kString,
+        kNameSkS,
+        kStringSkS,
+        kObjRef,
+        kObject,
+    };
+    Type fType;
 
+    SkPDFUnion(Type);
+    // We do not now need copy constructor and copy assignment, so we
+    // will disable this functionality.
+    SkPDFUnion& operator=(const SkPDFUnion&) = delete;
+    SkPDFUnion(const SkPDFUnion&) = delete;
+};
+SK_COMPILE_ASSERT(sizeof(SkString) == sizeof(void*), SkString_size);
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**  This class is a SkPDFUnion with SkPDFObject virtuals attached. */
+// TODO(halcanary): 99% of the uses of this class should be
+// transitioned to using a bare SkPDFUnion inside an array or dict.
+class SkPDFAtom : public SkPDFObject {
+public:
+    void emitObject(SkWStream* stream,
+                    const SkPDFObjNumMap& objNumMap,
+                    const SkPDFSubstituteMap& substitutes) final;
+    void addResources(SkPDFObjNumMap*, const SkPDFSubstituteMap&) const final;
+
+protected:
+    SkPDFAtom(SkPDFUnion&& v) : fValue(v.move()) {}
+
+private:
+    const SkPDFUnion fValue;
     typedef SkPDFObject INHERITED;
 };
 
-/** \class SkPDFInt
-
-    An integer object in a PDF.
-*/
-class SkPDFInt : public SkPDFObject {
+/** The following six classes exist only to ease transition to SkPDFUnion. */
+class SkPDFObjRef : public SkPDFAtom {
 public:
-    SK_DECLARE_INST_COUNT(SkPDFInt)
-
-    /** Create a PDF integer (usually for indirect reference purposes).
-     *  @param value An integer value between 2^31 - 1 and -2^31.
-     */
-    explicit SkPDFInt(int32_t value);
-    virtual ~SkPDFInt();
-
-    // The SkPDFObject interface.
-    virtual void emitObject(SkWStream* stream,
-                            const SkPDFObjNumMap& objNumMap,
-                            const SkPDFSubstituteMap& substitutes) override;
-
-private:
-    int32_t fValue;
-
-    typedef SkPDFObject INHERITED;
+    SK_DECLARE_INST_COUNT(SkPDFObjRef);
+    explicit SkPDFObjRef(SkPDFObject* obj)
+        : INHERITED(SkPDFUnion::ObjRef(SkRef(obj))) {}
+    typedef SkPDFAtom INHERITED;
 };
 
-/** \class SkPDFBool
-
-    An boolean value in a PDF.
-*/
-class SkPDFBool : public SkPDFObject {
+class SkPDFInt : public SkPDFAtom {
 public:
-    SK_DECLARE_INST_COUNT(SkPDFBool)
-
-    /** Create a PDF boolean.
-     *  @param value true or false.
-     */
-    explicit SkPDFBool(bool value);
-    virtual ~SkPDFBool();
-
-    // The SkPDFObject interface.
-    virtual void emitObject(SkWStream* stream,
-                            const SkPDFObjNumMap& objNumMap,
-                            const SkPDFSubstituteMap& substitutes) override;
-
-private:
-    bool fValue;
-
-    typedef SkPDFObject INHERITED;
+    SK_DECLARE_INST_COUNT(SkPDFInt);
+    explicit SkPDFInt(int32_t value) : INHERITED(SkPDFUnion::Int(value)) {}
+    typedef SkPDFAtom INHERITED;
 };
 
-/** \class SkPDFScalar
-
-    A real number object in a PDF.
-*/
-class SkPDFScalar : public SkPDFObject {
+class SkPDFBool : public SkPDFAtom {
 public:
-    SK_DECLARE_INST_COUNT(SkPDFScalar)
+    SK_DECLARE_INST_COUNT(SkPDFBool);
+    explicit SkPDFBool(bool value) : INHERITED(SkPDFUnion::Bool(value)) {}
+    typedef SkPDFAtom INHERITED;
+};
 
-    /** Create a PDF real number.
-     *  @param value A real value.
-     */
-    explicit SkPDFScalar(SkScalar value);
-    virtual ~SkPDFScalar();
-
+class SkPDFScalar : public SkPDFAtom {
+public:
+    SK_DECLARE_INST_COUNT(SkPDFScalar);
+    explicit SkPDFScalar(SkScalar value)
+        : INHERITED(SkPDFUnion::Scalar(value)) {}
     static void Append(SkScalar value, SkWStream* stream);
-
-    // The SkPDFObject interface.
-    virtual void emitObject(SkWStream* stream,
-                            const SkPDFObjNumMap& objNumMap,
-                            const SkPDFSubstituteMap& substitutes) override;
-
-private:
-    SkScalar fValue;
-
-    typedef SkPDFObject INHERITED;
+    typedef SkPDFAtom INHERITED;
 };
 
-/** \class SkPDFString
-
-    A string object in a PDF.
-*/
-class SkPDFString : public SkPDFObject {
+class SkPDFString : public SkPDFAtom {
 public:
-    SK_DECLARE_INST_COUNT(SkPDFString)
-
-    /** Create a PDF string. Maximum length (in bytes) is 65,535.
-     *  @param value A string value.
-     */
-    explicit SkPDFString(const char value[]);
-    explicit SkPDFString(const SkString& value);
-
-    virtual ~SkPDFString();
-
-    // The SkPDFObject interface.
-    virtual void emitObject(SkWStream* stream,
-                            const SkPDFObjNumMap& objNumMap,
-                            const SkPDFSubstituteMap& substitutes) override;
+    SK_DECLARE_INST_COUNT(SkPDFString);
+    explicit SkPDFString(const char value[])
+        : INHERITED(SkPDFUnion::String(value)) {}
+    explicit SkPDFString(const SkString& value)
+        : INHERITED(SkPDFUnion::String(value)) {}
 
     static SkString FormatString(const char* input, size_t len);
-private:
+
     static const size_t kMaxLen = 65535;
 
-    const SkString fValue;
-
-    typedef SkPDFObject INHERITED;
+private:
+    typedef SkPDFAtom INHERITED;
 };
 
-/** \class SkPDFName
-
-    A name object in a PDF.
-*/
-class SkPDFName : public SkPDFObject {
+class SkPDFName : public SkPDFAtom {
 public:
-    SK_DECLARE_INST_COUNT(SkPDFName)
+    SK_DECLARE_INST_COUNT(SkPDFName);
+    /** Create a PDF name object. Maximum length is 127 bytes. */
+    explicit SkPDFName(const char name[])
+        : INHERITED(SkPDFUnion::Name(SkString(name))) {}
+    explicit SkPDFName(const SkString& name)
+        : INHERITED(SkPDFUnion::Name(name)) {}
 
-    /** Create a PDF name object. Maximum length is 127 bytes.
-     *  @param value The name.
-     */
-    explicit SkPDFName(const char name[]);
-    explicit SkPDFName(const SkString& name);
-    virtual ~SkPDFName();
-
-    bool operator==(const SkPDFName& b) const;
-
-    // The SkPDFObject interface.
-    virtual void emitObject(SkWStream* stream,
-                            const SkPDFObjNumMap& objNumMap,
-                            const SkPDFSubstituteMap& substitutes) override;
-
-private:
     static const size_t kMaxLen = 127;
 
-    const SkString fValue;
-
-    static SkString FormatName(const SkString& input);
-
-    typedef SkPDFObject INHERITED;
+private:
+    typedef SkPDFAtom INHERITED;
 };
 
 /** \class SkPDFArray
@@ -229,6 +249,8 @@
 public:
     SK_DECLARE_INST_COUNT(SkPDFArray)
 
+    static const int kMaxLen = 8191;
+
     /** Create a PDF array. Maximum length is 8191.
      */
     SkPDFArray();
@@ -243,7 +265,7 @@
 
     /** The size of the array.
      */
-    int size() { return fValue.count(); }
+    int size() const;
 
     /** Preallocate space for the given number of entries.
      *  @param length The number of array slots to preallocate.
@@ -254,27 +276,26 @@
      *  @param value The value to add to the array.
      *  @return The value argument is returned.
      */
+    // DEPRECATED
     SkPDFObject* append(SkPDFObject* value);
 
-    /** Creates a SkPDFInt object and appends it to the array.
+    /** Appends a value to the end of the array.
      *  @param value The value to add to the array.
      */
-    void appendInt(int32_t value);
-
-    /** Creates a SkPDFScalar object and appends it to the array.
-     *  @param value The value to add to the array.
-     */
-    void appendScalar(SkScalar value);
-
-    /** Creates a SkPDFName object and appends it to the array.
-     *  @param value The value to add to the array.
-     */
-    void appendName(const char name[]);
+    void appendInt(int32_t);
+    void appendBool(bool);
+    void appendScalar(SkScalar);
+    void appendName(const char[]);
+    void appendName(const SkString&);
+    void appendString(const char[]);
+    void appendString(const SkString&);
+    /** appendObject and appendObjRef take ownership of the passed object */
+    void appendObject(SkPDFObject*);
+    void appendObjRef(SkPDFObject*);
 
 private:
-    static const int kMaxLen = 8191;
-    SkTDArray<SkPDFObject*> fValue;
-
+    SkTDArray<SkPDFUnion> fValues;
+    void append(SkPDFUnion&& value);
     typedef SkPDFObject INHERITED;
 };
 
@@ -313,104 +334,53 @@
      *  @param value The value for this dictionary entry.
      *  @return The value argument is returned.
      */
+    // DEPRECATED
     SkPDFObject* insert(SkPDFName* key, SkPDFObject* value);
-
-    /** Add the value to the dictionary with the given key.  Refs value.  The
-     *  method will create the SkPDFName object.
-     *  @param key   The text of the key for this dictionary entry.
-     *  @param value The value for this dictionary entry.
-     *  @return The value argument is returned.
-     */
+    // DEPRECATED
     SkPDFObject* insert(const char key[], SkPDFObject* value);
 
-    /** Add the int to the dictionary with the given key.
+    /** Add the value to the dictionary with the given key.  Takes
+     *  ownership of the object.
      *  @param key   The text of the key for this dictionary entry.
-     *  @param value The int value for this dictionary entry.
+     *  @param value The value for this dictionary entry.
+     */
+    void insertObject(const char key[], SkPDFObject* value);
+    void insertObject(const SkString& key, SkPDFObject* value);
+    void insertObjRef(const char key[], SkPDFObject* value);
+    void insertObjRef(const SkString& key, SkPDFObject* value);
+
+    /** Add the value to the dictionary with the given key.
+     *  @param key   The text of the key for this dictionary entry.
+     *  @param value The value for this dictionary entry.
      */
     void insertInt(const char key[], int32_t value);
-
-    /**
-     *  Calls insertInt() but asserts in debug builds that the value can be represented
-     *  by an int32_t.
-     */
-    void insertInt(const char key[], size_t value) {
-        this->insertInt(key, SkToS32(value));
-    }
-
-    /** Add the scalar to the dictionary with the given key.
-     *  @param key   The text of the key for this dictionary entry.
-     *  @param value The scalar value for this dictionary entry.
-     */
+    void insertInt(const char key[], size_t value);
     void insertScalar(const char key[], SkScalar value);
-
-    /** Add the name to the dictionary with the given key.
-     *  @param key   The text of the key for this dictionary entry.
-     *  @param name  The name for this dictionary entry.
-     */
-    void insertName(const char key[], const char name[]);
-
-    /** Add the name to the dictionary with the given key.
-     *  @param key   The text of the key for this dictionary entry.
-     *  @param name  The name for this dictionary entry.
-     */
-    void insertName(const char key[], const SkString& name) {
-        this->insertName(key, name.c_str());
-    }
+    void insertName(const char key[], const char nameValue[]);
+    void insertName(const char key[], const SkString& nameValue);
+    void insertString(const char key[], const char value[]);
+    void insertString(const char key[], const SkString& value);
 
     /** Remove all entries from the dictionary.
      */
     void clear();
 
-protected:
-    /** Use to remove a single key from the dictionary.
-     */
-    void remove(const char key[]);
-
-    /** Insert references to all of the key-value pairs from the other
-     *  dictionary into this one.
-     */
-    void mergeFrom(const SkPDFDict& other);
-
 private:
-    struct Rec {
-        SkPDFName* key;
-        SkPDFObject* value;
-        Rec(SkPDFName* k, SkPDFObject* v) : key(k), value(v) {}
+    struct Record {
+        SkPDFUnion fKey;
+        SkPDFUnion fValue;
     };
-
+    SkTDArray<Record> fRecords;
     static const int kMaxLen = 4095;
 
-    SkTDArray<struct Rec> fValue;
-
-    SkPDFObject* append(SkPDFName* key, SkPDFObject* value);
+    void set(const SkPDFUnion& name, const SkPDFUnion& value);
+    void set(SkPDFUnion&& name, SkPDFUnion&& value);
 
     typedef SkPDFObject INHERITED;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 
-/** \class SkPDFSubstituteMap
-
-    The PDF Substitute Map manages substitute objects and owns the
-    substitutes.
-*/
-class SkPDFSubstituteMap : SkNoncopyable {
-public:
-    ~SkPDFSubstituteMap();
-    /** Set substitute object for the passed object.
-        Refs substitute.
-     */
-    void setSubstitute(SkPDFObject* original, SkPDFObject* substitute);
-
-    /** Find and return any substitute object set for the passed object. If
-     *  there is none, return the passed object.
-     */
-    SkPDFObject* getSubstitute(SkPDFObject* object) const;
-
-private:
-    SkTHashMap<SkPDFObject*, SkPDFObject*> fSubstituteMap;
-};
-
 /** \class SkPDFObjNumMap
 
     The PDF Object Number Map manages object numbers.  It is used to
@@ -436,4 +406,32 @@
     SkTHashMap<SkPDFObject*, int32_t> fObjectNumbers;
 };
 
+////////////////////////////////////////////////////////////////////////////////
+
+/** \class SkPDFSubstituteMap
+
+    The PDF Substitute Map manages substitute objects and owns the
+    substitutes.
+*/
+class SkPDFSubstituteMap : SkNoncopyable {
+public:
+    ~SkPDFSubstituteMap();
+    /** Set substitute object for the passed object.
+        Refs substitute.
+     */
+    void setSubstitute(SkPDFObject* original, SkPDFObject* substitute);
+
+    /** Find and return any substitute object set for the passed object. If
+     *  there is none, return the passed object.
+     */
+    SkPDFObject* getSubstitute(SkPDFObject* object) const;
+
+    SkPDFObject* operator()(SkPDFObject* o) const {
+        return this->getSubstitute(o);
+    }
+
+private:
+    SkTHashMap<SkPDFObject*, SkPDFObject*> fSubstituteMap;
+};
+
 #endif
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index 99bf7ac..7f92e43 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -69,8 +69,12 @@
     SkDynamicMemoryWStream buffer;
     emit_object(obj, &buffer, catalog, substituteMap, false);
     REPORTER_ASSERT(reporter, directSize == buffer.getOffset());
-    REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedData,
-                                            directSize));
+    if (!stream_equals(buffer, 0, expectedData, directSize)) {
+        SkAutoTDelete<SkStreamAsset> asset(buffer.detachAsStream());
+        SkString s(asset->getLength());
+        asset->read(s.writable_str(), s.size());
+        ERRORF(reporter, "!stream_equals() '%s' '%s'", expectedData, s.c_str());
+    }
 
     if (indirect) {
         // Indirect output.
@@ -272,13 +276,11 @@
 
     SkAutoTUnref<SkPDFDict> dict(new SkPDFDict);
     SimpleCheckObjectOutput(reporter, dict.get(), "<<>>");
-    SkAutoTUnref<SkPDFName> n1(new SkPDFName("n1"));
-    dict->insert(n1.get(), int42.get());
+    dict->insert("n1", int42.get());
     SimpleCheckObjectOutput(reporter, dict.get(), "<</n1 42>>");
-    SkAutoTUnref<SkPDFName> n2(new SkPDFName("n2"));
-    SkAutoTUnref<SkPDFName> n3(new SkPDFName("n3"));
-    dict->insert(n2.get(), realHalf.get());
-    dict->insert(n3.get(), array.get());
+    SkString n3("n3");
+    dict->insert("n2", realHalf.get());
+    dict->insertObject(n3, array.detach());
     SimpleCheckObjectOutput(reporter, dict.get(),
                             "<</n1 42\n/n2 0.5\n/n3 [42 0.5 0]>>");