ref: 9a75fdb2a24e222ec203f88c47f21db92d5a9556
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;
u32int *color = (op->flags & Nonstroking) ? &p->GSactive->NSC : &p->GSactive->SC;
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;
*color = i == 2 ? 0xFF : 0;
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);
if(!iccbasedcspace(o, c))
return 0;
*color = *c == 2 ? 0xFF : 0;
return 1;
}
werrstr("cspace: TODO: %s color space", arrayget(o, 0)->name);
return 0;
}
static int
ccolour(Op *op, Page *p)
{
ColorSpace c = op->flags & Nonstroking ? p->GSactive->NSCS : p->GSactive->SCS;
u32int *color = op->flags & Nonstroking ? &p->GSactive->NSC : &p->GSactive->SC;
if(c == DeviceRGB){
*color = ((u8int)(255 * arrayget(p->stack, 0)->num.d)) << 24 | ((u8int)(255 * arrayget(p->stack, 1)->num.d)) << 16 | ((u8int)(255 * arrayget(p->stack, 2)->num.d)) << 8 | 0xff;
return 1;
}
return 0;
}
static int
ccolour2(Op *op, Page *p)
{
ColorSpace c = op->flags & Nonstroking ? p->GSactive->NSCS : p->GSactive->SCS;
if(c < 3)
return ccolour(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;
}
p->TS.inobj = 1;
p->GSactive->Font.font = nil;
matidentity(p->TS.Tm);
matidentity(p->TS.Tlm);
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("→");
break;
} else
d += 1;
}
}
return bufput(&p->buf, (uchar*)&c, 1) == 1;
}
// Returns glyph width in TEXT SPACE units.
static double
glyphwidth(Page *p, ulong c)
{
// TODO: type 3 fonts have explicit metrics? See 9.2.4 / 9.6.5 of the spec.
// For all other fonts, units are 1/1000th of text space
double gwidth = 435;
// if(c >= p->GSactive->Font.first && c <= p->GSactive->Font.last)
// gwidth = p->GSactive->Font.widths[c - p->GSactive->Font.first];
return gwidth / 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];
Memimage *ink;
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)((Trm[5] - p->TS.y) / p->GSactive->Font.size / 1.5) && i < 2; i += 1)
if(bufput(&p->buf, (uchar*)"\n", 1) != 1)
return 0;
} else if(Trm[4] - p->TS.x > 5) {
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){
ink = allocmemimage(Rect(0,0,1,1),RGB24);
if(ink == nil){
werrstr("failed to allocate ink");
return 0;
}
ink->flags |= Frepl;
ink->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
memfillcolor(ink, p->GSactive->NSC);
memimagestring(p->image, Pt(Trm[4], Trm[5]), ink, Pt(0,0), getmemdefont(), (char*)p->buf.b + oend);
freememimage(ink);
}
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 & TwTc){
werrstr("TODO thshow TwTc");
return 0;
}
if(op->flags & Nextline)
tmove(p, 0, 0 - p->TS.TL, 1);
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 - (p->GSactive->Font.size * 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,Nonstroking}, /* 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;
}
Object*
pagecropbox(Page *p)
{
Object *page = p->obj;
Object *o;
while(page != &null){
o = dictget(page, "CropBox");
if(o != &null)
return o;
o = dictget(page, "MediaBox");
if(o != &null)
return o;
page = dictget(page, "Parent");
}
return &null;
}
void
ctminit(Page *p, double *ctm, double w, double h)
{
Object *cropbox;
double mx, my;
matidentity(ctm);
cropbox = pagecropbox(p);
mx = arrayget(cropbox, 2)->num.d;
my = arrayget(cropbox, 3)->num.d;
ctm[0] = w / mx;
ctm[3] = 0-h / my;
ctm[5] = h;
}
static void
tsinit(Page *p)
{
p->TS.TL = 0;
p->TS.Tc = 0;
p->TS.Tw = 0;
}
void
gsinit(Page *p, GS *gs)
{
USED(p);
/* todo: actually initialize the full state */
ctminit(p, gs->CTM, 1100, 1300);
tsinit(p);
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,1100,1300), 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;
}