• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2011 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "SkData.h"
9 #include "SkDeflate.h"
10 #include "SkMakeUnique.h"
11 #include "SkPDFTypes.h"
12 #include "SkPDFUtils.h"
13 #include "SkStream.h"
14 #include "SkStreamPriv.h"
15 
16 ////////////////////////////////////////////////////////////////////////////////
17 
pun(char * x)18 SkString* pun(char* x) { return reinterpret_cast<SkString*>(x); }
pun(const char * x)19 const SkString* pun(const char* x) {
20     return reinterpret_cast<const SkString*>(x);
21 }
22 
SkPDFUnion(Type t)23 SkPDFUnion::SkPDFUnion(Type t) : fType(t) {}
24 
~SkPDFUnion()25 SkPDFUnion::~SkPDFUnion() {
26     switch (fType) {
27         case Type::kNameSkS:
28         case Type::kStringSkS:
29             pun(fSkString)->~SkString();
30             return;
31         case Type::kObjRef:
32         case Type::kObject:
33             SkASSERT(fObject);
34             fObject->unref();
35             return;
36         default:
37             return;
38     }
39 }
40 
operator =(SkPDFUnion && other)41 SkPDFUnion& SkPDFUnion::operator=(SkPDFUnion&& other) {
42     if (this != &other) {
43         this->~SkPDFUnion();
44         new (this) SkPDFUnion(std::move(other));
45     }
46     return *this;
47 }
48 
SkPDFUnion(SkPDFUnion && other)49 SkPDFUnion::SkPDFUnion(SkPDFUnion&& other) {
50     SkASSERT(this != &other);
51     memcpy(this, &other, sizeof(*this));
52     other.fType = Type::kDestroyed;
53 }
54 
55 #if 0
56 SkPDFUnion SkPDFUnion::copy() const {
57     SkPDFUnion u(fType);
58     memcpy(&u, this, sizeof(u));
59     switch (fType) {
60         case Type::kNameSkS:
61         case Type::kStringSkS:
62             new (pun(u.fSkString)) SkString(*pun(fSkString));
63             return u;
64         case Type::kObjRef:
65         case Type::kObject:
66             SkRef(u.fObject);
67             return u;
68         default:
69             return u;
70     }
71 }
72 SkPDFUnion& SkPDFUnion::operator=(const SkPDFUnion& other) {
73     return *this = other.copy();
74 }
75 SkPDFUnion::SkPDFUnion(const SkPDFUnion& other) {
76     *this = other.copy();
77 }
78 #endif
79 
isName() const80 bool SkPDFUnion::isName() const {
81     return Type::kName == fType || Type::kNameSkS == fType;
82 }
83 
84 #ifdef SK_DEBUG
85 // Most names need no escaping.  Such names are handled as static
86 // const strings.
is_valid_name(const char * n)87 bool is_valid_name(const char* n) {
88     static const char kControlChars[] = "/%()<>[]{}";
89     while (*n) {
90         if (*n < '!' || *n > '~' || strchr(kControlChars, *n)) {
91             return false;
92         }
93         ++n;
94     }
95     return true;
96 }
97 #endif  // SK_DEBUG
98 
99 // Given an arbitrary string, write it as a valid name (not including
100 // leading slash).
write_name_escaped(SkWStream * o,const char * name)101 static void write_name_escaped(SkWStream* o, const char* name) {
102     static const char kToEscape[] = "#/%()<>[]{}";
103     for (const uint8_t* n = reinterpret_cast<const uint8_t*>(name); *n; ++n) {
104         uint8_t v = *n;
105         if (v < '!' || v > '~' || strchr(kToEscape, v)) {
106             char buffer[3] = {'#',
107                               SkHexadecimalDigits::gUpper[v >> 4],
108                               SkHexadecimalDigits::gUpper[v & 0xF]};
109             o->write(buffer, sizeof(buffer));
110         } else {
111             o->write(n, 1);
112         }
113     }
114 }
115 
emitObject(SkWStream * stream,const SkPDFObjNumMap & objNumMap) const116 void SkPDFUnion::emitObject(SkWStream* stream,
117                             const SkPDFObjNumMap& objNumMap) const {
118     switch (fType) {
119         case Type::kInt:
120             stream->writeDecAsText(fIntValue);
121             return;
122         case Type::kColorComponent:
123             SkPDFUtils::AppendColorComponent(SkToU8(fIntValue), stream);
124             return;
125         case Type::kBool:
126             stream->writeText(fBoolValue ? "true" : "false");
127             return;
128         case Type::kScalar:
129             SkPDFUtils::AppendScalar(fScalarValue, stream);
130             return;
131         case Type::kName:
132             stream->writeText("/");
133             SkASSERT(is_valid_name(fStaticString));
134             stream->writeText(fStaticString);
135             return;
136         case Type::kString:
137             SkASSERT(fStaticString);
138             SkPDFUtils::WriteString(stream, fStaticString,
139                                     strlen(fStaticString));
140             return;
141         case Type::kNameSkS:
142             stream->writeText("/");
143             write_name_escaped(stream, pun(fSkString)->c_str());
144             return;
145         case Type::kStringSkS:
146             SkPDFUtils::WriteString(stream, pun(fSkString)->c_str(),
147                                     pun(fSkString)->size());
148             return;
149         case Type::kObjRef:
150             stream->writeDecAsText(objNumMap.getObjectNumber(fObject));
151             stream->writeText(" 0 R");  // Generation number is always 0.
152             return;
153         case Type::kObject:
154             fObject->emitObject(stream, objNumMap);
155             return;
156         default:
157             SkDEBUGFAIL("SkPDFUnion::emitObject with bad type");
158     }
159 }
160 
addResources(SkPDFObjNumMap * objNumMap) const161 void SkPDFUnion::addResources(SkPDFObjNumMap* objNumMap) const {
162     switch (fType) {
163         case Type::kInt:
164         case Type::kColorComponent:
165         case Type::kBool:
166         case Type::kScalar:
167         case Type::kName:
168         case Type::kString:
169         case Type::kNameSkS:
170         case Type::kStringSkS:
171             return;  // These have no resources.
172         case Type::kObjRef:
173             objNumMap->addObjectRecursively(fObject);
174             return;
175         case Type::kObject:
176             fObject->addResources(objNumMap);
177             return;
178         default:
179             SkDEBUGFAIL("SkPDFUnion::addResources with bad type");
180     }
181 }
182 
Int(int32_t value)183 SkPDFUnion SkPDFUnion::Int(int32_t value) {
184     SkPDFUnion u(Type::kInt);
185     u.fIntValue = value;
186     return u;
187 }
188 
ColorComponent(uint8_t value)189 SkPDFUnion SkPDFUnion::ColorComponent(uint8_t value) {
190     SkPDFUnion u(Type::kColorComponent);
191     u.fIntValue = value;
192     return u;
193 }
194 
Bool(bool value)195 SkPDFUnion SkPDFUnion::Bool(bool value) {
196     SkPDFUnion u(Type::kBool);
197     u.fBoolValue = value;
198     return u;
199 }
200 
Scalar(SkScalar value)201 SkPDFUnion SkPDFUnion::Scalar(SkScalar value) {
202     SkPDFUnion u(Type::kScalar);
203     u.fScalarValue = value;
204     return u;
205 }
206 
Name(const char * value)207 SkPDFUnion SkPDFUnion::Name(const char* value) {
208     SkPDFUnion u(Type::kName);
209     SkASSERT(value);
210     SkASSERT(is_valid_name(value));
211     u.fStaticString = value;
212     return u;
213 }
214 
String(const char * value)215 SkPDFUnion SkPDFUnion::String(const char* value) {
216     SkPDFUnion u(Type::kString);
217     SkASSERT(value);
218     u.fStaticString = value;
219     return u;
220 }
221 
Name(const SkString & s)222 SkPDFUnion SkPDFUnion::Name(const SkString& s) {
223     SkPDFUnion u(Type::kNameSkS);
224     new (pun(u.fSkString)) SkString(s);
225     return u;
226 }
227 
String(const SkString & s)228 SkPDFUnion SkPDFUnion::String(const SkString& s) {
229     SkPDFUnion u(Type::kStringSkS);
230     new (pun(u.fSkString)) SkString(s);
231     return u;
232 }
233 
ObjRef(sk_sp<SkPDFObject> objSp)234 SkPDFUnion SkPDFUnion::ObjRef(sk_sp<SkPDFObject> objSp) {
235     SkPDFUnion u(Type::kObjRef);
236     SkASSERT(objSp.get());
237     u.fObject = objSp.release();  // take ownership into union{}
238     return u;
239 }
240 
Object(sk_sp<SkPDFObject> objSp)241 SkPDFUnion SkPDFUnion::Object(sk_sp<SkPDFObject> objSp) {
242     SkPDFUnion u(Type::kObject);
243     SkASSERT(objSp.get());
244     u.fObject = objSp.release();  // take ownership into union{}
245     return u;
246 }
247 
248 ////////////////////////////////////////////////////////////////////////////////
249 
250 #if 0  // Enable if needed.
251 void SkPDFAtom::emitObject(SkWStream* stream,
252                            const SkPDFObjNumMap& objNumMap) const {
253     fValue.emitObject(stream, objNumMap);
254 }
255 void SkPDFAtom::addResources(SkPDFObjNumMap* map) const {
256     fValue.addResources(map);
257 }
258 #endif  // 0
259 
260 ////////////////////////////////////////////////////////////////////////////////
261 
SkPDFArray()262 SkPDFArray::SkPDFArray() { SkDEBUGCODE(fDumped = false;) }
263 
~SkPDFArray()264 SkPDFArray::~SkPDFArray() { this->drop(); }
265 
drop()266 void SkPDFArray::drop() {
267     fValues.reset();
268     SkDEBUGCODE(fDumped = true;)
269 }
270 
size() const271 int SkPDFArray::size() const { return fValues.count(); }
272 
reserve(int length)273 void SkPDFArray::reserve(int length) {
274     fValues.reserve(length);
275 }
276 
emitObject(SkWStream * stream,const SkPDFObjNumMap & objNumMap) const277 void SkPDFArray::emitObject(SkWStream* stream,
278                             const SkPDFObjNumMap& objNumMap) const {
279     SkASSERT(!fDumped);
280     stream->writeText("[");
281     for (int i = 0; i < fValues.count(); i++) {
282         fValues[i].emitObject(stream, objNumMap);
283         if (i + 1 < fValues.count()) {
284             stream->writeText(" ");
285         }
286     }
287     stream->writeText("]");
288 }
289 
addResources(SkPDFObjNumMap * catalog) const290 void SkPDFArray::addResources(SkPDFObjNumMap* catalog) const {
291     SkASSERT(!fDumped);
292     for (const SkPDFUnion& value : fValues) {
293         value.addResources(catalog);
294     }
295 }
296 
append(SkPDFUnion && value)297 void SkPDFArray::append(SkPDFUnion&& value) {
298     fValues.emplace_back(std::move(value));
299 }
300 
appendInt(int32_t value)301 void SkPDFArray::appendInt(int32_t value) {
302     this->append(SkPDFUnion::Int(value));
303 }
304 
appendColorComponent(uint8_t value)305 void SkPDFArray::appendColorComponent(uint8_t value) {
306     this->append(SkPDFUnion::ColorComponent(value));
307 }
308 
appendBool(bool value)309 void SkPDFArray::appendBool(bool value) {
310     this->append(SkPDFUnion::Bool(value));
311 }
312 
appendScalar(SkScalar value)313 void SkPDFArray::appendScalar(SkScalar value) {
314     this->append(SkPDFUnion::Scalar(value));
315 }
316 
appendName(const char name[])317 void SkPDFArray::appendName(const char name[]) {
318     this->append(SkPDFUnion::Name(SkString(name)));
319 }
320 
appendName(const SkString & name)321 void SkPDFArray::appendName(const SkString& name) {
322     this->append(SkPDFUnion::Name(name));
323 }
324 
appendString(const SkString & value)325 void SkPDFArray::appendString(const SkString& value) {
326     this->append(SkPDFUnion::String(value));
327 }
328 
appendString(const char value[])329 void SkPDFArray::appendString(const char value[]) {
330     this->append(SkPDFUnion::String(value));
331 }
332 
appendObject(sk_sp<SkPDFObject> objSp)333 void SkPDFArray::appendObject(sk_sp<SkPDFObject> objSp) {
334     this->append(SkPDFUnion::Object(std::move(objSp)));
335 }
336 
appendObjRef(sk_sp<SkPDFObject> objSp)337 void SkPDFArray::appendObjRef(sk_sp<SkPDFObject> objSp) {
338     this->append(SkPDFUnion::ObjRef(std::move(objSp)));
339 }
340 
341 ///////////////////////////////////////////////////////////////////////////////
342 
~SkPDFDict()343 SkPDFDict::~SkPDFDict() { this->drop(); }
344 
drop()345 void SkPDFDict::drop() {
346     fRecords.reset();
347     SkDEBUGCODE(fDumped = true;)
348 }
349 
SkPDFDict(const char type[])350 SkPDFDict::SkPDFDict(const char type[]) {
351     SkDEBUGCODE(fDumped = false;)
352     if (type) {
353         this->insertName("Type", type);
354     }
355 }
356 
emitObject(SkWStream * stream,const SkPDFObjNumMap & objNumMap) const357 void SkPDFDict::emitObject(SkWStream* stream,
358                            const SkPDFObjNumMap& objNumMap) const {
359     stream->writeText("<<");
360     this->emitAll(stream, objNumMap);
361     stream->writeText(">>");
362 }
363 
emitAll(SkWStream * stream,const SkPDFObjNumMap & objNumMap) const364 void SkPDFDict::emitAll(SkWStream* stream,
365                         const SkPDFObjNumMap& objNumMap) const {
366     SkASSERT(!fDumped);
367     for (int i = 0; i < fRecords.count(); i++) {
368         fRecords[i].fKey.emitObject(stream, objNumMap);
369         stream->writeText(" ");
370         fRecords[i].fValue.emitObject(stream, objNumMap);
371         if (i + 1 < fRecords.count()) {
372             stream->writeText("\n");
373         }
374     }
375 }
376 
addResources(SkPDFObjNumMap * catalog) const377 void SkPDFDict::addResources(SkPDFObjNumMap* catalog) const {
378     SkASSERT(!fDumped);
379     for (int i = 0; i < fRecords.count(); i++) {
380         fRecords[i].fKey.addResources(catalog);
381         fRecords[i].fValue.addResources(catalog);
382     }
383 }
384 
size() const385 int SkPDFDict::size() const { return fRecords.count(); }
386 
reserve(int n)387 void SkPDFDict::reserve(int n) {
388     fRecords.reserve(n);
389 }
390 
insertObjRef(const char key[],sk_sp<SkPDFObject> objSp)391 void SkPDFDict::insertObjRef(const char key[], sk_sp<SkPDFObject> objSp) {
392     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::ObjRef(std::move(objSp))});
393 }
394 
insertObjRef(const SkString & key,sk_sp<SkPDFObject> objSp)395 void SkPDFDict::insertObjRef(const SkString& key, sk_sp<SkPDFObject> objSp) {
396     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::ObjRef(std::move(objSp))});
397 }
398 
insertObject(const char key[],sk_sp<SkPDFObject> objSp)399 void SkPDFDict::insertObject(const char key[], sk_sp<SkPDFObject> objSp) {
400     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Object(std::move(objSp))});
401 }
insertObject(const SkString & key,sk_sp<SkPDFObject> objSp)402 void SkPDFDict::insertObject(const SkString& key, sk_sp<SkPDFObject> objSp) {
403     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Object(std::move(objSp))});
404 }
405 
insertBool(const char key[],bool value)406 void SkPDFDict::insertBool(const char key[], bool value) {
407     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Bool(value)});
408 }
409 
insertInt(const char key[],int32_t value)410 void SkPDFDict::insertInt(const char key[], int32_t value) {
411     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Int(value)});
412 }
413 
insertInt(const char key[],size_t value)414 void SkPDFDict::insertInt(const char key[], size_t value) {
415     this->insertInt(key, SkToS32(value));
416 }
417 
insertScalar(const char key[],SkScalar value)418 void SkPDFDict::insertScalar(const char key[], SkScalar value) {
419     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Scalar(value)});
420 }
421 
insertName(const char key[],const char name[])422 void SkPDFDict::insertName(const char key[], const char name[]) {
423     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Name(name)});
424 }
425 
insertName(const char key[],const SkString & name)426 void SkPDFDict::insertName(const char key[], const SkString& name) {
427     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Name(name)});
428 }
429 
insertString(const char key[],const char value[])430 void SkPDFDict::insertString(const char key[], const char value[]) {
431     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::String(value)});
432 }
433 
insertString(const char key[],const SkString & value)434 void SkPDFDict::insertString(const char key[], const SkString& value) {
435     fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::String(value)});
436 }
437 
438 ////////////////////////////////////////////////////////////////////////////////
439 
SkPDFSharedStream(std::unique_ptr<SkStreamAsset> data)440 SkPDFSharedStream::SkPDFSharedStream(std::unique_ptr<SkStreamAsset> data)
441     : fAsset(std::move(data)) {
442     SkASSERT(fAsset);
443 }
444 
~SkPDFSharedStream()445 SkPDFSharedStream::~SkPDFSharedStream() { this->drop(); }
446 
drop()447 void SkPDFSharedStream::drop() {
448     fAsset = nullptr;;
449     fDict.drop();
450 }
451 
452 #ifdef SK_PDF_LESS_COMPRESSION
emitObject(SkWStream * stream,const SkPDFObjNumMap & objNumMap) const453 void SkPDFSharedStream::emitObject(
454         SkWStream* stream,
455         const SkPDFObjNumMap& objNumMap) const {
456     SkASSERT(fAsset);
457     std::unique_ptr<SkStreamAsset> dup(fAsset->duplicate());
458     SkASSERT(dup && dup->hasLength());
459     size_t length = dup->getLength();
460     stream->writeText("<<");
461     fDict.emitAll(stream, objNumMap);
462     stream->writeText("\n");
463     SkPDFUnion::Name("Length").emitObject(stream, objNumMap);
464     stream->writeText(" ");
465     SkPDFUnion::Int(length).emitObject(stream, objNumMap);
466     stream->writeText("\n>>stream\n");
467     SkStreamCopy(stream, dup.get());
468     stream->writeText("\nendstream");
469 }
470 #else
emitObject(SkWStream * stream,const SkPDFObjNumMap & objNumMap) const471 void SkPDFSharedStream::emitObject(
472         SkWStream* stream,
473         const SkPDFObjNumMap& objNumMap) const {
474     SkASSERT(fAsset);
475     SkDynamicMemoryWStream buffer;
476     SkDeflateWStream deflateWStream(&buffer);
477     // Since emitObject is const, this function doesn't change the dictionary.
478     std::unique_ptr<SkStreamAsset> dup(fAsset->duplicate());  // Cheap copy
479     SkASSERT(dup);
480     SkStreamCopy(&deflateWStream, dup.get());
481     deflateWStream.finalize();
482     size_t length = buffer.bytesWritten();
483     stream->writeText("<<");
484     fDict.emitAll(stream, objNumMap);
485     stream->writeText("\n");
486     SkPDFUnion::Name("Length").emitObject(stream, objNumMap);
487     stream->writeText(" ");
488     SkPDFUnion::Int(length).emitObject(stream, objNumMap);
489     stream->writeText("\n");
490     SkPDFUnion::Name("Filter").emitObject(stream, objNumMap);
491     stream->writeText(" ");
492     SkPDFUnion::Name("FlateDecode").emitObject(stream, objNumMap);
493     stream->writeText(">>");
494     stream->writeText(" stream\n");
495     buffer.writeToAndReset(stream);
496     stream->writeText("\nendstream");
497 }
498 #endif
499 
addResources(SkPDFObjNumMap * catalog) const500 void SkPDFSharedStream::addResources(
501         SkPDFObjNumMap* catalog) const {
502     SkASSERT(fAsset);
503     fDict.addResources(catalog);
504 }
505 
506 
507 ////////////////////////////////////////////////////////////////////////////////
508 
SkPDFStream(sk_sp<SkData> data)509 SkPDFStream:: SkPDFStream(sk_sp<SkData> data) {
510     this->setData(skstd::make_unique<SkMemoryStream>(std::move(data)));
511 }
512 
SkPDFStream(std::unique_ptr<SkStreamAsset> stream)513 SkPDFStream::SkPDFStream(std::unique_ptr<SkStreamAsset> stream) {
514     this->setData(std::move(stream));
515 }
516 
SkPDFStream()517 SkPDFStream::SkPDFStream() {}
518 
~SkPDFStream()519 SkPDFStream::~SkPDFStream() {}
520 
addResources(SkPDFObjNumMap * catalog) const521 void SkPDFStream::addResources(SkPDFObjNumMap* catalog) const {
522     SkASSERT(fCompressedData);
523     fDict.addResources(catalog);
524 }
525 
drop()526 void SkPDFStream::drop() {
527     fCompressedData.reset(nullptr);
528     fDict.drop();
529 }
530 
emitObject(SkWStream * stream,const SkPDFObjNumMap & objNumMap) const531 void SkPDFStream::emitObject(SkWStream* stream,
532                              const SkPDFObjNumMap& objNumMap) const {
533     SkASSERT(fCompressedData);
534     fDict.emitObject(stream, objNumMap);
535     // duplicate (a cheap operation) preserves const on fCompressedData.
536     std::unique_ptr<SkStreamAsset> dup(fCompressedData->duplicate());
537     SkASSERT(dup);
538     SkASSERT(dup->hasLength());
539     stream->writeText(" stream\n");
540     stream->writeStream(dup.get(), dup->getLength());
541     stream->writeText("\nendstream");
542 }
543 
setData(std::unique_ptr<SkStreamAsset> stream)544 void SkPDFStream::setData(std::unique_ptr<SkStreamAsset> stream) {
545     SkASSERT(!fCompressedData);  // Only call this function once.
546     SkASSERT(stream);
547     // Code assumes that the stream starts at the beginning.
548 
549     #ifdef SK_PDF_LESS_COMPRESSION
550     fCompressedData = std::move(stream);
551     SkASSERT(fCompressedData && fCompressedData->hasLength());
552     fDict.insertInt("Length", fCompressedData->getLength());
553     #else
554 
555     SkASSERT(stream->hasLength());
556     SkDynamicMemoryWStream compressedData;
557     SkDeflateWStream deflateWStream(&compressedData);
558     if (stream->getLength() > 0) {
559         SkStreamCopy(&deflateWStream, stream.get());
560     }
561     deflateWStream.finalize();
562     size_t compressedLength = compressedData.bytesWritten();
563     size_t originalLength = stream->getLength();
564 
565     if (originalLength <= compressedLength + strlen("/Filter_/FlateDecode_")) {
566         SkAssertResult(stream->rewind());
567         fCompressedData = std::move(stream);
568         fDict.insertInt("Length", originalLength);
569         return;
570     }
571     fCompressedData = compressedData.detachAsStream();
572     fDict.insertName("Filter", "FlateDecode");
573     fDict.insertInt("Length", compressedLength);
574     #endif
575 }
576 
577 ////////////////////////////////////////////////////////////////////////////////
578 
addObjectRecursively(SkPDFObject * obj)579 void SkPDFObjNumMap::addObjectRecursively(SkPDFObject* obj) {
580     if (obj && !fObjectNumbers.find(obj)) {
581         fObjectNumbers.set(obj, fObjectNumbers.count() + 1);
582         fObjects.emplace_back(sk_ref_sp(obj));
583         obj->addResources(this);
584     }
585 }
586 
getObjectNumber(SkPDFObject * obj) const587 int32_t SkPDFObjNumMap::getObjectNumber(SkPDFObject* obj) const {
588     int32_t* objectNumberFound = fObjectNumbers.find(obj);
589     SkASSERT(objectNumberFound);
590     return *objectNumberFound;
591 }
592 
593 #ifdef SK_PDF_IMAGE_STATS
594 SkAtomic<int> gDrawImageCalls(0);
595 SkAtomic<int> gJpegImageObjects(0);
596 SkAtomic<int> gRegularImageObjects(0);
597 
SkPDFImageDumpStats()598 void SkPDFImageDumpStats() {
599     SkDebugf("\ntotal PDF drawImage/drawBitmap calls: %d\n"
600              "total PDF jpeg images: %d\n"
601              "total PDF regular images: %d\n",
602              gDrawImageCalls.load(),
603              gJpegImageObjects.load(),
604              gRegularImageObjects.load());
605 }
606 #endif // SK_PDF_IMAGE_STATS
607