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

Side by Side Diff: experimental/PdfViewer/pdfparser/native/SkPdfNativeTokenizer.cpp

Issue 18323019: work on the native parser, in progress, uploaded to have a backup (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: Created 7 years, 5 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 | Annotate | Revision Log
OLDNEW
1 1
2 #include "SkPdfNativeTokenizer.h" 2 #include "SkPdfNativeTokenizer.h"
3 3 #include "SkPdfObject.h"
4 SkPdfNativeTokenizer::SkPdfNativeTokenizer() { 4 #include "SkPdfConfig.h"
5 // TODO(edisonn): Auto-generated constructor stub 5
6 6 #include "SkPdfStreamCommonDictionary_autogen.h"
7
8 unsigned char* skipPdfWhiteSpaces(unsigned char* start, unsigned char* end) {
9 while (start < end && isPdfWhiteSpace(*start)) {
10 if (*start == kComment_PdfDelimiter) {
11 // skip the comment until end of line
12 while (start < end && !isPdfEOL(*start)) {
13 *start = '\0';
14 start++;
15 }
16 } else {
17 *start = '\0';
18 start++;
19 }
20 }
21 return start;
22 }
23
24 // TODO(edisonn) '(' can be used, will it break the string a delimiter or space inside () ?
25 unsigned char* endOfPdfToken(unsigned char* start, unsigned char* end) {
26 //int opened brackets
27 //TODO(edisonn): what out for special chars, like \n, \032
28
29 SkASSERT(!isPdfWhiteSpace(*start));
30
31 if (start < end && isPdfDelimiter(*start)) {
32 start++;
33 return start;
34 }
35
36 while (start < end && !isPdfWhiteSpaceOrPdfDelimiter(*start)) {
37 start++;
38 }
39 return start;
40 }
41
42 unsigned char* skipPdfComment(unsigned char* start, unsigned char* end) {
43 SkASSERT(start == end || *start == kComment_PdfDelimiter);
44 while (start < end && isPdfEOL(*start)) {
45 *start = '\0';
46 start++;
47 }
48 return start;
49 }
50
51 // last elem has to be ]
52 unsigned char* readArray(unsigned char* start, unsigned char* end, SkPdfObject* array, SkPdfAllocator* allocator) {
53 while (start < end) {
54 // skip white spaces
55 start = skipPdfWhiteSpaces(start, end);
56
57 unsigned char* endOfToken = endOfPdfToken(start, end);
58
59 if (endOfToken == start) {
60 // TODO(edisonn): report error in pdf file (end of stream with ] for end of aray
61 return start;
62 }
63
64 if (endOfToken == start + 1 && *start == kClosedSquareBracket_PdfDelimit er) {
65 return endOfToken;
66 }
67
68 SkPdfObject* newObj = allocator->allocObject();
69 start = nextObject(start, end, newObj, allocator);
70 // TODO(edisonn): perf/memory: put the variables on the stack, and flush them on the array only when
71 // we are sure they are not references!
72 if (newObj->isKeywordReference() && array->size() >= 2 && array->objAtAI ndex(array->size() - 1)->isInteger() && array->objAtAIndex(array->size() - 2)->i sInteger()) {
73 SkPdfObject* gen = array->removeLastInArray();
74 SkPdfObject* id = array->removeLastInArray();
75 newObj->reset();
76 SkPdfObject::makeReference(id->intValue(), gen->intValue(), newObj);
77 }
78 array->appendInArray(newObj);
79 }
80 // TODO(edisonn): report not reached, we should never get here
81 SkASSERT(false);
82 return start;
83 }
84
85 // When we read strings we will rewrite the string so we will reuse the memory
86 // when we start to read the string, we already consumed the opened bracket
87 unsigned char* readString(unsigned char* start, unsigned char* end, SkPdfObject* str) {
88 unsigned char* out = start;
89 unsigned char* in = start;
90
91 int openRoundBrackets = 0;
92 while (in < end && (*in != kClosedRoundBracket_PdfDelimiter || openRoundBrac kets > 0)) {
93 openRoundBrackets += ((*in) == kOpenedRoundBracket_PdfDelimiter);
94 openRoundBrackets -= ((*in) == kClosedRoundBracket_PdfDelimiter);
95 if (*in == kEscape_PdfSpecial) {
96 if (in + 1 < end) {
97 switch (in[1]) {
98 case 'n':
99 *out = kLF_PdfWhiteSpace;
100 out++;
101 in += 2;
102 break;
103
104 case 'r':
105 *out = kCR_PdfWhiteSpace;
106 out++;
107 in += 2;
108 break;
109
110 case 't':
111 *out = kHT_PdfWhiteSpace;
112 out++;
113 in += 2;
114 break;
115
116 case 'b':
117 // TODO(edisonn): any special meaning to backspace?
118 *out = kBackspace_PdfSpecial;
119 out++;
120 in += 2;
121 break;
122
123 case 'f':
124 *out = kFF_PdfWhiteSpace;
125 out++;
126 in += 2;
127 break;
128
129 case kOpenedRoundBracket_PdfDelimiter:
130 *out = kOpenedRoundBracket_PdfDelimiter;
131 out++;
132 in += 2;
133 break;
134
135 case kClosedRoundBracket_PdfDelimiter:
136 *out = kClosedRoundBracket_PdfDelimiter;
137 out++;
138 in += 2;
139 break;
140
141 case kEscape_PdfSpecial:
142 *out = kEscape_PdfSpecial;
143 out++;
144 in += 2;
145 break;
146
147 case '0':
148 case '1':
149 case '2':
150 case '3':
151 case '4':
152 case '5':
153 case '6':
154 case '7': {
155 //read octals
156 in++; // consume backslash
157
158 int code = 0;
159 int i = 0;
160 while (in < end && *in >= '0' && *in < '8') {
161 code = (code << 3) + ((*in) - '0'); // code * 8 + d
162 i++;
163 in++;
164 if (i == 3) {
165 *out = code & 0xff;
166 out++;
167 i = 0;
168 }
169 }
170 if (i > 0) {
171 *out = code & 0xff;
172 out++;
173 }
174 }
175 break;
176
177 default:
178 // Per spec, backslash is ignored is escaped ch is unkno wn
179 in++;
180 break;
181 }
182 }
183 } else {
184 // TODO(edisonn): perf, avoid copy into itself, maybe first do a sim ple scan until found backslash ?
185 // we could have one look that first just inc current, and when we f ind the backslash
186 // we go to this loop
187 *in = *out;
188 in++;
189 out++;
190 }
191 }
192
193
194 SkPdfObject::makeString(start, out, str);
195 return in + 1; // consume ) at the end of the string
196 }
197
198 unsigned char* readHexString(unsigned char* start, unsigned char* end, SkPdfObje ct* str) {
199 unsigned char* out = start;
200 unsigned char* in = start;
201
202 unsigned char code = 0;
203
204 while (in < end) {
205 while (in < end && isPdfWhiteSpace(*in)) {
206 in++;
207 }
208
209 if (*in == kClosedInequityBracket_PdfDelimiter) {
210 *in = '\0';
211 in++;
212 // normal exit
213 break;
214 }
215
216 if (in >= end) {
217 // end too soon
218 break;
219 }
220
221 switch (*in) {
222 case '0':
223 case '1':
224 case '2':
225 case '3':
226 case '4':
227 case '5':
228 case '6':
229 case '7':
230 case '8':
231 case '9':
232 code = (*in - '0') << 4;
233 break;
234
235 case 'a':
236 case 'b':
237 case 'c':
238 case 'd':
239 case 'e':
240 case 'f':
241 code = (*in - 'a' + 10) << 4;
242 break;
243
244 case 'A':
245 case 'B':
246 case 'C':
247 case 'D':
248 case 'E':
249 case 'F':
250 code = (*in - 'A' + 10) << 4;
251 break;
252
253 // TODO(edisonn): spec does not say how to handle this error
254 default:
255 break;
256 }
257
258 in++; // advance
259
260 while (in < end && isPdfWhiteSpace(*in)) {
261 in++;
262 }
263
264 // TODO(edisonn): report error
265 if (in >= end) {
266 *out = code;
267 out++;
268 break;
269 }
270
271 if (*in == kClosedInequityBracket_PdfDelimiter) {
272 *out = code;
273 out++;
274 break;
275 }
276
277 switch (*in) {
278 case '0':
279 case '1':
280 case '2':
281 case '3':
282 case '4':
283 case '5':
284 case '6':
285 case '7':
286 case '8':
287 case '9':
288 code += (*in - '0');
289 break;
290
291 case 'a':
292 case 'b':
293 case 'c':
294 case 'd':
295 case 'e':
296 case 'f':
297 code += (*in - 'a' + 10);
298 break;
299
300 case 'A':
301 case 'B':
302 case 'C':
303 case 'D':
304 case 'E':
305 case 'F':
306 code += (*in - 'A' + 10);
307 break;
308
309 // TODO(edisonn): spec does not say how to handle this error
310 default:
311 break;
312 }
313
314 *out = code;
315 out++;
316 in++;
317 }
318
319 if (out < in) {
320 *out = '\0';
321 }
322
323 SkPdfObject::makeHexString(start, out, str);
324 return in; // consume > at the end of the string
325 }
326
327 // TODO(edisonn): before PDF 1.2 name could not have special characters, add ver sion parameter
328 unsigned char* readName(unsigned char* start, unsigned char* end, SkPdfObject* n ame) {
329 unsigned char* out = start;
330 unsigned char* in = start;
331
332 unsigned char code = 0;
333
334 while (in < end) {
335 if (isPdfWhiteSpaceOrPdfDelimiter(*in)) {
336 break;
337 }
338
339 if (*in == '#' && in + 2 < end) {
340 in++;
341 switch (*in) {
342 case '0':
343 case '1':
344 case '2':
345 case '3':
346 case '4':
347 case '5':
348 case '6':
349 case '7':
350 case '8':
351 case '9':
352 code = (*in - '0') << 4;
353 break;
354
355 case 'a':
356 case 'b':
357 case 'c':
358 case 'd':
359 case 'e':
360 case 'f':
361 code = (*in - 'a' + 10) << 4;
362 break;
363
364 case 'A':
365 case 'B':
366 case 'C':
367 case 'D':
368 case 'E':
369 case 'F':
370 code = (*in - 'A' + 10) << 4;
371 break;
372
373 // TODO(edisonn): spec does not say how to handle this error
374 default:
375 break;
376 }
377
378 in++; // advance
379
380 switch (*in) {
381 case '0':
382 case '1':
383 case '2':
384 case '3':
385 case '4':
386 case '5':
387 case '6':
388 case '7':
389 case '8':
390 case '9':
391 code += (*in - '0');
392 break;
393
394 case 'a':
395 case 'b':
396 case 'c':
397 case 'd':
398 case 'e':
399 case 'f':
400 code += (*in - 'a' + 10);
401 break;
402
403 case 'A':
404 case 'B':
405 case 'C':
406 case 'D':
407 case 'E':
408 case 'F':
409 code += (*in - 'A' + 10);
410 break;
411
412 // TODO(edisonn): spec does not say how to handle this error
413 default:
414 break;
415 }
416
417 *out = code;
418 out++;
419 in++;
420 } else {
421 *out = *in;
422 out++;
423 in++;
424 }
425 }
426
427 SkPdfObject::makeName(start, out, name);
428 return in;
429 }
430
431 // TODO(edisonn): pdf spec let Length to be an indirect object define after the stream
432 // that makes for an interesting scenario, where the stream itself contains ends tream, together
433 // with a reference object with the length, but the real length object would be somewhere else
434 // it could confuse the parser
435 /*example:
436
437 7 0 obj
438 << /length 8 0 R>>
439 stream
440 ...............
441 endstream
442 8 0 obj #we are in stream actually, not a real object
443 << 10 >> #we are in stream actually, not a real object
444 endobj
445 endstream
446 8 0 obj #real obj
447 << 100 >> #real obj
448 endobj
449 and it could get worse, with multiple object like this
450 */
451
452 // right now implement the silly algorithm that assumes endstream is finishing t he stream
453
454
455 unsigned char* readStream(unsigned char* start, unsigned char* end, SkPdfObject* dict) {
456 start = skipPdfWhiteSpaces(start, end);
457 if (!(start[0] == 's' && start[1] == 't' && start[2] == 'r' && start[3] == ' e' && start[4] == 'a' && start[5] == 'm')) {
458 // no stream. return.
459 return start;
460 }
461
462 start += 6; // strlen("stream")
463 if (start[0] == kCR_PdfWhiteSpace && start[1] == kLF_PdfWhiteSpace) {
464 start += 2;
465 } else if (start[0] == kLF_PdfWhiteSpace) {
466 start += 1;
467 }
468
469 SkPdfStreamCommonDictionary* stream = (SkPdfStreamCommonDictionary*) dict;
470 // TODO(edisonn): load Length
471 int length = -1;
472
473 // TODO(edisonn): very basic implementation
474 if (stream->has_Length() && stream->Length(NULL) > 0) {
475 length = stream->Length(NULL);
476 }
477
478 // TODO(edisonn): laod external streams
479 // TODO(edisonn): look at the last filter, to determione how to deal with po ssible issue
480
481 if (length < 0) {
482 // scan the buffer, until we find first endstream
483 // TODO(edisonn): all buffers must have a 0 at the end now,
484 // TODO(edisonn): hack (mark end of content with 0)
485 unsigned char lastCh = *end;
486 *end = '\0';
487 //SkASSERT(*end == '\0');
488 unsigned char* endstream = (unsigned char*)strstr((const char*)start, "e ndstream");
489 *end = lastCh;
490
491 if (endstream) {
492 length = endstream - start;
493 if (*(endstream-1) == kLF_PdfWhiteSpace) length--;
494 if (*(endstream-1) == kCR_PdfWhiteSpace) length--;
495 }
496 }
497 if (length >= 0) {
498 unsigned char* endstream = start + length;
499
500 if (endstream[0] == kCR_PdfWhiteSpace && endstream[1] == kLF_PdfWhiteSpa ce) {
501 endstream += 2;
502 } else if (endstream[0] == kLF_PdfWhiteSpace) {
503 endstream += 1;
504 }
505
506 // TODO(edisonn): verify the next bytes are "endstream"
507
508 endstream += strlen("endstream");
509 // TODO(edisonn): Assert? report error/warning?
510 dict->addStream(start, length);
511 return endstream;
512 }
513 return start;
514 }
515
516 unsigned char* readDictionary(unsigned char* start, unsigned char* end, SkPdfObj ect* dict, SkPdfAllocator* allocator) {
517 SkPdfObject::makeEmptyDictionary(dict);
518
519 start = skipPdfWhiteSpaces(start, end);
520
521 while (start < end && *start == kNamed_PdfDelimiter) {
522 SkPdfObject key;
523 *start = '\0';
524 start++;
525 start = readName(start, end, &key);
526 start = skipPdfWhiteSpaces(start, end);
527
528 if (start < end) {
529 SkPdfObject* value = allocator->allocObject();
530 start = nextObject(start, end, value, allocator);
531
532 start = skipPdfWhiteSpaces(start, end);
533
534 if (start < end) {
535 // seems we have an indirect reference
536 if (isPdfDigit(*start)) {
537 SkPdfObject generation;
538 start = nextObject(start, end, &generation, allocator);
539
540 SkPdfObject keywordR;
541 start = nextObject(start, end, &keywordR, allocator);
542
543 if (value->isInteger() && generation.isInteger() && keywordR .isKeywordReference()) {
544 int64_t id = value->intValue();
545 value->reset();
546 SkPdfObject::makeReference(id, generation.intValue(), va lue);
547 dict->set(&key, value);
548 } else {
549 // error, ignore
550 dict->set(&key, value);
551 }
552 } else {
553 // next elem is not a digit, but it might not be / either!
554 dict->set(&key, value);
555 }
556 } else {
557 // /key >>
558 dict->set(&key, value);
559 return end;
560 }
561 start = skipPdfWhiteSpaces(start, end);
562 } else {
563 dict->set(&key, &SkPdfObject::kNull);
564 return end;
565 }
566 }
567
568 // TODO(edisonn): options to ignore these errors
569
570 // now we should expect >>
571 start = skipPdfWhiteSpaces(start, end);
572 start = endOfPdfToken(start, end); // >
573 start = endOfPdfToken(start, end); // >
574
575 // TODO(edisonn): read stream ... put dict and stream in a struct, and have a pointer to struct ...
576 // or alocate 2 objects, and if there is no stream, free it to be used by so meone else? or just leave it ?
577
578 start = readStream(start, end, dict);
579
580 return start;
581 }
582
583 unsigned char* nextObject(unsigned char* start, unsigned char* end, SkPdfObject* token, SkPdfAllocator* allocator) {
584 unsigned char* current;
585
586 // skip white spaces
587 start = skipPdfWhiteSpaces(start, end);
588
589 current = endOfPdfToken(start, end);
590
591 // no token, len would be 0
592 if (current == start) {
593 return NULL;
594 }
595
596 int tokenLen = current - start;
597
598 if (tokenLen == 1) {
599 // start array
600 switch (*start) {
601 case kOpenedSquareBracket_PdfDelimiter:
602 *start = '\0';
603 SkPdfObject::makeEmptyArray(token);
604 return readArray(current, end, token, allocator);
605
606 case kOpenedRoundBracket_PdfDelimiter:
607 *start = '\0';
608 return readString(start, end, token);
609
610 case kOpenedInequityBracket_PdfDelimiter:
611 *start = '\0';
612 if (end > start + 1 && start[1] == kOpenedInequityBracket_PdfDel imiter) {
613 // TODO(edisonn): pass here the length somehow?
614 return readDictionary(start + 2, end, token, allocator); // skip <<
615 } else {
616 return readHexString(start + 1, end, token); // skip <
617 }
618
619 case kNamed_PdfDelimiter:
620 *start = '\0';
621 return readName(start + 1, end, token);
622
623 // TODO(edisonn): what to do curly brackets? read spec!
624 case kOpenedCurlyBracket_PdfDelimiter:
625 default:
626 break;
627 }
628
629 SkASSERT(!isPdfWhiteSpace(*start));
630 if (isPdfDelimiter(*start)) {
631 // TODO(edisonn): how stream ] } > ) will be handled?
632 // for now ignore, and it will become a keyword to be ignored
633 }
634 }
635
636 if (tokenLen == 4 && start[0] == 'n' && start[1] == 'u' && start[2] == 'l' & & start[3] == 'l') {
637 SkPdfObject::makeNull(token);
638 return current;
639 }
640
641 if (tokenLen == 4 && start[0] == 't' && start[1] == 'r' && start[2] == 'u' & & start[3] == 'e') {
642 SkPdfObject::makeBoolean(true, token);
643 return current;
644 }
645
646 if (tokenLen == 5 && start[0] == 'f' && start[1] == 'a' && start[2] == 'l' & & start[3] == 's' && start[3] == 'e') {
647 SkPdfObject::makeBoolean(false, token);
648 return current;
649 }
650
651 if (isPdfNumeric(*start)) {
652 SkPdfObject::makeNumeric(start, current, token);
653 } else {
654 SkPdfObject::makeKeyword(start, current, token);
655 }
656 return current;
657 }
658
659 SkPdfObject* SkPdfAllocator::allocBlock() {
660 return new SkPdfObject[BUFFER_SIZE];
661 }
662
663 SkPdfAllocator::~SkPdfAllocator() {
664 for (int i = 0 ; i < fHandles.count(); i++) {
665 free(fHandles[i]);
666 }
667 for (int i = 0 ; i < fHistory.count(); i++) {
668 delete[] fHistory[i];
669 }
670 delete[] fCurrent;
671 }
672
673 SkPdfObject* SkPdfAllocator::allocObject() {
674 if (fCurrentUsed >= BUFFER_SIZE) {
675 fHistory.push(fCurrent);
676 fCurrent = allocBlock();
677 fCurrentUsed = 0;
678 }
679
680 fCurrentUsed++;
681 return &fCurrent[fCurrentUsed - 1];
682 }
683
684 // TODO(edisonn): perf: do no copy the buffers, but use them, and mark cache the result, so there is no need of a second pass
685 SkPdfNativeTokenizer::SkPdfNativeTokenizer(SkPdfObject* objWithStream, const SkP dfMapper* mapper, SkPdfAllocator* allocator) : fMapper(mapper), fAllocator(alloc ator), fUncompressedStream(NULL), fUncompressedStreamEnd(NULL), fEmpty(false), f HasPutBack(false) {
686 unsigned char* buffer = NULL;
687 size_t len = 0;
688 objWithStream->GetFilteredStreamRef(&buffer, &len, fAllocator);
689 fUncompressedStreamStart = fUncompressedStream = (unsigned char*)fAllocator- >alloc(len);
690 fUncompressedStreamEnd = fUncompressedStream + len;
691 memcpy(fUncompressedStream, buffer, len);}
692
693 SkPdfNativeTokenizer::SkPdfNativeTokenizer(unsigned char* buffer, int len, const SkPdfMapper* mapper, SkPdfAllocator* allocator) : fMapper(mapper), fAllocator(a llocator), fEmpty(false), fHasPutBack(false) {
694 fUncompressedStreamStart = fUncompressedStream = (unsigned char*)fAllocator- >alloc(len);
695 fUncompressedStreamEnd = fUncompressedStream + len;
696 memcpy(fUncompressedStream, buffer, len);
7 } 697 }
8 698
9 SkPdfNativeTokenizer::~SkPdfNativeTokenizer() { 699 SkPdfNativeTokenizer::~SkPdfNativeTokenizer() {
10 // TODO(edisonn): Auto-generated destructor stub 700 // free the unparsed stream, we don't need it.
11 } 701 // the parsed one is locked as it contains the strings and keywords referenc ed in objects
12 702 if (fUncompressedStream) {
703 realloc(fUncompressedStreamStart, fUncompressedStream - fUncompressedStr eamStart);
704 } else {
705 SkASSERT(false);
706 }
707 }
708
709 bool SkPdfNativeTokenizer::readTokenCore(PdfToken* token) {
710 token->fKeyword = NULL;
711 token->fObject = NULL;
712
713 fUncompressedStream = skipPdfWhiteSpaces(fUncompressedStream, fUncompressedS treamEnd);
714 if (fUncompressedStream >= fUncompressedStreamEnd) {
715 return false;
716 }
717
718 SkPdfObject obj;
719 fUncompressedStream = nextObject(fUncompressedStream, fUncompressedStreamEnd , &obj, fAllocator);
720
721 // If it is a keyword, we will only get the pointer of the string
722 if (obj.type() == SkPdfObject::kKeyword_PdfObjectType) {
723 token->fKeyword = obj.c_str();
724 token->fKeywordLength = obj.len();
725 token->fType = kKeyword_TokenType;
726 } else {
727 SkPdfObject* pobj = fAllocator->allocObject();
728 *pobj = obj;
729 token->fObject = pobj;
730 token->fType = kObject_TokenType;
731 }
732
733 #ifdef PDF_TRACE
734 static int read_op = 0;
735 read_op++;
736 if (182749 == read_op) {
737 printf("break;\n");
738 }
739 printf("%i READ %s %s\n", read_op, token->fType == kKeyword_TokenType ? "Key word" : "Object", token->fKeyword ? std::string(token->fKeyword, token->fKeyword Length).c_str() : token->fObject->toString().c_str());
740 #endif
741
742 return true;
743 }
744
745 void SkPdfNativeTokenizer::PutBack(PdfToken token) {
746 SkASSERT(!fHasPutBack);
747 fHasPutBack = true;
748 fPutBack = token;
749 #ifdef PDF_TRACE
750 printf("PUT_BACK %s %s\n", token.fType == kKeyword_TokenType ? "Keyword" : " Object", token.fKeyword ? std::string(token.fKeyword, token.fKeywordLength).c_st r(): token.fObject->toString().c_str());
751 #endif
752 }
753
754 bool SkPdfNativeTokenizer::readToken(PdfToken* token) {
755 if (fHasPutBack) {
756 *token = fPutBack;
757 fHasPutBack = false;
758 #ifdef PDF_TRACE
759 printf("READ_BACK %s %s\n", token->fType == kKeyword_TokenType ? "Keyword" : "Object", token->fKeyword ? std::string(token->fKeyword, token->fKeywordLength) .c_str() : token->fObject->toString().c_str());
760 #endif
761 return true;
762 }
763
764 if (fEmpty) {
765 #ifdef PDF_TRACE
766 printf("EMPTY TOKENIZER\n");
767 #endif
768 return false;
769 }
770
771 return readTokenCore(token);
772 }
OLDNEW
« no previous file with comments | « experimental/PdfViewer/pdfparser/native/SkPdfNativeTokenizer.h ('k') | experimental/PdfViewer/pdfparser/native/SkPdfObject.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698