Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(285)

Side by Side Diff: src/doc/SkDocument_PDF.cpp

Issue 1359943003: SkPDF: add basic metadata support (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: style change Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « include/core/SkTArray.h ('k') | tests/PDFMetadataAttributeTest.cpp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright 2011 Google Inc. 2 * Copyright 2011 Google Inc.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license that can be 4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file. 5 * found in the LICENSE file.
6 */ 6 */
7 7
8 #include "SkDocument.h" 8 #include "SkDocument.h"
9 #include "SkPDFCanon.h" 9 #include "SkPDFCanon.h"
10 #include "SkPDFDevice.h" 10 #include "SkPDFDevice.h"
11 #include "SkPDFFont.h" 11 #include "SkPDFFont.h"
12 #include "SkPDFStream.h" 12 #include "SkPDFStream.h"
13 #include "SkPDFTypes.h" 13 #include "SkPDFTypes.h"
14 #include "SkPDFUtils.h"
14 #include "SkStream.h" 15 #include "SkStream.h"
15 16
17 class SkPDFDict;
18
16 static void emit_pdf_header(SkWStream* stream) { 19 static void emit_pdf_header(SkWStream* stream) {
17 stream->writeText("%PDF-1.4\n%"); 20 stream->writeText("%PDF-1.4\n%");
18 // The PDF spec recommends including a comment with four bytes, all 21 // The PDF spec recommends including a comment with four bytes, all
19 // with their high bits set. This is "Skia" with the high bits set. 22 // with their high bits set. This is "Skia" with the high bits set.
20 stream->write32(0xD3EBE9E1); 23 stream->write32(0xD3EBE9E1);
21 stream->writeText("\n"); 24 stream->writeText("\n");
22 } 25 }
23 26
24 static void emit_pdf_footer(SkWStream* stream, 27 static void emit_pdf_footer(SkWStream* stream,
25 const SkPDFObjNumMap& objNumMap, 28 const SkPDFObjNumMap& objNumMap,
26 const SkPDFSubstituteMap& substitutes, 29 const SkPDFSubstituteMap& substitutes,
27 SkPDFObject* docCatalog, 30 SkPDFObject* docCatalog,
28 int64_t objCount, 31 int64_t objCount,
29 int32_t xRefFileOffset) { 32 int32_t xRefFileOffset,
33 SkPDFDict* info) {
30 SkPDFDict trailerDict; 34 SkPDFDict trailerDict;
31 // TODO(vandebo): Linearized format will take a Prev entry too. 35 // TODO(vandebo): Linearized format will take a Prev entry too.
32 // TODO(vandebo): PDF/A requires an ID entry. 36 // TODO(vandebo): PDF/A requires an ID entry.
33 trailerDict.insertInt("Size", int(objCount)); 37 trailerDict.insertInt("Size", int(objCount));
34 trailerDict.insertObjRef("Root", SkRef(docCatalog)); 38 trailerDict.insertObjRef("Root", SkRef(docCatalog));
39 SkASSERT(info);
40 trailerDict.insertObjRef("Info", SkRef(info));
35 41
36 stream->writeText("trailer\n"); 42 stream->writeText("trailer\n");
37 trailerDict.emitObject(stream, objNumMap, substitutes); 43 trailerDict.emitObject(stream, objNumMap, substitutes);
38 stream->writeText("\nstartxref\n"); 44 stream->writeText("\nstartxref\n");
39 stream->writeBigDecAsText(xRefFileOffset); 45 stream->writeBigDecAsText(xRefFileOffset);
40 stream->writeText("\n%%EOF"); 46 stream->writeText("\n%%EOF");
41 } 47 }
42 48
43 static void perform_font_subsetting( 49 static void perform_font_subsetting(
44 const SkTDArray<const SkPDFDevice*>& pageDevices, 50 const SkTDArray<const SkPDFDevice*>& pageDevices,
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
149 nextRoundNodes.rewind(); 155 nextRoundNodes.rewind();
150 treeCapacity *= kNodeSize; 156 treeCapacity *= kNodeSize;
151 } while (curNodes.count() > 1); 157 } while (curNodes.count() > 1);
152 158
153 pageTree->push(curNodes[0]); // Transfer reference. 159 pageTree->push(curNodes[0]); // Transfer reference.
154 if (rootNode) { 160 if (rootNode) {
155 *rootNode = curNodes[0]; 161 *rootNode = curNodes[0];
156 } 162 }
157 } 163 }
158 164
165 struct Metadata {
166 SkTArray<SkDocument::Attribute> fInfo;
167 SkAutoTDelete<const SkTime::DateTime> fCreation;
168 SkAutoTDelete<const SkTime::DateTime> fModified;
169 };
170
171 static SkString pdf_date(const SkTime::DateTime& dt) {
172 int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes);
173 char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-';
174 int timeZoneHours = SkTAbs(timeZoneMinutes) / 60;
175 timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60;
176 return SkStringPrintf(
177 "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'",
178 static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth),
179 static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour),
180 static_cast<unsigned>(dt.fMinute),
181 static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours,
182 timeZoneMinutes);
183 }
184
185 SkPDFDict* create_document_information_dict(const Metadata& metadata) {
186 SkAutoTUnref<SkPDFDict> dict(new SkPDFDict);
187 static const char* keys[] = {
188 "Title", "Author", "Subject", "Keywords", "Creator" };
189 for (const char* key : keys) {
190 for (const SkDocument::Attribute& keyValue : metadata.fInfo) {
191 if (keyValue.fKey.equals(key)) {
192 dict->insertString(key, keyValue.fValue);
193 }
194 }
195 }
196 dict->insertString("Producer", "Skia/PDF");
197 if (metadata.fCreation) {
198 dict->insertString("CreationDate", pdf_date(*metadata.fCreation.get()));
199 }
200 if (metadata.fModified) {
201 dict->insertString("ModDate", pdf_date(*metadata.fModified.get()));
202 }
203 return dict.detach();
204 }
205
159 static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, 206 static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices,
207 const Metadata& metadata,
160 SkWStream* stream) { 208 SkWStream* stream) {
161 if (pageDevices.isEmpty()) { 209 if (pageDevices.isEmpty()) {
162 return false; 210 return false;
163 } 211 }
164 212
165 SkTDArray<SkPDFDict*> pages; 213 SkTDArray<SkPDFDict*> pages;
166 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict); 214 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict);
167 215
168 for (int i = 0; i < pageDevices.count(); i++) { 216 for (int i = 0; i < pageDevices.count(); i++) {
169 SkASSERT(pageDevices[i]); 217 SkASSERT(pageDevices[i]);
(...skipping 21 matching lines...) Expand all
191 outputIntent->insertString("OutputConditionIdentifier", "sRGB"); 239 outputIntent->insertString("OutputConditionIdentifier", "sRGB");
192 SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray); 240 SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray);
193 intentArray->appendObject(SkRef(outputIntent.get())); 241 intentArray->appendObject(SkRef(outputIntent.get()));
194 docCatalog->insertObject("OutputIntent", intentArray.detach()); 242 docCatalog->insertObject("OutputIntent", intentArray.detach());
195 */ 243 */
196 244
197 // Build font subsetting info before proceeding. 245 // Build font subsetting info before proceeding.
198 SkPDFSubstituteMap substitutes; 246 SkPDFSubstituteMap substitutes;
199 perform_font_subsetting(pageDevices, &substitutes); 247 perform_font_subsetting(pageDevices, &substitutes);
200 248
249 SkAutoTUnref<SkPDFDict> infoDict(
250 create_document_information_dict(metadata));
201 SkPDFObjNumMap objNumMap; 251 SkPDFObjNumMap objNumMap;
252 if (objNumMap.addObject(infoDict)) {
253 infoDict->addResources(&objNumMap, substitutes);
254 }
202 if (objNumMap.addObject(docCatalog.get())) { 255 if (objNumMap.addObject(docCatalog.get())) {
203 docCatalog->addResources(&objNumMap, substitutes); 256 docCatalog->addResources(&objNumMap, substitutes);
204 } 257 }
205 size_t baseOffset = stream->bytesWritten(); 258 size_t baseOffset = stream->bytesWritten();
206 emit_pdf_header(stream); 259 emit_pdf_header(stream);
207 SkTDArray<int32_t> offsets; 260 SkTDArray<int32_t> offsets;
208 for (int i = 0; i < objNumMap.objects().count(); ++i) { 261 for (int i = 0; i < objNumMap.objects().count(); ++i) {
209 SkPDFObject* object = objNumMap.objects()[i]; 262 SkPDFObject* object = objNumMap.objects()[i];
210 size_t offset = stream->bytesWritten(); 263 size_t offset = stream->bytesWritten();
211 // This assert checks that size(pdf_header) > 0 and that 264 // This assert checks that size(pdf_header) > 0 and that
(...skipping 14 matching lines...) Expand all
226 279
227 stream->writeText("xref\n0 "); 280 stream->writeText("xref\n0 ");
228 stream->writeDecAsText(objCount); 281 stream->writeDecAsText(objCount);
229 stream->writeText("\n0000000000 65535 f \n"); 282 stream->writeText("\n0000000000 65535 f \n");
230 for (int i = 0; i < offsets.count(); i++) { 283 for (int i = 0; i < offsets.count(); i++) {
231 SkASSERT(offsets[i] > 0); 284 SkASSERT(offsets[i] > 0);
232 stream->writeBigDecAsText(offsets[i], 10); 285 stream->writeBigDecAsText(offsets[i], 10);
233 stream->writeText(" 00000 n \n"); 286 stream->writeText(" 00000 n \n");
234 } 287 }
235 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, 288 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount,
236 xRefFileOffset); 289 xRefFileOffset, infoDict);
237 290
238 // The page tree has both child and parent pointers, so it creates a 291 // The page tree has both child and parent pointers, so it creates a
239 // reference cycle. We must clear that cycle to properly reclaim memory. 292 // reference cycle. We must clear that cycle to properly reclaim memory.
240 for (int i = 0; i < pageTree.count(); i++) { 293 for (int i = 0; i < pageTree.count(); i++) {
241 pageTree[i]->clear(); 294 pageTree[i]->clear();
242 } 295 }
243 pageTree.safeUnrefAll(); 296 pageTree.safeUnrefAll();
244 pages.unrefAll(); 297 pages.unrefAll();
245 return true; 298 return true;
246 } 299 }
(...skipping 30 matching lines...) Expand all
277 } 330 }
278 if (notSubsettableCount) { 331 if (notSubsettableCount) {
279 *notSubsettableCount = notSubsettable; 332 *notSubsettableCount = notSubsettable;
280 333
281 } 334 }
282 if (notEmbeddableCount) { 335 if (notEmbeddableCount) {
283 *notEmbeddableCount = notEmbeddable; 336 *notEmbeddableCount = notEmbeddable;
284 } 337 }
285 } 338 }
286 #endif 339 #endif
340
341 template <typename T> static T* clone(const T* o) { return o ? new T(*o) : nullp tr; }
287 //////////////////////////////////////////////////////////////////////////////// 342 ////////////////////////////////////////////////////////////////////////////////
288 343
289 namespace { 344 namespace {
290 class SkDocument_PDF : public SkDocument { 345 class SkDocument_PDF : public SkDocument {
291 public: 346 public:
292 SkDocument_PDF(SkWStream* stream, 347 SkDocument_PDF(SkWStream* stream,
293 void (*doneProc)(SkWStream*, bool), 348 void (*doneProc)(SkWStream*, bool),
294 SkScalar rasterDpi) 349 SkScalar rasterDpi)
295 : SkDocument(stream, doneProc) 350 : SkDocument(stream, doneProc)
296 , fRasterDpi(rasterDpi) {} 351 , fRasterDpi(rasterDpi) {}
(...skipping 21 matching lines...) Expand all
318 373
319 void onEndPage() override { 374 void onEndPage() override {
320 SkASSERT(fCanvas.get()); 375 SkASSERT(fCanvas.get());
321 fCanvas->flush(); 376 fCanvas->flush();
322 fCanvas.reset(nullptr); 377 fCanvas.reset(nullptr);
323 } 378 }
324 379
325 bool onClose(SkWStream* stream) override { 380 bool onClose(SkWStream* stream) override {
326 SkASSERT(!fCanvas.get()); 381 SkASSERT(!fCanvas.get());
327 382
328 bool success = emit_pdf_document(fPageDevices, stream); 383 bool success = emit_pdf_document(fPageDevices, fMetadata, stream);
329 fPageDevices.unrefAll(); 384 fPageDevices.unrefAll();
330 fCanon.reset(); 385 fCanon.reset();
331 return success; 386 return success;
332 } 387 }
333 388
334 void onAbort() override { 389 void onAbort() override {
335 fPageDevices.unrefAll(); 390 fPageDevices.unrefAll();
336 fCanon.reset(); 391 fCanon.reset();
337 } 392 }
338 393
394 void setMetadata(const SkTArray<SkDocument::Attribute>& info,
395 const SkTime::DateTime* creationDate,
396 const SkTime::DateTime* modifiedDate) override {
397 fMetadata.fInfo = info;
398 fMetadata.fCreation.reset(clone(creationDate));
399 fMetadata.fModified.reset(clone(modifiedDate));
400 }
401
339 private: 402 private:
340 SkPDFCanon fCanon; 403 SkPDFCanon fCanon;
341 SkTDArray<const SkPDFDevice*> fPageDevices; 404 SkTDArray<const SkPDFDevice*> fPageDevices;
342 SkAutoTUnref<SkCanvas> fCanvas; 405 SkAutoTUnref<SkCanvas> fCanvas;
343 SkScalar fRasterDpi; 406 SkScalar fRasterDpi;
407 Metadata fMetadata;
344 }; 408 };
345 } // namespace 409 } // namespace
346 /////////////////////////////////////////////////////////////////////////////// 410 ///////////////////////////////////////////////////////////////////////////////
347 411
348 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { 412 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) {
349 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr; 413 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr;
350 } 414 }
351 415
352 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { 416 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) {
353 SkFILEWStream* stream = new SkFILEWStream(path); 417 SkFILEWStream* stream = new SkFILEWStream(path);
354 if (!stream->isValid()) { 418 if (!stream->isValid()) {
355 delete stream; 419 delete stream;
356 return nullptr; 420 return nullptr;
357 } 421 }
358 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; }; 422 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; };
359 return new SkDocument_PDF(stream, delete_wstream, dpi); 423 return new SkDocument_PDF(stream, delete_wstream, dpi);
360 } 424 }
OLDNEW
« no previous file with comments | « include/core/SkTArray.h ('k') | tests/PDFMetadataAttributeTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698