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 static const char kHex[] = "0123456789ABCDEF";
104 for (const uint8_t* n = reinterpret_cast<const uint8_t*>(name); *n; ++n) {
105 if (*n < '!' || *n > '~' || strchr(kToEscape, *n)) {
106 char buffer[3] = {'#', '\0', '\0'};
107 buffer[1] = kHex[(*n >> 4) & 0xF];
108 buffer[2] = kHex[*n & 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 // TODO(halcanary): implement SkTArray<T>::reserve() or change the
275 // contstructor of SkPDFArray to take reserve size.
276 }
277
emitObject(SkWStream * stream,const SkPDFObjNumMap & objNumMap) const278 void SkPDFArray::emitObject(SkWStream* stream,
279 const SkPDFObjNumMap& objNumMap) const {
280 SkASSERT(!fDumped);
281 stream->writeText("[");
282 for (int i = 0; i < fValues.count(); i++) {
283 fValues[i].emitObject(stream, objNumMap);
284 if (i + 1 < fValues.count()) {
285 stream->writeText(" ");
286 }
287 }
288 stream->writeText("]");
289 }
290
addResources(SkPDFObjNumMap * catalog) const291 void SkPDFArray::addResources(SkPDFObjNumMap* catalog) const {
292 SkASSERT(!fDumped);
293 for (const SkPDFUnion& value : fValues) {
294 value.addResources(catalog);
295 }
296 }
297
append(SkPDFUnion && value)298 void SkPDFArray::append(SkPDFUnion&& value) {
299 fValues.emplace_back(std::move(value));
300 }
301
appendInt(int32_t value)302 void SkPDFArray::appendInt(int32_t value) {
303 this->append(SkPDFUnion::Int(value));
304 }
305
appendColorComponent(uint8_t value)306 void SkPDFArray::appendColorComponent(uint8_t value) {
307 this->append(SkPDFUnion::ColorComponent(value));
308 }
309
appendBool(bool value)310 void SkPDFArray::appendBool(bool value) {
311 this->append(SkPDFUnion::Bool(value));
312 }
313
appendScalar(SkScalar value)314 void SkPDFArray::appendScalar(SkScalar value) {
315 this->append(SkPDFUnion::Scalar(value));
316 }
317
appendName(const char name[])318 void SkPDFArray::appendName(const char name[]) {
319 this->append(SkPDFUnion::Name(SkString(name)));
320 }
321
appendName(const SkString & name)322 void SkPDFArray::appendName(const SkString& name) {
323 this->append(SkPDFUnion::Name(name));
324 }
325
appendString(const SkString & value)326 void SkPDFArray::appendString(const SkString& value) {
327 this->append(SkPDFUnion::String(value));
328 }
329
appendString(const char value[])330 void SkPDFArray::appendString(const char value[]) {
331 this->append(SkPDFUnion::String(value));
332 }
333
appendObject(sk_sp<SkPDFObject> objSp)334 void SkPDFArray::appendObject(sk_sp<SkPDFObject> objSp) {
335 this->append(SkPDFUnion::Object(std::move(objSp)));
336 }
337
appendObjRef(sk_sp<SkPDFObject> objSp)338 void SkPDFArray::appendObjRef(sk_sp<SkPDFObject> objSp) {
339 this->append(SkPDFUnion::ObjRef(std::move(objSp)));
340 }
341
342 ///////////////////////////////////////////////////////////////////////////////
343
~SkPDFDict()344 SkPDFDict::~SkPDFDict() { this->drop(); }
345
drop()346 void SkPDFDict::drop() {
347 fRecords.reset();
348 SkDEBUGCODE(fDumped = true;)
349 }
350
SkPDFDict(const char type[])351 SkPDFDict::SkPDFDict(const char type[]) {
352 SkDEBUGCODE(fDumped = false;)
353 if (type) {
354 this->insertName("Type", type);
355 }
356 }
357
emitObject(SkWStream * stream,const SkPDFObjNumMap & objNumMap) const358 void SkPDFDict::emitObject(SkWStream* stream,
359 const SkPDFObjNumMap& objNumMap) const {
360 stream->writeText("<<");
361 this->emitAll(stream, objNumMap);
362 stream->writeText(">>");
363 }
364
emitAll(SkWStream * stream,const SkPDFObjNumMap & objNumMap) const365 void SkPDFDict::emitAll(SkWStream* stream,
366 const SkPDFObjNumMap& objNumMap) const {
367 SkASSERT(!fDumped);
368 for (int i = 0; i < fRecords.count(); i++) {
369 fRecords[i].fKey.emitObject(stream, objNumMap);
370 stream->writeText(" ");
371 fRecords[i].fValue.emitObject(stream, objNumMap);
372 if (i + 1 < fRecords.count()) {
373 stream->writeText("\n");
374 }
375 }
376 }
377
addResources(SkPDFObjNumMap * catalog) const378 void SkPDFDict::addResources(SkPDFObjNumMap* catalog) const {
379 SkASSERT(!fDumped);
380 for (int i = 0; i < fRecords.count(); i++) {
381 fRecords[i].fKey.addResources(catalog);
382 fRecords[i].fValue.addResources(catalog);
383 }
384 }
385
Record(SkPDFUnion && k,SkPDFUnion && v)386 SkPDFDict::Record::Record(SkPDFUnion&& k, SkPDFUnion&& v)
387 : fKey(std::move(k)), fValue(std::move(v)) {}
388
size() const389 int SkPDFDict::size() const { return fRecords.count(); }
390
insertObjRef(const char key[],sk_sp<SkPDFObject> objSp)391 void SkPDFDict::insertObjRef(const char key[], sk_sp<SkPDFObject> objSp) {
392 fRecords.emplace_back(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(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(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(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(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(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(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(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(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(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(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.writeToStream(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
addObject(SkPDFObject * obj)579 bool SkPDFObjNumMap::addObject(SkPDFObject* obj) {
580 if (fObjectNumbers.find(obj)) {
581 return false;
582 }
583 fObjectNumbers.set(obj, fObjectNumbers.count() + 1);
584 fObjects.emplace_back(sk_ref_sp(obj));
585 return true;
586 }
587
addObjectRecursively(SkPDFObject * obj)588 void SkPDFObjNumMap::addObjectRecursively(SkPDFObject* obj) {
589 if (obj && this->addObject(obj)) {
590 obj->addResources(this);
591 }
592 }
593
getObjectNumber(SkPDFObject * obj) const594 int32_t SkPDFObjNumMap::getObjectNumber(SkPDFObject* obj) const {
595 int32_t* objectNumberFound = fObjectNumbers.find(obj);
596 SkASSERT(objectNumberFound);
597 return *objectNumberFound;
598 }
599
600 #ifdef SK_PDF_IMAGE_STATS
601 SkAtomic<int> gDrawImageCalls(0);
602 SkAtomic<int> gJpegImageObjects(0);
603 SkAtomic<int> gRegularImageObjects(0);
604
SkPDFImageDumpStats()605 void SkPDFImageDumpStats() {
606 SkDebugf("\ntotal PDF drawImage/drawBitmap calls: %d\n"
607 "total PDF jpeg images: %d\n"
608 "total PDF regular images: %d\n",
609 gDrawImageCalls.load(),
610 gJpegImageObjects.load(),
611 gRegularImageObjects.load());
612 }
613 #endif // SK_PDF_IMAGE_STATS
614