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
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