From 8459d4e5e32608ec6da3f2b81731aaeb7b038843 Mon Sep 17 00:00:00 2001 From: "vandebo@chromium.org" Date: Fri, 24 Sep 2010 22:25:30 +0000 Subject: 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 --- Makefile | 11 ++ include/pdf/SkPDFCatalog.h | 84 ++++++++++++++ include/pdf/SkPDFStream.h | 57 ++++++++++ include/pdf/SkPDFTypes.h | 266 ++++++++++++++++++++++++++++++++++++++++++++ src/pdf/SkPDFCatalog.cpp | 72 ++++++++++++ src/pdf/SkPDFStream.cpp | 53 +++++++++ src/pdf/SkPDFTypes.cpp | 236 +++++++++++++++++++++++++++++++++++++++ src/pdf/pdf_files.mk | 5 + tests/PDFPrimitivesTest.cpp | 176 +++++++++++++++++++++++++++++ 9 files changed, 960 insertions(+) create mode 100644 include/pdf/SkPDFCatalog.h create mode 100644 include/pdf/SkPDFStream.h create mode 100644 include/pdf/SkPDFTypes.h create mode 100644 src/pdf/SkPDFCatalog.cpp create mode 100644 src/pdf/SkPDFStream.cpp create mode 100644 src/pdf/SkPDFTypes.cpp create mode 100644 src/pdf/pdf_files.mk create mode 100644 tests/PDFPrimitivesTest.cpp diff --git a/Makefile b/Makefile index 970f0b9ead..65f0b715b4 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,13 @@ SRC_LIST += $(addprefix src/images/, $(SOURCE)) include src/utils/utils_files.mk SRC_LIST += $(addprefix src/utils/, $(SOURCE)) +# pdf backend files +ifeq ($(SKIA_PDF_SUPPORT),true) + C_INCLUDES += -Iinclude/pdf + include src/pdf/pdf_files.mk + SRC_LIST += $(addprefix src/pdf/, $(SOURCE)) +endif + # extra files we want to build to prevent bit-rot, but not link JUST_COMPILE_LIST := src/ports/SkFontHost_tables.cpp @@ -137,6 +144,9 @@ bench: $(BENCH_OBJS) out/libskia.a C_INCLUDES += -Isrc/core include tests/tests_files.mk +ifeq ($(SKIA_PDF_SUPPORT),true) + SOURCE += PDFPrimitivesTest.cpp +endif TESTS_SRCS := $(addprefix tests/, $(SOURCE)) TESTS_OBJS := $(TESTS_SRCS:.cpp=.o) @@ -212,4 +222,5 @@ help: @echo " SKIA_DEBUG=true for debug build" @echo " SKIA_SCALAR=fixed for fixed-point build" @echo " SKIA_BUILD_FOR=mac for mac build (e.g. CG for image decoding)" + @echo " SKIA_PDF_SUPPORT=true to enable the pdf generation backend" @echo "" diff --git a/include/pdf/SkPDFCatalog.h b/include/pdf/SkPDFCatalog.h new file mode 100644 index 0000000000..fa159384de --- /dev/null +++ b/include/pdf/SkPDFCatalog.h @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#ifndef SkPDFCatalog_DEFINED +#define SkPDFCatalog_DEFINED + +#include "SkPDFTypes.h" +#include "SkRefCnt.h" +#include "SkTDArray.h" + +/** \class SkPDFCatalog + + The PDF catalog object manages object numbers and when emitted to the + PDF stream, indexes all the objects in the file by offset. +*/ +class SkPDFCatalog { +public: + /** Create a PDF catalog. + */ + SkPDFCatalog() + : fNextObjNum(1), + fStartedAssigningObjNums(false), + fAssigningFirstPageObjNums(false) { + } + virtual ~SkPDFCatalog() {} + + /** Add the passed object to the catalog. + * @param obj The object to add. + * @param onFirstPage Is the object on the first page. + */ + void addObject(SkPDFObject* obj, bool onFirstPage); + + /** Output the object number for the passed object. + * @param obj The object of interest. + * @param stream The writable output stream to send the output to. + */ + void emitObjectNumber(SkWStream* stream, SkPDFObject* obj); + + /** Return the number of bytes that would be emitted for the passed + * object's object number. + * @param obj The object of interest + */ + size_t getObjectNumberSize(SkPDFObject* obj); + +private: + struct Rec { + Rec(SkPDFObject* object, bool onFirstPage) + : fObject(object), + fFileOffset(0), + fObjNumAssigned(false), + fOnFirstPage(onFirstPage) { + } + SkPDFObject* fObject; + off_t fFileOffset; + bool fObjNumAssigned; + bool fOnFirstPage; + }; + + // TODO(vandebo) Make this a hash if it's a performance problem. + SkTDArray fCatalog; + + uint32_t fNextObjNum; + bool fStartedAssigningObjNums; + bool fAssigningFirstPageObjNums; + + int findObjectIndex(SkPDFObject* obj) const; + + int assignObjNum(SkPDFObject* obj); +}; + +#endif diff --git a/include/pdf/SkPDFStream.h b/include/pdf/SkPDFStream.h new file mode 100644 index 0000000000..b83aa591f6 --- /dev/null +++ b/include/pdf/SkPDFStream.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#ifndef SkPDFStream_DEFINED +#define SkPDFStream_DEFINED + +#include "SkPDFTypes.h" +#include "SkRefCnt.h" +#include "SkTemplates.h" + +class SkStream; +class SkPDFCatalog; + +/** \class SkPDFStream + + A stream object in a PDF. +*/ +// TODO(vandebo) This should handle filters as well. +class SkPDFStream : public SkPDFObject { +public: + /** Create a PDF stream. A Length entry is automatically added to the + * stream dictionary. + * @param stream The data part of the stream. + */ + explicit SkPDFStream(SkStream* stream); + virtual ~SkPDFStream(); + + // The SkPDFObject interface. + virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect); + virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect); + + /** Add the value to the stream dictionary with the given key. + * @param index The index into the array to set. + * @param value The value to add to the array. + */ + void insert(SkPDFName* key, SkPDFObject* value); + +private: + SkPDFDict fDict; + SkRefPtr fData; +}; + +#endif diff --git a/include/pdf/SkPDFTypes.h b/include/pdf/SkPDFTypes.h new file mode 100644 index 0000000000..247bd3712e --- /dev/null +++ b/include/pdf/SkPDFTypes.h @@ -0,0 +1,266 @@ +/* + * 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. + */ + +#ifndef SkPDFTypes_DEFINED +#define SkPDFTypes_DEFINED + +#include "SkRefCnt.h" +#include "SkScalar.h" +#include "SkString.h" +#include "SkTDArray.h" + +class SkPDFCatalog; +class SkWStream; + +/** \class SkPDFObject + + 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: + /** Create a PDF object. + */ + SkPDFObject() {} + virtual ~SkPDFObject() {} + + /** Subclasses must implement this method to print the object to the + * PDF file. + * @param catalog The object catalog to use. + * @param indirect If true, output an object identifier with the object. + * @param stream The writable output stream to send the output to. + */ + virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) = 0; + + /** Return the size (number of bytes) of this object in the final output + * file. Compound objects or objects that are computationally intensive + * to output should override this method. + * @param catalog The object catalog to use. + * @param indirect If true, output an object identifier with the object. + */ + virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect); + + /** Helper function to output an indirect object. + * @param catalog The object catalog to use. + * @param stream The writable output stream to send the output to. + */ + void emitIndirectObject(SkWStream* stream, SkPDFCatalog* catalog); + + /** Helper function to find the size of an indirect object. + * @param catalog The object catalog to use. + */ + size_t getIndirectOutputSize(SkPDFCatalog* catalog); +}; + +/** \class SkPDFObjRef + + An indirect reference to a PDF object. +*/ +class SkPDFObjRef : public SkPDFObject { +public: + /** Create a reference to an existing SkPDFObject. + * @param obj The object to reference. + */ + explicit SkPDFObjRef(SkPDFObject* obj) : fObj(obj) {} + virtual ~SkPDFObjRef() {} + + // The SkPDFObject interface. + virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect); + virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect); + +private: + SkRefPtr fObj; +}; + +/** \class SkPDFInt + + An integer object in a PDF. +*/ +class SkPDFInt : public SkPDFObject { +public: + /** Create a PDF integer (usually for indirect reference purposes). + * @param value An integer value between 2^31 - 1 and -2^31. + */ + SkPDFInt(int32_t value) : fValue(value) {} + virtual ~SkPDFInt() {} + + // The SkPDFObject interface. + virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect); + +private: + int32_t fValue; +}; + +/** \class SkPDFScalar + + A real number object in a PDF. +*/ +class SkPDFScalar : public SkPDFObject { +public: + /** Create a PDF real number. + * @param value A real value. + */ + SkPDFScalar(SkScalar value) : fValue(value) {} + virtual ~SkPDFScalar() {} + + // The SkPDFObject interface. + virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect); + +private: + SkScalar fValue; +}; + +/** \class SkPDFString + + A string object in a PDF. +*/ +class SkPDFString : public SkPDFObject { +public: + /** Create a PDF string. Maximum length (in bytes) is 65,535. + * @param value A string value. + */ + SkPDFString(const char value[]); + SkPDFString(const SkString& value); + virtual ~SkPDFString() {} + + // The SkPDFObject interface. + virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect); + virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect); + +private: + static const int kMaxLen = 65535; + + const SkString fValue; + + SkString formatString(const SkString& input); +}; + +/** \class SkPDFName + + A name object in a PDF. +*/ +class SkPDFName : public SkPDFObject { +public: + /** Create a PDF name object. Maximum length is 127 bytes. + * @param value The name. + */ + SkPDFName(const char name[]); + SkPDFName(const SkString& name); + virtual ~SkPDFName() {} + + // The SkPDFObject interface. + virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect); + virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect); + +private: + static const int kMaxLen = 127; + + const SkString fValue; + + SkString formatName(const SkString& input); +}; + +/** \class SkPDFArray + + An array object in a PDF. +*/ +class SkPDFArray : public SkPDFObject { +public: + /** Create a PDF array. Maximum length is 8191. + */ + SkPDFArray() {} + virtual ~SkPDFArray(); + + // The SkPDFObject interface. + virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect); + virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect); + + /** The size of the array. + */ + int size() { return fValue.count(); } + + /** Preallocate space for the given number of entries. + * @param length The number of array slots to preallocate. + */ + void reserve(int length); + + /** Returns the object at the given offset in the array. + * @param index The index into the array to retrieve. + */ + SkPDFObject* getAt(int index) { return fValue[index]; } + + /** Set the object at the given offset in the array. + * @param index The index into the array to set. + * @param value The value to add to the array. + */ + void setAt(int index, SkPDFObject* value); + + /** Append the object to the end of the array. + * @param value The value to add to the array. + */ + void append(SkPDFObject* value); + +private: + static const int kMaxLen = 8191; + SkTDArray fValue; +}; + +/** \class SkPDFDict + + A dictionary object in a PDF. +*/ +class SkPDFDict : public SkPDFObject { +public: + /** Create a PDF dictionary. Maximum number of entries is 4095. + */ + SkPDFDict() {} + virtual ~SkPDFDict(); + + // The SkPDFObject interface. + virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect); + virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect); + + /** The size of the dictionary. + */ + int size() { return fValue.count(); } + + /** Add the value to the dictionary with the given key. + * @param key The key for this dictionary entry. + * @param value The value for this dictionary entry. + */ + void insert(SkPDFName* key, SkPDFObject* value); + +private: + static const int kMaxLen = 4095; + + struct Rec { + SkPDFName* key; + SkPDFObject* value; + }; + + SkTDArray fValue; +}; + +#endif diff --git a/src/pdf/SkPDFCatalog.cpp b/src/pdf/SkPDFCatalog.cpp new file mode 100644 index 0000000000..dd3cd7eab6 --- /dev/null +++ b/src/pdf/SkPDFCatalog.cpp @@ -0,0 +1,72 @@ +/* + * 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" + +void SkPDFCatalog::addObject(SkPDFObject* obj, bool onFirstPage) { + SkASSERT(findObjectIndex(obj) == -1); + SkASSERT(!fStartedAssigningObjNums); + + struct Rec newEntry(obj, onFirstPage); + fCatalog.append(1, &newEntry); +} + +void SkPDFCatalog::emitObjectNumber(SkWStream* stream, SkPDFObject* obj) { + stream->writeDecAsText(assignObjNum(obj)); + stream->writeText(" 0"); // Generation number is always 0. +} + +size_t SkPDFCatalog::getObjectNumberSize(SkPDFObject* obj) { + SkDynamicMemoryWStream buffer; + emitObjectNumber(&buffer, obj); + return buffer.getOffset(); +} + +int SkPDFCatalog::findObjectIndex(SkPDFObject* obj) const { + for (int i = 0; i < fCatalog.count(); i++) { + if (fCatalog[i].fObject == obj) + return i; + } + return -1; +} + +int SkPDFCatalog::assignObjNum(SkPDFObject* obj) { + int pos = findObjectIndex(obj); + SkASSERT(pos >= 0); + uint32_t currentIndex = pos; + if (fCatalog[currentIndex].fObjNumAssigned) + return currentIndex + 1; + + fStartedAssigningObjNums = true; + if (fCatalog[currentIndex].fOnFirstPage) { + fAssigningFirstPageObjNums = true; + } else { + SkASSERT(!fAssigningFirstPageObjNums); + } + + // When we assign an object an object number, we put it in that array + // offset (minus 1 because object number 0 is reserved). + if (fNextObjNum - 1 != currentIndex) { + Rec other = fCatalog[fNextObjNum - 1]; + fCatalog[fNextObjNum - 1] = fCatalog[currentIndex]; + fCatalog[currentIndex] = other; + } + fCatalog[fNextObjNum - 1].fObjNumAssigned = true; + fNextObjNum++; + return fNextObjNum - 1; +} diff --git a/src/pdf/SkPDFStream.cpp b/src/pdf/SkPDFStream.cpp new file mode 100644 index 0000000000..a7fbc62521 --- /dev/null +++ b/src/pdf/SkPDFStream.cpp @@ -0,0 +1,53 @@ +/* + * 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 "SkPDFStream.h" +#include "SkStream.h" + +SkPDFStream::SkPDFStream(SkStream* stream) : fData(stream) { + SkRefPtr lenKey = new SkPDFName("Length"); + lenKey->unref(); // SkRefPtr and new both took a reference. + SkRefPtr lenValue = new SkPDFInt(fData->read(NULL, 0)); + lenValue->unref(); // SkRefPtr and new both took a reference. + fDict.insert(lenKey.get(), lenValue.get()); +} + +SkPDFStream::~SkPDFStream() { +} + +void SkPDFStream::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + + fDict.emitObject(stream, catalog, false); + stream->writeText(" stream\n"); + stream->write(fData->getMemoryBase(), fData->read(NULL, 0)); + stream->writeText("endstream\n"); +} + +size_t SkPDFStream::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + if (indirect) + return getIndirectOutputSize(catalog); + + return fDict.getOutputSize(catalog, false) + + strlen(" stream\nendstream\n") + fData->read(NULL, 0); +} + +void SkPDFStream::insert(SkPDFName* key, SkPDFObject* value) { + fDict.insert(key, value); +} diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp new file mode 100644 index 0000000000..83f133eda2 --- /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); +} diff --git a/src/pdf/pdf_files.mk b/src/pdf/pdf_files.mk new file mode 100644 index 0000000000..5e41eeacdb --- /dev/null +++ b/src/pdf/pdf_files.mk @@ -0,0 +1,5 @@ +SOURCE := \ + SkPDFCatalog.cpp \ + SkPDFStream.cpp \ + SkPDFTypes.cpp \ + diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp new file mode 100644 index 0000000000..81d8c0a7b0 --- /dev/null +++ b/tests/PDFPrimitivesTest.cpp @@ -0,0 +1,176 @@ +#include + +#include "Test.h" +#include "SkPDFCatalog.h" +#include "SkPDFStream.h" +#include "SkPDFTypes.h" +#include "SkScalar.h" +#include "SkStream.h" + +static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj, + const std::string& representation, + bool indirect) { + size_t directSize = obj->getOutputSize(NULL, false); + REPORTER_ASSERT(reporter, directSize == representation.size()); + + SkDynamicMemoryWStream buffer; + obj->emitObject(&buffer, NULL, false); + REPORTER_ASSERT(reporter, directSize == buffer.getOffset()); + REPORTER_ASSERT(reporter, memcmp(buffer.getStream(), representation.c_str(), + directSize) == 0); + + if (indirect) { + // Indirect output. + static char header[] = "1 0 obj\n"; + static size_t headerLen = strlen(header); + static char footer[] = "\nendobj\n"; + static size_t footerLen = strlen(footer); + + SkPDFCatalog catalog; + catalog.addObject(obj, false); + + size_t indirectSize = obj->getOutputSize(&catalog, true); + REPORTER_ASSERT(reporter, + indirectSize == directSize + headerLen + footerLen); + + buffer.reset(); + obj->emitObject(&buffer, &catalog, true); + REPORTER_ASSERT(reporter, indirectSize == buffer.getOffset()); + REPORTER_ASSERT(reporter, memcmp(buffer.getStream(), header, + headerLen) == 0); + REPORTER_ASSERT(reporter, + memcmp(buffer.getStream() + headerLen, + representation.c_str(), directSize) == 0); + REPORTER_ASSERT(reporter, + memcmp(buffer.getStream() + headerLen + directSize, + footer, footerLen) == 0); + } +} + +static void TestCatalog(skiatest::Reporter* reporter) { + SkPDFCatalog catalog; + SkRefPtr int1 = new SkPDFInt(1); + int1->unref(); // SkRefPtr and new both took a reference. + SkRefPtr int2 = new SkPDFInt(2); + int2->unref(); // SkRefPtr and new both took a reference. + SkRefPtr int3 = new SkPDFInt(3); + int3->unref(); // SkRefPtr and new both took a reference. + SkRefPtr int1Again(int1.get()); + + catalog.addObject(int1.get(), false); + catalog.addObject(int2.get(), false); + catalog.addObject(int3.get(), false); + + REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3); + REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int2.get()) == 3); + REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int3.get()) == 3); + + SkDynamicMemoryWStream buffer; + catalog.emitObjectNumber(&buffer, int1.get()); + catalog.emitObjectNumber(&buffer, int2.get()); + catalog.emitObjectNumber(&buffer, int3.get()); + catalog.emitObjectNumber(&buffer, int1Again.get()); + char expectedResult[] = "1 02 03 01 0"; + REPORTER_ASSERT(reporter, memcmp(buffer.getStream(), expectedResult, + strlen(expectedResult)) == 0); +} + +static void TestObjectRef(skiatest::Reporter* reporter) { + SkRefPtr int1 = new SkPDFInt(1); + int1->unref(); // SkRefPtr and new both took a reference. + SkRefPtr int2 = new SkPDFInt(2); + int2->unref(); // SkRefPtr and new both took a reference. + SkRefPtr int2ref = new SkPDFObjRef(int2.get()); + int2ref->unref(); // SkRefPtr and new both took a reference. + + SkPDFCatalog catalog; + catalog.addObject(int1.get(), false); + catalog.addObject(int2.get(), false); + REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3); + REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int2.get()) == 3); + + char expectedResult[] = "2 0 R"; + SkDynamicMemoryWStream buffer; + int2ref->emitObject(&buffer, &catalog, false); + REPORTER_ASSERT(reporter, buffer.getOffset() == strlen(expectedResult)); + REPORTER_ASSERT(reporter, memcmp(buffer.getStream(), expectedResult, + buffer.getOffset()) == 0); +} + +static void TestPDFPrimitives(skiatest::Reporter* reporter) { + SkRefPtr int42 = new SkPDFInt(42); + int42->unref(); // SkRefPtr and new both took a reference. + CheckObjectOutput(reporter, int42.get(), "42", true); + + SkRefPtr realHalf = new SkPDFScalar(SK_ScalarHalf); + realHalf->unref(); // SkRefPtr and new both took a reference. + CheckObjectOutput(reporter, realHalf.get(), "0.5", true); + + SkRefPtr stringSimple = new SkPDFString("test ) string ( foo"); + stringSimple->unref(); // SkRefPtr and new both took a reference. + CheckObjectOutput(reporter, stringSimple.get(), "(test \\) string \\( foo)", + true); + SkRefPtr stringComplex = + new SkPDFString("\ttest ) string ( foo"); + stringComplex->unref(); // SkRefPtr and new both took a reference. + CheckObjectOutput(reporter, stringComplex.get(), + "<0974657374202920737472696E67202820666F6F>", true); + + SkRefPtr name = new SkPDFName("Test name\twith#tab"); + name->unref(); // SkRefPtr and new both took a reference. + CheckObjectOutput(reporter, name.get(), "/Test#20name#09with#23tab", false); + + SkRefPtr array = new SkPDFArray; + array->unref(); // SkRefPtr and new both took a reference. + CheckObjectOutput(reporter, array.get(), "[]", true); + array->append(int42.get()); + CheckObjectOutput(reporter, array.get(), "[42]", true); + array->append(realHalf.get()); + CheckObjectOutput(reporter, array.get(), "[42 0.5]", true); + SkRefPtr int0 = new SkPDFInt(0); + int0->unref(); // SkRefPtr and new both took a reference. + array->append(int0.get()); + CheckObjectOutput(reporter, array.get(), "[42 0.5 0]", true); + SkRefPtr int1 = new SkPDFInt(1); + int1->unref(); // SkRefPtr and new both took a reference. + array->setAt(0, int1.get()); + CheckObjectOutput(reporter, array.get(), "[1 0.5 0]", true); + + SkRefPtr dict = new SkPDFDict; + dict->unref(); // SkRefPtr and new both took a reference. + CheckObjectOutput(reporter, dict.get(), "<<>>", true); + SkRefPtr n1 = new SkPDFName("n1"); + n1->unref(); // SkRefPtr and new both took a reference. + dict->insert(n1.get(), int42.get()); + CheckObjectOutput(reporter, dict.get(), "<>", true); + SkRefPtr n2 = new SkPDFName("n2"); + n2->unref(); // SkRefPtr and new both took a reference. + SkRefPtr n3 = new SkPDFName("n3"); + n3->unref(); // SkRefPtr and new both took a reference. + dict->insert(n2.get(), realHalf.get()); + dict->insert(n3.get(), array.get()); + CheckObjectOutput(reporter, dict.get(), + "<>", true); + + char streamBytes[] = "Test\nFoo\tBar"; + SkRefPtr streamData = new SkMemoryStream( + streamBytes, strlen(streamBytes), true); + streamData->unref(); // SkRefPtr and new both took a reference. + SkRefPtr stream = new SkPDFStream(streamData.get()); + stream->unref(); // SkRefPtr and new both took a reference. + CheckObjectOutput(reporter, stream.get(), + "<> stream\nTest\nFoo\tBarendstream\n", + true); + stream->insert(n1.get(), int42.get()); + CheckObjectOutput(reporter, stream.get(), + "<> stream\nTest\nFoo\tBar" + "endstream\n", + true); + + TestCatalog(reporter); + + TestObjectRef(reporter); +} + +#include "TestClassDef.h" +DEFINE_TESTCLASS("PDFPrimitives", PDFPrimitivesTestClass, TestPDFPrimitives) -- cgit v1.2.3