[PDF] Make stream compression optional on a per device basis.

There are a lot of small pieces to make this change work:
- SkPDFDocument (and SkPDFCatalog) take flags to disable compression (and font embedding - not implemented yet, can disable font subsetting for now).
- SkPDFStream now defers compression until the size/emit step.
- Classes that *had* a stream (because they didn't know the stream size at construction time) now *are* streams to make the substitution work correctly.
- The SkPDFShader implementation got pulled apart into two classes, one that is a SkPDFDict, and one that is a SkPDFStream (making the common ancestor SkPDFObject).
- Added helper methods in SkPDFObject for children that have simple resource lists.
- Added an iterator to SkPDFDict so that a substitute SkPDFStream can get a copy of the stream dictionary.
- Change SkPDFDocument to have a pointer to an SkPDFCatalog to remove a new circular header reference.

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

git-svn-id: http://skia.googlecode.com/svn/trunk@1911 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
vandebo@chromium.org 2011-07-20 17:39:01 +00:00
parent f6c3ebdeb1
commit 421d6443fb
20 changed files with 488 additions and 360 deletions

View File

@ -19,6 +19,7 @@
#include <sys/types.h>
#include "SkPDFDocument.h"
#include "SkPDFTypes.h"
#include "SkRefCnt.h"
#include "SkTDArray.h"
@ -32,7 +33,7 @@ class SK_API SkPDFCatalog {
public:
/** Create a PDF catalog.
*/
SkPDFCatalog();
explicit SkPDFCatalog(SkPDFDocument::Flags flags);
~SkPDFCatalog();
/** Add the passed object to the catalog. Refs obj.
@ -62,6 +63,10 @@ public:
*/
size_t getObjectNumberSize(SkPDFObject* obj);
/** Return the document flags in effect for this catalog/document.
*/
SkPDFDocument::Flags getDocumentFlags() const { return fDocumentFlags; }
/** Output the cross reference table for objects in the catalog.
* Returns the total number of objects.
* @param stream The writable output stream to send the output to.
@ -127,6 +132,8 @@ private:
// Next object number to assign on the first page.
uint32_t fNextFirstPageObjNum;
SkPDFDocument::Flags fDocumentFlags;
int findObjectIndex(SkPDFObject* obj) const;
int assignObjNum(SkPDFObject* obj);

View File

@ -170,7 +170,7 @@ private:
SkTDArray<SkPDFGraphicState*> fGraphicStateResources;
SkTDArray<SkPDFObject*> fXObjectResources;
SkTDArray<SkPDFFont*> fFontResources;
SkTDArray<SkPDFShader*> fShaderResources;
SkTDArray<SkPDFObject*> fShaderResources;
SkTScopedPtr<ContentEntry> fContentEntries;
ContentEntry* fLastContentEntry;

View File

@ -17,11 +17,12 @@
#ifndef SkPDFDocument_DEFINED
#define SkPDFDocument_DEFINED
#include "SkPDFCatalog.h"
#include "SkPDFTypes.h"
#include "SkRefCnt.h"
#include "SkTDArray.h"
#include "SkTScopedPtr.h"
class SkPDFCatalog;
class SkPDFDevice;
class SkPDFPage;
class SkWSteam;
@ -32,9 +33,15 @@ class SkWSteam;
*/
class SkPDFDocument {
public:
enum Flags {
kNoCompression_Flag = 0x01, //!< mask disable stream compression.
kNoEmbedding_Flag = 0x02, //!< mask do not embed fonts.
kDraftMode_Flags = 0x03,
};
/** Create a PDF document.
*/
SK_API SkPDFDocument();
explicit SK_API SkPDFDocument(Flags flags = (Flags)0);
SK_API ~SkPDFDocument();
/** Output the PDF to the passed stream. It is an error to call this (it
@ -67,7 +74,7 @@ public:
SK_API const SkTDArray<SkPDFPage*>& getPages();
private:
SkPDFCatalog fCatalog;
SkTScopedPtr<SkPDFCatalog> fCatalog;
int64_t fXRefFileOffset;
SkTDArray<SkPDFPage*> fPages;

View File

@ -36,7 +36,7 @@ class SkPDFCatalog;
// The caller could keep track of the form XObjects it creates and
// canonicalize them, but the Skia API doesn't provide enough context to
// automatically do it (trivially).
class SkPDFFormXObject : public SkPDFObject {
class SkPDFFormXObject : public SkPDFStream {
public:
/** Create a PDF form XObject. Entries for the dictionary entries are
* automatically added.
@ -46,27 +46,9 @@ public:
virtual ~SkPDFFormXObject();
// The SkPDFObject interface.
virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect);
virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
virtual void getResources(SkTDArray<SkPDFObject*>* resourceList);
/** Add the value to the stream dictionary with the given key. Refs value.
* @param key The key for this dictionary entry.
* @param value The value for this dictionary entry.
* @return The value argument is returned.
*/
SkPDFObject* insert(SkPDFName* key, SkPDFObject* value);
/** Add the value to the stream dictionary with the given key. Refs value.
* @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.
*/
SkPDFObject* insert(const char key[], SkPDFObject* value);
private:
SkRefPtr<SkPDFStream> fStream;
SkTDArray<SkPDFObject*> fResources;
};

View File

@ -34,7 +34,7 @@ struct SkIRect;
// We could play the same trick here as is done in SkPDFGraphicState, storing
// a copy of the Bitmap object (not the pixels), the pixel generation number,
// and settings used from the paint to canonicalize image objects.
class SkPDFImage : public SkPDFObject {
class SkPDFImage : public SkPDFStream {
public:
/** Create a new Image XObject to represent the passed bitmap.
* @param bitmap The image to encode.
@ -56,13 +56,9 @@ public:
SkPDFImage* addSMask(SkPDFImage* mask);
// The SkPDFObject interface.
virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect);
virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
virtual void getResources(SkTDArray<SkPDFObject*>* resourceList);
private:
SkRefPtr<SkPDFStream> fStream;
SkTDArray<SkPDFObject*> fResources;
/** Create a PDF image XObject. Entries for the image properties are
@ -76,20 +72,6 @@ private:
*/
SkPDFImage(SkStream* imageData, const SkBitmap& bitmap,
const SkIRect& srcRect, bool alpha, const SkPaint& paint);
/** Add the value to the stream dictionary with the given key. Refs value.
* @param key The key for this dictionary entry.
* @param value The value for this dictionary entry.
* @return The value argument is returned.
*/
SkPDFObject* insert(SkPDFName* key, SkPDFObject* value);
/** Add the value to the stream dictionary with the given key. Refs value.
* @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.
*/
SkPDFObject* insert(const char key[], SkPDFObject* value);
};
#endif

View File

@ -32,16 +32,8 @@ class SkPDFCatalog;
pattern color space is selected.
*/
class SkPDFShader : public SkPDFObject {
class SkPDFShader {
public:
virtual ~SkPDFShader();
// The SkPDFObject interface.
virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect);
virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
virtual void getResources(SkTDArray<SkPDFObject*>* resourceList);
/** Get the PDF shader for the passed SkShader. If the SkShader is
* invalid in some way, returns NULL. The reference count of
* the object is incremented and it is the caller's responsibility to
@ -54,57 +46,27 @@ public:
* @param surfceBBox The bounding box of the drawing surface (with matrix
* already applied).
*/
static SkPDFShader* GetPDFShader(const SkShader& shader,
static SkPDFObject* GetPDFShader(const SkShader& shader,
const SkMatrix& matrix,
const SkIRect& surfaceBBox);
private:
class State {
public:
SkShader::GradientType fType;
SkShader::GradientInfo fInfo;
SkAutoFree fColorData;
SkMatrix fCanvasTransform;
SkMatrix fShaderTransform;
SkIRect fBBox;
SkBitmap fImage;
uint32_t fPixelGeneration;
SkShader::TileMode fImageTileModes[2];
explicit State(const SkShader& shader, const SkMatrix& canvasTransform,
const SkIRect& bbox);
bool operator==(const State& b) const;
};
SkRefPtr<SkPDFDict> fContent;
SkTDArray<SkPDFObject*> fResources;
SkAutoTDelete<const State> fState;
protected:
class State;
class ShaderCanonicalEntry {
public:
SkPDFShader* fPDFShader;
const State* fState;
ShaderCanonicalEntry(SkPDFObject* pdfShader, const State* state);
bool operator==(const ShaderCanonicalEntry& b) const;
bool operator==(const ShaderCanonicalEntry& b) const {
return fPDFShader == b.fPDFShader || *fState == *b.fState;
}
ShaderCanonicalEntry(SkPDFShader* pdfShader, const State* state)
: fPDFShader(pdfShader),
fState(state) {
}
SkPDFObject* fPDFShader;
const State* fState;
};
// This should be made a hash table if performance is a problem.
static SkTDArray<ShaderCanonicalEntry>& CanonicalShaders();
static SkMutex& CanonicalShadersMutex();
static void RemoveShader(SkPDFObject* shader);
static SkPDFObject* RangeObject();
SkPDFShader(State* state);
void doFunctionShader();
void doImageShader();
SkPDFStream* makePSFunction(const SkString& psCode, SkPDFArray* domain);
SkPDFShader();
};
#endif

View File

@ -34,9 +34,15 @@ public:
/** Create a PDF stream. A Length entry is automatically added to the
* stream dictionary. The stream may be retained (stream->ref() may be
* called) so its contents must not be changed after calling this.
* @param stream The data part of the stream.
* @param data The data part of the stream.
*/
explicit SkPDFStream(SkData* data);
/** Deprecated constructor. */
explicit SkPDFStream(SkStream* stream);
/** Create a PDF stream with the same content and dictionary entries
* as the passed one.
*/
explicit SkPDFStream(const SkPDFStream& pdfStream);
virtual ~SkPDFStream();
// The SkPDFObject interface.
@ -44,13 +50,33 @@ public:
bool indirect);
virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
protected:
/* Create a PDF stream with no data. The setData method must be called to
* set the data.
*/
SkPDFStream();
void setData(SkStream* stream);
private:
size_t fLength;
// Only one of the two streams will be valid.
SkRefPtr<SkStream> fPlainData;
SkDynamicMemoryWStream fCompressedData;
enum State {
kUnused_State, //!< The stream hasn't been requested yet.
kNoCompression_State, //!< The stream's been requested in an
// uncompressed form.
kCompressed_State, //!< The stream's already been compressed.
};
// Indicates what form (or if) the stream has been requested.
State fState;
// TODO(vandebo) Use SkData (after removing deprecated constructor).
SkRefPtr<SkStream> fData;
SkRefPtr<SkPDFStream> fSubstitute;
typedef SkPDFDict INHERITED;
// Populate the stream dictionary. This method returns false if
// fSubstitute should be used.
bool populate(SkPDFCatalog* catalog);
};
#endif

View File

@ -73,6 +73,22 @@ public:
*/
size_t getIndirectOutputSize(SkPDFCatalog* catalog);
/** Static helper function to add a resource to a list. The list takes
* a reference.
* @param resource The resource to add.
* @param list The list to add the resource to.
*/
static void AddResourceHelper(SkPDFObject* resource,
SkTDArray<SkPDFObject*>* list);
/** Static helper function to copy and reference the resources (and all
* their subresources) into a new list.
* @param resources The resource list.
* @param result The list to add to.
*/
static void GetResourcesHelper(SkTDArray<SkPDFObject*>* resources,
SkTDArray<SkPDFObject*>* result);
protected:
/** Subclasses must implement this method to print the object to the
* PDF file.
@ -219,6 +235,8 @@ public:
explicit SkPDFName(const SkString& name);
virtual ~SkPDFName();
bool operator==(const SkPDFName& b) const;
// The SkPDFObject interface.
virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect);
@ -367,13 +385,25 @@ public:
void clear();
private:
static const int kMaxLen = 4095;
struct Rec {
SkPDFName* key;
SkPDFObject* value;
};
public:
class Iter {
public:
explicit Iter(const SkPDFDict& dict);
SkPDFName* next(SkPDFObject** value);
private:
Rec* fIter;
Rec* fStop;
};
private:
static const int kMaxLen = 4095;
SkTDArray<struct Rec> fValue;
};

View File

@ -26,11 +26,7 @@ bool SkFlate::Inflate(SkStream*, SkWStream*) { return false; }
// static
bool SkFlate::HaveFlate() {
#ifdef SK_DEBUG
return false;
#else
return true;
#endif
}
namespace {

View File

@ -19,10 +19,11 @@
#include "SkStream.h"
#include "SkTypes.h"
SkPDFCatalog::SkPDFCatalog()
SkPDFCatalog::SkPDFCatalog(SkPDFDocument::Flags flags)
: fFirstPageCount(0),
fNextObjNum(1),
fNextFirstPageObjNum(0) {
fNextFirstPageObjNum(0),
fDocumentFlags(flags) {
}
SkPDFCatalog::~SkPDFCatalog() {

View File

@ -1394,7 +1394,7 @@ void SkPDFDevice::populateGraphicStateEntryFromPaint(
entry->fClipRegion = clipRegion;
// PDF treats a shader as a color, so we only set one or the other.
SkRefPtr<SkPDFShader> pdfShader;
SkRefPtr<SkPDFObject> pdfShader;
const SkShader* shader = paint.getShader();
SkColor color = paint.getColor();
if (shader) {

View File

@ -14,6 +14,7 @@
* limitations under the License.
*/
#include "SkPDFCatalog.h"
#include "SkPDFDevice.h"
#include "SkPDFDocument.h"
#include "SkPDFPage.h"
@ -36,12 +37,13 @@ void addResourcesToCatalog(int firstIndex, bool firstPage,
}
}
SkPDFDocument::SkPDFDocument()
SkPDFDocument::SkPDFDocument(Flags flags)
: fXRefFileOffset(0),
fSecondPageFirstResourceIndex(0) {
fCatalog.reset(new SkPDFCatalog(flags));
fDocCatalog = new SkPDFDict("Catalog");
fDocCatalog->unref(); // SkRefPtr and new both took a reference.
fCatalog.addObject(fDocCatalog.get(), true);
fCatalog->addObject(fDocCatalog.get(), true);
}
SkPDFDocument::~SkPDFDocument() {
@ -68,7 +70,7 @@ bool SkPDFDocument::emitPDF(SkWStream* stream) {
// We haven't emitted the document before if fPageTree is empty.
if (fPageTree.count() == 0) {
SkPDFDict* pageTreeRoot;
SkPDFPage::GeneratePageTree(fPages, &fCatalog, &fPageTree,
SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
&pageTreeRoot);
fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
@ -87,9 +89,9 @@ bool SkPDFDocument::emitPDF(SkWStream* stream) {
bool firstPage = true;
for (int i = 0; i < fPages.count(); i++) {
int resourceCount = fPageResources.count();
fPages[i]->finalizePage(&fCatalog, firstPage, &fPageResources);
fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
&fCatalog);
fCatalog.get());
if (i == 0) {
firstPage = false;
fSecondPageFirstResourceIndex = fPageResources.count();
@ -98,57 +100,67 @@ bool SkPDFDocument::emitPDF(SkWStream* stream) {
// Figure out the size of things and inform the catalog of file offsets.
off_t fileOffset = headerSize();
fileOffset += fCatalog.setFileOffset(fDocCatalog.get(), fileOffset);
fileOffset += fCatalog.setFileOffset(fPages[0], fileOffset);
fileOffset += fPages[0]->getPageSize(&fCatalog, fileOffset);
for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
fileOffset += fCatalog->setFileOffset(fDocCatalog.get(), fileOffset);
fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
fileOffset += fPages[0]->getPageSize(fCatalog.get(), fileOffset);
for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
fileOffset += fCatalog->setFileOffset(fPageResources[i],
fileOffset);
}
// Add the size of resources of substitute objects used on page 1.
fileOffset += fCatalog.setSubstituteResourcesOffsets(fileOffset, true);
fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
if (fPages.count() > 1) {
// TODO(vandebo) For linearized format, save the start of the
// first page xref table and calculate the size.
}
for (int i = 0; i < fPageTree.count(); i++)
fileOffset += fCatalog.setFileOffset(fPageTree[i], fileOffset);
fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
for (int i = 1; i < fPages.count(); i++)
fileOffset += fPages[i]->getPageSize(&fCatalog, fileOffset);
fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
for (int i = fSecondPageFirstResourceIndex;
i < fPageResources.count();
i++)
fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
fileOffset += fCatalog->setFileOffset(fPageResources[i],
fileOffset);
fileOffset += fCatalog.setSubstituteResourcesOffsets(fileOffset, false);
fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
false);
fXRefFileOffset = fileOffset;
}
emitHeader(stream);
fDocCatalog->emitObject(stream, &fCatalog, true);
fPages[0]->emitObject(stream, &fCatalog, true);
fPages[0]->emitPage(stream, &fCatalog);
for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
fPageResources[i]->emit(stream, &fCatalog, true);
fCatalog.emitSubstituteResources(stream, true);
fDocCatalog->emitObject(stream, fCatalog.get(), true);
fPages[0]->emitObject(stream, fCatalog.get(), true);
fPages[0]->emitPage(stream, fCatalog.get());
for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
fPageResources[i]->emit(stream, fCatalog.get(), true);
}
fCatalog->emitSubstituteResources(stream, true);
// TODO(vandebo) support linearized format
//if (fPages.size() > 1) {
// // TODO(vandebo) save the file offset for the first page xref table.
// fCatalog.emitXrefTable(stream, true);
// fCatalog->emitXrefTable(stream, true);
//}
for (int i = 0; i < fPageTree.count(); i++)
fPageTree[i]->emitObject(stream, &fCatalog, true);
for (int i = 0; i < fPageTree.count(); i++) {
fPageTree[i]->emitObject(stream, fCatalog.get(), true);
}
for (int i = 1; i < fPages.count(); i++)
fPages[i]->emitPage(stream, &fCatalog);
for (int i = 1; i < fPages.count(); i++) {
fPages[i]->emitPage(stream, fCatalog.get());
}
for (int i = fSecondPageFirstResourceIndex; i < fPageResources.count(); i++)
fPageResources[i]->emit(stream, &fCatalog, true);
for (int i = fSecondPageFirstResourceIndex;
i < fPageResources.count();
i++) {
fPageResources[i]->emit(stream, fCatalog.get(), true);
}
fCatalog.emitSubstituteResources(stream, false);
int64_t objCount = fCatalog.emitXrefTable(stream, fPages.count() > 1);
fCatalog->emitSubstituteResources(stream, false);
int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
emitFooter(stream, objCount);
return true;
}
@ -217,7 +229,7 @@ void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
}
stream->writeText("trailer\n");
fTrailerDict->emitObject(stream, &fCatalog, false);
fTrailerDict->emitObject(stream, fCatalog.get(), false);
stream->writeText("\nstartxref\n");
stream->writeBigDecAsText(fXRefFileOffset);
stream->writeText("\n%%EOF");

