Initial PDF backend commit: directories, build rules, primitives

This change establishes and tests the building blocks of the PDF file format.
For now, PDF code is not compiled by default.

Review URL: http://codereview.appspot.com/1950044

git-svn-id: http://skia.googlecode.com/svn/trunk@600 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
new file mode 100644
index 0000000..83f133e
--- /dev/null
+++ b/src/pdf/SkPDFTypes.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SkPDFCatalog.h"
+#include "SkPDFTypes.h"
+#include "SkStream.h"
+
+size_t SkPDFObject::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    SkDynamicMemoryWStream buffer;
+    emitObject(&buffer, catalog, indirect);
+    return buffer.getOffset();
+}
+
+void SkPDFObject::emitIndirectObject(SkWStream* stream, SkPDFCatalog* catalog) {
+    catalog->emitObjectNumber(stream, this);
+    stream->writeText(" obj\n");
+    emitObject(stream, catalog, false);
+    stream->writeText("\nendobj\n");
+}
+
+size_t SkPDFObject::getIndirectOutputSize(SkPDFCatalog* catalog) {
+    return catalog->getObjectNumberSize(this) + strlen(" obj\n") +
+        this->getOutputSize(catalog, false) + strlen("\nendobj\n");
+}
+
+void SkPDFObjRef::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                             bool indirect) {
+    SkASSERT(!indirect);
+    catalog->emitObjectNumber(stream, fObj.get());
+    stream->writeText(" R");
+}
+
+size_t SkPDFObjRef::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    SkASSERT(!indirect);
+    return catalog->getObjectNumberSize(fObj.get()) + strlen(" R");
+}
+
+void SkPDFInt::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                          bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+    stream->writeDecAsText(fValue);
+}
+
+void SkPDFScalar::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                             bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+    stream->writeScalarAsText(fValue);
+}
+
+SkPDFString::SkPDFString(const char value[])
+    : fValue(formatString(SkString(value))) {
+}
+
+SkPDFString::SkPDFString(const SkString& value)
+    : fValue(formatString(value)) {
+}
+
+void SkPDFString::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                             bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+    stream->write(fValue.c_str(), fValue.size());
+}
+
+size_t SkPDFString::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    if (indirect)
+        return getIndirectOutputSize(catalog);
+    return fValue.size();
+}
+
+SkString SkPDFString::formatString(const SkString& input) {
+    SkASSERT(input.size() <= 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;
+    for (size_t i = 0; i < input.size(); i++) {
+        if (input[i] > 0x7F || input[i] < ' ') {
+            sevenBitClean = false;
+            break;
+        }
+    }
+
+    SkString result;
+    if (sevenBitClean) {
+        result.append("(");
+        for (size_t i = 0; i < input.size(); i++) {
+            if (input[i] == '\\' || input[i] == '(' || input[i] == ')')
+                result.append("\\");
+            result.append(input.c_str() + i, 1);
+        }
+        result.append(")");
+    } else {
+        result.append("<");
+        for (size_t i = 0; i < input.size(); i++)
+            result.appendHex(input[i], 2);
+        result.append(">");
+    }
+
+    return result;
+}
+
+SkPDFName::SkPDFName(const char name[]) : fValue(formatName(SkString(name))) {}
+SkPDFName::SkPDFName(const SkString& name) : fValue(formatName(name)) {}
+
+void SkPDFName::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                           bool indirect) {
+    SkASSERT(!indirect);
+    stream->write(fValue.c_str(), fValue.size());
+}
+
+size_t SkPDFName::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    SkASSERT(!indirect);
+    return fValue.size();
+}
+
+SkString SkPDFName::formatName(const SkString& input) {
+    SkASSERT(input.size() <= kMaxLen);
+
+    SkString result("/");
+    for (size_t i = 0; i < input.size(); i++) {
+        if (input[i] > 0x7F || input[i] < '!' || input[i] == '#') {
+            result.append("#");
+            result.appendHex(input[i], 2);
+        } else {
+            result.append(input.c_str() + i, 1);
+        }
+    }
+
+    return result;
+}
+
+SkPDFArray::~SkPDFArray() {
+    fValue.safeUnrefAll();
+}
+
+void SkPDFArray::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+
+    stream->writeText("[");
+    for (int i = 0; i < fValue.count(); i++) {
+        fValue[i]->emitObject(stream, catalog, false);
+        if (i + 1 < fValue.count())
+            stream->writeText(" ");
+    }
+    stream->writeText("]");
+}
+
+size_t SkPDFArray::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    if (indirect)
+        return getIndirectOutputSize(catalog);
+
+    size_t result = strlen("[]");
+    if (fValue.count())
+        result += fValue.count() - 1;
+    for (int i = 0; i < fValue.count(); i++)
+        result += fValue[i]->getOutputSize(catalog, false);
+    return result;
+}
+
+void SkPDFArray::reserve(int length) {
+    SkASSERT(length <= kMaxLen);
+    fValue.setReserve(length);
+}
+
+void SkPDFArray::setAt(int offset, SkPDFObject* value) {
+    SkASSERT(offset < fValue.count());
+    SkSafeUnref(fValue[offset]);
+    fValue[offset] = value;
+    SkSafeRef(fValue[offset]);
+}
+
+void SkPDFArray::append(SkPDFObject* value) {
+    SkASSERT(fValue.count() < kMaxLen);
+    SkSafeRef(value);
+    fValue.push(value);
+}
+
+SkPDFDict::~SkPDFDict() {
+    for (int i = 0; i < fValue.count(); i++) {
+        SkSafeUnref(fValue[i].key);
+        SkSafeUnref(fValue[i].value);
+    }
+}
+
+void SkPDFDict::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                           bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+
+    stream->writeText("<<");
+    for (int i = 0; i < fValue.count(); i++) {
+        fValue[i].key->emitObject(stream, catalog, false);
+        stream->writeText(" ");
+        fValue[i].value->emitObject(stream, catalog, false);
+        stream->writeText("\n");
+    }
+    stream->writeText(">>");
+}
+
+size_t SkPDFDict::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    if (indirect)
+        return getIndirectOutputSize(catalog);
+
+    size_t result = strlen("<<>>") + (fValue.count() * 2);
+    for (int i = 0; i < fValue.count(); i++) {
+        result += fValue[i].key->getOutputSize(catalog, false);
+        result += fValue[i].value->getOutputSize(catalog, false);
+    }
+    return result;
+}
+
+void SkPDFDict::insert(SkPDFName* key, SkPDFObject* value) {
+    struct Rec* newEntry = fValue.append();
+    newEntry->key = key;
+    SkSafeRef(newEntry->key);
+    newEntry->value = value;
+    SkSafeRef(newEntry->value);
+}