#include "fitz.h" #include "mupdf.h" #define TILE void pdf_initgstate(pdf_gstate *gs, fz_matrix ctm) { gs->ctm = ctm; gs->clipdepth = 0; gs->strokestate.linewidth = 1; gs->strokestate.linecap = 0; gs->strokestate.linejoin = 0; gs->strokestate.miterlimit = 10; gs->strokestate.dashphase = 0; gs->strokestate.dashlen = 0; memset(gs->strokestate.dashlist, 0, sizeof(gs->strokestate.dashlist)); gs->stroke.kind = PDF_MCOLOR; gs->stroke.colorspace = fz_keepcolorspace(fz_devicegray); gs->stroke.v[0] = 0; gs->stroke.pattern = nil; gs->stroke.shade = nil; gs->stroke.alpha = 1; gs->fill.kind = PDF_MCOLOR; gs->fill.colorspace = fz_keepcolorspace(fz_devicegray); gs->fill.v[0] = 0; gs->fill.pattern = nil; gs->fill.shade = nil; gs->fill.alpha = 1; gs->charspace = 0; gs->wordspace = 0; gs->scale = 1; gs->leading = 0; gs->font = nil; gs->size = -1; gs->render = 0; gs->rise = 0; gs->blendmode = FZ_BNORMAL; gs->softmask = nil; gs->softmaskctm = fz_identity; gs->luminosity = 0; } void pdf_setcolorspace(pdf_csi *csi, int what, fz_colorspace *colorspace) { pdf_gstate *gs = csi->gstate + csi->gtop; pdf_material *mat; pdf_flushtext(csi); mat = what == PDF_MFILL ? &gs->fill : &gs->stroke; fz_dropcolorspace(mat->colorspace); mat->kind = PDF_MCOLOR; mat->colorspace = fz_keepcolorspace(colorspace); mat->v[0] = 0; mat->v[1] = 0; mat->v[2] = 0; mat->v[3] = 1; } void pdf_setcolor(pdf_csi *csi, int what, float *v) { pdf_gstate *gs = csi->gstate + csi->gtop; pdf_material *mat; int i; pdf_flushtext(csi); mat = what == PDF_MFILL ? &gs->fill : &gs->stroke; switch (mat->kind) { case PDF_MPATTERN: case PDF_MCOLOR: if (!strcmp(mat->colorspace->name, "Lab")) { mat->v[0] = v[0] / 100; mat->v[1] = (v[1] + 100) / 200; mat->v[2] = (v[2] + 100) / 200; } for (i = 0; i < mat->colorspace->n; i++) mat->v[i] = v[i]; break; default: fz_warn("color incompatible with material"); } } static void pdf_unsetpattern(pdf_csi *csi, int what) { pdf_gstate *gs = csi->gstate + csi->gtop; pdf_material *mat; mat = what == PDF_MFILL ? &gs->fill : &gs->stroke; if (mat->kind == PDF_MPATTERN) { if (mat->pattern) pdf_droppattern(mat->pattern); mat->pattern = nil; mat->kind = PDF_MCOLOR; } } void pdf_setpattern(pdf_csi *csi, int what, pdf_pattern *pat, float *v) { pdf_gstate *gs = csi->gstate + csi->gtop; pdf_material *mat; pdf_flushtext(csi); mat = what == PDF_MFILL ? &gs->fill : &gs->stroke; if (mat->pattern) pdf_droppattern(mat->pattern); mat->kind = PDF_MPATTERN; if (pat) mat->pattern = pdf_keeppattern(pat); else mat->pattern = nil; if (v) pdf_setcolor(csi, what, v); } void pdf_setshade(pdf_csi *csi, int what, fz_shade *shade) { pdf_gstate *gs = csi->gstate + csi->gtop; pdf_material *mat; pdf_flushtext(csi); mat = what == PDF_MFILL ? &gs->fill : &gs->stroke; if (mat->shade) fz_dropshade(mat->shade); mat->kind = PDF_MSHADE; mat->shade = fz_keepshade(shade); } static void pdf_showpattern(pdf_csi *csi, pdf_pattern *pat, fz_rect area, int what) { pdf_gstate *gstate; fz_matrix ptm, invptm; fz_matrix oldtopctm; fz_error error; int x0, y0, x1, y1; int oldtop; pdf_gsave(csi); gstate = csi->gstate + csi->gtop; if (pat->ismask) { pdf_unsetpattern(csi, PDF_MFILL); pdf_unsetpattern(csi, PDF_MSTROKE); if (what == PDF_MFILL) { pdf_dropmaterial(&gstate->stroke); pdf_keepmaterial(&gstate->fill); gstate->stroke = gstate->fill; } if (what == PDF_MSTROKE) { pdf_dropmaterial(&gstate->fill); pdf_keepmaterial(&gstate->stroke); gstate->fill = gstate->stroke; } } else { // TODO: unset only the current fill/stroke or both? pdf_unsetpattern(csi, what); } /* don't apply softmasks to objects in the pattern as well */ if (gstate->softmask) { pdf_dropxobject(gstate->softmask); gstate->softmask = nil; } ptm = fz_concat(pat->matrix, csi->topctm); invptm = fz_invertmatrix(ptm); /* patterns are painted using the ctm in effect at the beginning of the content stream */ /* get bbox of shape in pattern space for stamping */ area = fz_transformrect(invptm, area); x0 = floorf(area.x0 / pat->xstep); y0 = floorf(area.y0 / pat->ystep); x1 = ceilf(area.x1 / pat->xstep); y1 = ceilf(area.y1 / pat->ystep); oldtopctm = csi->topctm; oldtop = csi->gtop; #ifdef TILE if ((x1 - x0) * (y1 - y0) > 0) { csi->dev->begintile(csi->dev->user, area, pat->bbox, pat->xstep, pat->ystep, ptm); gstate->ctm = ptm; csi->topctm = gstate->ctm; error = pdf_runcsibuffer(csi, pat->resources, pat->contents); if (error) fz_catch(error, "cannot render pattern tile"); while (oldtop < csi->gtop) pdf_grestore(csi); csi->dev->endtile(csi->dev->user); } #else { int x, y; for (y = y0; y < y1; y++) { for (x = x0; x < x1; x++) { gstate->ctm = fz_concat(fz_translate(x * pat->xstep, y * pat->ystep), ptm); csi->topctm = gstate->ctm; error = pdf_runcsibuffer(csi, pat->resources, pat->contents); while (oldtop < csi->gtop) pdf_grestore(csi); if (error) { fz_catch(error, "cannot render pattern tile"); goto cleanup; } } } } cleanup: #endif csi->topctm = oldtopctm; pdf_grestore(csi); } static void pdf_begingroup(pdf_csi *csi, fz_rect bbox) { pdf_gstate *gstate = csi->gstate + csi->gtop; fz_error error; if (gstate->softmask) { pdf_xobject *softmask = gstate->softmask; fz_rect bbox = fz_transformrect(gstate->ctm, softmask->bbox); gstate->softmask = nil; csi->dev->beginmask(csi->dev->user, bbox, gstate->luminosity, softmask->colorspace, gstate->softmaskbc); error = pdf_runxobject(csi, nil, softmask, fz_identity); if (error) fz_catch(error, "cannot run softmask"); csi->dev->endmask(csi->dev->user); gstate->softmask = softmask; } if (gstate->blendmode != FZ_BNORMAL) csi->dev->begingroup(csi->dev->user, bbox, 0, 0, gstate->blendmode, 1); } static void pdf_endgroup(pdf_csi *csi) { pdf_gstate *gstate = csi->gstate + csi->gtop; if (gstate->blendmode != FZ_BNORMAL) csi->dev->endgroup(csi->dev->user); if (gstate->softmask) csi->dev->popclip(csi->dev->user); } void pdf_showshade(pdf_csi *csi, fz_shade *shd) { pdf_gstate *gstate = csi->gstate + csi->gtop; fz_rect bbox; bbox = fz_boundshade(shd, gstate->ctm); pdf_begingroup(csi, bbox); csi->dev->fillshade(csi->dev->user, shd, gstate->ctm, gstate->fill.alpha); pdf_endgroup(csi); } void pdf_showimage(pdf_csi *csi, fz_pixmap *image) { pdf_gstate *gstate = csi->gstate + csi->gtop; fz_rect bbox; bbox = fz_transformrect(gstate->ctm, fz_unitrect); if (image->mask) { /* apply blend group even though we skip the softmask */ if (gstate->blendmode != FZ_BNORMAL) csi->dev->begingroup(csi->dev->user, bbox, 0, 0, gstate->blendmode, 1); csi->dev->clipimagemask(csi->dev->user, image->mask, gstate->ctm); } else pdf_begingroup(csi, bbox); if (!image->colorspace) { switch (gstate->fill.kind) { case PDF_MNONE: break; case PDF_MCOLOR: csi->dev->fillimagemask(csi->dev->user, image, gstate->ctm, gstate->fill.colorspace, gstate->fill.v, gstate->fill.alpha); break; case PDF_MPATTERN: if (gstate->fill.pattern) { csi->dev->clipimagemask(csi->dev->user, image, gstate->ctm); pdf_showpattern(csi, gstate->fill.pattern, bbox, PDF_MFILL); csi->dev->popclip(csi->dev->user); } break; case PDF_MSHADE: if (gstate->fill.shade) { csi->dev->clipimagemask(csi->dev->user, image, gstate->ctm); csi->dev->fillshade(csi->dev->user, gstate->fill.shade, gstate->ctm, gstate->fill.alpha); csi->dev->popclip(csi->dev->user); } break; } } else { csi->dev->fillimage(csi->dev->user, image, gstate->ctm, gstate->fill.alpha); } if (image->mask) { csi->dev->popclip(csi->dev->user); if (gstate->blendmode != FZ_BNORMAL) csi->dev->endgroup(csi->dev->user); } else pdf_endgroup(csi); } void pdf_showpath(pdf_csi *csi, int doclose, int dofill, int dostroke, int evenodd) { pdf_gstate *gstate = csi->gstate + csi->gtop; fz_path *path; fz_rect bbox; path = csi->path; csi->path = fz_newpath(); if (doclose) fz_closepath(path); if (dostroke) bbox = fz_boundpath(path, &gstate->strokestate, gstate->ctm); else bbox = fz_boundpath(path, nil, gstate->ctm); if (csi->clip) { gstate->clipdepth++; csi->dev->clippath(csi->dev->user, path, evenodd, gstate->ctm); csi->clip = 0; } pdf_begingroup(csi, bbox); if (dofill) { switch (gstate->fill.kind) { case PDF_MNONE: break; case PDF_MCOLOR: csi->dev->fillpath(csi->dev->user, path, evenodd, gstate->ctm, gstate->fill.colorspace, gstate->fill.v, gstate->fill.alpha); break; case PDF_MPATTERN: if (gstate->fill.pattern) { csi->dev->clippath(csi->dev->user, path, evenodd, gstate->ctm); pdf_showpattern(csi, gstate->fill.pattern, bbox, PDF_MFILL); csi->dev->popclip(csi->dev->user); } break; case PDF_MSHADE: if (gstate->fill.shade) { csi->dev->clippath(csi->dev->user, path, evenodd, gstate->ctm); csi->dev->fillshade(csi->dev->user, gstate->fill.shade, csi->topctm, gstate->fill.alpha); csi->dev->popclip(csi->dev->user); } break; } } if (dostroke) { switch (gstate->stroke.kind) { case PDF_MNONE: break; case PDF_MCOLOR: csi->dev->strokepath(csi->dev->user, path, &gstate->strokestate, gstate->ctm, gstate->stroke.colorspace, gstate->stroke.v, gstate->stroke.alpha); break; case PDF_MPATTERN: if (gstate->stroke.pattern) { csi->dev->clipstrokepath(csi->dev->user, path, &gstate->strokestate, gstate->ctm); pdf_showpattern(csi, gstate->stroke.pattern, bbox, PDF_MFILL); csi->dev->popclip(csi->dev->user); } break; case PDF_MSHADE: if (gstate->stroke.shade) { csi->dev->clipstrokepath(csi->dev->user, path, &gstate->strokestate, gstate->ctm); csi->dev->fillshade(csi->dev->user, gstate->stroke.shade, csi->topctm, gstate->stroke.alpha); csi->dev->popclip(csi->dev->user); } break; } } pdf_endgroup(csi); fz_freepath(path); } void pdf_flushtext(pdf_csi *csi) { pdf_gstate *gstate = csi->gstate + csi->gtop; fz_text *text; int dofill = 0; int dostroke = 0; int doclip = 0; int doinvisible = 0; fz_rect bbox; if (!csi->text) return; text = csi->text; csi->text = nil; dofill = dostroke = doclip = doinvisible = 0; switch (csi->textmode) { case 0: dofill = 1; break; case 1: dostroke = 1; break; case 2: dofill = dostroke = 1; break; case 3: doinvisible = 1; break; case 4: dofill = doclip = 1; break; case 5: dostroke = doclip = 1; break; case 6: dofill = dostroke = doclip = 1; break; case 7: doclip = 1; break; } bbox = fz_boundtext(text, gstate->ctm); pdf_begingroup(csi, bbox); if (doinvisible) csi->dev->ignoretext(csi->dev->user, text, gstate->ctm); if (doclip) { if (csi->accumulate < 2) gstate->clipdepth++; csi->dev->cliptext(csi->dev->user, text, gstate->ctm, csi->accumulate); csi->accumulate = 2; } if (dofill) { switch (gstate->fill.kind) { case PDF_MNONE: break; case PDF_MCOLOR: csi->dev->filltext(csi->dev->user, text, gstate->ctm, gstate->fill.colorspace, gstate->fill.v, gstate->fill.alpha); break; case PDF_MPATTERN: if (gstate->fill.pattern) { csi->dev->cliptext(csi->dev->user, text, gstate->ctm, 0); pdf_showpattern(csi, gstate->fill.pattern, bbox, PDF_MFILL); csi->dev->popclip(csi->dev->user); } break; case PDF_MSHADE: if (gstate->fill.shade) { csi->dev->cliptext(csi->dev->user, text, gstate->ctm, 0); csi->dev->fillshade(csi->dev->user, gstate->fill.shade, csi->topctm, gstate->fill.alpha); csi->dev->popclip(csi->dev->user); } break; } } if (dostroke) { switch (gstate->stroke.kind) { case PDF_MNONE: break; case PDF_MCOLOR: csi->dev->stroketext(csi->dev->user, text, &gstate->strokestate, gstate->ctm, gstate->stroke.colorspace, gstate->stroke.v, gstate->stroke.alpha); break; case PDF_MPATTERN: if (gstate->stroke.pattern) { csi->dev->clipstroketext(csi->dev->user, text, &gstate->strokestate, gstate->ctm); pdf_showpattern(csi, gstate->stroke.pattern, bbox, PDF_MFILL); csi->dev->popclip(csi->dev->user); } break; case PDF_MSHADE: if (gstate->stroke.shade) { csi->dev->clipstroketext(csi->dev->user, text, &gstate->strokestate, gstate->ctm); csi->dev->fillshade(csi->dev->user, gstate->stroke.shade, csi->topctm, gstate->stroke.alpha); csi->dev->popclip(csi->dev->user); } break; } } pdf_endgroup(csi); fz_freetext(text); } static void pdf_showglyph(pdf_csi *csi, int cid) { pdf_gstate *gstate = csi->gstate + csi->gtop; pdf_fontdesc *fontdesc = gstate->font; fz_matrix tsm, trm; float w0, w1, tx, ty; pdf_hmtx h; pdf_vmtx v; int gid; int ucsbuf[8]; int ucslen; int i; tsm.a = gstate->size * gstate->scale; tsm.b = 0; tsm.c = 0; tsm.d = gstate->size; tsm.e = 0; tsm.f = gstate->rise; ucslen = 0; if (fontdesc->tounicode) ucslen = pdf_lookupcmapfull(fontdesc->tounicode, cid, ucsbuf); if (ucslen == 0 && cid < fontdesc->ncidtoucs) { ucsbuf[0] = fontdesc->cidtoucs[cid]; ucslen = 1; } if (ucslen == 0 || (ucslen == 1 && ucsbuf[0] == 0)) { ucsbuf[0] = '?'; ucslen = 1; } gid = pdf_fontcidtogid(fontdesc, cid); if (fontdesc->wmode == 1) { v = pdf_getvmtx(fontdesc, cid); tsm.e -= v.x * gstate->size * 0.001f; tsm.f -= v.y * gstate->size * 0.001f; } trm = fz_concat(tsm, csi->tm); /* flush buffered text if face or matrix or rendermode has changed */ if (!csi->text || fontdesc->font != csi->text->font || fontdesc->wmode != csi->text->wmode || fabsf(trm.a - csi->text->trm.a) > FLT_EPSILON || fabsf(trm.b - csi->text->trm.b) > FLT_EPSILON || fabsf(trm.c - csi->text->trm.c) > FLT_EPSILON || fabsf(trm.d - csi->text->trm.d) > FLT_EPSILON || gstate->render != csi->textmode) { pdf_flushtext(csi); csi->text = fz_newtext(fontdesc->font, trm, fontdesc->wmode); csi->text->trm.e = 0; csi->text->trm.f = 0; csi->textmode = gstate->render; } /* add glyph to textobject */ fz_addtext(csi->text, gid, ucsbuf[0], trm.e, trm.f); /* add filler glyphs for one-to-many unicode mapping */ for (i = 1; i < ucslen; i++) fz_addtext(csi->text, -1, ucsbuf[i], trm.e, trm.f); if (fontdesc->wmode == 0) { h = pdf_gethmtx(fontdesc, cid); w0 = h.w * 0.001f; tx = (w0 * gstate->size + gstate->charspace) * gstate->scale; csi->tm = fz_concat(fz_translate(tx, 0), csi->tm); } if (fontdesc->wmode == 1) { w1 = v.w * 0.001f; ty = w1 * gstate->size + gstate->charspace; csi->tm = fz_concat(fz_translate(0, ty), csi->tm); } } void pdf_showspace(pdf_csi *csi, float tadj) { pdf_gstate *gstate = csi->gstate + csi->gtop; pdf_fontdesc *fontdesc = gstate->font; if (!fontdesc) { fz_warn("cannot draw text since font and size not set"); return; } if (fontdesc->wmode == 0) csi->tm = fz_concat(fz_translate(tadj * gstate->scale, 0), csi->tm); else csi->tm = fz_concat(fz_translate(0, tadj), csi->tm); } void pdf_showstring(pdf_csi *csi, unsigned char *buf, int len) { pdf_gstate *gstate = csi->gstate + csi->gtop; pdf_fontdesc *fontdesc = gstate->font; unsigned char *end = buf + len; int cpt, cid; if (!fontdesc) { fz_warn("cannot draw text since font and size not set"); return; } while (buf < end) { buf = pdf_decodecmap(fontdesc->encoding, buf, &cpt); cid = pdf_lookupcmap(fontdesc->encoding, cpt); if (cid >= 0) pdf_showglyph(csi, cid); else fz_warn("cannot encode character with code point %#x", cpt); if (cpt == 32) pdf_showspace(csi, gstate->wordspace); } } void pdf_showtext(pdf_csi *csi, fz_obj *text) { pdf_gstate *gstate = csi->gstate + csi->gtop; int i; if (fz_isarray(text)) { for (i = 0; i < fz_arraylen(text); i++) { fz_obj *item = fz_arrayget(text, i); if (fz_isstring(item)) pdf_showstring(csi, (unsigned char *)fz_tostrbuf(item), fz_tostrlen(item)); else pdf_showspace(csi, - fz_toreal(item) * gstate->size * 0.001f); } } else if (fz_isstring(text)) { pdf_showstring(csi, (unsigned char *)fz_tostrbuf(text), fz_tostrlen(text)); } }