View File

@ -435,12 +435,7 @@ SkPDFFont::~SkPDFFont() {
}
void SkPDFFont::getResources(SkTDArray<SkPDFObject*>* resourceList) {
resourceList->setReserve(resourceList->count() + fResources.count());
for (int i = 0; i < fResources.count(); i++) {
resourceList->push(fResources[i]);
fResources[i]->ref();
fResources[i]->getResources(resourceList);
}
GetResourcesHelper(&fResources, resourceList);
}
SkTypeface* SkPDFFont::typeface() {

View File

@ -31,8 +31,7 @@ SkPDFFormXObject::SkPDFFormXObject(SkPDFDevice* device) {
SkRefPtr<SkStream> content = device->content();
content->unref(); // SkRefPtr and content() both took a reference.
fStream = new SkPDFStream(content.get());
fStream->unref(); // SkRefPtr and new both took a reference.
setData(content.get());
insert("Type", new SkPDFName("XObject"))->unref();
insert("Subtype", new SkPDFName("Form"))->unref();
@ -62,33 +61,6 @@ SkPDFFormXObject::~SkPDFFormXObject() {
fResources.unrefAll();
}
void SkPDFFormXObject::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect) {
if (indirect)
return emitIndirectObject(stream, catalog);
fStream->emitObject(stream, catalog, indirect);
}
size_t SkPDFFormXObject::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
if (indirect)
return getIndirectOutputSize(catalog);
return fStream->getOutputSize(catalog, indirect);
}
void SkPDFFormXObject::getResources(SkTDArray<SkPDFObject*>* resourceList) {
resourceList->setReserve(resourceList->count() + fResources.count());
for (int i = 0; i < fResources.count(); i++) {
resourceList->push(fResources[i]);
fResources[i]->ref();
}
}
SkPDFObject* SkPDFFormXObject::insert(SkPDFName* key, SkPDFObject* value) {
return fStream->insert(key, value);
}
SkPDFObject* SkPDFFormXObject::insert(const char key[], SkPDFObject* value) {
return fStream->insert(key, value);
GetResourcesHelper(&fResources, resourceList);
}

