History

Significantly improved text output

Noam Preil committed 5 years ago

Commit b574ea61 parent 8a7f9b45 files · +508 −162Browse files

Unified · Split

5 files
  1. main.c +19 −9
  2. misc.c +1 −0
  3. object.c +74 −40
  4. op.c +363 −102
  5. pdf.h +51 −11
main.c+19−9
@@ -17,17 +17,21 @@ usage(void) · 16 unmodified lines
17 threadexitsall("usage");17 threadexitsall("usage");
18}18}
19 19
20static void20static int
21dumppage(Object *page)21dumppage(Object *page)
22{22{
23 int ret;
23 Page p;24 Page p;
24 pageinit(&p);25 pageinit(&p, page);
25 if(pagerender(&p, page) && p.buf.sz != 0)26 ret = pagerender(&p);
26 write(1, p.buf.b, p.buf.sz);27 if(ret)
28 if(p.buf.sz != 0)
29 fprint(1, "%s", (char*)p.buf.b);
27 pagefree(&p);30 pagefree(&p);
31 return ret;
28}32}
29 33
30static void34static int
31dumppages(Object *pages)35dumppages(Object *pages)
32{36{
33 Object *page, *kids, *type;37 Object *page, *kids, *type;
@@ -39,13 +43,19 @@ dumppages(Object *pages) · 5 unmodified lines
39 // Must be a dict, either Page or Pages43 // Must be a dict, either Page or Pages
40 type = dictget(page, "Type");44 type = dictget(page, "Type");
41 // MUST be a name.45 // MUST be a name.
42 if(strcmp(type->name, "Pages") == 0)46 if(strcmp(type->name, "Pages") == 0){
43 dumppages(page);47 if(!dumppages(page))
44 else if(strcmp(type->name, "Page") == 0)48 return 0;
45 dumppage(page);49 }
50 else if(strcmp(type->name, "Page") == 0){
51 if(!dumppage(page))
52 return 0;
53 print("\n");
54 }
46 else55 else
47 sysfatal("Unexpected page node type '%s'", type->name);56 sysfatal("Unexpected page node type '%s'", type->name);
48 }57 }
58 return 1;
49}59}
50 60
51 61
misc.c+1−0
@@ -50,6 +50,7 @@ Ofmt(Fmt *f) · 49 unmodified lines
50 return fmtprint(f, "%g", o->num.d);50 return fmtprint(f, "%g", o->num.d);
51 51
52 case Ostr:52 case Ostr:
53 case Oop:
53 if(isutf8(o->str, o->len))54 if(isutf8(o->str, o->len))
54 return fmtprint(f, "%q", o->str);55 return fmtprint(f, "%q", o->str);
55 return fmtprint(f, "<%.*H>", o->len, o->str);56 return fmtprint(f, "<%.*H>", o->len, o->str);
object.c+74−40
@@ -8,21 +8,90 @@ Object *pdfname(Stream *s); · 7 unmodified lines
8Object *pdfarray(Pdf *pdf, Stream *s);8Object *pdfarray(Pdf *pdf, Stream *s);
9Object *pdfdict(Pdf *pdf, Stream *s);9Object *pdfdict(Pdf *pdf, Stream *s);
10 10
11/* returns 1 if str is at the beginning of the stream, and
12 is followed either by whitespace or, if delim is 1,
13 a delimiter.
14 strlen(str) must be in (0, 16)
15 on match, the stream seeks to right after the string.
16 otherwise, the stream position is unchanged. */
17static int
18sismatch(Stream *s, char *str, int delim)
19{
20 long len = strlen(str);
21 vlong off = Soffset(s);
22 char b[16];
23 if(len == 0 || len > 16)
24 return 0;
25 if(Sread(s, b, len + 1) == len + 1 && memcmp(b, str, len) == 0 && (isws(b[len]) || (delim && isdelim(b[len])))){
26 Sungetc(s);
27 return 1;
28 }
29
30 Sseek(s, off, 0);
31 return 0;
32}
33
34char *
35suntilend(Stream *s)
36{
37 int sz, c, full = 0;
38 char buf[8];
39 for(sz = 0; sz < 7;){
40 c = Sgetc(s);
41 if(c < 0)
42 break;
43 if(isws(c) || isdelim(c)){
44 Sungetc(s);
45 full = 1;
46 break;
47 }
48 buf[sz] = c;
49 sz += 1;
50 }
51 if(!full)
52 return nil;
53 buf[sz] = 0;
54 return strdup(buf);
55}
56
11/* General function to parse an object of any type. */57/* General function to parse an object of any type. */
12Object *58Object *
13pdfobj(Pdf *pdf, Stream *s)59pdfobj(Pdf *pdf, Stream *s)
14{60{
15 Object *o, *o2;61 Object *o, *o2;
16 vlong off;62 vlong off;
17 int c, tf;63 int c;
18 Xref xref;64 Xref xref;
19 char b[16];
20 65
21 o = o2 = nil;66 o = o2 = nil;
22 do; while(isws(c = Sgetc(s)));67 do; while(isws(c = Sgetc(s)));
23 if(c < 0)68 if(c < 0)
24 goto err;69 goto err;
25 70
71 if(isascii(c) && isalpha(c)){
72 Sungetc(s);
73 // bool, null, or op
74 if(sismatch(s, "null", 1)){
75 fprint(1, "NULL\n");
76 return &null;
77 }
78 if((o = calloc(1, sizeof(*o))) == nil)
79 goto err;
80 o->type = Obool;
81 o->pdf = pdf;
82 if(sismatch(s, "true", 1)){
83 o->bool = 1;
84 return o;
85 }
86 if(sismatch(s, "false", 1)){
87 o->bool = 0;
88 return o;
89 }
90 o->type = Oop;
91 o->str = suntilend(s);
92 return o;
93 }
94
26 switch(c){95 switch(c){
27 case '<': /* dictionary or a string */96 case '<': /* dictionary or a string */
28 c = Sgetc(s);97 c = Sgetc(s);
@@ -33,7 +102,8 @@ pdfobj(Pdf *pdf, Stream *s) · 4 unmodified lines
33 off = Soffset(s);102 off = Soffset(s);
34 do; while(isws(Sgetc(s)));103 do; while(isws(Sgetc(s)));
35 Sungetc(s);104 Sungetc(s);
36 if(Sread(s, b, 7) == 7 && memcmp(b, "stream", 6) == 0 && isws(c = b[6])){105 if(sismatch(s, "stream", 0)){
106 c = Sgetc(s);
37 /* there IS a stream */107 /* there IS a stream */
38 if(c == '\r' && (c = Sgetc(s)) < 0)108 if(c == '\r' && (c = Sgetc(s)) < 0)
39 goto err;109 goto err;
@@ -71,45 +141,8 @@ pdfobj(Pdf *pdf, Stream *s) · 31 unmodified lines
71 o->pdf = pdf;141 o->pdf = pdf;
72 return o;142 return o;
73 143
74 case 'n':
75 off = Soffset(s);
76 if(Sgetc(s) == 'u' && Sgetc(s) == 'l' && Sgetc(s) == 'l' && (isws(c = Sgetc(s)) || isdelim(c))){
77 Sungetc(s);
78 return &null;
79 }
80 Sseek(s, off, 0);
81 c = 'n';
82 goto unexpected;
83
84 case 't':
85 off = Soffset(s);
86 tf = 1;
87 if(Sgetc(s) == 'r' && Sgetc(s) == 'u' && Sgetc(s) == 'e' && (isws(c = Sgetc(s)) || isdelim(c)))
88 goto bool;
89 Sseek(s, off, 0);
90 c = 't';
91 goto unexpected;
92
93 case 'f':
94 off = Soffset(s);
95 tf = 0;
96 if(Sgetc(s) == 'a' && Sgetc(s) == 'l' && Sgetc(s) == 's' && Sgetc(s) == 'e' && (isws(c = Sgetc(s)) || isdelim(c)))
97 goto bool;
98 Sseek(s, off, 0);
99 c = 'f';
100 goto unexpected;
101bool:
102 Sungetc(s);
103 if((o = calloc(1, sizeof(*o))) == nil)
104 goto err;
105 o->type = Obool;
106 o->pdf = pdf;
107 o->bool = tf;
108 return o;
109
110 default:144 default:
111 if(!isdigit(c) && c != '-'){145 if(!isdigit(c) && c != '-'){
112unexpected:
113 Sungetc(s);146 Sungetc(s);
114 werrstr("unexpected char '%c' at %#x+%#x (%d left)", c, Sobjoffset(s), Soffset(s), Ssize(s));147 werrstr("unexpected char '%c' at %#x+%#x (%d left)", c, Sobjoffset(s), Soffset(s), Ssize(s));
115 goto err;148 goto err;
@@ -178,6 +211,7 @@ pdfobjfree(Object *o) · 62 unmodified lines
178 return;211 return;
179 212
180 case Ostr:213 case Ostr:
214 case Oop:
181 case Oname:215 case Oname:
182 free(o->str);216 free(o->str);
183 break;217 break;
op.c+363−102
@@ -12,6 +12,30 @@ enum { · 11 unmodified lines
12 12
13typedef struct Op Op;13typedef struct Op Op;
14 14
15static void
16matidentity(double *arr)
17{
18 double src[6] = {
19 1, 0,
20 0, 1,
21 0, 0
22 };
23 memcpy(arr, src, sizeof(double) * 6);
24}
25
26static void
27matmult(double *m1, double *m2, double *out)
28{
29 double result[6];
30 result[0] = m1[0] * m2[0] + m1[1] * m2[2];
31 result[1] = m1[0] * m2[1] + m1[1] * m2[3];
32 result[2] = m1[2] * m2[0] + m1[3] * m2[2];
33 result[3] = m1[2] * m2[1] + m1[3] * m2[3];
34 result[4] = m1[4] * m2[0] + m1[5] * m2[2] + m2[4];
35 result[5] = m1[4] * m2[1] + m1[5] * m2[3] + m2[5];
36 memcpy(out, result, sizeof(double) * 6);
37}
38
15struct Op {39struct Op {
16 char *s;40 char *s;
17 int (*f)(Op *op, Page *p);41 int (*f)(Op *op, Page *p);
@@ -19,6 +43,16 @@ struct Op { · 1 unmodified line
19 int flags;43 int flags;
20};44};
21 45
46static int
47flagless(Op *op)
48{
49 if(op->flags != 0){
50 fprint(2, "Op '%s' expected no flags\n", op->s);
51 return 0;
52 }
53 return 1;
54}
55
22static int56static int
23cobegin(Op *op, Page *p)57cobegin(Op *op, Page *p)
24{58{
@@ -36,29 +70,54 @@ coend(Op *op, Page *p) · 11 unmodified lines
36static int70static int
37gspush(Op *op, Page *p)71gspush(Op *op, Page *p)
38{72{
39 USED(op, p);73 USED(op);
40 return 0;74 GS *r = realloc(p->GS, sizeof(GS) * (p->nGS + 1));
75 if(r == nil)
76 return 0;
77 p->GS = r;
78 p->nGS += 1;
79 p->GSactive = &p->GS[p->nGS - 1];
80 *(p->GSactive) = p->GS[p->nGS - 2];
81 return 1;
41}82}
42 83
43static int84static int
44gspop(Op *op, Page *p)85gspop(Op *op, Page *p)
45{86{
46 USED(op, p);87 USED(op);
47 return 0;88 GS *r = realloc(p->GS, sizeof(GS) * (p->nGS - 1));
89 if(r == nil)
90 return 0;
91 p->GS = r;
92 p->nGS -= 1;
93 p->GSactive = &p->GS[p->nGS - 1];
94 return 1;
48}95}
49 96
97/* six parameters give the inputs a,b,c,d,e,f for the matrix
98 [a b 0]
99 [c d 0]
100 [e f 1]
101 That matrix should be premultiplied with the current matrix
102 newCTM = input x oldCTM
103 (8.3.4)
104 */
50static int105static int
51gsctm(Op *op, Page *p)106gsctm(Op *op, Page *p)
52{107{
53 USED(op, p);108 double input[6];
54 return 0;109 int i;
110 for(i = 0; i < 6; i += 1)
111 input[i] = arrayget(p->stack, i)->num.d;
112 matmult(input, p->GSactive->CTM, p->GSactive->CTM);
113 return flagless(op);
55}114}
56 115
57static int116static int
58gswidth(Op *op, Page *p)117gswidth(Op *op, Page *p)
59{118{
60 USED(op, p);119 p->GSactive->LW = arrayget(p->stack, 0)->num.i;
61 return 0;120 return flagless(op);
62}121}
63 122
64static int123static int
@@ -99,8 +158,8 @@ gsintent(Op *op, Page *p) · 34 unmodified lines
99static int158static int
100gsflatness(Op *op, Page *p)159gsflatness(Op *op, Page *p)
101{160{
102 USED(op, p);161 p->GSactive->FL = arrayget(p->stack, 0)->num.d;
103 return 0;162 return flagless(op);
104}163}
105 164
106static int165static int
@@ -114,21 +173,21 @@ static int · 7 unmodified lines
114pcmove(Op *op, Page *p)173pcmove(Op *op, Page *p)
115{174{
116 USED(op, p);175 USED(op, p);
117 return 0;176 return 1;
118}177}
119 178
120static int179static int
121pcline(Op *op, Page *p)180pcline(Op *op, Page *p)
122{181{
123 USED(op, p);182 USED(op, p);
124 return 0;183 return 1;
125}184}
126 185
127static int186static int
128pccurve(Op *op, Page *p)187pccurve(Op *op, Page *p)
129{188{
130 USED(op, p);189 USED(op, p);
131 return 0;190 return 1;
132}191}
133 192
134static int193static int
@@ -149,7 +208,7 @@ static int · 14 unmodified lines
149ppstroke(Op *op, Page *p)208ppstroke(Op *op, Page *p)
150{209{
151 USED(op, p);210 USED(op, p);
152 return 0;211 return 1;
153}212}
154 213
155static int214static int
@@ -218,8 +277,21 @@ ccolour2(Op *op, Page *p) · 62 unmodified lines
218static int277static int
219cgray(Op *op, Page *p)278cgray(Op *op, Page *p)
220{279{
221 USED(op, p);280 int value = 255 * arrayget(p->stack, 0)->num.d;
222 return 0;281 int i;
282 u32int *color;
283 if(op->flags & Nonstroking){
284 color = &p->GSactive->NSC;
285 p->GSactive->NSCS = DeviceGray;
286 } else{
287 color = &p->GSactive->SC;
288 p->GSactive->SCS = DeviceGray;
289 }
290 *color = 0;
291 for(i = 0; i < 3; i += 1)
292 *color = (*color | value) << 8;
293 *color |= 255;
294 return 1;
223}295}
224 296
225static int297static int
@@ -295,21 +367,51 @@ tshscale(Op *op, Page *p) · 69 unmodified lines
295static int367static int
296tslead(Op *op, Page *p)368tslead(Op *op, Page *p)
297{369{
298 int d = arrayget(p->stack, 0)->num.d / 20;370 p->TS.TL = arrayget(p->stack, 0)->num.d;
299 while(d > 0){371 return flagless(op);
300 d -= 1;372}
301 if(bufput(&p->buf, (uchar*)"\n", 1) == -1)373
302 sysfatal("OOM");374static int
375fontwidths(Page *p)
376{
377 Object *o;
378 int i;
379 if(p->GSactive->Font.widths != nil)
380 free(p->GSactive->Font.widths);
381 o = dictget(p->GSactive->Font.font, "FirstChar");
382 if(o == nil)
383 return 1;
384 p->GSactive->Font.first = o->num.i;
385 p->GSactive->Font.last = dictget(p->GSactive->Font.font, "LastChar")->num.i;
386 p->GSactive->Font.widths = malloc(sizeof(int) * (p->GSactive->Font.last - p->GSactive->Font.first + 1));
387 if(p->GSactive->Font.widths == nil){
388 print("Failed to allocate for (%d, %d): %d\n", p->GSactive->Font.first, p->GSactive->Font.last, p->GSactive->Font.last - p->GSactive->Font.first + 1);
389 return 1;
303 }390 }
304 USED(op, p);391 o = dictget(p->GSactive->Font.font, "Widths");
305 return 0;392 if(o == nil)
393 return 0;
394 for(i = 0; i < arraylen(o); i += 1)
395 p->GSactive->Font.widths[i] = arrayget(o, i)->num.i;
396 o = dictget(p->GSactive->Font.font, "FontDescriptor");
397 p->GSactive->Font.defwidth = dictget(o, "MissingWidth")->num.i;
398 return 1;
306}399}
307 400
308static int401static int
309tsfontsz(Op *op, Page *p)402tsfontsz(Op *op, Page *p)
310{403{
311 USED(op, p);404 char *name = arrayget(p->stack, 0)->name;
312 return 0;405 p->GSactive->Font.font = dictget(dictget(dictget(p->obj, "Resources"), "Font"), name);
406 if(p->GSactive->Font.font == nil){
407 werrstr("Font not found: '%s'", name);
408 return 0;
409 }
410 p->GSactive->Font.enc = dictget(p->GSactive->Font.font, "Encoding");
411 if(p->GSactive->Font.enc)
412 p->GSactive->Font.enc = dictget(p->GSactive->Font.enc, "Differences");
413 p->GSactive->Font.size = arrayget(p->stack, 1)->num.d;
414 return fontwidths(p) && flagless(op);
313}415}
314 416
315static int417static int
@@ -329,15 +431,39 @@ tsrise(Op *op, Page *p) · 13 unmodified lines
329static int431static int
330tobegin(Op *op, Page *p)432tobegin(Op *op, Page *p)
331{433{
332 USED(op, p);434 if(p->TS.inobj){
333 return 0;435 werrstr("Text objects must not be nested");
436 return 0;
437 }
438 matidentity(p->TS.Tm);
439 matidentity(p->TS.Tlm);
440 p->TS.inobj = 1;
441 p->GSactive->Font.font = nil;
442 return flagless(op);
334}443}
335 444
336static int445static int
337toend(Op *op, Page *p)446toend(Op *op, Page *p)
338{447{
339 USED(op, p);448 if(!p->TS.inobj){
340 return 0;449 werrstr("ET found without BT");
450 return 0;
451 }
452 p->TS.inobj = 0;
453 return flagless(op);
454}
455
456static int
457tmove(Page *p, double x, double y, int tlm)
458{
459 double shift[6] = {1, 0, 0, 1, x, y};
460 if(tlm){
461 matmult(shift, p->TS.Tlm, p->TS.Tlm);
462 memcpy(p->TS.Tm, p->TS.Tlm, sizeof(double) * 6);
463 } else{
464 matmult(shift, p->TS.Tm, p->TS.Tm);
465 }
466 return 1;
341}467}
342 468
343static int469static int
@@ -346,62 +472,141 @@ tpmove(Op *op, Page *p) · 2 unmodified lines
346 Object *x, *y;472 Object *x, *y;
347 x = arrayget(p->stack, 0);473 x = arrayget(p->stack, 0);
348 y = arrayget(p->stack, 1);474 y = arrayget(p->stack, 1);
349 if(y->num.d != 0){475 if(op->flags & Leading)
350 if(bufput(&p->buf, (uchar*)"\n", 1) == -1)476 p->TS.TL = -y->num.d;
351 sysfatal("OOM");477 return tmove(p, x->num.d, y->num.d, 1);
352 }
353 else if(x->num.d < 50)
354 if(bufput(&p->buf, (uchar*)" ", 1) == -1)
355 sysfatal("OOM");
356 USED(op, p);
357 return 0;
358}478}
359 479
360static int480static int
361tpmatrix(Op *op, Page *p)481tpmatrix(Op *op, Page *p)
362{482{
363 USED(op, p);483 int i;
364 return 0;484 for(i = 0; i < 6; i += 1){
485 p->TS.Tm[i] = arrayget(p->stack, i)->num.d;
486 p->TS.Tlm[i] = p->TS.Tm[i];
487 }
488 return flagless(op);
365}489}
366 490
367static int491static int
368tpmove0(Op *op, Page *p)492tpmove0(Op *op, Page *p)
369{493{
370 USED(op, p);494 return tmove(p, 0, 0 - p->TS.TL, 1) && flagless(op);
371 if(bufput(&p->buf, (uchar*)"\n", 1) == -1)495}
372 sysfatal("OOM");496
373 return 0;497static int
498writepatched(Page *p, uchar c)
499{
500 int i, len, d = 0;
501 Object *o;
502 if(p->GSactive->Font.enc != nil){
503 len = arraylen(p->GSactive->Font.enc);
504 for(i = 0; i < len; i += 1){
505 o = arrayget(p->GSactive->Font.enc, i);
506 if(o->type == Onum)
507 d = o->num.i;
508 else if(d == c){
509 if(strcmp(o->name, "endash") == 0)
510 return bufput(&p->buf, (uchar*)"-", 1) == 1;
511 if(strcmp(o->name, "fi") == 0)
512 return bufput(&p->buf, (uchar*)"fi", 2) == 2;
513 if(strcmp(o->name, "ff") == 0)
514 return bufput(&p->buf, (uchar*)"ff", 2) == 2;
515 if(strcmp(o->name, "ffi") == 0)
516 return bufput(&p->buf, (uchar*)"ffi", 3) == 3;
517 if(strcmp(o->name, "bullet") == 0)
518 return bufput(&p->buf, (uchar*)"•", strlen("•")) == 3;
519 if(strcmp(o->name, "quotedblleft") == 0)
520 return bufput(&p->buf, (uchar*)"\"", 1) == 1;
521 if(strcmp(o->name, "quotedblright") == 0)
522 return bufput(&p->buf, (uchar*)"\"", 1) == 1;
523 if(strcmp(o->name, "quoteleft") == 0)
524 return bufput(&p->buf, (uchar*)"'", 1) == 1;
525 if(strcmp(o->name, "quoteright") == 0)
526 return bufput(&p->buf, (uchar*)"'", 1) == 1;
527 fprint(2, "TODO: recognize glyph name '%s'\n", o->name);
528 return 1;
529 } else
530 d += 1;
531 }
532 }
533 return bufput(&p->buf, (uchar*)&c, 1) == 1;
534}
535
536/* Renders one character / glyph and updates the text state */
537static int
538tchar(Page *p, ulong c)
539{
540 double Trm[6] = {p->GSactive->Font.size, 0, 0, p->GSactive->Font.size, 0, 0};
541 double tx;
542 int i;
543 matmult(Trm, p->TS.Tm, Trm);
544 matmult(Trm, p->GSactive->CTM, Trm);
545 tx = p->GSactive->Font.size / 1000;
546 if(c >= p->GSactive->Font.first && c <= p->GSactive->Font.last)
547 tx = tx * (double)p->GSactive->Font.widths[c - p->GSactive->Font.first];
548 else
549 tx = tx * (double)p->GSactive->Font.defwidth;
550 // Check if whitespace is needed
551 if(p->buf.sz > 1){
552 if(p->TS.y != Trm[5]){
553 for(i = 0; i < (int)((p->TS.y - Trm[5]) / p->GSactive->Font.size); i += 1)
554 if(bufput(&p->buf, (uchar*)"\n", 1) != 1)
555 return 0;
556 }
557 if(Trm[4] - p->TS.x > 2.5){
558 if(bufput(&p->buf, (uchar*)" ", 1) != 1)
559 return 0;
560 }
561 }
562 if(!writepatched(p, c) || !tmove(p, tx, 0, 0))
563 return 0;
564 p->TS.x = Trm[4] + tx;
565 p->TS.y = Trm[5];
566 return 1;
567}
568
569static int
570tstr(Page *p, char *str, ulong len)
571{
572 ulong i;
573 for(i = 0; i < len; i += 1)
574 if(!tchar(p, str[i]))
575 return 0;
576 return 1;
374}577}
375 578
376static int579static int
377thshow(Op *op, Page *p)580thshow(Op *op, Page *p)
378{581{
582 if(op->flags != 0){
583 fprint(2, "TODO: thshow != Tj\n");
584 return 0;
585 }
379 Object *o = arrayget(p->stack, 0);586 Object *o = arrayget(p->stack, 0);
380 if(bufput(&p->buf, (uchar*)o->str, o->len) == -1)587 if(!tstr(p, o->str, o->len))
381 sysfatal("OOM");588 return 0;
382 USED(op);589 return 1;
383 return 0;
384}590}
385 591
386static int592static int
387thshowarr(Op *op, Page *p)593thshowarr(Op *op, Page *p)
388{594{
389 Object *arr = arrayget(p->stack, 0);595 Object *o, *arr = arrayget(p->stack, 0);
390 Object *o;
391 int i;596 int i;
392 for(i = 0; i < arraylen(arr); i += 1){597 for(i = 0; i < arraylen(arr); i += 1){
393 o = arrayget(arr, i);598 o = arrayget(arr, i);
394 if(o->type == Ostr){599 if(o->type == Ostr){
395 if(bufput(&p->buf, (uchar*)o->str, o->len) == -1)600 if(!tstr(p, o->str, o->len))
396 sysfatal("OOM");601 return 0;
397 }602 }
398 else if(o->num.d < -150){603 else{
399 if(bufput(&p->buf, (uchar*)" ", 1) == -1)604 double shift = 0 - (p->GSactive->Font.size * o->num.d / 1000);
400 sysfatal("OOM");605 if(!tmove(p, shift, 0, 0))
606 return 0;
401 }607 }
402 }608 }
403 USED(op);609 return flagless(op);
404 return 0;
405}610}
406 611
407static int612static int
@@ -746,7 +951,7 @@ static int · 338 unmodified lines
746opignore(Op *op, Page *p)951opignore(Op *op, Page *p)
747{952{
748 USED(op, p);953 USED(op, p);
749 return 1;954 return 0;
750}955}
751 956
752static Op ops[] = {957static Op ops[] = {
@@ -833,8 +1038,8 @@ static Op ops[] = { · 80 unmodified lines
833 /* 9.4.2 Text position operators */1038 /* 9.4.2 Text position operators */
834 {"Td", tpmove, 2,}, /* move, next line */1039 {"Td", tpmove, 2,}, /* move, next line */
835 {"TD", tpmove, 2, Leading,}, /* move, next line, leading */1040 {"TD", tpmove, 2, Leading,}, /* move, next line, leading */
836 {"Tm", tpmatrix, 6,}, /* (line) matrix */1041 {"Tm", tpmatrix, 6,}, /* set Tm and Tlm */
837 {"T*", tpmove0, 0, Leading,}, /* move, next line, leading */1042 {"T*", tpmove0, 0,}, /* move, next line, leading */
838 1043
839 /* 9.4.3 Text showing operators */1044 /* 9.4.3 Text showing operators */
840 {"Tj", thshow, 1,}, /* show string */1045 {"Tj", thshow, 1,}, /* show string */
@@ -902,56 +1107,92 @@ static Op ops[] = { · 61 unmodified lines
902 {nil, nil, 0,},1107 {nil, nil, 0,},
903};1108};
904 1109
905// If an op is found at the current position in the stream, the associated Op is
906// returned and the stream is advanced. Otherwise, nil is returned and the stream
907// is left unchanged.
908Op *1110Op *
909opfind(Stream *s)1111opfind(char *name)
910{1112{
911 int i;1113 int i = 0;
912 uint len;
913 Op *op;1114 Op *op;
914 char *b = (char*)s->buf.b + s->buf.off;
915 i = 0;
916 while(ops[i].s != nil){1115 while(ops[i].s != nil){
917 op = &ops[i];1116 op = &ops[i];
918 len = strlen(op->s);1117 if(strcmp(op->s, name) == 0)
919 if(strncmp(op->s, b, len) == 0 && (isws(b[len]) || isdelim(b[len]))){
920 s->buf.off += len;
921 return op;1118 return op;
922 }
923 i += 1;1119 i += 1;
924 }1120 }
925 return nil;1121 return nil;
926}1122}
927 1123
928void1124void
929pageinit(Page *page)1125pageinit(Page *page, Object *o)
930{1126{
931 bufinit(&page->buf, 0, 0);1127 bufinit(&page->buf, 0, 0);
932 // Stack is per-content-stream, so we don't create it here1128 // Stack is per-content-stream, so we don't create it here
933 page->stack = nil;1129 page->stack = nil;
1130 page->obj = o;
1131 page->TS.inobj = 0;
1132 page->TS.x = 0;
1133 page->TS.y = 0;
934}1134}
935 1135
936void1136void
937pagefree(Page *p)1137gsinit(Page *p, GS *gs)
1138{
1139 USED(p);
1140 /* todo: actually initialize the full state */
1141 /* CTM maps user coords to device coords.
1142 TODO: use mediabox and screen info to init CTM
1143 */
1144 matidentity(gs->CTM);
1145 gs->LW = 1;
1146 gs->LC = 0;
1147 gs->LJ = 0;
1148 gs->ML = 10;
1149 gs->SCS = gs->NSCS = DeviceGray;
1150 // Alpha is lowest byte; this is (0, 0, 0, 255) == black
1151 gs->SC = gs->NSC = 255;
1152 gs->Font.font = nil;
1153 gs->Font.enc = nil;
1154 gs->Font.widths = nil;
1155}
1156
1157void
1158gsfree(GS gs)
938{1159{
939 buffree(&p->buf);1160 free(gs.Font.widths);
940 pdfobjfree(p->stack);1161 pdfobjfree(gs.Font.font);
1162 gs.Font.font = nil;
1163 gs.Font.enc = nil;
1164 gs.Font.widths = nil;
941}1165}
942 1166
943static void1167void
944stackreset(Object *stack)1168pagegsclean(Page *p)
945{1169{
946 int i;1170 int i;
947 for(i = 0; i < stack->array.ne; i += 1)1171 p->GSactive = nil;
948 pdfobjfree(stack->array.e[i]);1172 for(i = 0; i < p->nGS; i += 1)
949 stack->array.ne = 0;1173 gsfree(p->GS[i]);
950 free(stack->array.e);1174 free(p->GS);
951 stack->array.e = nil;1175 p->GS = nil;
1176 p->nGS = 0;
952}1177}
953 1178
954static void1179static int
1180stackreset(Page *p)
1181{
1182 pdfobjfree(p->stack);
1183 p->stack = arraynew(p->obj->pdf);
1184 return p->stack != nil;
1185}
1186
1187void
1188pagefree(Page *p)
1189{
1190 buffree(&p->buf);
1191 pdfobjfree(p->stack);
1192 pagegsclean(p);
1193}
1194
1195static int
955pagerendercontent(Page *p, Object *content)1196pagerendercontent(Page *p, Object *content)
956{1197{
957 Stream *s;1198 Stream *s;
@@ -964,43 +1205,63 @@ pagerendercontent(Page *p, Object *content) · 6 unmodified lines
964 }1205 }
965 p->stack = arraynew(content->pdf);1206 p->stack = arraynew(content->pdf);
966 if(p->stack == nil)1207 if(p->stack == nil)
967 return;1208 return 0;
968 while(s->buf.off != s->buf.sz){1209 while(s->buf.off != s->buf.sz){
969 while(isws(s->buf.b[s->buf.off]) && s->buf.off != s->buf.sz)1210 while(isws(s->buf.b[s->buf.off]) && s->buf.off != s->buf.sz)
970 s->buf.off += 1;1211 s->buf.off += 1;
971 if(s->buf.off == s->buf.sz)1212 if(s->buf.off == s->buf.sz)
972 break;1213 break;
973 op = opfind(s);1214 o = pdfobj(content->pdf, s);
974 if(op != nil){1215 if(o == nil)
975 op->f(op, p);1216 return 0;
976 stackreset(p->stack);1217 if(o->type == Oop){
977 } else{1218 op = opfind(o->str);
978 o = pdfobj(content->pdf, s);1219 if(op == nil){
979 if(o == nil){1220 fprint(2, "Unknown op: %s\n", o->str);
980 fprint(2, "failed to read operand: %r\n");1221 pdfobjfree(o);
981 break;1222 return 0;
1223 }
1224 pdfobjfree(o);
1225 if(!op->f(op, p)){
1226 fprint(2, "'%s' failed!\n", op->s);
1227 return 0;
982 }1228 }
1229 if(!stackreset(p))
1230 return 0;
1231 } else{
983 if(!arrayadd(p->stack, o)){1232 if(!arrayadd(p->stack, o)){
984 fprint(2, "Failed to push operand to stack: %r\n");1233 fprint(2, "Failed to push operand to stack: %r\n");
985 break;1234 return 0;
986 }1235 }
987 }1236 }
988 }1237 }
989 if(bufput(&p->buf, (uchar*)"\n", 1) == -1)1238 if(bufput(&p->buf, (uchar*)"\n\0", 2) != 2)
990 sysfatal("OOM");1239 return 0;
991 Sclose(s);1240 Sclose(s);
1241 return 1;
992}1242}
993 1243
994int1244int
995pagerender(Page *p, Object *o)1245pagerender(Page *p)
996{1246{
997 Object *content;1247 Object *content;
998 int i;1248 int i;
999 content = dictget(o, "Contents");1249 p->nGS = 1;
1000 if(content->type == Oarray)1250 p->GS = malloc(sizeof(GS));
1251 if(p->GS == nil){
1252 werrstr("Out of memory");
1253 return 0;
1254 }
1255 gsinit(p, p->GS);
1256 content = dictget(p->obj, "Contents");
1257 if(content->type == Oarray){
1001 for(i = 0; i < arraylen(content); i += 1)1258 for(i = 0; i < arraylen(content); i += 1)
1002 pagerendercontent(p, arrayget(content, i));1259 if(!pagerendercontent(p, arrayget(content, i)))
1260 return 0;
1261 }
1003 else if(content->type != Onull)1262 else if(content->type != Onull)
1004 pagerendercontent(p, content);1263 if(!pagerendercontent(p, content))
1264 return 0;
1265 pagegsclean(p);
1005 return 1;1266 return 1;
1006}1267}
pdf.h+51−11
@@ -8,10 +8,12 @@ enum { · 7 unmodified lines
8 Ostream, /* 7.3.8 */8 Ostream, /* 7.3.8 */
9 Onull, /* 7.3.9 */9 Onull, /* 7.3.9 */
10 Oindir, /* 7.3.10 */10 Oindir, /* 7.3.10 */
11 Oop, /* 7.8.2 */
11};12};
12 13
13typedef struct Buffer Buffer;14typedef struct Buffer Buffer;
14typedef struct Filter Filter;15typedef struct Filter Filter;
16typedef struct TS TS;
15typedef struct GS GS;17typedef struct GS GS;
16typedef struct GSD GSD;18typedef struct GSD GSD;
17typedef struct GSFont GSFont;19typedef struct GSFont GSFont;
@@ -35,11 +37,6 @@ struct Buffer { · 17 unmodified lines
35 void *(*memimage)(Buffer *b);37 void *(*memimage)(Buffer *b);
36};38};
37 39
38struct Page {
39 Object *stack;
40 Buffer buf;
41};
42
43struct Filter {40struct Filter {
44 char *name;41 char *name;
45 int (*readall)(void *aux, Buffer *bi, Buffer *bo);42 int (*readall)(void *aux, Buffer *bi, Buffer *bo);
@@ -104,22 +101,65 @@ struct GSD { · 58 unmodified lines
104struct GSFont {101struct GSFont {
105 Object *font;102 Object *font;
106 double size;103 double size;
104 Object *enc; /* TODO: drop enc, use the encoding table */
105 struct{
106 // If a character c is in [first, last], replace it with values[c], which may be multibyte.
107 int first, last;
108 char **values;
109 } encoding;
110 struct{
111 int first, last;
112 int *widths;
113 int defwidth;
114 };
107};115};
108 116
117/* Color spaces; 8.6.3 / table 61 */
118typedef enum ColorSpace {
119 DeviceGray, DeviceRGB, DeviceCMYK, /* Device family */
120 CalGray, CalRGB, Lab, ICCBased, /* CIE-based family */
121 Pattern, Indexed, Separation, DeviceN, /* Special family */
122} ColorSpace;
123
109struct GS {124struct GS {
125 double CTM[6]; /* current transformation matrix ; 8.3 */
110 Object *BG, *UCR, *UCR2, *TR, *TR2, *HT, *BM, *SMask, *UseBlackPTComp, *HTO;126 Object *BG, *UCR, *UCR2, *TR, *TR2, *HT, *BM, *SMask, *UseBlackPTComp, *HTO;
111 int LW, LC, LJ, ML, RI, OP, op, OPM, SA, AIS, TK;127 int LW, LC, LJ, ML, RI, OP, op, OPM, SA, AIS, TK;
112 double SM, CA, ca;128 double SM, CA, ca, FL;
113 struct {129 struct{ /* coloring info */
114 GSFont *Font;130 ColorSpace SCS, NSCS; /* stroking color space and nonstroking color space */
115 int nFont;131 u32int SC, NSC;
116 };132 };
133 GSFont Font;
117 struct {134 struct {
118 GSD *d;135 GSD *d;
119 int nd;136 int nd;
120 };137 };
121};138};
122 139
140struct TS {
141 double Tm[6]; /* text matrix */
142 double Tlm[6]; /* text line matrix */
143 /* Tracks if we're in a text object; nesting is verboten */
144 int inobj;
145 double TL;
146 /* Temporary, for pdf2txt functionality: tracks the last character's position so we know whether whitespace is needed */
147 double x, y;
148};
149
150struct Page {
151 Object *obj;
152 Object *stack;
153 Buffer buf;
154 /* The graphical state stack. GSactive is always a shortcut for the top of the stack, GS[nGS - 1] */
155 struct{
156 GS *GS;
157 GS *GSactive;
158 int nGS;
159 };
160 TS TS;
161};
162
123struct Pdf {163struct Pdf {
124 Stream *s;164 Stream *s;
125 Xref *xref;165 Xref *xref;
@@ -246,8 +286,8 @@ int bufput(Buffer *b, uchar *d, int sz); · 120 unmodified lines
246int bufget(Buffer *b, uchar *d, int sz);286int bufget(Buffer *b, uchar *d, int sz);
247void bufdump(Buffer *b);287void bufdump(Buffer *b);
248 288
249void pageinit(Page *p);289void pageinit(Page *p, Object *o);
250int pagerender(Page *p, Object *o);290int pagerender(Page *p);
251void pagefree(Page *p);291void pagefree(Page *p);
252 292
253#pragma varargck type "O" Object*293#pragma varargck type "O" Object*

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t