ref: 7b9490c36eed8a9545890a108e003c5b0e57a39f
dir: /op.c/
#include <u.h> #include <libc.h> #include <draw.h> #include <memdraw.h> #include "pdf.h" enum { Evenodd = 1<<0, Nonstroking = 1<<1, Leading = 1<<2, Nextline = 1<<3, TwTc = 1<<4, }; typedef struct Op Op; static void matidentity(double *arr) { double src[6] = { 1, 0, 0, 1, 0, 0 }; memcpy(arr, src, sizeof(double) * 6); } static void matmult(double *m1, double *m2, double *out) { double result[6]; result[0] = m1[0] * m2[0] + m1[1] * m2[2]; result[1] = m1[0] * m2[1] + m1[1] * m2[3]; result[2] = m1[2] * m2[0] + m1[3] * m2[2]; result[3] = m1[2] * m2[1] + m1[3] * m2[3]; result[4] = m1[4] * m2[0] + m1[5] * m2[2] + m2[4]; result[5] = m1[4] * m2[1] + m1[5] * m2[3] + m2[5]; memcpy(out, result, sizeof(double) * 6); } static void mattrans(double *m1, double x, double y, double *out) { double mult[6] = { 1, 0, 0, 1, x, y, }; matmult(mult, m1, out); } static void matscale(double *m1, double x, double y, double *out) { double mult[6] = { x, 0, 0, y, 0, 0, }; matmult(mult, m1, out); } struct Op { char *s; int (*f)(Op *op, Page *p); int argc; int flags; }; static int flagless(Op *op) { if(op->flags != 0){ fprint(2, "Op '%s' expected no flags\n", op->s); return 0; } return 1; } static int cobegin(Op *op, Page *p) { USED(op, p); return 0; } static int coend(Op *op, Page *p) { USED(op, p); return 0; } static int gspush(Op *op, Page *p) { USED(op); GS *r = realloc(p->GS, sizeof(GS) * (p->nGS + 1)); if(r == nil) return 0; p->GS = r; p->nGS += 1; p->GSactive = &p->GS[p->nGS - 1]; *(p->GSactive) = p->GS[p->nGS - 2]; if(p->GSactive->Font.widths != nil){ p->GSactive->Font.widths = malloc(sizeof(int) * (p->GSactive->Font.last - p->GSactive->Font.first + 1)); if(p->GSactive->Font.widths == nil){ werrstr("gspush: out of memory allocating space for glyph widths"); return 0; } memcpy(p->GSactive->Font.widths, p->GS[p->nGS - 2].Font.widths, sizeof(int) * (p->GSactive->Font.last - p->GSactive->Font.first + 1)); } return 1; } static int gspop(Op *op, Page *p) { USED(op); free(p->GSactive->Font.widths); GS *r = realloc(p->GS, sizeof(GS) * (p->nGS - 1)); if(r == nil) return 0; p->GS = r; p->nGS -= 1; p->GSactive = &p->GS[p->nGS - 1]; return 1; } /* six parameters give the inputs a,b,c,d,e,f for the matrix [a b 0] [c d 0] [e f 1] That matrix should be premultiplied with the current matrix newCTM = input x oldCTM (8.3.4) */ static int gsctm(Op *op, Page *p) { double input[6]; int i; for(i = 0; i < 6; i += 1) input[i] = arrayget(p->stack, i)->num.d; matmult(input, p->GSactive->CTM, p->GSactive->CTM); return flagless(op); } static int gswidth(Op *op, Page *p) { p->GSactive->LW = arrayget(p->stack, 0)->num.d; if(p->GSactive->LW < 0){ werrstr("line width cannot be negative: %f", p->GSactive->LW); return 0; } return flagless(op); } static int gscap(Op *op, Page *p) { p->GSactive->LC = arrayget(p->stack, 0)->num.d; if(p->GSactive->LC > CapEND){ werrstr("invalid line cap: %d", p->GSactive->LC); return 0; } return flagless(op); } static int gsjoin(Op *op, Page *p) { p->GSactive->LJ = arrayget(p->stack, 0)->num.d; if(p->GSactive->LJ > JoinEND){ werrstr("invalid line join: %d", p->GSactive->LJ); return 0; } return flagless(op); } static int gsmiterlim(Op *op, Page *p) { p->GSactive->ML = arrayget(p->stack, 0)->num.d; if(p->GSactive->ML < 0){ werrstr("miter limit cannot be negative: %f", p->GSactive->ML); return 0; } return flagless(op); } static int gsdash(Op *op, Page *p) { USED(op, p); return 0; } static int gsintent(Op *op, Page *p) { USED(op, p); return 0; } static int gsflatness(Op *op, Page *p) { p->GSactive->FL = arrayget(p->stack, 0)->num.d; return flagless(op); } static int gsstate(Op *op, Page *p) { char *name = arrayget(p->stack, 0)->name; Object *gs, *o; if((gs = dictget(dictget(dictget(p->obj, "Resources"), "ExtGState"), name)) == &null){ werrstr("extgstate dictionary not found: %s", name); return 0; } if((o = dictget(gs, "Type")) != &null && strcmp(o->name, "ExtGState") != 0){ werrstr("invalid type on ExtGState object: %s", o->name); return 0; } if((o = dictget(gs, "LW")) != &null){ if(o->type != Onum){ werrstr("line width must be a number, %d", o->type); return 0; } p->GSactive->LW = o->num.d; } if((o = dictget(gs, "LC")) != &null){ if(o->type != Onum){ werrstr("line cap must be a number"); return 0; } p->GSactive->LC = o->num.i; } if((o = dictget(gs, "LJ")) != &null){ if(o->type != Onum){ werrstr("line join must be a number"); return 0; } p->GSactive->LJ = o->num.i; } if((o = dictget(gs, "ML")) != &null){ if(o->type != Onum){ werrstr("miter limit must be a number"); return 0; } p->GSactive->ML = o->num.d; } return flagless(op); } static int pcmove(Op *op, Page *p) { USED(op, p); return 0; } static int pcline(Op *op, Page *p) { USED(op, p); return 0; } static int pccurve(Op *op, Page *p) { USED(op, p); return 0; } static int pcsubpath(Op *op, Page *p) { USED(op, p); return 0; } static int pcrect(Op *op, Page *p) { USED(op, p); return 0; } static int ppstroke(Op *op, Page *p) { USED(op, p); return 0; } static int ppstrokec(Op *op, Page *p) { USED(op, p); return 0; } static int ppfill(Op *op, Page *p) { USED(op, p); return 0; } static int ppfills(Op *op, Page *p) { USED(op, p); return 0; } static int ppfillcfs(Op *op, Page *p) { USED(op, p); return 0; } static int ppc(Op *op, Page *p) { USED(op, p); return 0; } static int cpclip(Op *op, Page *p) { USED(op, p); return 0; } char *colorspacedevices[] = {"DeviceGray", "DeviceRGB", "DeviceCMYK"}; ColorSpace colorspacechans[] = { [1] = DeviceGray, [3] = DeviceRGB, [4] = DeviceCMYK, }; static int iccbasedcspace(Object *icc, ColorSpace *c) { Object *o; *c = colorspacechans[dictint(icc, "N")]; if((o = dictget(icc, "Alternate")) != &null){ if(*c > 2 || strcmp(colorspacedevices[*c], o->name) != 0){ werrstr("ICCBased cspace: TODO: handle non-device alternate '%s'", o->name); return 0; } } return 1; } static int cspace(Op *op, Page *p) { ColorSpace *c = (op->flags & Nonstroking) ? &p->GSactive->NSCS : &p->GSactive->SCS; char *s = arrayget(p->stack, 0)->name; Object *o; int i; for(i = 0; i < 3; i += 1) if(strcmp(s, colorspacedevices[i]) == 0){ *c = i; return 1; } if((o = dictget(dictget(p->obj, "Resources"), "ColorSpace")) == &null || (o = dictget(o, s)) == &null){ werrstr("colorspace '%s' not found", s); return 0; } if(strcmp(arrayget(o, 0)->name, "ICCBased") == 0){ /* Don't support ICC; fall back to Alternate or default */ o = arrayget(o, 1); return iccbasedcspace(o, c); } werrstr("cspace: TODO: %s color space", arrayget(o, 0)->name); return 0; } static int ccolour(Op *op, Page *p) { USED(op, p); return 0; } static int ccolour2(Op *op, Page *p) { USED(op, p); return 0; } static int cgray(Op *op, Page *p) { int value = 255 * arrayget(p->stack, 0)->num.d; int i; u32int *color; if(op->flags & Nonstroking){ color = &p->GSactive->NSC; p->GSactive->NSCS = DeviceGray; } else{ color = &p->GSactive->SC; p->GSactive->SCS = DeviceGray; } *color = 0; for(i = 0; i < 3; i += 1) *color = (*color | value) << 8; *color |= 255; return 1; } static int crgb(Op *op, Page *p) { USED(op, p); return 0; } static int ccmyk(Op *op, Page *p) { USED(op, p); return 0; } static int sshade(Op *op, Page *p) { USED(op, p); return 0; } static int eoobject(Op *op, Page *p) { USED(op, p); return 0; } static int iibegin(Op *op, Page *p) { USED(op, p); return 0; } static int iidata(Op *op, Page *p) { USED(op, p); return 0; } static int iiend(Op *op, Page *p) { USED(op, p); return 0; } static int tsspace(Op *op, Page *p) { p->TS.Tc = arrayget(p->stack, 0)->num.d; return 1; } static int tswspace(Op *op, Page *p) { p->TS.Tw = arrayget(p->stack, 0)->num.d; return 1; } static int tshscale(Op *op, Page *p) { USED(op, p); return 0; } static int tslead(Op *op, Page *p) { p->TS.TL = arrayget(p->stack, 0)->num.d; return flagless(op); } static int fontwidths(Page *p) { Object *o; int i; if(p->GSactive->Font.widths != nil) free(p->GSactive->Font.widths); o = dictget(p->GSactive->Font.font, "FirstChar"); if(o == nil) return 1; p->GSactive->Font.first = o->num.i; p->GSactive->Font.last = dictget(p->GSactive->Font.font, "LastChar")->num.i; p->GSactive->Font.widths = malloc(sizeof(int) * (p->GSactive->Font.last - p->GSactive->Font.first + 1)); if(p->GSactive->Font.widths == nil){ 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); return 1; } o = dictget(p->GSactive->Font.font, "Widths"); if(o == nil) return 0; for(i = 0; i < arraylen(o); i += 1) p->GSactive->Font.widths[i] = arrayget(o, i)->num.i; o = dictget(p->GSactive->Font.font, "FontDescriptor"); p->GSactive->Font.defwidth = dictget(o, "MissingWidth")->num.i; return 1; } static int tsfontsz(Op *op, Page *p) { char *name = arrayget(p->stack, 0)->name; p->GSactive->Font.font = dictget(dictget(dictget(p->obj, "Resources"), "Font"), name); if(p->GSactive->Font.font == nil){ werrstr("Font not found: '%s'", name); return 0; } p->GSactive->Font.enc = dictget(p->GSactive->Font.font, "Encoding"); if(p->GSactive->Font.enc) p->GSactive->Font.enc = dictget(p->GSactive->Font.enc, "Differences"); p->GSactive->Font.size = arrayget(p->stack, 1)->num.d; return fontwidths(p) && flagless(op); } static int tsrendmode(Op *op, Page *p) { USED(op, p); return 0; } static int tsrise(Op *op, Page *p) { USED(op, p); return 0; } static int tobegin(Op *op, Page *p) { if(p->TS.inobj){ werrstr("Text objects must not be nested"); return 0; } matidentity(p->TS.Tm); matidentity(p->TS.Tlm); p->TS.inobj = 1; p->TS.TL = 0; p->TS.Tc = 0; p->TS.Tw = 0; p->GSactive->Font.font = nil; return flagless(op); } static int toend(Op *op, Page *p) { if(!p->TS.inobj){ werrstr("ET found without BT"); return 0; } p->TS.inobj = 0; return flagless(op); } static void tmove(Page *p, double x, double y, int tlm) { double *mat = tlm ? p->TS.Tlm : p->TS.Tm; mattrans(mat, x, y, mat); if(tlm) memcpy(p->TS.Tm, p->TS.Tlm, sizeof(double) * 6); } static int tpmove(Op *op, Page *p) { Object *x, *y; x = arrayget(p->stack, 0); y = arrayget(p->stack, 1); if(op->flags & Leading) p->TS.TL = -y->num.d; tmove(p, x->num.d, y->num.d, 1); return 1; } static int tpmatrix(Op *op, Page *p) { int i; for(i = 0; i < 6; i += 1){ p->TS.Tm[i] = arrayget(p->stack, i)->num.d; p->TS.Tlm[i] = p->TS.Tm[i]; } return flagless(op); } static int tpmove0(Op *op, Page *p) { tmove(p, 0, 0 - p->TS.TL, 1); return flagless(op); } //TODO: replace this with a precomputed map when the font is loaded static int writepatched(Page *p, uchar c) { int i, len, d = 0; char buf[2]; Object *o; if(p->GSactive->Font.enc != nil){ len = arraylen(p->GSactive->Font.enc); for(i = 0; i < len; i += 1){ o = arrayget(p->GSactive->Font.enc, i); if(o->type == Onum) d = o->num.i; else if(d == c){ if(strcmp(o->name, "Omega") == 0) return bufput(&p->buf, (uchar*)"Ω", strlen("Ω")) == strlen("Ω"); if(strcmp(o->name, "Pi") == 0) return bufput(&p->buf, (uchar*)"Π", strlen("Π")) == strlen("Π"); if(strcmp(o->name, "mu") == 0) return bufput(&p->buf, (uchar*)"μ", strlen("μ")) == strlen("μ"); if(strcmp(o->name, "pi") == 0) return bufput(&p->buf, (uchar*)"π", strlen("π")) == strlen("π"); if(strcmp(o->name, "heart") == 0) return bufput(&p->buf, (uchar*)"♥", strlen("♥")) == strlen("♥"); if(strcmp(o->name, "minus") == 0) return bufput(&p->buf, (uchar*)"1", 1) == 1; if(strcmp(o->name, "fl") == 0) return bufput(&p->buf, (uchar*)"fl", 2) == 2; if(strcmp(o->name, "dieresis") == 0) return bufput(&p->buf, (uchar*)"¨", strlen("¨")) == strlen("¨"); if(strcmp(o->name, "endash") == 0) return bufput(&p->buf, (uchar*)"-", 1) == 1; if(strcmp(o->name, "fi") == 0) return bufput(&p->buf, (uchar*)"fi", 2) == 2; if(strcmp(o->name, "ff") == 0) return bufput(&p->buf, (uchar*)"ff", 2) == 2; if(strcmp(o->name, "ffi") == 0) return bufput(&p->buf, (uchar*)"ffi", 3) == 3; if(strcmp(o->name, "bullet") == 0) return bufput(&p->buf, (uchar*)"•", strlen("•")) == strlen("•"); if(strcmp(o->name, "quotedblleft") == 0) return bufput(&p->buf, (uchar*)"\"", 1) == 1; if(strcmp(o->name, "quotedblright") == 0) return bufput(&p->buf, (uchar*)"\"", 1) == 1; if(strcmp(o->name, "quoteleft") == 0) return bufput(&p->buf, (uchar*)"'", 1) == 1; if(strcmp(o->name, "zero") == 0) return bufput(&p->buf, (uchar*)"0", 1) == 1; if(strcmp(o->name, "one") == 0) return bufput(&p->buf, (uchar*)"1", 1) == 1; if(strcmp(o->name, "two") == 0) return bufput(&p->buf, (uchar*)"2", 1) == 1; if(strcmp(o->name, "three") == 0) return bufput(&p->buf, (uchar*)"3", 1) == 1; if(strcmp(o->name, "four") == 0) return bufput(&p->buf, (uchar*)"4", 1) == 1; if(strcmp(o->name, "five") == 0) return bufput(&p->buf, (uchar*)"5", 1) == 1; if(strcmp(o->name, "six") == 0) return bufput(&p->buf, (uchar*)"6", 1) == 1; if(strcmp(o->name, "seven") == 0) return bufput(&p->buf, (uchar*)"7", 1) == 1; if(strcmp(o->name, "eight") == 0) return bufput(&p->buf, (uchar*)"8", 1) == 1; if(strcmp(o->name, "nine") == 0) return bufput(&p->buf, (uchar*)"9", 1) == 1; if(strcmp(o->name, "space") == 0) return bufput(&p->buf, (uchar*)" ", 1) == 1; if(strcmp(o->name, "quoteright") == 0) return bufput(&p->buf, (uchar*)"'", 1) == 1; if(strcmp(o->name, "backslash") == 0) return bufput(&p->buf, (uchar*)"\\", 1) == 1; if(strcmp(o->name, "braceright") == 0) return bufput(&p->buf, (uchar*)"}", 1) == 1; if(strcmp(o->name, "period") == 0) return bufput(&p->buf, (uchar*)".", 1) == 1; if(strcmp(o->name, "comma") == 0) return bufput(&p->buf, (uchar*)",", 1) == 1; if(strcmp(o->name, "braceleft") == 0) return bufput(&p->buf, (uchar*)"{", 1) == 1; if(strcmp(o->name, "arrowright") == 0) return bufput(&p->buf, (uchar*)"→", strlen("→")) == strlen("→"); buf[1] = 0; for(buf[0] = 'A'; buf[0] <= 'Z'; buf[0] += 1) if(strcmp(buf, o->name) == 0) return bufput(&p->buf, (uchar*)buf, 1) == 1; for(buf[0] = 'a'; buf[0] <= 'z'; buf[0] += 1) if(strcmp(buf, o->name) == 0) return bufput(&p->buf, (uchar*)buf, 1) == 1; fprint(2, "TODO: recognize glyph name '%s'\n", o->name); return 1; } else d += 1; } } return bufput(&p->buf, (uchar*)&c, 1) == 1; } static double glyphwidth(Page *p, ulong c) { if(c >= p->GSactive->Font.first && c <= p->GSactive->Font.last) return (double)p->GSactive->Font.widths[c - p->GSactive->Font.first] / 1000; return (double)p->GSactive->Font.defwidth / 1000; } static double glyphspacing(Page *p, ulong c) { double Tfs = p->GSactive->Font.size; double w0 = glyphwidth(p, c); double tx = w0 * Tfs + p->TS.Tc; if(c == ' ') tx += p->TS.Tw; return tx; } /* Renders one character / glyph and updates the text state */ static int tchar(Page *p, ulong c) { double Tfs = p->GSactive->Font.size; double Trm[6]; double tx; int i; /* Text rendering matrix converts coordinates from font space to device space */ matscale(p->TS.Tm, Tfs, Tfs, Trm); matmult(Trm, p->GSactive->CTM, Trm); // Check if whitespace is needed if(p->buf.sz > 1){ if(p->TS.y != Trm[5]){ for(i = 0; i < (int)((p->TS.y - Trm[5]) / p->GSactive->Font.size) && i < 1; i += 1) if(bufput(&p->buf, (uchar*)"\n", 1) != 1) return 0; } if(Trm[4] - p->TS.x > 14){ if(bufput(&p->buf, (uchar*)" ", 1) != 1) return 0; } } usize oend = p->buf.sz; if(!writepatched(p, c)){ werrstr("tchar: failed to write character: %r"); return 0; } if(p->buf.b != nil) memimagestring(p->image, Pt(Trm[4], Trm[5]), memblack, Pt(0,0), getmemdefont(), (char*)p->buf.b + oend); tx = glyphspacing(p, c); mattrans(p->TS.Tm, tx, 0, p->TS.Tm); p->TS.x = Trm[4] + tx; p->TS.y = Trm[5]; return 1; } static int tstr(Page *p, char *str, ulong len) { ulong i; for(i = 0; i < len; i += 1) if(!tchar(p, str[i])) return 0; return 1; } static int thshow(Op *op, Page *p) { if(op->flags != 0){ fprint(2, "TODO: thshow != Tj\n"); return 0; } Object *o = arrayget(p->stack, 0); if(!tstr(p, o->str, o->len)) return 0; return 1; } static int thshowarr(Op *op, Page *p) { Object *o, *arr = arrayget(p->stack, 0); int i; for(i = 0; i < arraylen(arr); i += 1){ o = arrayget(arr, i); if(o->type == Ostr) if(!tstr(p, o->str, o->len)) return 0; else mattrans(p->TS.Tm, 0 - (o->num.d / 1000), 0, p->TS.Tm); } return flagless(op); } static int t3width(Op *op, Page *p) { USED(op, p); return 0; } static int t3widthbb(Op *op, Page *p) { USED(op, p); return 0; } static int t4add(Op *op, Page *p) { USED(op, p); /* double x; x = objat(s+1, Onum)->num.d + objat(s+0, Onum)->num.d; s = pop(s); s->num.d = x; s->num.i = x; */ return 0; } static int t4sub(Op *op, Page *p) { USED(op, p); /* double x; x = objat(s+1, Onum)->num.d - objat(s+0, Onum)->num.d; s = pop(s); s->num.d = x; s->num.i = x; */ return 0; } static int t4mul(Op *op, Page *p) { USED(op, p); /* double x; x = objat(s+1, Onum)->num.d * objat(s+0, Onum)->num.d; s = pop(s); s->num.d = x; s->num.i = x; */ return 0; } static int t4div(Op *op, Page *p) { USED(op, p); /* double x; x = objat(s+1, Onum)->num.d / objat(s+0, Onum)->num.d; s = pop(s); s->num.d = x; s->num.i = x; */ return 0; } static int t4idiv(Op *op, Page *p) { USED(op, p); return 0; } static int t4mod(Op *op, Page *p) { USED(op, p); return 0; } static int t4neg(Op *op, Page *p) { USED(op, p); return 0; } static int t4abs(Op *op, Page *p) { USED(op, p); return 0; } static int t4ceiling(Op *op, Page *p) { USED(op, p); return 0; } static int t4floor(Op *op, Page *p) { USED(op, p); return 0; } static int t4round(Op *op, Page *p) { USED(op, p); return 0; } static int t4truncate(Op *op, Page *p) { USED(op, p); return 0; } static int t4sqrt(Op *op, Page *p) { USED(op, p); return 0; } static int t4sin(Op *op, Page *p) { USED(op, p); return 0; } static int t4cos(Op *op, Page *p) { USED(op, p); return 0; } static int t4atan(Op *op, Page *p) { USED(op, p); return 0; } static int t4exp(Op *op, Page *p) { USED(op, p); return 0; } static int t4ln(Op *op, Page *p) { USED(op, p); return 0; } static int t4log(Op *op, Page *p) { USED(op, p); return 0; } static int t4cvi(Op *op, Page *p) { USED(op, p); return 0; } static int t4cvr(Op *op, Page *p) { USED(op, p); return 0; } static int t4eq(Op *op, Page *p) { USED(op, p); return 0; } static int t4ne(Op *op, Page *p) { USED(op, p); return 0; } static int t4gt(Op *op, Page *p) { USED(op, p); return 0; } static int t4ge(Op *op, Page *p) { USED(op, p); return 0; } static int t4lt(Op *op, Page *p) { USED(op, p); return 0; } static int t4le(Op *op, Page *p) { USED(op, p); return 0; } static int t4and(Op *op, Page *p) { USED(op, p); return 0; } static int t4or(Op *op, Page *p) { USED(op, p); return 0; } static int t4xor(Op *op, Page *p) { USED(op, p); return 0; } static int t4not(Op *op, Page *p) { USED(op, p); return 0; } static int t4bitshift(Op *op, Page *p) { USED(op, p); return 0; } static int t4true(Op *op, Page *p) { USED(op, p); return 0; } static int t4false(Op *op, Page *p) { USED(op, p); return 0; } static int t4if(Op *op, Page *p) { USED(op, p); return 0; } static int t4ifelse(Op *op, Page *p) { USED(op, p); return 0; } static int t4pop(Op *op, Page *p) { USED(op, p); return 0; } static int t4exch(Op *op, Page *p) { USED(op, p); return 0; } static int t4dup(Op *op, Page *p) { USED(op, p); return 0; } static int t4copy(Op *op, Page *p) { USED(op, p); return 0; } static int t4index(Op *op, Page *p) { USED(op, p); return 0; } static int t4roll(Op *op, Page *p) { USED(op, p); return 0; } static int opignore(Op *op, Page *p) { USED(op, p); return 0; } static Op ops[] = { /* 7.8.2 Compatibility operators */ {"BX", cobegin, 0,}, /* begin a compatilibity section */ {"EX", coend, 0,}, /* end a compatilibity section */ /* 8.4.4 Graphics state operators */ {"q", gspush, 0,}, /* push the current graphics state (gs) on gs stack */ {"Q", gspop, 0,}, /* pop ^ */ {"cm", gsctm, 6,}, /* current transformation matrix (ctm) */ {"w", gswidth, 1,}, /* line width */ {"J", gscap, 1,}, /* line cap style */ {"j", gsjoin, 1,}, /* line join style */ {"M", gsmiterlim, 1,}, /* miter limit */ {"d", gsdash, 2,}, /* line dash pattern */ {"ri", gsintent, 1,}, /* colour rendering intent */ {"i", gsflatness, 1,}, /* flatness tolerance */ {"gs", gsstate, 1,}, /* graphics state parameters */ /* 8.5.2 Path construction operators */ {"m", pcmove, 2,}, /* move to coords */ {"l", pcline, 2,}, /* straight line to coords */ {"c", pccurve, 6,}, /* Bézier curve */ {"v", pccurve, 4,}, /* Bézier curve */ {"y", pccurve, 4,}, /* Bézier curve */ {"h", pcsubpath, 0}, /* close subpath */ {"re", pcrect, 4,}, /* rectangle */ /* 8.5.3 Path painting operators */ {"S", ppstroke, 0,}, /* stroke the path */ {"s", ppstrokec, 0,}, /* close and stroke */ {"f", ppfill, 0,}, /* fill */ {"F", ppfill, 0,}, /* same damn thing, but DEPRECATED */ {"f*", ppfill, 0, Evenodd,}, /* fill, even/odd rule */ {"B", ppfills, 0,}, /* fill and stroke */ {"B*", ppfills, 0, Evenodd,}, /* fill and stroke, even/odd rule */ {"b", ppfillcfs, 0,}, /* close, fill and stroke */ {"b*", ppfillcfs, 0, Evenodd,}, /* close, fill and stroke, even/odd rule */ {"n", ppc, 0, 0}, /* end the path */ /* 8.5.4 Clipping path operators */ {"W", cpclip, 0,}, /* clip */ {"W*", cpclip, 0, Evenodd,}, /* clip, even/odd rule */ /* 8.6.8 Colour operators */ {"CS", cspace, 1,}, /* colour space */ {"cs", cspace, 1, Nonstroking,}, /* colour space, nonstroking */ {"SC", ccolour, -1,}, /* colour */ {"sc", ccolour, -1, Nonstroking,}, /* colour, nonstroking */ {"SCN", ccolour2, -1,}, /* color (more spaces) */ {"scn", ccolour2, -1,}, /* color (more spaces), nonstroking */ {"G", cgray, 1,}, /* gray */ {"g", cgray, 1, Nonstroking,}, /* gray, nonstroking */ {"RG", crgb, 3,}, /* RGB */ {"rg", crgb, 3, Nonstroking,}, /* RGB, nonstroking */ {"K", ccmyk, 4,}, /* CMYK */ {"k", ccmyk, 4, Nonstroking,}, /* CMYK, nonstroking */ /* 8.7.4.2 Shading operator */ {"sh", sshade, 1,}, /* shading */ /* 8.8 External objects */ {"Do", eoobject, 1,}, /* paint XObject */ /* 8.9.7 Inline images */ {"BI", iibegin, 0,}, /* begin */ {"ID", iidata, 0,}, /* data */ {"EI", iiend, 0,}, /* end */ /* 9.3.1 Text state parameters */ {"Tc", tsspace, 1,}, /* spacing */ {"Tw", tswspace, 1,}, /* word spacing */ {"Tz", tshscale, 1,}, /* horizontal spacing */ {"TL", tslead, 1,}, /* leading */ {"Tf", tsfontsz, 1,}, /* font size */ {"Tr", tsrendmode, 1,}, /* rendeing mode */ {"Ts", tsrise, 1,}, /* rise */ /* 9.4.1 Text objects */ {"BT", tobegin, 0,}, /* begin */ {"ET", toend, 0,}, /* end */ /* 9.4.2 Text position operators */ {"Td", tpmove, 2,}, /* move, next line */ {"TD", tpmove, 2, Leading,}, /* move, next line, leading */ {"Tm", tpmatrix, 6,}, /* set Tm and Tlm */ {"T*", tpmove0, 0,}, /* move, next line, leading */ /* 9.4.3 Text showing operators */ {"Tj", thshow, 1,}, /* show string */ {"'", thshow, 1, Nextline,}, /* next line & show */ {"\"", thshow, 3, Nextline|TwTc,}, /* next line, Tw, Tc & show */ {"TJ", thshowarr, 1,}, /* show array */ /* 9.6.4 Type 3 font operators */ {"d0", t3width, 2,}, /* width info */ {"d1", t3widthbb, 6,}, /* width & bounding box */ /* 14.6 Marked content */ {"BDC", opignore, 2,}, {"EMC", opignore, 0,}, /* 7.10.5.2 Operators and operands */ /* B.2 Arithmetic operators */ {"add", t4add, 2,}, {"sub", t4sub, 2,}, {"mul", t4mul, 2,}, {"div", t4div, 2,}, {"idiv", t4idiv, 2,}, {"mod", t4mod, 2,}, {"neg", t4neg, 1,}, {"abs", t4abs, 1,}, {"ceiling", t4ceiling, 1,}, {"floor", t4floor, 1,}, {"round", t4round, 1,}, {"truncate", t4truncate, 1,}, {"sqrt", t4sqrt, 1,}, {"sin", t4sin, 1,}, {"cos", t4cos, 1,}, {"atan", t4atan, 2,}, {"exp", t4exp, 2,}, {"ln", t4ln, 1,}, {"log", t4log, 1,}, {"cvi", t4cvi, 1,}, {"cvr", t4cvr, 1,}, /* B.3 Relational, boolean, and bitwise operators */ {"eq", t4eq, 2,}, {"ne", t4ne, 2,}, {"gt", t4gt, 2,}, {"ge", t4ge, 2,}, {"lt", t4lt, 2,}, {"le", t4le, 2,}, {"and", t4and, 2,}, {"or", t4or, 2,}, {"xor", t4xor, 2,}, {"not", t4not, 1,}, {"bitshift", t4bitshift, 2,}, {"true", t4true, 0,}, {"false", t4false, 0,}, /* B.4 Conditional operators */ {"if", t4if, 2,}, {"ifelse", t4ifelse, 3,}, /* B.5 Stack operators */ {"pop", t4pop, 1,}, {"exch", t4exch, 2,}, {"dup", t4dup, 1,}, {"copy", t4copy, -1,}, {"index", t4index, -1,}, {"roll", t4roll, -2,}, /* terminator */ {nil, nil, 0,}, }; Op * opfind(char *name) { int i = 0; Op *op; while(ops[i].s != nil){ op = &ops[i]; if(strcmp(op->s, name) == 0) return op; i += 1; } return nil; } void pageinit(Page *page, Object *o) { bufinit(&page->buf, 0, 0); // Stack is per-content-stream, so we don't create it here page->stack = nil; page->obj = o; page->TS.inobj = 0; page->TS.x = 0; page->TS.y = 0; } void ctminit(Page *p, double *ctm) { Object *cropbox; double mx, my; matidentity(ctm); if((cropbox = dictget(p->obj, "CropBox")) == nil) return; mx = arrayget(cropbox, 2)->num.d; my = arrayget(cropbox, 3)->num.d; ctm[0] = 850 / mx; ctm[3] = -1100 / my; ctm[5] = 1100; } void gsinit(Page *p, GS *gs) { USED(p); /* todo: actually initialize the full state */ ctminit(p, gs->CTM); gs->LW = 1.0; gs->LC = CapButt; gs->LJ = JoinMiter; gs->ML = 10.0; gs->SCS = gs->NSCS = DeviceGray; // Alpha is lowest byte; this is (0, 0, 0, 255) == black gs->SC = gs->NSC = 255; gs->Font.font = nil; gs->Font.enc = nil; gs->Font.widths = nil; } void gsfree(GS gs) { free(gs.Font.widths); } void pagegsclean(Page *p) { int i; if(p->GSactive != nil){ p->GSactive = nil; for(i = 0; i < p->nGS; i += 1) gsfree(p->GS[i]); free(p->GS); p->GS = nil; p->nGS = 0; } } static int stackreset(Page *p) { pdfobjfree(p->stack); p->stack = arraynew(p->obj->pdf); return p->stack != nil; } void pagefree(Page *p) { buffree(&p->buf); pdfobjfree(p->stack); pagegsclean(p); } static int pagerendercontent(Page *p, Object *content) { Stream *s; Object *o; Op *op; s = Sopen(content); if(s == nil){ fprint(2, "%O\n", content); sysfatal("%r"); } p->stack = arraynew(content->pdf); if(p->stack == nil) return 0; while(s->buf.off != s->buf.sz){ while(isws(s->buf.b[s->buf.off]) && s->buf.off != s->buf.sz) s->buf.off += 1; if(s->buf.off == s->buf.sz) break; o = pdfobj(content->pdf, s); if(o == nil){ werrstr("pagerendercontent: failed to parse op: %r"); return 0; } if(o->type == Oop){ op = opfind(o->str); if(op == nil){ fprint(2, "Unknown op: %s\n", o->str); pdfobjfree(o); return 0; } pdfobjfree(o); werrstr("unimplemented or error not reported properly"); if(!op->f(op, p)){ werrstr("renderer: %s failed: %r", op->s); fprint(2, "%r\n"); // return 0; } if(!stackreset(p)) return 0; } else{ if(!arrayadd(p->stack, o)){ fprint(2, "Failed to push operand to stack: %r\n"); return 0; } } } if(bufput(&p->buf, (uchar*)"\n\0", 2) != 2) return 0; Sclose(s); return 1; } int pagerender(Page *p) { Object *content; int i; p->nGS = 1; p->GS = malloc(sizeof(GS)); if(p->GS == nil){ werrstr("Out of memory"); return 0; } p->GSactive = p->GS; gsinit(p, p->GS); if((p->image = allocmemimage(Rect(0,0,850,1100), strtochan("r8g8b8"))) == nil){ werrstr("unable to allocate image: %r"); pagegsclean(p); return 0; } memfillcolor(p->image, 0xFFFFFFFF); content = dictget(p->obj, "Contents"); if(content->type == Oarray){ for(i = 0; i < arraylen(content); i += 1) if(!pagerendercontent(p, arrayget(content, i))) return 0; } else if(content->type != Onull) if(!pagerendercontent(p, content)) return 0; pagegsclean(p); return 1; }