View File

@ -67,12 +67,7 @@ SkPDFGraphicState::~SkPDFGraphicState() {
}
void SkPDFGraphicState::getResources(SkTDArray<SkPDFObject*>* resourceList) {
resourceList->setReserve(resourceList->count() + fResources.count());
for (int i = 0; i < fResources.count(); i++) {
resourceList->push(fResources[i]);
fResources[i]->ref();
fResources[i]->getResources(resourceList);
}
GetResourcesHelper(&fResources, resourceList);
}
void SkPDFGraphicState::emitObject(SkWStream* stream, SkPDFCatalog* catalog,

View File

@ -281,38 +281,14 @@ SkPDFImage* SkPDFImage::addSMask(SkPDFImage* mask) {
return mask;
}
void SkPDFImage::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect) {
if (indirect)
return emitIndirectObject(stream, catalog);
fStream->emitObject(stream, catalog, indirect);
}
size_t SkPDFImage::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
if (indirect)
return getIndirectOutputSize(catalog);
return fStream->getOutputSize(catalog, indirect);
}
void SkPDFImage::getResources(SkTDArray<SkPDFObject*>* resourceList) {
if (fResources.count()) {
resourceList->setReserve(resourceList->count() + fResources.count());
for (int i = 0; i < fResources.count(); i++) {
resourceList->push(fResources[i]);
fResources[i]->ref();
fResources[i]->getResources(resourceList);
}
}
GetResourcesHelper(&fResources, resourceList);
}
SkPDFImage::SkPDFImage(SkStream* imageData, const SkBitmap& bitmap,
const SkIRect& srcRect, bool doingAlpha,
const SkPaint& paint) {
fStream = new SkPDFStream(imageData);
fStream->unref(); // SkRefPtr and new both took a reference.
this->setData(imageData);
SkBitmap::Config config = bitmap.getConfig();
bool alphaOnly = (config == SkBitmap::kA1_Config ||
config == SkBitmap::kA8_Config);
@ -372,11 +348,3 @@ SkPDFImage::SkPDFImage(SkStream* imageData, const SkBitmap& bitmap,
insert("Decode", decodeValue.get());
}
}
SkPDFObject* SkPDFImage::insert(SkPDFName* key, SkPDFObject* value) {
return fStream->insert(key, value);
}
SkPDFObject* SkPDFImage::insert(const char key[], SkPDFObject* value) {
return fStream->insert(key, value);
}

