| OLD | NEW |
| 1 | 1 |
| 2 /* | 2 /* |
| 3 * Copyright 2011 Google Inc. | 3 * Copyright 2011 Google Inc. |
| 4 * | 4 * |
| 5 * Use of this source code is governed by a BSD-style license that can be | 5 * Use of this source code is governed by a BSD-style license that can be |
| 6 * found in the LICENSE file. | 6 * found in the LICENSE file. |
| 7 */ | 7 */ |
| 8 | 8 |
| 9 | 9 |
| 10 #include "SkPDFCatalog.h" | 10 #include "SkPDFCatalog.h" |
| 11 #include "SkPDFDevice.h" | 11 #include "SkPDFDevice.h" |
| 12 #include "SkPDFDocument.h" | 12 #include "SkPDFDocument.h" |
| 13 #include "SkPDFFont.h" | 13 #include "SkPDFFont.h" |
| 14 #include "SkPDFPage.h" | 14 #include "SkPDFPage.h" |
| 15 #include "SkPDFTypes.h" | 15 #include "SkPDFTypes.h" |
| 16 #include "SkStream.h" | 16 #include "SkStream.h" |
| 17 | 17 |
| 18 static void addResourcesToCatalog(bool firstPage, | |
| 19 SkTSet<SkPDFObject*>* resourceSet, | |
| 20 SkPDFCatalog* catalog) { | |
| 21 for (int i = 0; i < resourceSet->count(); i++) { | |
| 22 catalog->addObject((*resourceSet)[i], firstPage); | |
| 23 } | |
| 24 } | |
| 25 | 18 |
| 26 static void perform_font_subsetting(SkPDFCatalog* catalog, | 19 static void perform_font_subsetting(SkPDFCatalog* catalog, |
| 27 const SkTDArray<SkPDFPage*>& pages, | 20 const SkTDArray<SkPDFPage*>& pages, |
| 28 SkTDArray<SkPDFObject*>* substitutes) { | 21 SkTDArray<SkPDFObject*>* substitutes) { |
| 29 SkASSERT(catalog); | 22 SkASSERT(catalog); |
| 30 SkASSERT(substitutes); | 23 SkASSERT(substitutes); |
| 31 | 24 |
| 32 SkPDFGlyphSetMap usage; | 25 SkPDFGlyphSetMap usage; |
| 33 for (int i = 0; i < pages.count(); ++i) { | 26 for (int i = 0; i < pages.count(); ++i) { |
| 34 usage.merge(pages[i]->getFontGlyphUsage()); | 27 usage.merge(pages[i]->getFontGlyphUsage()); |
| 35 } | 28 } |
| 36 SkPDFGlyphSetMap::F2BIter iterator(usage); | 29 SkPDFGlyphSetMap::F2BIter iterator(usage); |
| 37 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next(); | 30 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next(); |
| 38 while (entry) { | 31 while (entry) { |
| 39 SkPDFFont* subsetFont = | 32 SkPDFFont* subsetFont = |
| 40 entry->fFont->getFontSubset(entry->fGlyphSet); | 33 entry->fFont->getFontSubset(entry->fGlyphSet); |
| 41 if (subsetFont) { | 34 if (subsetFont) { |
| 42 catalog->setSubstitute(entry->fFont, subsetFont); | 35 catalog->setSubstitute(entry->fFont, subsetFont); |
| 43 substitutes->push(subsetFont); // Transfer ownership to substitutes | 36 substitutes->push(subsetFont); // Transfer ownership to substitutes |
| 44 } | 37 } |
| 45 entry = iterator.next(); | 38 entry = iterator.next(); |
| 46 } | 39 } |
| 47 } | 40 } |
| 48 | 41 |
| 49 SkPDFDocument::SkPDFDocument() | 42 SkPDFDocument::SkPDFDocument() {} |
| 50 : fXRefFileOffset(0), | 43 |
| 51 fTrailerDict(NULL) { | 44 SkPDFDocument::~SkPDFDocument() { fPageDevices.unrefAll(); } |
| 52 fCatalog.reset(SkNEW(SkPDFCatalog)); | 45 |
| 53 fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog")); | 46 static void emit_pdf_header(SkWStream* stream) { |
| 54 fCatalog->addObject(fDocCatalog, true); | 47 stream->writeText("%PDF-1.4\n%"); |
| 55 fFirstPageResources = NULL; | 48 // The PDF spec recommends including a comment with four bytes, all |
| 56 fOtherPageResources = NULL; | 49 // with their high bits set. This is "Skia" with the high bits set. |
| 50 stream->write32(0xD3EBE9E1); |
| 51 stream->writeText("\n"); |
| 57 } | 52 } |
| 58 | 53 |
| 59 SkPDFDocument::~SkPDFDocument() { | 54 static void emit_pdf_footer(SkWStream* stream, |
| 60 fPages.safeUnrefAll(); | 55 SkPDFCatalog* catalog, |
| 56 SkPDFObject* docCatalog, |
| 57 int64_t objCount, |
| 58 int32_t xRefFileOffset) { |
| 59 SkPDFDict trailerDict; |
| 60 // TODO(vandebo): Linearized format will take a Prev entry too. |
| 61 // TODO(vandebo): PDF/A requires an ID entry. |
| 62 trailerDict.insertInt("Size", int(objCount)); |
| 63 trailerDict.insert("Root", new SkPDFObjRef(docCatalog))->unref(); |
| 64 |
| 65 stream->writeText("trailer\n"); |
| 66 trailerDict.emitObject(stream, catalog); |
| 67 stream->writeText("\nstartxref\n"); |
| 68 stream->writeBigDecAsText(xRefFileOffset); |
| 69 stream->writeText("\n%%EOF"); |
| 70 } |
| 71 |
| 72 bool SkPDFDocument::emitPDF(SkWStream* stream) { |
| 73 // SkTDArray<SkPDFDevice*> fPageDevices; |
| 74 if (fPageDevices.isEmpty()) { |
| 75 return false; |
| 76 } |
| 77 SkTDArray<SkPDFPage*> pages; |
| 78 for (int i = 0; i < fPageDevices.count(); i++) { |
| 79 // Reference from new passed to pages. |
| 80 pages.push(SkNEW_ARGS(SkPDFPage, (fPageDevices[i]))); |
| 81 } |
| 82 SkPDFCatalog catalog; |
| 83 |
| 84 SkTDArray<SkPDFDict*> pageTree; |
| 85 SkAutoTUnref<SkPDFDict> docCatalog(SkNEW_ARGS(SkPDFDict, ("Catalog"))); |
| 86 SkTSet<SkPDFObject*> firstPageResources; |
| 87 SkTSet<SkPDFObject*> otherPageResources; |
| 88 SkTDArray<SkPDFObject*> substitutes; |
| 89 catalog.addObject(docCatalog.get(), true); |
| 90 |
| 91 SkPDFDict* pageTreeRoot; |
| 92 SkPDFPage::GeneratePageTree(pages, &catalog, &pageTree, &pageTreeRoot); |
| 93 docCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref(); |
| 94 |
| 95 /* TODO(vandebo): output intent |
| 96 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); |
| 97 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref(); |
| 98 outputIntent->insert("OutputConditionIdentifier", |
| 99 new SkPDFString("sRGB"))->unref(); |
| 100 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray; |
| 101 intentArray->append(outputIntent.get()); |
| 102 docCatalog->insert("OutputIntent", intentArray.get()); |
| 103 */ |
| 104 |
| 105 SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict)); |
| 106 |
| 107 bool firstPage = true; |
| 108 /* The references returned in newResources are transfered to |
| 109 * firstPageResources or otherPageResources depending on firstPage and |
| 110 * knownResources doesn't have a reference but just relies on the other |
| 111 * two sets to maintain a reference. |
| 112 */ |
| 113 SkTSet<SkPDFObject*> knownResources; |
| 114 |
| 115 // mergeInto returns the number of duplicates. |
| 116 // If there are duplicates, there is a bug and we mess ref counting. |
| 117 SkDEBUGCODE(int duplicates = ) knownResources.mergeInto(firstPageResources); |
| 118 SkASSERT(duplicates == 0); |
| 119 |
| 120 for (int i = 0; i < pages.count(); i++) { |
| 121 if (i == 1) { |
| 122 firstPage = false; |
| 123 SkDEBUGCODE(duplicates = ) |
| 124 knownResources.mergeInto(otherPageResources); |
| 125 } |
| 126 SkTSet<SkPDFObject*> newResources; |
| 127 pages[i]->finalizePage(&catalog, firstPage, knownResources, |
| 128 &newResources); |
| 129 for (int j = 0; j < newResources.count(); j++) { |
| 130 catalog.addObject(newResources[i], firstPage); |
| 131 } |
| 132 if (firstPage) { |
| 133 SkDEBUGCODE(duplicates = ) |
| 134 firstPageResources.mergeInto(newResources); |
| 135 } else { |
| 136 SkDEBUGCODE(duplicates = ) |
| 137 otherPageResources.mergeInto(newResources); |
| 138 } |
| 139 SkASSERT(duplicates == 0); |
| 140 |
| 141 SkDEBUGCODE(duplicates = ) knownResources.mergeInto(newResources); |
| 142 SkASSERT(duplicates == 0); |
| 143 |
| 144 pages[i]->appendDestinations(dests); |
| 145 } |
| 146 |
| 147 if (dests->size() > 0) { |
| 148 SkPDFDict* raw_dests = dests.get(); |
| 149 firstPageResources.add(dests.detach()); // Transfer ownership. |
| 150 catalog.addObject(raw_dests, true /* onFirstPage */); |
| 151 docCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (raw_dests))) |
| 152 ->unref(); |
| 153 } |
| 154 |
| 155 // Build font subsetting info before proceeding. |
| 156 perform_font_subsetting(&catalog, pages, &substitutes); |
| 157 |
| 158 SkTSet<SkPDFObject*> resourceSet; |
| 159 if (resourceSet.add(docCatalog.get())) { |
| 160 docCatalog->addResources(&resourceSet, &catalog); |
| 161 } |
| 162 size_t baseOffset = SkToOffT(stream->bytesWritten()); |
| 163 emit_pdf_header(stream); |
| 164 for (int i = 0; i < resourceSet.count(); ++i) { |
| 165 SkPDFObject* object = resourceSet[i]; |
| 166 catalog.setFileOffset(object, |
| 167 SkToOffT(stream->bytesWritten() - baseOffset)); |
| 168 SkASSERT(object == catalog.getSubstituteObject(object)); |
| 169 stream->writeDecAsText(catalog.getObjectNumber(object)); |
| 170 stream->writeText(" 0 obj\n"); // Generation number is always 0. |
| 171 object->emitObject(stream, &catalog); |
| 172 stream->writeText("\nendobj\n"); |
| 173 } |
| 174 int32_t xRefFileOffset = SkToS32(stream->bytesWritten() - baseOffset); |
| 175 int64_t objCount = catalog.emitXrefTable(stream, pages.count() > 1); |
| 176 |
| 177 emit_pdf_footer(stream, &catalog, docCatalog.get(), objCount, |
| 178 xRefFileOffset); |
| 61 | 179 |
| 62 // The page tree has both child and parent pointers, so it creates a | 180 // The page tree has both child and parent pointers, so it creates a |
| 63 // reference cycle. We must clear that cycle to properly reclaim memory. | 181 // reference cycle. We must clear that cycle to properly reclaim memory. |
| 64 for (int i = 0; i < fPageTree.count(); i++) { | 182 for (int i = 0; i < pageTree.count(); i++) { |
| 65 fPageTree[i]->clear(); | 183 pageTree[i]->clear(); |
| 66 } | 184 } |
| 67 fPageTree.safeUnrefAll(); | 185 pageTree.safeUnrefAll(); |
| 186 pages.unrefAll(); |
| 68 | 187 |
| 69 if (fFirstPageResources) { | 188 firstPageResources.safeUnrefAll(); |
| 70 fFirstPageResources->safeUnrefAll(); | 189 otherPageResources.safeUnrefAll(); |
| 71 } | |
| 72 if (fOtherPageResources) { | |
| 73 fOtherPageResources->safeUnrefAll(); | |
| 74 } | |
| 75 | 190 |
| 76 fSubstitutes.safeUnrefAll(); | 191 substitutes.unrefAll(); |
| 77 | 192 docCatalog.reset(NULL); |
| 78 fDocCatalog->unref(); | |
| 79 SkSafeUnref(fTrailerDict); | |
| 80 SkDELETE(fFirstPageResources); | |
| 81 SkDELETE(fOtherPageResources); | |
| 82 } | |
| 83 | |
| 84 bool SkPDFDocument::emitPDF(SkWStream* stream) { | |
| 85 if (fPages.isEmpty()) { | |
| 86 return false; | |
| 87 } | |
| 88 for (int i = 0; i < fPages.count(); i++) { | |
| 89 if (fPages[i] == NULL) { | |
| 90 return false; | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 fFirstPageResources = SkNEW(SkTSet<SkPDFObject*>); | |
| 95 fOtherPageResources = SkNEW(SkTSet<SkPDFObject*>); | |
| 96 | |
| 97 // We haven't emitted the document before if fPageTree is empty. | |
| 98 if (fPageTree.isEmpty()) { | |
| 99 SkPDFDict* pageTreeRoot; | |
| 100 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree, | |
| 101 &pageTreeRoot); | |
| 102 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref(); | |
| 103 | |
| 104 /* TODO(vandebo): output intent | |
| 105 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); | |
| 106 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref(); | |
| 107 outputIntent->insert("OutputConditionIdentifier", | |
| 108 new SkPDFString("sRGB"))->unref(); | |
| 109 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray; | |
| 110 intentArray->append(outputIntent.get()); | |
| 111 fDocCatalog->insert("OutputIntent", intentArray.get()); | |
| 112 */ | |
| 113 | |
| 114 SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict)); | |
| 115 | |
| 116 bool firstPage = true; | |
| 117 /* The references returned in newResources are transfered to | |
| 118 * fFirstPageResources or fOtherPageResources depending on firstPage and | |
| 119 * knownResources doesn't have a reference but just relies on the other | |
| 120 * two sets to maintain a reference. | |
| 121 */ | |
| 122 SkTSet<SkPDFObject*> knownResources; | |
| 123 | |
| 124 // mergeInto returns the number of duplicates. | |
| 125 // If there are duplicates, there is a bug and we mess ref counting. | |
| 126 SkDEBUGCODE(int duplicates =) knownResources.mergeInto(*fFirstPageResour
ces); | |
| 127 SkASSERT(duplicates == 0); | |
| 128 | |
| 129 for (int i = 0; i < fPages.count(); i++) { | |
| 130 if (i == 1) { | |
| 131 firstPage = false; | |
| 132 SkDEBUGCODE(duplicates =) knownResources.mergeInto(*fOtherPageRe
sources); | |
| 133 } | |
| 134 SkTSet<SkPDFObject*> newResources; | |
| 135 fPages[i]->finalizePage( | |
| 136 fCatalog.get(), firstPage, knownResources, &newResources); | |
| 137 addResourcesToCatalog(firstPage, &newResources, fCatalog.get()); | |
| 138 if (firstPage) { | |
| 139 SkDEBUGCODE(duplicates =) fFirstPageResources->mergeInto(newReso
urces); | |
| 140 } else { | |
| 141 SkDEBUGCODE(duplicates =) fOtherPageResources->mergeInto(newReso
urces); | |
| 142 } | |
| 143 SkASSERT(duplicates == 0); | |
| 144 | |
| 145 SkDEBUGCODE(duplicates =) knownResources.mergeInto(newResources); | |
| 146 SkASSERT(duplicates == 0); | |
| 147 | |
| 148 fPages[i]->appendDestinations(dests); | |
| 149 } | |
| 150 | |
| 151 if (dests->size() > 0) { | |
| 152 SkPDFDict* raw_dests = dests.get(); | |
| 153 fFirstPageResources->add(dests.detach()); // Transfer ownership. | |
| 154 fCatalog->addObject(raw_dests, true /* onFirstPage */); | |
| 155 fDocCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (raw_dests)))->
unref(); | |
| 156 } | |
| 157 | |
| 158 // Build font subsetting info before proceeding. | |
| 159 perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes); | |
| 160 } | |
| 161 | |
| 162 SkTSet<SkPDFObject*> resourceSet; | |
| 163 if (resourceSet.add(fDocCatalog)) { | |
| 164 fDocCatalog->addResources(&resourceSet, fCatalog); | |
| 165 } | |
| 166 off_t baseOffset = SkToOffT(stream->bytesWritten()); | |
| 167 emitHeader(stream); | |
| 168 for (int i = 0; i < resourceSet.count(); ++i) { | |
| 169 SkPDFObject* object = resourceSet[i]; | |
| 170 fCatalog->setFileOffset(object, | |
| 171 SkToOffT(stream->bytesWritten()) - baseOffset); | |
| 172 SkASSERT(object == fCatalog->getSubstituteObject(object)); | |
| 173 stream->writeDecAsText(fCatalog->getObjectNumber(object)); | |
| 174 stream->writeText(" 0 obj\n"); // Generation number is always 0. | |
| 175 object->emitObject(stream, fCatalog); | |
| 176 stream->writeText("\nendobj\n"); | |
| 177 } | |
| 178 fXRefFileOffset = SkToOffT(stream->bytesWritten()) - baseOffset; | |
| 179 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1); | |
| 180 emitFooter(stream, objCount); | |
| 181 return true; | 193 return true; |
| 182 } | 194 } |
| 183 | 195 |
| 184 // TODO(halcanary): remove this method, since it is unused. | |
| 185 bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) { | |
| 186 if (!fPageTree.isEmpty()) { | |
| 187 return false; | |
| 188 } | |
| 189 | |
| 190 pageNumber--; | |
| 191 SkASSERT(pageNumber >= 0); | |
| 192 | |
| 193 if (pageNumber >= fPages.count()) { | |
| 194 int oldSize = fPages.count(); | |
| 195 fPages.setCount(pageNumber + 1); | |
| 196 for (int i = oldSize; i <= pageNumber; i++) { | |
| 197 fPages[i] = NULL; | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 SkPDFPage* page = new SkPDFPage(pdfDevice); | |
| 202 SkSafeUnref(fPages[pageNumber]); | |
| 203 fPages[pageNumber] = page; // Reference from new passed to fPages. | |
| 204 return true; | |
| 205 } | |
| 206 | |
| 207 bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) { | |
| 208 if (!fPageTree.isEmpty()) { | |
| 209 return false; | |
| 210 } | |
| 211 | |
| 212 SkPDFPage* page = new SkPDFPage(pdfDevice); | |
| 213 fPages.push(page); // Reference from new passed to fPages. | |
| 214 return true; | |
| 215 } | |
| 216 | |
| 217 // Deprecated. | |
| 218 // TODO(halcanary): remove | |
| 219 void SkPDFDocument::getCountOfFontTypes( | |
| 220 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 2]) const { | |
| 221 sk_bzero(counts, sizeof(int) * | |
| 222 (SkAdvancedTypefaceMetrics::kOther_Font + 2)); | |
| 223 SkTDArray<SkFontID> seenFonts; | |
| 224 int notEmbeddable = 0; | |
| 225 | |
| 226 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) { | |
| 227 const SkTDArray<SkPDFFont*>& fontResources = | |
| 228 fPages[pageNumber]->getFontResources(); | |
| 229 for (int font = 0; font < fontResources.count(); font++) { | |
| 230 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); | |
| 231 if (seenFonts.find(fontID) == -1) { | |
| 232 counts[fontResources[font]->getType()]++; | |
| 233 seenFonts.push(fontID); | |
| 234 if (!fontResources[font]->canEmbed()) { | |
| 235 notEmbeddable++; | |
| 236 } | |
| 237 } | |
| 238 } | |
| 239 } | |
| 240 counts[SkAdvancedTypefaceMetrics::kOther_Font + 1] = notEmbeddable; | |
| 241 } | |
| 242 | |
| 243 // TODO(halcanary): expose notEmbeddableCount in SkDocument | 196 // TODO(halcanary): expose notEmbeddableCount in SkDocument |
| 244 void SkPDFDocument::getCountOfFontTypes( | 197 void SkPDFDocument::getCountOfFontTypes( |
| 245 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1], | 198 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1], |
| 246 int* notSubsettableCount, | 199 int* notSubsettableCount, |
| 247 int* notEmbeddableCount) const { | 200 int* notEmbeddableCount) const { |
| 248 sk_bzero(counts, sizeof(int) * | 201 sk_bzero(counts, sizeof(int) * |
| 249 (SkAdvancedTypefaceMetrics::kOther_Font + 1)); | 202 (SkAdvancedTypefaceMetrics::kOther_Font + 1)); |
| 250 SkTDArray<SkFontID> seenFonts; | 203 SkTDArray<SkFontID> seenFonts; |
| 251 int notSubsettable = 0; | 204 int notSubsettable = 0; |
| 252 int notEmbeddable = 0; | 205 int notEmbeddable = 0; |
| 253 | 206 |
| 254 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) { | 207 for (int pageNumber = 0; pageNumber < fPageDevices.count(); pageNumber++) { |
| 255 const SkTDArray<SkPDFFont*>& fontResources = | 208 const SkTDArray<SkPDFFont*>& fontResources = |
| 256 fPages[pageNumber]->getFontResources(); | 209 fPageDevices[pageNumber]->getFontResources(); |
| 257 for (int font = 0; font < fontResources.count(); font++) { | 210 for (int font = 0; font < fontResources.count(); font++) { |
| 258 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); | 211 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); |
| 259 if (seenFonts.find(fontID) == -1) { | 212 if (seenFonts.find(fontID) == -1) { |
| 260 counts[fontResources[font]->getType()]++; | 213 counts[fontResources[font]->getType()]++; |
| 261 seenFonts.push(fontID); | 214 seenFonts.push(fontID); |
| 262 if (!fontResources[font]->canSubset()) { | 215 if (!fontResources[font]->canSubset()) { |
| 263 notSubsettable++; | 216 notSubsettable++; |
| 264 } | 217 } |
| 265 if (!fontResources[font]->canEmbed()) { | 218 if (!fontResources[font]->canEmbed()) { |
| 266 notEmbeddable++; | 219 notEmbeddable++; |
| 267 } | 220 } |
| 268 } | 221 } |
| 269 } | 222 } |
| 270 } | 223 } |
| 271 if (notSubsettableCount) { | 224 if (notSubsettableCount) { |
| 272 *notSubsettableCount = notSubsettable; | 225 *notSubsettableCount = notSubsettable; |
| 273 | 226 |
| 274 } | 227 } |
| 275 if (notEmbeddableCount) { | 228 if (notEmbeddableCount) { |
| 276 *notEmbeddableCount = notEmbeddable; | 229 *notEmbeddableCount = notEmbeddable; |
| 277 } | 230 } |
| 278 } | 231 } |
| 279 | |
| 280 void SkPDFDocument::emitHeader(SkWStream* stream) { | |
| 281 stream->writeText("%PDF-1.4\n%"); | |
| 282 // The PDF spec recommends including a comment with four bytes, all | |
| 283 // with their high bits set. This is "Skia" with the high bits set. | |
| 284 stream->write32(0xD3EBE9E1); | |
| 285 stream->writeText("\n"); | |
| 286 } | |
| 287 | |
| 288 //TODO(halcanary): remove this function | |
| 289 size_t SkPDFDocument::headerSize() { | |
| 290 SkDynamicMemoryWStream buffer; | |
| 291 emitHeader(&buffer); | |
| 292 return buffer.getOffset(); | |
| 293 } | |
| 294 | |
| 295 void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) { | |
| 296 if (NULL == fTrailerDict) { | |
| 297 fTrailerDict = SkNEW(SkPDFDict); | |
| 298 | |
| 299 // TODO(vandebo): Linearized format will take a Prev entry too. | |
| 300 // TODO(vandebo): PDF/A requires an ID entry. | |
| 301 fTrailerDict->insertInt("Size", int(objCount)); | |
| 302 fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref(); | |
| 303 } | |
| 304 | |
| 305 stream->writeText("trailer\n"); | |
| 306 fTrailerDict->emitObject(stream, fCatalog.get()); | |
| 307 stream->writeText("\nstartxref\n"); | |
| 308 stream->writeBigDecAsText(fXRefFileOffset); | |
| 309 stream->writeText("\n%%EOF"); | |
| 310 } | |
| OLD | NEW |