blob: 3d17fb9d4eb75053a8009effdc36f7547cf74e89 [file] [log] [blame]
edisonn@google.com3aa35552013-08-14 18:26:20 +00001#include "SkPdfNativeDoc.h"
edisonn@google.com571c70b2013-07-10 17:09:50 +00002#include "SkPdfNativeTokenizer.h"
edisonn@google.com3aa35552013-08-14 18:26:20 +00003#include "SkPdfNativeObject.h"
edisonn@google.com3aac1f92013-07-02 22:42:53 +00004
edisonn@google.com571c70b2013-07-10 17:09:50 +00005#include <stdio.h>
6#include <string.h>
7#include <sys/types.h>
8#include <sys/stat.h>
edisonn@google.com3aac1f92013-07-02 22:42:53 +00009
edisonn@google.com571c70b2013-07-10 17:09:50 +000010#include "SkPdfFileTrailerDictionary_autogen.h"
11#include "SkPdfCatalogDictionary_autogen.h"
12#include "SkPdfPageObjectDictionary_autogen.h"
13#include "SkPdfPageTreeNodeDictionary_autogen.h"
14#include "SkPdfMapper_autogen.h"
15
edisonn@google.com147adb12013-07-24 15:56:19 +000016#include "SkStream.h"
edisonn@google.com571c70b2013-07-10 17:09:50 +000017
18
edisonn@google.coma3356fc2013-07-10 18:20:06 +000019static long getFileSize(const char* filename)
edisonn@google.com571c70b2013-07-10 17:09:50 +000020{
21 struct stat stat_buf;
22 int rc = stat(filename, &stat_buf);
edisonn@google.coma3356fc2013-07-10 18:20:06 +000023 return rc == 0 ? (long)stat_buf.st_size : -1;
edisonn@google.com3aac1f92013-07-02 22:42:53 +000024}
25
edisonn@google.com2ccc3af2013-07-23 17:43:18 +000026static const unsigned char* lineHome(const unsigned char* start, const unsigned char* current) {
edisonn@google.com571c70b2013-07-10 17:09:50 +000027 while (current > start && !isPdfEOL(*(current - 1))) {
28 current--;
29 }
30 return current;
31}
32
edisonn@google.com2ccc3af2013-07-23 17:43:18 +000033static const unsigned char* previousLineHome(const unsigned char* start, const unsigned char* current) {
edisonn@google.com571c70b2013-07-10 17:09:50 +000034 if (current > start && isPdfEOL(*(current - 1))) {
35 current--;
36 }
37
38 // allows CR+LF, LF+CR but not two CR+CR or LF+LF
39 if (current > start && isPdfEOL(*(current - 1)) && *current != *(current - 1)) {
40 current--;
41 }
42
43 while (current > start && !isPdfEOL(*(current - 1))) {
44 current--;
45 }
46
47 return current;
48}
49
edisonn@google.com2ccc3af2013-07-23 17:43:18 +000050static const unsigned char* ignoreLine(const unsigned char* current, const unsigned char* end) {
edisonn@google.com571c70b2013-07-10 17:09:50 +000051 while (current < end && !isPdfEOL(*current)) {
52 current++;
53 }
54 current++;
55 if (current < end && isPdfEOL(*current) && *current != *(current - 1)) {
56 current++;
57 }
58 return current;
59}
60
edisonn@google.com3aa35552013-08-14 18:26:20 +000061SkPdfNativeDoc* gDoc = NULL;
edisonn@google.com571c70b2013-07-10 17:09:50 +000062
63// TODO(edisonn): NYI
64// TODO(edisonn): 3 constructuctors from URL, from stream, from file ...
65// TODO(edisonn): write one that accepts errors in the file and ignores/fixis them
66// TODO(edisonn): testing:
67// 1) run on a lot of file
68// 2) recoverable corupt file: remove endobj, endsteam, remove other keywords, use other white spaces, insert comments randomly, ...
69// 3) irrecoverable corrupt file
edisonn@google.com147adb12013-07-24 15:56:19 +000070
edisonn@google.com3aa35552013-08-14 18:26:20 +000071SkPdfNativeDoc::SkPdfNativeDoc(SkStream* stream)
edisonn@google.com147adb12013-07-24 15:56:19 +000072 : fAllocator(new SkPdfAllocator())
73 , fFileContent(NULL)
74 , fContentLength(0)
75 , fRootCatalogRef(NULL)
76 , fRootCatalog(NULL) {
77 size_t size = stream->getLength();
78 void* ptr = sk_malloc_throw(size);
79 stream->read(ptr, size);
80
81 init(ptr, size);
82}
83
edisonn@google.com3aa35552013-08-14 18:26:20 +000084SkPdfNativeDoc::SkPdfNativeDoc(const char* path)
edisonn@google.com432640a2013-07-10 22:53:40 +000085 : fAllocator(new SkPdfAllocator())
edisonn@google.com147adb12013-07-24 15:56:19 +000086 , fFileContent(NULL)
87 , fContentLength(0)
edisonn@google.com432640a2013-07-10 22:53:40 +000088 , fRootCatalogRef(NULL)
89 , fRootCatalog(NULL) {
edisonn@google.com222382b2013-07-10 22:33:10 +000090 gDoc = this;
edisonn@google.com571c70b2013-07-10 17:09:50 +000091 FILE* file = fopen(path, "r");
edisonn@google.come57c62d2013-08-07 18:04:15 +000092 // TODO(edisonn): put this in a function that can return NULL
93 if (file) {
94 size_t size = getFileSize(path);
95 void* content = sk_malloc_throw(size);
96 bool ok = (0 != fread(content, size, 1, file));
97 fclose(file);
98 if (!ok) {
99 sk_free(content);
100 // TODO(edisonn): report read error
101 // TODO(edisonn): not nice to return like this from constructor, create a static
102 // function that can report NULL for failures.
103 return; // Doc will have 0 pages
104 }
edisonn@google.com571c70b2013-07-10 17:09:50 +0000105
edisonn@google.come57c62d2013-08-07 18:04:15 +0000106 init(content, size);
edisonn@google.com620edc52013-07-18 13:03:03 +0000107 }
edisonn@google.com147adb12013-07-24 15:56:19 +0000108}
109
edisonn@google.com3aa35552013-08-14 18:26:20 +0000110void SkPdfNativeDoc::init(const void* bytes, size_t length) {
edisonn@google.com147adb12013-07-24 15:56:19 +0000111 fFileContent = (const unsigned char*)bytes;
112 fContentLength = length;
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000113 const unsigned char* eofLine = lineHome(fFileContent, fFileContent + fContentLength - 1);
114 const unsigned char* xrefByteOffsetLine = previousLineHome(fFileContent, eofLine);
115 const unsigned char* xrefstartKeywordLine = previousLineHome(fFileContent, xrefByteOffsetLine);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000116
117 if (strcmp((char*)xrefstartKeywordLine, "startxref") != 0) {
118 // TODO(edisonn): report/issue
119 }
120
121 long xrefByteOffset = atol((const char*)xrefByteOffsetLine);
122
123 bool storeCatalog = true;
124 while (xrefByteOffset >= 0) {
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000125 const unsigned char* trailerStart = readCrossReferenceSection(fFileContent + xrefByteOffset, xrefstartKeywordLine);
edisonn@google.com24cdf132013-07-30 16:06:12 +0000126 xrefByteOffset = -1;
127 if (trailerStart < xrefstartKeywordLine) {
128 readTrailer(trailerStart, xrefstartKeywordLine, storeCatalog, &xrefByteOffset, false);
129 storeCatalog = false;
130 }
edisonn@google.com571c70b2013-07-10 17:09:50 +0000131 }
132
133 // TODO(edisonn): warn/error expect fObjects[fRefCatalogId].fGeneration == fRefCatalogGeneration
134 // TODO(edisonn): security, verify that SkPdfCatalogDictionary is indeed using mapper
135 // load catalog
edisonn@google.com571c70b2013-07-10 17:09:50 +0000136
edisonn@google.com432640a2013-07-10 22:53:40 +0000137 if (fRootCatalogRef) {
138 fRootCatalog = (SkPdfCatalogDictionary*)resolveReference(fRootCatalogRef);
edisonn@google.com8bad7372013-07-10 23:36:56 +0000139 if (fRootCatalog->isDictionary() && fRootCatalog->valid()) {
140 SkPdfPageTreeNodeDictionary* tree = fRootCatalog->Pages(this);
141 if (tree && tree->isDictionary() && tree->valid()) {
142 fillPages(tree);
143 }
144 }
edisonn@google.com432640a2013-07-10 22:53:40 +0000145 }
edisonn@google.com571c70b2013-07-10 17:09:50 +0000146
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000147 // TODO(edisonn): clean up this doc, or better, let the caller call again and build a new doc
148 // caller should be a static function.
149 if (pages() == 0) {
150 loadWithoutXRef();
151 }
152
edisonn@google.com8bad7372013-07-10 23:36:56 +0000153 // TODO(edisonn): corrupted pdf, read it from beginning and rebuild (xref, trailer, or just reall all objects)
154 // 0 pages
155
edisonn@google.com571c70b2013-07-10 17:09:50 +0000156 // now actually read all objects if we want, or do it lazyly
157 // and resolve references?... or not ...
158}
159
edisonn@google.com3aa35552013-08-14 18:26:20 +0000160void SkPdfNativeDoc::loadWithoutXRef() {
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000161 const unsigned char* current = fFileContent;
162 const unsigned char* end = fFileContent + fContentLength;
163
164 // TODO(edisonn): read pdf version
165 current = ignoreLine(current, end);
166
167 current = skipPdfWhiteSpaces(0, current, end);
168 while (current < end) {
edisonn@google.com3aa35552013-08-14 18:26:20 +0000169 SkPdfNativeObject token;
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000170 current = nextObject(0, current, end, &token, NULL, NULL);
171 if (token.isInteger()) {
172 int id = (int)token.intValue();
173
174 token.reset();
175 current = nextObject(0, current, end, &token, NULL, NULL);
176 // int generation = (int)token.intValue(); // TODO(edisonn): ignored for now
177
178 token.reset();
179 current = nextObject(0, current, end, &token, NULL, NULL);
180 // TODO(edisonn): must be obj, return error if not? ignore ?
181 if (!token.isKeyword("obj")) {
182 continue;
183 }
184
185 while (fObjects.count() < id + 1) {
186 reset(fObjects.append());
187 }
188
189 fObjects[id].fOffset = current - fFileContent;
190
edisonn@google.com3aa35552013-08-14 18:26:20 +0000191 SkPdfNativeObject* obj = fAllocator->allocObject();
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000192 current = nextObject(0, current, end, obj, fAllocator, this);
193
194 fObjects[id].fResolvedReference = obj;
195 fObjects[id].fObj = obj;
196
197 // set objects
198 } else if (token.isKeyword("trailer")) {
199 long dummy;
200 current = readTrailer(current, end, true, &dummy, true);
201 } else if (token.isKeyword("startxref")) {
202 token.reset();
203 current = nextObject(0, current, end, &token, NULL, NULL); // ignore
204 }
205
206 current = skipPdfWhiteSpaces(0, current, end);
207 }
208
edisonn@google.com4f898b72013-08-07 21:11:57 +0000209 // TODO(edisonn): hack, detect root catalog - we need to implement liniarized support, and remove this hack.
210 if (!fRootCatalogRef) {
211 for (unsigned int i = 0 ; i < objects(); i++) {
edisonn@google.com3aa35552013-08-14 18:26:20 +0000212 SkPdfNativeObject* obj = object(i);
213 SkPdfNativeObject* root = (obj && obj->isDictionary()) ? obj->get("Root") : NULL;
edisonn@google.com4f898b72013-08-07 21:11:57 +0000214 if (root && root->isReference()) {
215 fRootCatalogRef = root;
216 }
217 }
218 }
219
220
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000221 if (fRootCatalogRef) {
222 fRootCatalog = (SkPdfCatalogDictionary*)resolveReference(fRootCatalogRef);
223 if (fRootCatalog->isDictionary() && fRootCatalog->valid()) {
224 SkPdfPageTreeNodeDictionary* tree = fRootCatalog->Pages(this);
225 if (tree && tree->isDictionary() && tree->valid()) {
226 fillPages(tree);
227 }
228 }
229 }
230
edisonn@google.com4f898b72013-08-07 21:11:57 +0000231
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000232}
233
edisonn@google.com571c70b2013-07-10 17:09:50 +0000234// TODO(edisonn): NYI
edisonn@google.com3aa35552013-08-14 18:26:20 +0000235SkPdfNativeDoc::~SkPdfNativeDoc() {
edisonn@google.com147adb12013-07-24 15:56:19 +0000236 sk_free((void*)fFileContent);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000237 delete fAllocator;
238}
239
edisonn@google.com3aa35552013-08-14 18:26:20 +0000240const unsigned char* SkPdfNativeDoc::readCrossReferenceSection(const unsigned char* xrefStart, const unsigned char* trailerEnd) {
241 SkPdfNativeObject xref;
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000242 const unsigned char* current = nextObject(0, xrefStart, trailerEnd, &xref, NULL, NULL);
243
244 if (!xref.isKeyword("xref")) {
245 return trailerEnd;
246 }
edisonn@google.com571c70b2013-07-10 17:09:50 +0000247
edisonn@google.com3aa35552013-08-14 18:26:20 +0000248 SkPdfNativeObject token;
edisonn@google.com571c70b2013-07-10 17:09:50 +0000249 while (current < trailerEnd) {
250 token.reset();
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000251 const unsigned char* previous = current;
252 current = nextObject(0, current, trailerEnd, &token, NULL, NULL);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000253 if (!token.isInteger()) {
254 return previous;
255 }
256
edisonn@google.coma3356fc2013-07-10 18:20:06 +0000257 int startId = (int)token.intValue();
edisonn@google.com571c70b2013-07-10 17:09:50 +0000258 token.reset();
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000259 current = nextObject(0, current, trailerEnd, &token, NULL, NULL);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000260
261 if (!token.isInteger()) {
262 // TODO(edisonn): report/warning
263 return current;
264 }
265
edisonn@google.coma3356fc2013-07-10 18:20:06 +0000266 int entries = (int)token.intValue();
edisonn@google.com571c70b2013-07-10 17:09:50 +0000267
268 for (int i = 0; i < entries; i++) {
269 token.reset();
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000270 current = nextObject(0, current, trailerEnd, &token, NULL, NULL);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000271 if (!token.isInteger()) {
272 // TODO(edisonn): report/warning
273 return current;
274 }
edisonn@google.coma3356fc2013-07-10 18:20:06 +0000275 int offset = (int)token.intValue();
edisonn@google.com571c70b2013-07-10 17:09:50 +0000276
277 token.reset();
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000278 current = nextObject(0, current, trailerEnd, &token, NULL, NULL);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000279 if (!token.isInteger()) {
280 // TODO(edisonn): report/warning
281 return current;
282 }
edisonn@google.coma3356fc2013-07-10 18:20:06 +0000283 int generation = (int)token.intValue();
edisonn@google.com571c70b2013-07-10 17:09:50 +0000284
285 token.reset();
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000286 current = nextObject(0, current, trailerEnd, &token, NULL, NULL);
edisonn@google.come878e722013-07-29 19:10:58 +0000287 if (!token.isKeyword() || token.lenstr() != 1 || (*token.c_str() != 'f' && *token.c_str() != 'n')) {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000288 // TODO(edisonn): report/warning
289 return current;
290 }
291
292 addCrossSectionInfo(startId + i, generation, offset, *token.c_str() == 'f');
293 }
294 }
295 // TODO(edisonn): it should never get here? there is no trailer?
296 return current;
297}
298
edisonn@google.com3aa35552013-08-14 18:26:20 +0000299const unsigned char* SkPdfNativeDoc::readTrailer(const unsigned char* trailerStart, const unsigned char* trailerEnd, bool storeCatalog, long* prev, bool skipKeyword) {
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000300 *prev = -1;
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000301
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000302 const unsigned char* current = trailerStart;
303 if (!skipKeyword) {
edisonn@google.com3aa35552013-08-14 18:26:20 +0000304 SkPdfNativeObject trailerKeyword;
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000305 // TODO(edisonn): use null allocator, and let it just fail if memory
306 // needs allocated (but no crash)!
307 current = nextObject(0, current, trailerEnd, &trailerKeyword, NULL, NULL);
308
309 if (!trailerKeyword.isKeyword() || strlen("trailer") != trailerKeyword.lenstr() ||
310 strncmp(trailerKeyword.c_str(), "trailer", strlen("trailer")) != 0) {
311 // TODO(edisonn): report warning, rebuild trailer from objects.
312 return current;
313 }
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000314 }
edisonn@google.com571c70b2013-07-10 17:09:50 +0000315
edisonn@google.com3aa35552013-08-14 18:26:20 +0000316 SkPdfNativeObject token;
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000317 current = nextObject(0, current, trailerEnd, &token, fAllocator, NULL);
edisonn@google.com432640a2013-07-10 22:53:40 +0000318 if (!token.isDictionary()) {
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000319 return current;
edisonn@google.com432640a2013-07-10 22:53:40 +0000320 }
edisonn@google.com571c70b2013-07-10 17:09:50 +0000321 SkPdfFileTrailerDictionary* trailer = (SkPdfFileTrailerDictionary*)&token;
edisonn@google.com432640a2013-07-10 22:53:40 +0000322 if (!trailer->valid()) {
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000323 return current;
edisonn@google.com432640a2013-07-10 22:53:40 +0000324 }
edisonn@google.com571c70b2013-07-10 17:09:50 +0000325
326 if (storeCatalog) {
edisonn@google.com3aa35552013-08-14 18:26:20 +0000327 SkPdfNativeObject* ref = trailer->Root(NULL);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000328 if (ref == NULL || !ref->isReference()) {
329 // TODO(edisonn): oops, we have to fix the corrup pdf file
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000330 return current;
edisonn@google.com571c70b2013-07-10 17:09:50 +0000331 }
332 fRootCatalogRef = ref;
333 }
334
335 if (trailer->has_Prev()) {
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000336 *prev = (long)trailer->Prev(NULL);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000337 }
338
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000339 return current;
edisonn@google.com571c70b2013-07-10 17:09:50 +0000340}
341
edisonn@google.com3aa35552013-08-14 18:26:20 +0000342void SkPdfNativeDoc::addCrossSectionInfo(int id, int generation, int offset, bool isFreed) {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000343 // TODO(edisonn): security here
344 while (fObjects.count() < id + 1) {
345 reset(fObjects.append());
346 }
347
348 fObjects[id].fOffset = offset;
349 fObjects[id].fObj = NULL;
edisonn@google.com4ef4bed2013-07-29 22:14:45 +0000350 fObjects[id].fResolvedReference = NULL;
edisonn@google.com571c70b2013-07-10 17:09:50 +0000351}
352
edisonn@google.com3aa35552013-08-14 18:26:20 +0000353SkPdfNativeObject* SkPdfNativeDoc::readObject(int id/*, int expectedGeneration*/) {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000354 long startOffset = fObjects[id].fOffset;
355 //long endOffset = fObjects[id].fOffsetEnd;
356 // TODO(edisonn): use hinted endOffset
357 // TODO(edisonn): current implementation will result in a lot of memory usage
358 // to decrease memory usage, we wither need to be smart and know where objects end, and we will
359 // alocate only the chancks needed, or the tokenizer will not make copies, but then it needs to
360 // cache the results so it does not go twice on the same buffer
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000361 const unsigned char* current = fFileContent + startOffset;
362 const unsigned char* end = fFileContent + fContentLength;
edisonn@google.com571c70b2013-07-10 17:09:50 +0000363
edisonn@google.com951d6532013-07-10 23:17:31 +0000364 SkPdfNativeTokenizer tokenizer(current, end - current, fMapper, fAllocator, this);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000365
edisonn@google.com3aa35552013-08-14 18:26:20 +0000366 SkPdfNativeObject idObj;
367 SkPdfNativeObject generationObj;
368 SkPdfNativeObject objKeyword;
369 SkPdfNativeObject* dict = fAllocator->allocObject();
edisonn@google.com571c70b2013-07-10 17:09:50 +0000370
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000371 current = nextObject(0, current, end, &idObj, NULL, NULL);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000372 if (current >= end) {
373 // TODO(edisonn): report warning/error
374 return NULL;
375 }
376
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000377 current = nextObject(0, current, end, &generationObj, NULL, NULL);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000378 if (current >= end) {
379 // TODO(edisonn): report warning/error
380 return NULL;
381 }
382
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000383 current = nextObject(0, current, end, &objKeyword, NULL, NULL);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000384 if (current >= end) {
385 // TODO(edisonn): report warning/error
386 return NULL;
387 }
388
389 if (!idObj.isInteger() || !generationObj.isInteger() || id != idObj.intValue()/* || generation != generationObj.intValue()*/) {
390 // TODO(edisonn): report warning/error
391 }
392
393 if (!objKeyword.isKeyword() || strcmp(objKeyword.c_str(), "obj") != 0) {
394 // TODO(edisonn): report warning/error
395 }
396
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000397 current = nextObject(1, current, end, dict, fAllocator, this);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000398
399 // TODO(edisonn): report warning/error - verify last token is endobj
400
401 return dict;
402}
403
edisonn@google.com3aa35552013-08-14 18:26:20 +0000404void SkPdfNativeDoc::fillPages(SkPdfPageTreeNodeDictionary* tree) {
edisonn@google.comb0145ce2013-08-05 16:23:23 +0000405 SkPdfArray* kids = tree->Kids(this);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000406 if (kids == NULL) {
407 *fPages.append() = (SkPdfPageObjectDictionary*)tree;
408 return;
409 }
410
411 int cnt = kids->size();
412 for (int i = 0; i < cnt; i++) {
edisonn@google.com3aa35552013-08-14 18:26:20 +0000413 SkPdfNativeObject* obj = resolveReference(kids->objAtAIndex(i));
414 if (fMapper->mapPageObjectDictionary(obj) != kPageObjectDictionary_SkPdfNativeObjectType) {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000415 *fPages.append() = (SkPdfPageObjectDictionary*)obj;
416 } else {
417 // TODO(edisonn): verify that it is a page tree indeed
418 fillPages((SkPdfPageTreeNodeDictionary*)obj);
419 }
420 }
421}
422
edisonn@google.com3aa35552013-08-14 18:26:20 +0000423int SkPdfNativeDoc::pages() const {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000424 return fPages.count();
425}
426
edisonn@google.com3aa35552013-08-14 18:26:20 +0000427SkPdfPageObjectDictionary* SkPdfNativeDoc::page(int page) {
edisonn@google.com88fc03d2013-07-30 13:34:10 +0000428 SkASSERT(page >= 0 && page < fPages.count());
429 return fPages[page];
430}
431
432
edisonn@google.com3aa35552013-08-14 18:26:20 +0000433SkPdfResourceDictionary* SkPdfNativeDoc::pageResources(int page) {
edisonn@google.com88fc03d2013-07-30 13:34:10 +0000434 SkASSERT(page >= 0 && page < fPages.count());
edisonn@google.com571c70b2013-07-10 17:09:50 +0000435 return fPages[page]->Resources(this);
436}
437
438// TODO(edisonn): Partial implemented. Move the logics directly in the code generator for inheritable and default value?
edisonn@google.com3aa35552013-08-14 18:26:20 +0000439SkRect SkPdfNativeDoc::MediaBox(int page) {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000440 SkPdfPageObjectDictionary* current = fPages[page];
441 while (!current->has_MediaBox() && current->has_Parent()) {
442 current = (SkPdfPageObjectDictionary*)current->Parent(this);
443 }
444 if (current) {
445 return current->MediaBox(this);
446 }
447 return SkRect::MakeEmpty();
448}
449
450// TODO(edisonn): stream or array ... ? for now only array
edisonn@google.com3aa35552013-08-14 18:26:20 +0000451SkPdfNativeTokenizer* SkPdfNativeDoc::tokenizerOfPage(int page,
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000452 SkPdfAllocator* allocator) {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000453 if (fPages[page]->isContentsAStream(this)) {
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000454 return tokenizerOfStream(fPages[page]->getContentsAsStream(this), allocator);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000455 } else {
456 // TODO(edisonn): NYI, we need to concatenate all streams in the array or make the tokenizer smart
457 // so we don't allocate new memory
458 return NULL;
459 }
460}
461
edisonn@google.com3aa35552013-08-14 18:26:20 +0000462SkPdfNativeTokenizer* SkPdfNativeDoc::tokenizerOfStream(SkPdfNativeObject* stream,
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000463 SkPdfAllocator* allocator) {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000464 if (stream == NULL) {
465 return NULL;
466 }
467
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000468 return new SkPdfNativeTokenizer(stream, fMapper, allocator, this);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000469}
470
471// TODO(edisonn): NYI
edisonn@google.com3aa35552013-08-14 18:26:20 +0000472SkPdfNativeTokenizer* SkPdfNativeDoc::tokenizerOfBuffer(const unsigned char* buffer, size_t len,
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000473 SkPdfAllocator* allocator) {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000474 // warning does not track two calls in the same buffer! the buffer is updated!
475 // make a clean copy if needed!
edisonn@google.com2ccc3af2013-07-23 17:43:18 +0000476 return new SkPdfNativeTokenizer(buffer, len, fMapper, allocator, this);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000477}
478
edisonn@google.com3aa35552013-08-14 18:26:20 +0000479size_t SkPdfNativeDoc::objects() const {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000480 return fObjects.count();
481}
482
edisonn@google.com3aa35552013-08-14 18:26:20 +0000483SkPdfNativeObject* SkPdfNativeDoc::object(int i) {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000484 SkASSERT(!(i < 0 || i > fObjects.count()));
485
486 if (i < 0 || i > fObjects.count()) {
487 return NULL;
488 }
489
490 if (fObjects[i].fObj == NULL) {
491 // TODO(edisonn): when we read the cross reference sections, store the start of the next object
492 // and fill fOffsetEnd
493 fObjects[i].fObj = readObject(i);
494 }
495
496 return fObjects[i].fObj;
497}
498
edisonn@google.com3aa35552013-08-14 18:26:20 +0000499const SkPdfMapper* SkPdfNativeDoc::mapper() const {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000500 return fMapper;
501}
502
edisonn@google.com3aa35552013-08-14 18:26:20 +0000503SkPdfReal* SkPdfNativeDoc::createReal(double value) const {
504 SkPdfNativeObject* obj = fAllocator->allocObject();
505 SkPdfNativeObject::makeReal(value, obj);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000506 return (SkPdfReal*)obj;
507}
508
edisonn@google.com3aa35552013-08-14 18:26:20 +0000509SkPdfInteger* SkPdfNativeDoc::createInteger(int value) const {
510 SkPdfNativeObject* obj = fAllocator->allocObject();
511 SkPdfNativeObject::makeInteger(value, obj);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000512 return (SkPdfInteger*)obj;
513}
514
edisonn@google.com3aa35552013-08-14 18:26:20 +0000515SkPdfString* SkPdfNativeDoc::createString(const unsigned char* sz, size_t len) const {
516 SkPdfNativeObject* obj = fAllocator->allocObject();
517 SkPdfNativeObject::makeString(sz, len, obj);
edisonn@google.com571c70b2013-07-10 17:09:50 +0000518 return (SkPdfString*)obj;
519}
520
edisonn@google.com3aa35552013-08-14 18:26:20 +0000521SkPdfAllocator* SkPdfNativeDoc::allocator() const {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000522 return fAllocator;
523}
524
edisonn@google.com571c70b2013-07-10 17:09:50 +0000525// TODO(edisonn): fix infinite loop if ref to itself!
526// TODO(edisonn): perf, fix refs at load, and resolve will simply return fResolvedReference?
edisonn@google.com3aa35552013-08-14 18:26:20 +0000527SkPdfNativeObject* SkPdfNativeDoc::resolveReference(SkPdfNativeObject* ref) {
edisonn@google.com571c70b2013-07-10 17:09:50 +0000528 if (ref && ref->isReference()) {
529 int id = ref->referenceId();
530 // TODO(edisonn): generation/updates not supported now
531 //int gen = ref->referenceGeneration();
532
edisonn@google.com641cce92013-07-30 12:09:14 +0000533 // TODO(edisonn): verify id and gen expected
534 if (id < 0 || id >= fObjects.count()) {
535 // TODO(edisonn): report error/warning
edisonn@google.com571c70b2013-07-10 17:09:50 +0000536 return NULL;
537 }
538
edisonn@google.com571c70b2013-07-10 17:09:50 +0000539 if (fObjects[id].fResolvedReference != NULL) {
edisonn@google.com276fed92013-08-01 21:20:47 +0000540
541#ifdef PDF_TRACE
542 printf("\nresolve(%s) = %s\n", ref->toString(0).c_str(), fObjects[id].fResolvedReference->toString(0, ref->toString().size() + 13).c_str());
543#endif
544
edisonn@google.com571c70b2013-07-10 17:09:50 +0000545 return fObjects[id].fResolvedReference;
546 }
547
548 if (fObjects[id].fObj == NULL) {
549 fObjects[id].fObj = readObject(id);
550 }
551
552 if (fObjects[id].fResolvedReference == NULL) {
553 if (!fObjects[id].fObj->isReference()) {
554 fObjects[id].fResolvedReference = fObjects[id].fObj;
555 } else {
556 fObjects[id].fResolvedReference = resolveReference(fObjects[id].fObj);
557 }
558 }
559
edisonn@google.com276fed92013-08-01 21:20:47 +0000560#ifdef PDF_TRACE
561 printf("\nresolve(%s) = %s\n", ref->toString(0).c_str(), fObjects[id].fResolvedReference->toString(0, ref->toString().size() + 13).c_str());
562#endif
edisonn@google.com571c70b2013-07-10 17:09:50 +0000563 return fObjects[id].fResolvedReference;
564 }
edisonn@google.com276fed92013-08-01 21:20:47 +0000565
edisonn@google.com571c70b2013-07-10 17:09:50 +0000566 // TODO(edisonn): fix the mess with const, probably we need to remove it pretty much everywhere
edisonn@google.com3aa35552013-08-14 18:26:20 +0000567 return (SkPdfNativeObject*)ref;
edisonn@google.com3aac1f92013-07-02 22:42:53 +0000568}
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000569
edisonn@google.com3aa35552013-08-14 18:26:20 +0000570size_t SkPdfNativeDoc::bytesUsed() const {
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000571 return fAllocator->bytesUsed() +
572 fContentLength +
573 fObjects.count() * sizeof(PublicObjectEntry) +
574 fPages.count() * sizeof(SkPdfPageObjectDictionary*) +
575 sizeof(*this);
576}