View File

@ -17,6 +17,7 @@
#include "SkPDFShader.h"
#include "SkCanvas.h"
#include "SkData.h"
#include "SkPDFCatalog.h"
#include "SkPDFDevice.h"
#include "SkPDFTypes.h"
@ -286,65 +287,107 @@ static SkString sweepCode(const SkShader::GradientInfo& info) {
return function;
}
SkPDFShader::~SkPDFShader() {
class SkPDFShader::State {
public:
SkShader::GradientType fType;
SkShader::GradientInfo fInfo;
SkAutoFree fColorData;
SkMatrix fCanvasTransform;
SkMatrix fShaderTransform;
SkIRect fBBox;
SkBitmap fImage;
uint32_t fPixelGeneration;
SkShader::TileMode fImageTileModes[2];
explicit State(const SkShader& shader, const SkMatrix& canvasTransform,
const SkIRect& bbox);
bool operator==(const State& b) const;
};
class SkPDFFunctionShader : public SkPDFDict, public SkPDFShader {
public:
SkPDFFunctionShader(SkPDFShader::State* state);
~SkPDFFunctionShader() {
if (isValid()) {
RemoveShader(this);
}
fResources.unrefAll();
}
bool isValid() { return fResources.count() > 0; }
void getResources(SkTDArray<SkPDFObject*>* resourceList) {
GetResourcesHelper(&fResources, resourceList);
}
private:
static SkPDFObject* RangeObject();
SkTDArray<SkPDFObject*> fResources;
SkAutoTDelete<const SkPDFShader::State> fState;
SkPDFStream* makePSFunction(const SkString& psCode, SkPDFArray* domain);
};
class SkPDFImageShader : public SkPDFStream, public SkPDFShader {
public:
SkPDFImageShader(SkPDFShader::State* state);
~SkPDFImageShader() {
RemoveShader(this);
fResources.unrefAll();
}
void getResources(SkTDArray<SkPDFObject*>* resourceList) {
GetResourcesHelper(&fResources, resourceList);
}
private:
SkTDArray<SkPDFObject*> fResources;
SkAutoTDelete<const SkPDFShader::State> fState;
};
SkPDFShader::SkPDFShader() {}
// static
void SkPDFShader::RemoveShader(SkPDFObject* shader) {
SkAutoMutexAcquire lock(CanonicalShadersMutex());
ShaderCanonicalEntry entry(this, fState.get());
ShaderCanonicalEntry entry(shader, NULL);
int index = CanonicalShaders().find(entry);
if (fContent.get()) {
SkASSERT(index >= 0);
CanonicalShaders().removeShuffle(index);
}
fResources.unrefAll();
}
void SkPDFShader::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect) {
if (indirect)
return emitIndirectObject(stream, catalog);
fContent->emitObject(stream, catalog, indirect);
}
size_t SkPDFShader::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
if (indirect)
return getIndirectOutputSize(catalog);
return fContent->getOutputSize(catalog, indirect);
}
void SkPDFShader::getResources(SkTDArray<SkPDFObject*>* resourceList) {
resourceList->setReserve(resourceList->count() + fResources.count());
for (int i = 0; i < fResources.count(); i++) {
resourceList->push(fResources[i]);
fResources[i]->ref();
}
SkASSERT(index >= 0);
CanonicalShaders().removeShuffle(index);
}
// static
SkPDFShader* SkPDFShader::GetPDFShader(const SkShader& shader,
SkPDFObject* SkPDFShader::GetPDFShader(const SkShader& shader,
const SkMatrix& matrix,
const SkIRect& surfaceBBox) {
SkRefPtr<SkPDFShader> pdfShader;
SkPDFObject* result;
SkAutoMutexAcquire lock(CanonicalShadersMutex());
SkAutoTDelete<State> shaderState(new State(shader, matrix, surfaceBBox));
ShaderCanonicalEntry entry(NULL, shaderState.get());
int index = CanonicalShaders().find(entry);
if (index >= 0) {
SkPDFShader* result = CanonicalShaders()[index].fPDFShader;
result = CanonicalShaders()[index].fPDFShader;
result->ref();
return result;
}
// The PDFShader takes ownership of the shaderSate.
pdfShader = new SkPDFShader(shaderState.detach());
// Check for a valid shader.
if (pdfShader->fContent.get() == NULL) {
pdfShader->unref();
return NULL;
if (shaderState.get()->fType == SkShader::kNone_GradientType) {
result = new SkPDFImageShader(shaderState.detach());
} else {
SkPDFFunctionShader* functionShader =
new SkPDFFunctionShader(shaderState.detach());
if (!functionShader->isValid()) {
delete functionShader;
return NULL;
}
result = functionShader;
}
entry.fPDFShader = pdfShader.get();
entry.fPDFShader = result;
CanonicalShaders().push(entry);
return pdfShader.get(); // return the reference that came from new.
return result; // return the reference that came from new.
}
// static
@ -362,7 +405,7 @@ SkMutex& SkPDFShader::CanonicalShadersMutex() {
}
// static
SkPDFObject* SkPDFShader::RangeObject() {
SkPDFObject* SkPDFFunctionShader::RangeObject() {
// This initialization is only thread safe with gcc.
static SkPDFArray* range = NULL;
// This method is only used with CanonicalShadersMutex, so it's safe to
@ -380,15 +423,9 @@ SkPDFObject* SkPDFShader::RangeObject() {
return range;
}
SkPDFShader::SkPDFShader(State* state) : fState(state) {
if (fState.get()->fType == SkShader::kNone_GradientType) {
doImageShader();
} else {
doFunctionShader();
}
}
void SkPDFShader::doFunctionShader() {
SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state)
: SkPDFDict("Pattern"),
fState(state) {
SkString (*codeFunction)(const SkShader::GradientInfo& info) = NULL;
SkPoint transformPoints[2];
@ -407,9 +444,8 @@ void SkPDFShader::doFunctionShader() {
codeFunction = &radialCode;
break;
case SkShader::kRadial2_GradientType: {
// Bail out if the radii are the same. Not setting fContent will
// cause the higher level code to detect the resulting object
// as invalid.
// Bail out if the radii are the same. Empty fResources signals
// an error and isValid will return false.
if (info->fRadius[0] == info->fRadius[1]) {
return;
}
@ -479,15 +515,12 @@ void SkPDFShader::doFunctionShader() {
pdfShader->insert("Domain", domain.get());
pdfShader->insert("Function", new SkPDFObjRef(function.get()))->unref();
fContent = new SkPDFDict("Pattern");
fContent->unref(); // SkRefPtr and new both took a reference.
fContent->insertInt("PatternType", 2);
fContent->insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
fContent->insert("Shading", pdfShader.get());
insertInt("PatternType", 2);
insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
insert("Shading", pdfShader.get());
}
// SkShader* shader, SkMatrix matrix, const SkRect& surfaceBBox
void SkPDFShader::doImageShader() {
SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) {
fState.get()->fImage.lockPixels();
SkMatrix finalMatrix = fState.get()->fCanvasTransform;
@ -666,34 +699,43 @@ void SkPDFShader::doImageShader() {
content->unref(); // SkRefPtr and content() both took a reference.
pattern.getResources(&fResources);
fContent = new SkPDFStream(content.get());
fContent->unref(); // SkRefPtr and new both took a reference.
fContent->insertName("Type", "Pattern");
fContent->insertInt("PatternType", 1);
fContent->insertInt("PaintType", 1);
fContent->insertInt("TilingType", 1);
fContent->insert("BBox", patternBBoxArray.get());
fContent->insertScalar("XStep", patternBBox.width());
fContent->insertScalar("YStep", patternBBox.height());
fContent->insert("Resources", pattern.getResourceDict().get());
fContent->insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
setData(content.get());
insertName("Type", "Pattern");
insertInt("PatternType", 1);
insertInt("PaintType", 1);
insertInt("TilingType", 1);
insert("BBox", patternBBoxArray.get());
insertScalar("XStep", patternBBox.width());
insertScalar("YStep", patternBBox.height());
insert("Resources", pattern.getResourceDict().get());
insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
fState.get()->fImage.unlockPixels();
}
SkPDFStream* SkPDFShader::makePSFunction(const SkString& psCode,
SkPDFArray* domain) {
SkRefPtr<SkMemoryStream> funcStream =
new SkMemoryStream(psCode.c_str(), psCode.size(), true);
funcStream->unref(); // SkRefPtr and new both took a reference.
SkPDFStream* result = new SkPDFStream(funcStream.get());
SkPDFStream* SkPDFFunctionShader::makePSFunction(const SkString& psCode,
SkPDFArray* domain) {
SkAutoDataUnref funcData(SkData::NewWithCopy(psCode.c_str(),
psCode.size()));
SkPDFStream* result = new SkPDFStream(funcData.get());
result->insertInt("FunctionType", 4);
result->insert("Domain", domain);
result->insert("Range", RangeObject());
return result;
}
SkPDFShader::ShaderCanonicalEntry::ShaderCanonicalEntry(SkPDFObject* pdfShader,
const State* state)
: fPDFShader(pdfShader),
fState(state) {
}
bool SkPDFShader::ShaderCanonicalEntry::operator==(
const ShaderCanonicalEntry& b) const {
return fPDFShader == b.fPDFShader ||
(fState != NULL && b.fState != NULL && *fState == *b.fState);
}
bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const {
if (fType != b.fType ||
fCanvasTransform != b.fCanvasTransform ||

View File

@ -20,45 +20,105 @@
#include "SkPDFStream.h"
#include "SkStream.h"
SkPDFStream::SkPDFStream(SkStream* stream) {
if (SkFlate::HaveFlate())
SkAssertResult(SkFlate::Deflate(stream, &fCompressedData));
static bool skip_compression(SkPDFCatalog* catalog) {
return catalog->getDocumentFlags() & SkPDFDocument::kNoCompression_Flag;
}
if (SkFlate::HaveFlate() &&
fCompressedData.getOffset() < stream->getLength()) {
fLength = fCompressedData.getOffset();
insert("Filter", new SkPDFName("FlateDecode"))->unref();
} else {
fCompressedData.reset();
fPlainData = stream;
fLength = fPlainData->getLength();
SkPDFStream::SkPDFStream(SkStream* stream)
: fState(kUnused_State),
fData(stream) {
}
SkPDFStream::SkPDFStream(SkData* data) : fState(kUnused_State) {
SkMemoryStream* stream = new SkMemoryStream;
stream->setData(data);
fData = stream;
fData->unref(); // SkRefPtr and new both took a reference.
}
SkPDFStream::SkPDFStream(const SkPDFStream& pdfStream)
: SkPDFDict(),
fState(kUnused_State),
fData(pdfStream.fData) {
bool removeLength = true;
// Don't uncompress an already compressed stream, but we could.
if (pdfStream.fState == kCompressed_State) {
fState = kCompressed_State;
removeLength = false;
}
SkPDFDict::Iter dict(pdfStream);
SkPDFName* key;
SkPDFObject* value;
SkPDFName lengthName("Length");
for (key = dict.next(&value); key != NULL; key = dict.next(&value)) {
if (removeLength && *key == lengthName) {
continue;
}
this->insert(key, value);
}
insertInt("Length", fLength);
}
SkPDFStream::~SkPDFStream() {
}
SkPDFStream::~SkPDFStream() {}
void SkPDFStream::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect) {
if (indirect)
if (indirect) {
return emitIndirectObject(stream, catalog);
}
if (!this->populate(catalog)) {
return fSubstitute->emitObject(stream, catalog, indirect);
}
this->INHERITED::emitObject(stream, catalog, false);
stream->writeText(" stream\n");
if (fPlainData.get()) {
stream->write(fPlainData->getMemoryBase(), fLength);
} else {
SkAutoDataUnref data(fCompressedData.copyToData());
stream->write(data.data(), fLength);
}
stream->write(fData->getMemoryBase(), fData->getLength());
stream->writeText("\nendstream");
}
size_t SkPDFStream::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
if (indirect)
if (indirect) {
return getIndirectOutputSize(catalog);
}
if (!this->populate(catalog)) {
return fSubstitute->getOutputSize(catalog, indirect);
}
return this->INHERITED::getOutputSize(catalog, false) +
strlen(" stream\n\nendstream") + fLength;
strlen(" stream\n\nendstream") + fData->getLength();
}
SkPDFStream::SkPDFStream() : fState(kUnused_State) {}
void SkPDFStream::setData(SkStream* stream) {
fData = stream;
}
bool SkPDFStream::populate(SkPDFCatalog* catalog) {
if (fState == kUnused_State) {
if (!skip_compression(catalog) && SkFlate::HaveFlate()) {
SkDynamicMemoryWStream compressedData;
SkAssertResult(SkFlate::Deflate(fData.get(), &compressedData));
if (compressedData.getOffset() < fData->getLength()) {
SkMemoryStream* stream = new SkMemoryStream;
stream->setData(compressedData.copyToData());
fData = stream;
fData->unref(); // SkRefPtr and new both took a reference.
insertName("Filter", "FlateDecode");
}
fState = kCompressed_State;
} else {
fState = kNoCompression_State;
}
insertInt("Length", fData->getLength());
} else if (fState == kNoCompression_State && !skip_compression(catalog) &&
SkFlate::HaveFlate()) {
if (!fSubstitute.get()) {
fSubstitute = new SkPDFStream(*this);
fSubstitute->unref(); // SkRefPtr and new both took a reference.
catalog->setSubstitute(this, fSubstitute.get());
}
return false;
}
return true;
}

View File

@ -48,14 +48,32 @@ void SkPDFObject::emitIndirectObject(SkWStream* stream, SkPDFCatalog* catalog) {
stream->writeText("\nendobj\n");
}
SkPDFObjRef::SkPDFObjRef(SkPDFObject* obj) : fObj(obj) {}
SkPDFObjRef::~SkPDFObjRef() {}
size_t SkPDFObject::getIndirectOutputSize(SkPDFCatalog* catalog) {
return catalog->getObjectNumberSize(this) + strlen(" obj\n") +
this->getOutputSize(catalog, false) + strlen("\nendobj\n");
}
void SkPDFObject::AddResourceHelper(SkPDFObject* resource,
SkTDArray<SkPDFObject*>* list) {
list->push(resource);
resource->ref();
}
void SkPDFObject::GetResourcesHelper(SkTDArray<SkPDFObject*>* resources,
SkTDArray<SkPDFObject*>* result) {
if (resources->count()) {
result->setReserve(result->count() + resources->count());
for (int i = 0; i < resources->count(); i++) {
result->push((*resources)[i]);
(*resources)[i]->ref();
(*resources)[i]->getResources(result);
}
}
}
SkPDFObjRef::SkPDFObjRef(SkPDFObject* obj) : fObj(obj) {}
SkPDFObjRef::~SkPDFObjRef() {}
void SkPDFObjRef::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect) {
SkASSERT(!indirect);
@ -258,6 +276,10 @@ 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, SkPDFCatalog* catalog,
bool indirect) {
SkASSERT(!indirect);
@ -433,3 +455,19 @@ void SkPDFDict::clear() {
}
fValue.reset();
}
SkPDFDict::Iter::Iter(const SkPDFDict& dict)
: fIter(dict.fValue.begin()),
fStop(dict.fValue.end()) {
}
SkPDFName* SkPDFDict::Iter::next(SkPDFObject** value) {
if (fIter != fStop) {
Rec* cur = fIter;
fIter++;
*value = cur->value;
return cur->key;
}
*value = NULL;
return NULL;
}

View File

@ -18,11 +18,13 @@
#include "Test.h"
#include "SkData.h"
#include "SkFlate.h"
#include "SkPDFCatalog.h"
#include "SkPDFStream.h"
#include "SkPDFTypes.h"
#include "SkScalar.h"
#include "SkStream.h"
#include "SkTypes.h"
class SkPDFTestDict : public SkPDFDict {
public:
@ -52,16 +54,20 @@ static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset,
}
static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj,
const std::string& representation,
bool indirect) {
SkPDFCatalog catalog;
const char* expectedData, size_t expectedSize,
bool indirect, bool compression) {
SkPDFDocument::Flags docFlags = (SkPDFDocument::Flags) 0;
if (!compression) {
docFlags = SkTBitOr(docFlags, SkPDFDocument::kNoCompression_Flag);
}
SkPDFCatalog catalog(docFlags);
size_t directSize = obj->getOutputSize(&catalog, false);
REPORTER_ASSERT(reporter, directSize == representation.size());
REPORTER_ASSERT(reporter, directSize == expectedSize);
SkDynamicMemoryWStream buffer;
obj->emit(&buffer, &catalog, false);
REPORTER_ASSERT(reporter, directSize == buffer.getOffset());
REPORTER_ASSERT(reporter, stream_equals(buffer, 0, representation.c_str(),
REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedData,
directSize));
if (indirect) {
@ -81,16 +87,74 @@ static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj,
obj->emit(&buffer, &catalog, true);
REPORTER_ASSERT(reporter, indirectSize == buffer.getOffset());
REPORTER_ASSERT(reporter, stream_equals(buffer, 0, header, headerLen));
REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen,
representation.c_str(),
REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen, expectedData,
directSize));
REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen + directSize,
footer, footerLen));
}
}
static void SimpleCheckObjectOutput(skiatest::Reporter* reporter,
SkPDFObject* obj,
const std::string& expectedResult) {
CheckObjectOutput(reporter, obj, expectedResult.c_str(),
expectedResult.length(), true, false);
}
static void TestPDFStream(skiatest::Reporter* reporter) {
char streamBytes[] = "Test\nFoo\tBar";
SkRefPtr<SkMemoryStream> streamData = new SkMemoryStream(
streamBytes, strlen(streamBytes), true);
streamData->unref(); // SkRefPtr and new both took a reference.
SkRefPtr<SkPDFStream> stream = new SkPDFStream(streamData.get());
stream->unref(); // SkRefPtr and new both took a reference.
SimpleCheckObjectOutput(
reporter, stream.get(),
"<</Length 12\n>> stream\nTest\nFoo\tBar\nendstream");
stream->insert("Attribute", new SkPDFInt(42))->unref();
SimpleCheckObjectOutput(reporter, stream.get(),
"<</Length 12\n/Attribute 42\n>> stream\n"
"Test\nFoo\tBar\nendstream");
if (SkFlate::HaveFlate()) {
char streamBytes2[] = "This is a longer string, so that compression "
"can do something with it. With shorter strings, "
"the short circuit logic cuts in and we end up "
"with an uncompressed string.";
SkAutoDataUnref streamData2(SkData::NewWithCopy(streamBytes2,
strlen(streamBytes2)));
SkRefPtr<SkPDFStream> stream = new SkPDFStream(streamData2.get());
stream->unref(); // SkRefPtr and new both took a reference.
SkDynamicMemoryWStream compressedByteStream;
SkFlate::Deflate(streamData2.get(), &compressedByteStream);
SkAutoDataUnref compressedData(compressedByteStream.copyToData());
// Check first without compression.
SkDynamicMemoryWStream expectedResult1;
expectedResult1.writeText("<</Length 167\n>> stream\n");
expectedResult1.writeText(streamBytes2);
expectedResult1.writeText("\nendstream");
SkAutoDataUnref expectedResultData1(expectedResult1.copyToData());
CheckObjectOutput(reporter, stream.get(),
(const char*) expectedResultData1.data(),
expectedResultData1.size(), true, false);
// Then again with compression.
SkDynamicMemoryWStream expectedResult2;
expectedResult2.writeText("<</Filter /FlateDecode\n/Length 116\n"
">> stream\n");
expectedResult2.write(compressedData.data(), compressedData.size());
expectedResult2.writeText("\nendstream");
SkAutoDataUnref expectedResultData2(expectedResult2.copyToData());
CheckObjectOutput(reporter, stream.get(),
(const char*) expectedResultData2.data(),
expectedResultData2.size(), true, true);
}
}
static void TestCatalog(skiatest::Reporter* reporter) {
SkPDFCatalog catalog;
SkPDFCatalog catalog((SkPDFDocument::Flags)0);
SkRefPtr<SkPDFInt> int1 = new SkPDFInt(1);
int1->unref(); // SkRefPtr and new both took a reference.
SkRefPtr<SkPDFInt> int2 = new SkPDFInt(2);
@ -125,7 +189,7 @@ static void TestObjectRef(skiatest::Reporter* reporter) {
SkRefPtr<SkPDFObjRef> int2ref = new SkPDFObjRef(int2.get());
int2ref->unref(); // SkRefPtr and new both took a reference.
SkPDFCatalog catalog;
SkPDFCatalog catalog((SkPDFDocument::Flags)0);
catalog.addObject(int1.get(), false);
catalog.addObject(int2.get(), false);
REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3);
@ -155,7 +219,7 @@ static void TestSubstitute(skiatest::Reporter* reporter) {
stubResource->insert("InnerValue", int44.get());
stub->addResource(stubResource.get());
SkPDFCatalog catalog;
SkPDFCatalog catalog((SkPDFDocument::Flags)0);
catalog.addObject(proxy.get(), false);
catalog.setSubstitute(proxy.get(), stub.get());
@ -173,90 +237,79 @@ static void TestSubstitute(skiatest::Reporter* reporter) {
static void TestPDFPrimitives(skiatest::Reporter* reporter) {
SkRefPtr<SkPDFInt> int42 = new SkPDFInt(42);
int42->unref(); // SkRefPtr and new both took a reference.
CheckObjectOutput(reporter, int42.get(), "42", true);
SimpleCheckObjectOutput(reporter, int42.get(), "42");
SkRefPtr<SkPDFScalar> realHalf = new SkPDFScalar(SK_ScalarHalf);
realHalf->unref(); // SkRefPtr and new both took a reference.
CheckObjectOutput(reporter, realHalf.get(), "0.5", true);
SimpleCheckObjectOutput(reporter, realHalf.get(), "0.5");
#if defined(SK_SCALAR_IS_FLOAT)
SkRefPtr<SkPDFScalar> bigScalar = new SkPDFScalar(110999.75);
bigScalar->unref(); // SkRefPtr and new both took a reference.
#if !defined(SK_ALLOW_LARGE_PDF_SCALARS)
CheckObjectOutput(reporter, bigScalar.get(), "111000", true);
SimpleCheckObjectOutput(reporter, bigScalar.get(), "111000");
#else
CheckObjectOutput(reporter, bigScalar.get(), "110999.75", true);
SimpleCheckObjectOutput(reporter, bigScalar.get(), "110999.75");
SkRefPtr<SkPDFScalar> biggerScalar = new SkPDFScalar(50000000.1);
biggerScalar->unref(); // SkRefPtr and new both took a reference.
CheckObjectOutput(reporter, biggerScalar.get(), "50000000", true);
SimpleCheckObjectOutput(reporter, biggerScalar.get(), "50000000");
SkRefPtr<SkPDFScalar> smallestScalar = new SkPDFScalar(1.0/65536);
smallestScalar->unref(); // SkRefPtr and new both took a reference.
CheckObjectOutput(reporter, smallestScalar.get(), "0.00001526", true);
SimpleCheckObjectOutput(reporter, smallestScalar.get(), "0.00001526");
#endif
#endif
SkRefPtr<SkPDFString> stringSimple = new SkPDFString("test ) string ( foo");
stringSimple->unref(); // SkRefPtr and new both took a reference.
CheckObjectOutput(reporter, stringSimple.get(), "(test \\) string \\( foo)",
true);
SimpleCheckObjectOutput(reporter, stringSimple.get(),
"(test \\) string \\( foo)");
SkRefPtr<SkPDFString> stringComplex =
new SkPDFString("\ttest ) string ( foo");
stringComplex->unref(); // SkRefPtr and new both took a reference.
CheckObjectOutput(reporter, stringComplex.get(),
"<0974657374202920737472696E67202820666F6F>", true);
SimpleCheckObjectOutput(reporter, stringComplex.get(),
"<0974657374202920737472696E67202820666F6F>");
SkRefPtr<SkPDFName> 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);
const char expectedResult[] = "/Test#20name#09with#23tab";
CheckObjectOutput(reporter, name.get(), expectedResult,
strlen(expectedResult), false, false);
SkRefPtr<SkPDFArray> array = new SkPDFArray;
array->unref(); // SkRefPtr and new both took a reference.
CheckObjectOutput(reporter, array.get(), "[]", true);
SimpleCheckObjectOutput(reporter, array.get(), "[]");
array->append(int42.get());
CheckObjectOutput(reporter, array.get(), "[42]", true);
SimpleCheckObjectOutput(reporter, array.get(), "[42]");
array->append(realHalf.get());
CheckObjectOutput(reporter, array.get(), "[42 0.5]", true);
SimpleCheckObjectOutput(reporter, array.get(), "[42 0.5]");
SkRefPtr<SkPDFInt> 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);
SimpleCheckObjectOutput(reporter, array.get(), "[42 0.5 0]");
SkRefPtr<SkPDFInt> 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);
SimpleCheckObjectOutput(reporter, array.get(), "[1 0.5 0]");
SkRefPtr<SkPDFDict> dict = new SkPDFDict;
dict->unref(); // SkRefPtr and new both took a reference.
CheckObjectOutput(reporter, dict.get(), "<<>>", true);
SimpleCheckObjectOutput(reporter, dict.get(), "<<>>");
SkRefPtr<SkPDFName> n1 = new SkPDFName("n1");
n1->unref(); // SkRefPtr and new both took a reference.
dict->insert(n1.get(), int42.get());
CheckObjectOutput(reporter, dict.get(), "<</n1 42\n>>", true);
SimpleCheckObjectOutput(reporter, dict.get(), "<</n1 42\n>>");
SkRefPtr<SkPDFName> n2 = new SkPDFName("n2");
n2->unref(); // SkRefPtr and new both took a reference.
SkRefPtr<SkPDFName> 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(),
"<</n1 42\n/n2 0.5\n/n3 [1 0.5 0]\n>>", true);
SimpleCheckObjectOutput(reporter, dict.get(),
"<</n1 42\n/n2 0.5\n/n3 [1 0.5 0]\n>>");
char streamBytes[] = "Test\nFoo\tBar";
SkRefPtr<SkMemoryStream> streamData = new SkMemoryStream(
streamBytes, strlen(streamBytes), true);
streamData->unref(); // SkRefPtr and new both took a reference.
SkRefPtr<SkPDFStream> stream = new SkPDFStream(streamData.get());
stream->unref(); // SkRefPtr and new both took a reference.
CheckObjectOutput(reporter, stream.get(),
"<</Length 12\n>> stream\nTest\nFoo\tBar\nendstream",
true);
stream->insert(n1.get(), int42.get());
CheckObjectOutput(reporter, stream.get(),
"<</Length 12\n/n1 42\n>> stream\nTest\nFoo\tBar"
"\nendstream",
true);
TestPDFStream(reporter);
TestCatalog(reporter);