Diffstat (limited to 'noncore/unsupported/qpdf/xpdf/Gfx.cc') (more/less context) (ignore whitespace changes)
-rw-r--r-- | noncore/unsupported/qpdf/xpdf/Gfx.cc | 589 |
1 files changed, 444 insertions, 145 deletions
diff --git a/noncore/unsupported/qpdf/xpdf/Gfx.cc b/noncore/unsupported/qpdf/xpdf/Gfx.cc index c19971c..17d613e 100644 --- a/noncore/unsupported/qpdf/xpdf/Gfx.cc +++ b/noncore/unsupported/qpdf/xpdf/Gfx.cc @@ -1,66 +1,77 @@ //======================================================================== // // Gfx.cc // -// Copyright 1996 Derek B. Noonburg +// Copyright 1996-2002 Glyph & Cog, LLC // //======================================================================== #ifdef __GNUC__ #pragma implementation #endif #include <aconf.h> #include <stdio.h> #include <stddef.h> #include <string.h> #include <math.h> #include "gmem.h" #include "CharTypes.h" #include "Object.h" #include "Array.h" #include "Dict.h" #include "Stream.h" #include "Lexer.h" #include "Parser.h" #include "GfxFont.h" #include "GfxState.h" #include "OutputDev.h" #include "Page.h" #include "Error.h" #include "Gfx.h" +// the MSVC math.h doesn't define this +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + //------------------------------------------------------------------------ // constants //------------------------------------------------------------------------ // Max number of splits along the t axis for an axial shading fill. #define axialMaxSplits 256 // Max delta allowed in any color component for an axial shading fill. #define axialColorDelta (1 / 256.0) +// Max number of splits along the t axis for a radial shading fill. +#define radialMaxSplits 256 + +// Max delta allowed in any color component for a radial shading fill. +#define radialColorDelta (1 / 256.0) + //------------------------------------------------------------------------ // Operator table //------------------------------------------------------------------------ Operator Gfx::opTab[] = { {"\"", 3, {tchkNum, tchkNum, tchkString}, &Gfx::opMoveSetShowText}, {"'", 1, {tchkString}, &Gfx::opMoveShowText}, {"B", 0, {tchkNone}, &Gfx::opFillStroke}, {"B*", 0, {tchkNone}, &Gfx::opEOFillStroke}, {"BDC", 2, {tchkName, tchkProps}, &Gfx::opBeginMarkedContent}, {"BI", 0, {tchkNone}, &Gfx::opBeginImage}, {"BMC", 1, {tchkName}, &Gfx::opBeginMarkedContent}, {"BT", 0, {tchkNone}, &Gfx::opBeginText}, {"BX", 0, {tchkNone}, &Gfx::opBeginIgnoreUndef}, {"CS", 1, {tchkName}, @@ -353,194 +364,228 @@ GBool GfxResources::lookupGState(char *name, Object *obj) { GfxResources *resPtr; for (resPtr = this; resPtr; resPtr = resPtr->next) { if (resPtr->gStateDict.isDict()) { if (!resPtr->gStateDict.dictLookup(name, obj)->isNull()) { return gTrue; } obj->free(); } } error(-1, "ExtGState '%s' is unknown", name); return gFalse; } //------------------------------------------------------------------------ // Gfx //------------------------------------------------------------------------ Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict, fouble dpi, PDFRectangle *box, GBool crop, PDFRectangle *cropBox, int rotate, GBool printCommandsA) { int i; xref = xrefA; + subPage = gFalse; printCommands = printCommandsA; // start the resource stack res = new GfxResources(xref, resDict, NULL); // initialize out = outA; state = new GfxState(dpi, box, rotate, out->upsideDown()); fontChanged = gFalse; clip = clipNone; ignoreUndef = 0; out->startPage(pageNum, state); out->setDefaultCTM(state->getCTM()); out->updateAll(state); for (i = 0; i < 6; ++i) { baseMatrix[i] = state->getCTM()[i]; } // set crop box if (crop) { state->moveTo(cropBox->x1, cropBox->y1); state->lineTo(cropBox->x2, cropBox->y1); state->lineTo(cropBox->x2, cropBox->y2); state->lineTo(cropBox->x1, cropBox->y2); state->closePath(); state->clip(); out->clip(state); state->clearPath(); } } -Gfx::~Gfx() { - GfxResources *resPtr; +Gfx::Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict, + PDFRectangle *box, GBool crop, PDFRectangle *cropBox) { + int i; + xref = xrefA; + subPage = gTrue; + printCommands = gFalse; + + // start the resource stack + res = new GfxResources(xref, resDict, NULL); + + // initialize + out = outA; + state = new GfxState(72, box, 0, gFalse); + fontChanged = gFalse; + clip = clipNone; + ignoreUndef = 0; + for (i = 0; i < 6; ++i) { + baseMatrix[i] = state->getCTM()[i]; + } + + // set crop box + if (crop) { + state->moveTo(cropBox->x1, cropBox->y1); + state->lineTo(cropBox->x2, cropBox->y1); + state->lineTo(cropBox->x2, cropBox->y2); + state->lineTo(cropBox->x1, cropBox->y2); + state->closePath(); + state->clip(); + out->clip(state); + state->clearPath(); + } +} + +Gfx::~Gfx() { while (state->hasSaves()) { state = state->restore(); out->restoreState(state); } - out->endPage(); + if (!subPage) { + out->endPage(); + } while (res) { - resPtr = res->getNext(); - delete res; - res = resPtr; + popResources(); } - if (state) + if (state) { delete state; + } } void Gfx::display(Object *obj, GBool topLevel) { Object obj2; int i; if (obj->isArray()) { for (i = 0; i < obj->arrayGetLength(); ++i) { obj->arrayGet(i, &obj2); if (!obj2.isStream()) { error(-1, "Weird page contents"); obj2.free(); return; } obj2.free(); } } else if (!obj->isStream()) { error(-1, "Weird page contents"); return; } parser = new Parser(xref, new Lexer(xref, obj)); go(topLevel); delete parser; parser = NULL; } void Gfx::go(GBool topLevel) { Object obj; Object args[maxArgs]; - int numCmds, numArgs; + int numArgs; int i; // scan a sequence of objects - numCmds = 0; + updateLevel = 0; numArgs = 0; parser->getObj(&obj); while (!obj.isEOF()) { // got a command - execute it if (obj.isCmd()) { if (printCommands) { obj.print(stdout); for (i = 0; i < numArgs; ++i) { printf(" "); args[i].print(stdout); } printf("\n"); fflush(stdout); } execOp(&obj, args, numArgs); obj.free(); for (i = 0; i < numArgs; ++i) args[i].free(); numArgs = 0; // periodically update display - if (++numCmds == 200) { + if (++updateLevel >= 20000) { out->dump(); - numCmds = 0; + updateLevel = 0; } // got an argument - save it } else if (numArgs < maxArgs) { args[numArgs++] = obj; // too many arguments - something is wrong } else { error(getPos(), "Too many args in content stream"); if (printCommands) { printf("throwing away arg: "); obj.print(stdout); printf("\n"); fflush(stdout); } obj.free(); } // grab the next object parser->getObj(&obj); } obj.free(); // args at end with no command if (numArgs > 0) { error(getPos(), "Leftover args in content stream"); if (printCommands) { printf("%d leftovers:", numArgs); for (i = 0; i < numArgs; ++i) { printf(" "); args[i].print(stdout); } printf("\n"); fflush(stdout); } for (i = 0; i < numArgs; ++i) args[i].free(); } // update display - if (topLevel && numCmds > 0) { + if (topLevel && updateLevel > 0) { out->dump(); } } void Gfx::execOp(Object *cmd, Object args[], int numArgs) { Operator *op; char *name; int i; // find operator name = cmd->getName(); if (!(op = findOp(name))) { if (ignoreUndef == 0) error(getPos(), "Unknown operator '%s'", name); return; } // type check args if (op->numArgs >= 0) { if (numArgs != op->numArgs) { error(getPos(), "Wrong number (%d) of args to '%s' operator", numArgs, name); return; } @@ -1117,49 +1162,49 @@ void Gfx::opCloseEOFillStroke(Object args[], int numArgs) { //error(getPos(), "No path in closepath/eofill/stroke"); return; } if (state->isPath()) { state->closePath(); if (state->getFillColorSpace()->getMode() == csPattern) { doPatternFill(gTrue); } else { out->eoFill(state); } out->stroke(state); } doEndPath(); } void Gfx::doPatternFill(GBool eoFill) { GfxPatternColorSpace *patCS; GfxPattern *pattern; GfxTilingPattern *tPat; GfxColorSpace *cs; fouble xMin, yMin, xMax, yMax, x, y, x1, y1; fouble cxMin, cyMin, cxMax, cyMax; int xi0, yi0, xi1, yi1, xi, yi; fouble *ctm, *btm, *ptm; - fouble m[6], ictm[6], m1[6], im[6], imb[6]; + fouble m[6], ictm[6], m1[6], imb[6]; fouble det; fouble xstep, ystep; int i; // this is a bit of a kludge -- patterns can be really slow, so we // skip them if we're only doing text extraction, since they almost // certainly don't contain any text if (!out->needNonText()) { return; } // get color space patCS = (GfxPatternColorSpace *)state->getFillColorSpace(); // get pattern if (!(pattern = state->getFillPattern())) { return; } if (pattern->getType() != 1) { return; } tPat = (GfxTilingPattern *)pattern; // construct a (pattern space) -> (current space) transform matrix @@ -1168,57 +1213,48 @@ void Gfx::doPatternFill(GBool eoFill) { ptm = tPat->getMatrix(); // iCTM = invert CTM det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); ictm[0] = ctm[3] * det; ictm[1] = -ctm[1] * det; ictm[2] = -ctm[2] * det; ictm[3] = ctm[0] * det; ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; // m1 = PTM * BTM = PTM * base transform matrix m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2]; m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3]; m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2]; m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3]; m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4]; m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5]; // m = m1 * iCTM = (PTM * BTM) * (iCTM) m[0] = m1[0] * ictm[0] + m1[1] * ictm[2]; m[1] = m1[0] * ictm[1] + m1[1] * ictm[3]; m[2] = m1[2] * ictm[0] + m1[3] * ictm[2]; m[3] = m1[2] * ictm[1] + m1[3] * ictm[3]; m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4]; m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5]; - // construct a (current space) -> (pattern space) transform matrix - det = 1 / (m[0] * m[3] - m[1] * m[2]); - im[0] = m[3] * det; - im[1] = -m[1] * det; - im[2] = -m[2] * det; - im[3] = m[0] * det; - im[4] = (m[2] * m[5] - m[3] * m[4]) * det; - im[5] = (m[1] * m[4] - m[0] * m[5]) * det; - // construct a (base space) -> (pattern space) transform matrix det = 1 / (m1[0] * m1[3] - m1[1] * m1[2]); imb[0] = m1[3] * det; imb[1] = -m1[1] * det; imb[2] = -m1[2] * det; imb[3] = m1[0] * det; imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det; imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det; // save current graphics state out->saveState(state); state = state->save(); // set underlying color space (for uncolored tiling patterns) if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) { state->setFillColorSpace(cs->copy()); } else { state->setFillColorSpace(new GfxDeviceGrayColorSpace()); } state->setFillPattern(NULL); out->updateFillColor(state); // clip to current path state->clip(); @@ -1310,122 +1346,76 @@ void Gfx::opShFill(Object args[], int numArgs) { out->saveState(state); state = state->save(); // clip to bbox if (shading->getHasBBox()) { shading->getBBox(&xMin, &yMin, &xMax, &yMax); state->moveTo(xMin, yMin); state->lineTo(xMax, yMin); state->lineTo(xMax, yMax); state->lineTo(xMin, yMax); state->closePath(); state->clip(); out->clip(state); state->clearPath(); } // set the color space state->setFillColorSpace(shading->getColorSpace()->copy()); // do shading type-specific operations switch (shading->getType()) { case 2: doAxialShFill((GfxAxialShading *)shading); break; + case 3: + doRadialShFill((GfxRadialShading *)shading); + break; } // restore graphics state state = state->restore(); out->restoreState(state); delete shading; } void Gfx::doAxialShFill(GfxAxialShading *shading) { fouble xMin, yMin, xMax, yMax; fouble x0, y0, x1, y1; - fouble det; - fouble *ctm; - fouble ictm[6]; fouble dx, dy, mul; fouble tMin, tMax, t, tx, ty; fouble s[4], sMin, sMax, tmp; fouble ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1; fouble t0, t1, tt; fouble ta[axialMaxSplits + 1]; int next[axialMaxSplits + 1]; GfxColor color0, color1; int nComps; int i, j, k, kk; - // get clip region bbox and transform to current user space - state->getClipBBox(&x0, &y0, &x1, &y1); - ctm = state->getCTM(); - det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); - ictm[0] = ctm[3] * det; - ictm[1] = -ctm[1] * det; - ictm[2] = -ctm[2] * det; - ictm[3] = ctm[0] * det; - ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; - ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; - xMin = xMax = x0 * ictm[0] + y0 * ictm[2] + ictm[4]; - yMin = yMax = x0 * ictm[1] + y0 * ictm[3] + ictm[5]; - tx = x0 * ictm[0] + y1 * ictm[2] + ictm[4]; - ty = x0 * ictm[1] + y1 * ictm[3] + ictm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - tx = x1 * ictm[0] + y0 * ictm[2] + ictm[4]; - ty = x1 * ictm[1] + y0 * ictm[3] + ictm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - tx = x1 * ictm[0] + y1 * ictm[2] + ictm[4]; - ty = x1 * ictm[1] + y1 * ictm[3] + ictm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } + // get the clip region bbox + state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); // compute min and max t values, based on the four corners of the // clip region bbox shading->getCoords(&x0, &y0, &x1, &y1); dx = x1 - x0; dy = y1 - y0; mul = 1 / (dx * dx + dy * dy); tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul; t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul; if (t < tMin) { tMin = t; } else if (t > tMax) { tMax = t; } t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul; if (t < tMin) { tMin = t; } else if (t > tMax) { tMax = t; } t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul; if (t < tMin) { tMin = t; } else if (t > tMax) { @@ -1598,48 +1588,244 @@ void Gfx::doAxialShFill(GfxAxialShading *shading) { // set the color state->setFillColor(&color0); out->updateFillColor(state); // fill the region state->moveTo(ux0, uy0); state->lineTo(vx0, vy0); state->lineTo(vx1, vy1); state->lineTo(ux1, uy1); state->closePath(); out->fill(state); state->clearPath(); // set up for next region ux0 = ux1; uy0 = uy1; vx0 = vx1; vy0 = vy1; color0 = color1; i = next[i]; } } +void Gfx::doRadialShFill(GfxRadialShading *shading) { + fouble sMin, sMax, xMin, yMin, xMax, yMax; + fouble x0, y0, r0, x1, y1, r1, t0, t1; + int nComps; + GfxColor colorA, colorB; + fouble xa, ya, xb, yb, ra, rb; + fouble ta, tb, sa, sb; + int ia, ib, k, n; + fouble *ctm; + fouble angle, t; + + // get the shading info + shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1); + t0 = shading->getDomain0(); + t1 = shading->getDomain1(); + nComps = shading->getColorSpace()->getNComps(); + + // compute the (possibly extended) s range + sMin = 0; + sMax = 1; + if (shading->getExtend0()) { + if (r0 < r1) { + // extend the smaller end + sMin = -r0 / (r1 - r0); + } else { + // extend the larger end + //~ this computes the diagonal of the bounding box -- we should + //~ really compute the intersection of the moving/expanding + //~ circles with each of the four corners and look for the max + //~ radius + state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); + sMin = (sqrt((xMax - xMin) * (xMax - xMin) + + (yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0); + if (sMin > 0) { + sMin = 0; + } else if (sMin < -20) { + // sanity check + sMin = -20; + } + } + } + if (shading->getExtend1()) { + if (r1 < r0) { + // extend the smaller end + sMax = -r0 / (r1 - r0); + } else if (r1 > r0) { + // extend the larger end + state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); + sMax = (sqrt((xMax - xMin) * (xMax - xMin) + + (yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0); + if (sMax < 1) { + sMin = 1; + } else if (sMax > 20) { + // sanity check + sMax = 20; + } + } + } + + // compute the number of steps into which circles must be divided to + // achieve a curve flatness of 0.1 pixel in device space for the + // largest circle (note that "device space" is 72 dpi when generating + // PostScript, hence the relatively small 0.1 pixel accuracy) + ctm = state->getCTM(); + t = fabs(ctm[0]); + if (fabs(ctm[1]) > t) { + t = fabs(ctm[1]); + } + if (fabs(ctm[2]) > t) { + t = fabs(ctm[2]); + } + if (fabs(ctm[3]) > t) { + t = fabs(ctm[3]); + } + if (r0 > r1) { + t *= r0; + } else { + t *= r1; + } + if (t < 1) { + n = 3; + } else { + n = (int)(M_PI / acos(1 - 0.1 / t)); + if (n < 3) { + n = 3; + } else if (n > 200) { + n = 200; + } + } + + // Traverse the t axis and do the shading. + // + // This generates and fills a series of rings. Each ring is defined + // by two circles: + // sa, ta, xa, ya, ra, colorA + // sb, tb, xb, yb, rb, colorB + // + // The s/t axis is divided into radialMaxSplits parts; these parts + // are combined as much as possible while respecting the + // radialColorDelta parameter. + + // setup for the start circle + ia = 0; + sa = sMin; + ta = t0 + sa * (t1 - t0); + xa = x0 + sa * (x1 - x0); + ya = y0 + sa * (y1 - y0); + ra = r0 + sa * (r1 - r0); + if (ta < t0) { + shading->getColor(t0, &colorA); + } else if (ta > t1) { + shading->getColor(t1, &colorA); + } else { + shading->getColor(ta, &colorA); + } + + while (ia < radialMaxSplits) { + + // go as far along the t axis (toward t1) as we can, such that the + // color difference is within the tolerance (radialColorDelta) -- + // this uses bisection (between the current value, t, and t1), + // limited to radialMaxSplits points along the t axis + ib = radialMaxSplits; + sb = sMin + ((fouble)ib / (fouble)radialMaxSplits) * (sMax - sMin); + tb = t0 + sb * (t1 - t0); + if (tb < t0) { + shading->getColor(t0, &colorB); + } else if (tb > t1) { + shading->getColor(t1, &colorB); + } else { + shading->getColor(tb, &colorB); + } + while (ib - ia > 1) { + for (k = 0; k < nComps; ++k) { + if (fabs(colorB.c[k] - colorA.c[k]) > radialColorDelta) { + break; + } + } + if (k == nComps) { + break; + } + ib = (ia + ib) / 2; + sb = sMin + ((fouble)ib / (fouble)radialMaxSplits) * (sMax - sMin); + tb = t0 + sb * (t1 - t0); + if (tb < t0) { + shading->getColor(t0, &colorB); + } else if (tb > t1) { + shading->getColor(t1, &colorB); + } else { + shading->getColor(tb, &colorB); + } + } + + // compute center and radius of the circle + xb = x0 + sb * (x1 - x0); + yb = y0 + sb * (y1 - y0); + rb = r0 + sb * (r1 - r0); + + // use the average of the colors at the two circles + for (k = 0; k < nComps; ++k) { + colorA.c[k] = 0.5 * (colorA.c[k] + colorB.c[k]); + } + state->setFillColor(&colorA); + out->updateFillColor(state); + + // construct path for first circle + state->moveTo(xa + ra, ya); + for (k = 1; k < n; ++k) { + angle = ((fouble)k / (fouble)n) * 2 * M_PI; + state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle)); + } + state->closePath(); + + // construct and append path for second circle + state->moveTo(xb + rb, yb); + for (k = 1; k < n; ++k) { + angle = ((fouble)k / (fouble)n) * 2 * M_PI; + state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle)); + } + state->closePath(); + + // fill the ring + out->eoFill(state); + state->clearPath(); + + // step to the next value of t + ia = ib; + sa = sb; + ta = tb; + xa = xb; + ya = yb; + ra = rb; + colorA = colorB; + } +} + void Gfx::doEndPath() { if (state->isPath() && clip != clipNone) { state->clip(); if (clip == clipNormal) { out->clip(state); } else { out->eoClip(state); } } clip = clipNone; state->clearPath(); } //------------------------------------------------------------------------ // path clipping operators //------------------------------------------------------------------------ void Gfx::opClip(Object args[], int numArgs) { clip = clipNormal; } void Gfx::opEOClip(Object args[], int numArgs) { clip = clipEO; } @@ -1779,241 +1965,282 @@ void Gfx::opMoveShowText(Object args[], int numArgs) { doShowText(args[0].getString()); } void Gfx::opMoveSetShowText(Object args[], int numArgs) { fouble tx, ty; if (!state->getFont()) { error(getPos(), "No font in move/set/show"); return; } state->setWordSpace(args[0].getNum()); state->setCharSpace(args[1].getNum()); tx = state->getLineX(); ty = state->getLineY() - state->getLeading(); state->textMoveTo(tx, ty); out->updateWordSpace(state); out->updateCharSpace(state); out->updateTextPos(state); doShowText(args[2].getString()); } void Gfx::opShowSpaceText(Object args[], int numArgs) { Array *a; Object obj; + int wMode; int i; if (!state->getFont()) { error(getPos(), "No font in show/space"); return; } + wMode = state->getFont()->getWMode(); a = args[0].getArray(); for (i = 0; i < a->getLength(); ++i) { a->get(i, &obj); if (obj.isNum()) { - state->textShift(-obj.getNum() * 0.001 * state->getFontSize()); + if (wMode) { + state->textShift(0, -obj.getNum() * 0.001 * state->getFontSize()); + } else { + state->textShift(-obj.getNum() * 0.001 * state->getFontSize(), 0); + } out->updateTextShift(state, obj.getNum()); } else if (obj.isString()) { doShowText(obj.getString()); } else { error(getPos(), "Element of show/space array must be number or string"); } obj.free(); } } void Gfx::doShowText(GString *s) { GfxFont *font; + int wMode; fouble riseX, riseY; CharCode code; Unicode u[8]; - fouble dx, dy, dx2, dy2, tdx, tdy; + fouble x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy; fouble originX, originY, tOriginX, tOriginY; + fouble oldCTM[6], newCTM[6]; + fouble *mat; + Object charProc; + Dict *resDict; + Parser *oldParser; char *p; - int len, n, uLen, nChars, nSpaces; + int len, n, uLen, nChars, nSpaces, i; if (fontChanged) { out->updateFont(state); fontChanged = gFalse; } font = state->getFont(); + wMode = font->getWMode(); -#if 0 //~type3 - fouble x, y; - fouble oldCTM[6], newCTM[6]; - fouble *mat; - Object charProc; - Parser *oldParser; - int i; - - //~ also check out->renderType3() - if (font->getType() == fontType3) { + if (out->useDrawChar()) { out->beginString(state, s); + } + + // handle a Type 3 char + if (font->getType() == fontType3 && out->interpretType3Chars()) { mat = state->getCTM(); for (i = 0; i < 6; ++i) { oldCTM[i] = mat[i]; } mat = state->getTextMat(); newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2]; newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3]; newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2]; newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3]; mat = font->getFontMatrix(); newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2]; newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3]; newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2]; newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3]; newCTM[0] *= state->getFontSize(); newCTM[3] *= state->getFontSize(); newCTM[0] *= state->getHorizScaling(); newCTM[2] *= state->getHorizScaling(); state->textTransformDelta(0, state->getRise(), &riseX, &riseY); + curX = state->getCurX(); + curY = state->getCurY(); oldParser = parser; p = s->getCString(); len = s->getLength(); while (len > 0) { n = font->getNextChar(p, len, &code, u, (int)(sizeof(u) / sizeof(Unicode)), &uLen, &dx, &dy, &originX, &originY); - state->transform(state->getCurX() + riseX, state->getCurY() + riseY, - &x, &y); - out->saveState(state); - state = state->save(); - state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y); - //~ out->updateCTM(???) - ((Gfx8BitFont *)font)->getCharProc(code, &charProc); - if (charProc.isStream()) { - display(&charProc, gFalse); - } else { - error(getPos(), "Missing or bad Type3 CharProc entry"); - } - state = state->restore(); - out->restoreState(state); - charProc.free(); dx = dx * state->getFontSize() + state->getCharSpace(); if (n == 1 && *p == ' ') { dx += state->getWordSpace(); } dx *= state->getHorizScaling(); dy *= state->getFontSize(); state->textTransformDelta(dx, dy, &tdx, &tdy); - state->shift(tdx, tdy); + state->transform(curX + riseX, curY + riseY, &x, &y); + out->saveState(state); + state = state->save(); + state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y); + //~ out->updateCTM(???) + if (!out->beginType3Char(state, code, u, uLen)) { + ((Gfx8BitFont *)font)->getCharProc(code, &charProc); + if ((resDict = ((Gfx8BitFont *)font)->getResources())) { + pushResources(resDict); + } + if (charProc.isStream()) { + display(&charProc, gFalse); + } else { + error(getPos(), "Missing or bad Type3 CharProc entry"); + } + out->endType3Char(state); + if (resDict) { + popResources(); + } + charProc.free(); + } + state = state->restore(); + out->restoreState(state); + // GfxState::restore() does *not* restore the current position, + // so we track it here with (curX, curY) + curX += tdx; + curY += tdy; + state->moveTo(curX, curY); p += n; len -= n; } parser = oldParser; - out->endString(state); - return; - } -#endif - if (out->useDrawChar()) { + } else if (out->useDrawChar()) { state->textTransformDelta(0, state->getRise(), &riseX, &riseY); - out->beginString(state, s); p = s->getCString(); len = s->getLength(); while (len > 0) { n = font->getNextChar(p, len, &code, u, (int)(sizeof(u) / sizeof(Unicode)), &uLen, &dx, &dy, &originX, &originY); - dx = dx * state->getFontSize() + state->getCharSpace(); - if (n == 1 && *p == ' ') { - dx += state->getWordSpace(); + if (wMode) { + dx *= state->getFontSize(); + dy = dy * state->getFontSize() + state->getCharSpace(); + if (n == 1 && *p == ' ') { + dy += state->getWordSpace(); + } + } else { + dx = dx * state->getFontSize() + state->getCharSpace(); + if (n == 1 && *p == ' ') { + dx += state->getWordSpace(); + } + dx *= state->getHorizScaling(); + dy *= state->getFontSize(); } - dx *= state->getHorizScaling(); - dy *= state->getFontSize(); state->textTransformDelta(dx, dy, &tdx, &tdy); originX *= state->getFontSize(); originY *= state->getFontSize(); state->textTransformDelta(originX, originY, &tOriginX, &tOriginY); out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY, tdx, tdy, tOriginX, tOriginY, code, u, uLen); state->shift(tdx, tdy); p += n; len -= n; } - out->endString(state); } else { dx = dy = 0; p = s->getCString(); len = s->getLength(); nChars = nSpaces = 0; while (len > 0) { n = font->getNextChar(p, len, &code, u, (int)(sizeof(u) / sizeof(Unicode)), &uLen, &dx2, &dy2, &originX, &originY); dx += dx2; dy += dy2; if (n == 1 && *p == ' ') { ++nSpaces; } ++nChars; p += n; len -= n; } - dx = dx * state->getFontSize() - + nChars * state->getCharSpace() - + nSpaces * state->getWordSpace(); - dx *= state->getHorizScaling(); - dy *= state->getFontSize(); + if (wMode) { + dx *= state->getFontSize(); + dy = dy * state->getFontSize() + + nChars * state->getCharSpace() + + nSpaces * state->getWordSpace(); + } else { + dx = dx * state->getFontSize() + + nChars * state->getCharSpace() + + nSpaces * state->getWordSpace(); + dx *= state->getHorizScaling(); + dy *= state->getFontSize(); + } state->textTransformDelta(dx, dy, &tdx, &tdy); out->drawString(state, s); state->shift(tdx, tdy); } + + if (out->useDrawChar()) { + out->endString(state); + } + + updateLevel += 10 * s->getLength(); } //------------------------------------------------------------------------ // XObject operators //------------------------------------------------------------------------ void Gfx::opXObject(Object args[], int numArgs) { - Object obj1, obj2, refObj; + Object obj1, obj2, obj3, refObj; #if OPI_SUPPORT Object opiDict; #endif if (!res->lookupXObject(args[0].getName(), &obj1)) { return; } if (!obj1.isStream()) { error(getPos(), "XObject '%s' is wrong type", args[0].getName()); obj1.free(); return; } #if OPI_SUPPORT obj1.streamGetDict()->lookup("OPI", &opiDict); if (opiDict.isDict()) { out->opiBegin(state, opiDict.getDict()); } #endif obj1.streamGetDict()->lookup("Subtype", &obj2); if (obj2.isName("Image")) { res->lookupXObjectNF(args[0].getName(), &refObj); doImage(&refObj, obj1.getStream(), gFalse); refObj.free(); } else if (obj2.isName("Form")) { doForm(&obj1); + } else if (obj2.isName("PS")) { + obj1.streamGetDict()->lookup("Level1", &obj3); + out->psXObject(obj1.getStream(), + obj3.isStream() ? obj3.getStream() : (Stream *)NULL); } else if (obj2.isName()) { error(getPos(), "Unknown XObject subtype '%s'", obj2.getName()); } else { error(getPos(), "XObject subtype is missing or wrong type"); } obj2.free(); #if OPI_SUPPORT if (opiDict.isDict()) { out->opiEnd(state, opiDict.getDict()); } opiDict.free(); #endif obj1.free(); } void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { Dict *dict; int width, height; int bits; GBool mask; GBool invert; GfxColorSpace *colorSpace; GfxImageColorMap *colorMap; Object maskObj; @@ -2127,48 +2354,53 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { delete colorMap; goto err1; } // get the mask haveMask = gFalse; dict->lookup("Mask", &maskObj); if (maskObj.isArray()) { for (i = 0; i < maskObj.arrayGetLength(); ++i) { maskObj.arrayGet(i, &obj1); maskColors[i] = obj1.getInt(); obj1.free(); } haveMask = gTrue; } // draw it out->drawImage(state, ref, str, width, height, colorMap, haveMask ? maskColors : (int *)NULL, inlineImg); delete colorMap; maskObj.free(); } + if ((i = width * height) > 1000) { + i = 1000; + } + updateLevel += i; + return; err2: obj1.free(); err1: error(getPos(), "Bad image parameters"); } void Gfx::doForm(Object *str) { Dict *dict; Object matrixObj, bboxObj; fouble m[6], bbox[6]; Object resObj; Dict *resDict; Object obj1; int i; // get stream dict dict = str->streamGetDict(); // check form type dict->lookup("FormType", &obj1); if (!(obj1.isInt() && obj1.getInt() == 1)) { error(getPos(), "Unknown form type"); @@ -2194,251 +2426,318 @@ void Gfx::doForm(Object *str) { dict->lookup("Matrix", &matrixObj); if (matrixObj.isArray()) { for (i = 0; i < 6; ++i) { matrixObj.arrayGet(i, &obj1); m[i] = obj1.getNum(); obj1.free(); } } else { m[0] = 1; m[1] = 0; m[2] = 0; m[3] = 1; m[4] = 0; m[5] = 0; } matrixObj.free(); // get resources dict->lookup("Resources", &resObj); resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL; // draw it doForm1(str, resDict, m, bbox); resObj.free(); } -void Gfx::doWidgetForm(Object *str, fouble xMin, fouble yMin, - fouble xMax, fouble yMax) { +void Gfx::doAnnot(Object *str, fouble xMin, fouble yMin, + fouble xMax, fouble yMax) { Dict *dict, *resDict; Object matrixObj, bboxObj, resObj; Object obj1; - fouble m[6], bbox[6]; - fouble sx, sy; + fouble m[6], bbox[6], ictm[6]; + fouble *ctm; + fouble formX0, formY0, formX1, formY1; + fouble annotX0, annotY0, annotX1, annotY1; + fouble det, x, y, sx, sy; int i; // get stream dict dict = str->streamGetDict(); - // get bounding box + // get the form bounding box dict->lookup("BBox", &bboxObj); if (!bboxObj.isArray()) { bboxObj.free(); error(getPos(), "Bad form bounding box"); return; } for (i = 0; i < 4; ++i) { bboxObj.arrayGet(i, &obj1); bbox[i] = obj1.getNum(); obj1.free(); } bboxObj.free(); - // get matrix + // get the form matrix dict->lookup("Matrix", &matrixObj); if (matrixObj.isArray()) { for (i = 0; i < 6; ++i) { matrixObj.arrayGet(i, &obj1); m[i] = obj1.getNum(); obj1.free(); } } else { m[0] = 1; m[1] = 0; m[2] = 0; m[3] = 1; m[4] = 0; m[5] = 0; } matrixObj.free(); - // scale form bbox to widget rectangle - sx = fabs((xMax - xMin) / (bbox[2] - bbox[0])); - sy = fabs((yMax - yMin) / (bbox[3] - bbox[1])); - m[0] *= sx; m[1] *= sy; - m[2] *= sx; m[3] *= sy; - m[4] *= sx; m[5] *= sy; + // transform the form bbox from form space to user space + formX0 = bbox[0] * m[0] + bbox[1] * m[2] + m[4]; + formY0 = bbox[0] * m[1] + bbox[1] * m[3] + m[5]; + formX1 = bbox[2] * m[0] + bbox[3] * m[2] + m[4]; + formY1 = bbox[2] * m[1] + bbox[3] * m[3] + m[5]; + + // transform the annotation bbox from default user space to user + // space: (bbox * baseMatrix) * iCTM + ctm = state->getCTM(); + det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); + ictm[0] = ctm[3] * det; + ictm[1] = -ctm[1] * det; + ictm[2] = -ctm[2] * det; + ictm[3] = ctm[0] * det; + ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; + ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; + x = baseMatrix[0] * xMin + baseMatrix[2] * yMin + baseMatrix[4]; + y = baseMatrix[1] * xMin + baseMatrix[3] * yMin + baseMatrix[5]; + annotX0 = ictm[0] * x + ictm[2] * y + ictm[4]; + annotY0 = ictm[1] * x + ictm[3] * y + ictm[5]; + x = baseMatrix[0] * xMax + baseMatrix[2] * yMax + baseMatrix[4]; + y = baseMatrix[1] * xMax + baseMatrix[3] * yMax + baseMatrix[5]; + annotX1 = ictm[0] * x + ictm[2] * y + ictm[4]; + annotY1 = ictm[1] * x + ictm[3] * y + ictm[5]; + + // swap min/max coords + if (formX0 > formX1) { + x = formX0; formX0 = formX1; formX1 = x; + } + if (formY0 > formY1) { + y = formY0; formY0 = formY1; formY1 = y; + } + if (annotX0 > annotX1) { + x = annotX0; annotX0 = annotX1; annotX1 = x; + } + if (annotY0 > annotY1) { + y = annotY0; annotY0 = annotY1; annotY1 = y; + } - // translate to widget rectangle - m[4] += xMin; - m[5] += yMin; + // scale the form to fit the annotation bbox + if (formX1 == formX0) { + // this shouldn't happen + sx = 1; + } else { + sx = (annotX1 - annotX0) / (formX1 - formX0); + } + if (formY1 == formY0) { + // this shouldn't happen + sy = 1; + } else { + sy = (annotY1 - annotY0) / (formY1 - formY0); + } + m[0] *= sx; + m[2] *= sx; + m[4] = (m[4] - formX0) * sx + annotX0; + m[1] *= sy; + m[3] *= sy; + m[5] = (m[5] - formY0) * sy + annotY0; // get resources dict->lookup("Resources", &resObj); resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL; // draw it doForm1(str, resDict, m, bbox); resObj.free(); bboxObj.free(); } void Gfx::doForm1(Object *str, Dict *resDict, fouble *matrix, fouble *bbox) { Parser *oldParser; fouble oldBaseMatrix[6]; - GfxResources *resPtr; int i; // push new resources on stack - res = new GfxResources(xref, resDict, res); + pushResources(resDict); // save current graphics state out->saveState(state); state = state->save(); // save current parser oldParser = parser; // set form transformation matrix state->concatCTM(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); out->updateCTM(state, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); // set new base matrix for (i = 0; i < 6; ++i) { oldBaseMatrix[i] = baseMatrix[i]; baseMatrix[i] = state->getCTM()[i]; } // set form bounding box state->moveTo(bbox[0], bbox[1]); state->lineTo(bbox[2], bbox[1]); state->lineTo(bbox[2], bbox[3]); state->lineTo(bbox[0], bbox[3]); state->closePath(); state->clip(); out->clip(state); state->clearPath(); // draw the form display(str, gFalse); // restore base matrix for (i = 0; i < 6; ++i) { baseMatrix[i] = oldBaseMatrix[i]; } // restore parser parser = oldParser; // restore graphics state state = state->restore(); out->restoreState(state); // pop resource stack + popResources(); + + return; +} + +void Gfx::pushResources(Dict *resDict) { + res = new GfxResources(xref, resDict, res); +} + +void Gfx::popResources() { + GfxResources *resPtr; + resPtr = res->getNext(); delete res; res = resPtr; - - return; } //------------------------------------------------------------------------ // in-line image operators //------------------------------------------------------------------------ void Gfx::opBeginImage(Object args[], int numArgs) { Stream *str; int c1, c2; // build dict/stream str = buildImageStream(); // display the image if (str) { doImage(NULL, str, gTrue); // skip 'EI' tag c1 = str->getBaseStream()->getChar(); c2 = str->getBaseStream()->getChar(); while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) { c1 = c2; c2 = str->getBaseStream()->getChar(); } delete str; } } Stream *Gfx::buildImageStream() { Object dict; Object obj; char *key; Stream *str; // build dictionary dict.initDict(xref); parser->getObj(&obj); while (!obj.isCmd("ID") && !obj.isEOF()) { if (!obj.isName()) { error(getPos(), "Inline image dictionary key must be a name object"); obj.free(); - parser->getObj(&obj); } else { key = copyString(obj.getName()); obj.free(); parser->getObj(&obj); - if (obj.isEOF() || obj.isError()) + if (obj.isEOF() || obj.isError()) { + gfree(key); break; + } dict.dictAdd(key, &obj); } parser->getObj(&obj); } - if (obj.isEOF()) + if (obj.isEOF()) { error(getPos(), "End of file in inline image"); + obj.free(); + dict.free(); + return NULL; + } obj.free(); // make stream str = new EmbedStream(parser->getStream(), &dict); str = str->addFilters(&dict); return str; } void Gfx::opImageData(Object args[], int numArgs) { error(getPos(), "Internal: got 'ID' operator"); } void Gfx::opEndImage(Object args[], int numArgs) { error(getPos(), "Internal: got 'EI' operator"); } //------------------------------------------------------------------------ // type 3 font operators //------------------------------------------------------------------------ void Gfx::opSetCharWidth(Object args[], int numArgs) { - error(getPos(), "Encountered 'd0' operator in content stream"); + out->type3D0(state, args[0].getNum(), args[1].getNum()); } void Gfx::opSetCacheDevice(Object args[], int numArgs) { - error(getPos(), "Encountered 'd1' operator in content stream"); + out->type3D1(state, args[0].getNum(), args[1].getNum(), + args[2].getNum(), args[3].getNum(), + args[4].getNum(), args[5].getNum()); } //------------------------------------------------------------------------ // compatibility operators //------------------------------------------------------------------------ void Gfx::opBeginIgnoreUndef(Object args[], int numArgs) { ++ignoreUndef; } void Gfx::opEndIgnoreUndef(Object args[], int numArgs) { if (ignoreUndef > 0) --ignoreUndef; } //------------------------------------------------------------------------ // marked content operators //------------------------------------------------------------------------ void Gfx::opBeginMarkedContent(Object args[], int numArgs) { if (printCommands) { printf(" marked content: %s ", args[0].getName()); if (numArgs == 2) args[2].print(stdout); |