diff options
Diffstat (limited to 'pdf/xpdf/SplashOutputDev.cc')
-rw-r--r-- | pdf/xpdf/SplashOutputDev.cc | 1345 |
1 files changed, 1345 insertions, 0 deletions
diff --git a/pdf/xpdf/SplashOutputDev.cc b/pdf/xpdf/SplashOutputDev.cc new file mode 100644 index 0000000..0284d82 --- /dev/null +++ b/pdf/xpdf/SplashOutputDev.cc @@ -0,0 +1,1345 @@ +//======================================================================== +// +// SplashOutputDev.cc +// +// Copyright 2003 Glyph & Cog, LLC +// +//======================================================================== + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <string.h> +#include <math.h> +#include "gfile.h" +#include "GlobalParams.h" +#include "Error.h" +#include "Object.h" +#include "GfxState.h" +#include "GfxFont.h" +#include "Link.h" +#include "CharCodeToUnicode.h" +#include "FontEncodingTables.h" +#include "FoFiTrueType.h" +#include "SplashBitmap.h" +#include "SplashGlyphBitmap.h" +#include "SplashPattern.h" +#include "SplashScreen.h" +#include "SplashPath.h" +#include "SplashState.h" +#include "SplashErrorCodes.h" +#include "SplashFontEngine.h" +#include "SplashFont.h" +#include "SplashFontFile.h" +#include "SplashFontFileID.h" +#include "Splash.h" +#include "SplashOutputDev.h" + +//------------------------------------------------------------------------ +// Font substitutions +//------------------------------------------------------------------------ + +struct SplashOutFontSubst { + char *name; + double mWidth; +}; + +// index: {symbolic:12, fixed:8, serif:4, sans-serif:0} + bold*2 + italic +static SplashOutFontSubst splashOutSubstFonts[16] = { + {"Helvetica", 0.833}, + {"Helvetica-Oblique", 0.833}, + {"Helvetica-Bold", 0.889}, + {"Helvetica-BoldOblique", 0.889}, + {"Times-Roman", 0.788}, + {"Times-Italic", 0.722}, + {"Times-Bold", 0.833}, + {"Times-BoldItalic", 0.778}, + {"Courier", 0.600}, + {"Courier-Oblique", 0.600}, + {"Courier-Bold", 0.600}, + {"Courier-BoldOblique", 0.600}, + {"Symbol", 0.576}, + {"Symbol", 0.576}, + {"Symbol", 0.576}, + {"Symbol", 0.576} +}; + +//------------------------------------------------------------------------ + +#define soutRound(x) ((int)(x + 0.5)) + +//------------------------------------------------------------------------ +// SplashOutFontFileID +//------------------------------------------------------------------------ + +class SplashOutFontFileID: public SplashFontFileID { +public: + + SplashOutFontFileID(Ref *rA) { r = *rA; substIdx = -1; } + + ~SplashOutFontFileID() {} + + GBool matches(SplashFontFileID *id) { + return ((SplashOutFontFileID *)id)->r.num == r.num && + ((SplashOutFontFileID *)id)->r.gen == r.gen; + } + + void setSubstIdx(int substIdxA) { substIdx = substIdxA; } + int getSubstIdx() { return substIdx; } + +private: + + Ref r; + int substIdx; +}; + +//------------------------------------------------------------------------ +// T3FontCache +//------------------------------------------------------------------------ + +struct T3FontCacheTag { + Gushort code; + Gushort mru; // valid bit (0x8000) and MRU index +}; + +class T3FontCache { +public: + + T3FontCache(Ref *fontID, double m11A, double m12A, + double m21A, double m22A, + int glyphXA, int glyphYA, int glyphWA, int glyphHA, + GBool aa); + ~T3FontCache(); + GBool matches(Ref *idA, double m11A, double m12A, + double m21A, double m22A) + { return fontID.num == idA->num && fontID.gen == idA->gen && + m11 == m11A && m12 == m12A && m21 == m21A && m22 == m22A; } + + Ref fontID; // PDF font ID + double m11, m12, m21, m22; // transform matrix + int glyphX, glyphY; // pixel offset of glyph bitmaps + int glyphW, glyphH; // size of glyph bitmaps, in pixels + int glyphSize; // size of glyph bitmaps, in bytes + int cacheSets; // number of sets in cache + int cacheAssoc; // cache associativity (glyphs per set) + Guchar *cacheData; // glyph pixmap cache + T3FontCacheTag *cacheTags; // cache tags, i.e., char codes +}; + +T3FontCache::T3FontCache(Ref *fontIDA, double m11A, double m12A, + double m21A, double m22A, + int glyphXA, int glyphYA, int glyphWA, int glyphHA, + GBool aa) { + int i; + + fontID = *fontIDA; + m11 = m11A; + m12 = m12A; + m21 = m21A; + m22 = m22A; + glyphX = glyphXA; + glyphY = glyphYA; + glyphW = glyphWA; + glyphH = glyphHA; + if (aa) { + glyphSize = glyphW * glyphH; + } else { + glyphSize = ((glyphW + 7) >> 3) * glyphH; + } + cacheAssoc = 8; + if (glyphSize <= 256) { + cacheSets = 8; + } else if (glyphSize <= 512) { + cacheSets = 4; + } else if (glyphSize <= 1024) { + cacheSets = 2; + } else { + cacheSets = 1; + } + cacheData = (Guchar *)gmalloc(cacheSets * cacheAssoc * glyphSize); + cacheTags = (T3FontCacheTag *)gmalloc(cacheSets * cacheAssoc * + sizeof(T3FontCacheTag)); + for (i = 0; i < cacheSets * cacheAssoc; ++i) { + cacheTags[i].mru = i & (cacheAssoc - 1); + } +} + +T3FontCache::~T3FontCache() { + gfree(cacheData); + gfree(cacheTags); +} + +struct T3GlyphStack { + Gushort code; // character code + double x, y; // position to draw the glyph + + //----- cache info + T3FontCache *cache; // font cache for the current font + T3FontCacheTag *cacheTag; // pointer to cache tag for the glyph + Guchar *cacheData; // pointer to cache data for the glyph + + //----- saved state + SplashBitmap *origBitmap; + Splash *origSplash; + double origCTM4, origCTM5; + + T3GlyphStack *next; // next object on stack +}; + +//------------------------------------------------------------------------ +// SplashOutputDev +//------------------------------------------------------------------------ + +SplashOutputDev::SplashOutputDev(SplashColorMode colorModeA, + GBool reverseVideoA, + SplashColor paperColorA) { + colorMode = colorModeA; + reverseVideo = reverseVideoA; + paperColor = paperColorA; + + xref = NULL; + + bitmap = new SplashBitmap(1, 1, colorMode); + splash = new Splash(bitmap); + splash->clear(paperColor); + + fontEngine = NULL; + + nT3Fonts = 0; + t3GlyphStack = NULL; + + font = NULL; + needFontUpdate = gFalse; + textClipPath = NULL; + + underlayCbk = NULL; + underlayCbkData = NULL; +} + +SplashOutputDev::~SplashOutputDev() { + int i; + + for (i = 0; i < nT3Fonts; ++i) { + delete t3FontCache[i]; + } + if (fontEngine) { + delete fontEngine; + } + if (splash) { + delete splash; + } + if (bitmap) { + delete bitmap; + } +} + +void SplashOutputDev::startDoc(XRef *xrefA) { + int i; + + xref = xrefA; + if (fontEngine) { + delete fontEngine; + } + fontEngine = new SplashFontEngine( +#if HAVE_T1LIB_H + globalParams->getEnableT1lib(), +#endif +#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H + globalParams->getEnableFreeType(), +#endif + globalParams->getAntialias()); + for (i = 0; i < nT3Fonts; ++i) { + delete t3FontCache[i]; + } + nT3Fonts = 0; +} + +void SplashOutputDev::startPage(int pageNum, GfxState *state) { + int w, h; + SplashColor color; + + w = state ? (int)(state->getPageWidth() + 0.5) : 1; + h = state ? (int)(state->getPageHeight() + 0.5) : 1; + if (splash) { + delete splash; + } + if (!bitmap || w != bitmap->getWidth() || h != bitmap->getHeight()) { + if (bitmap) { + delete bitmap; + } + bitmap = new SplashBitmap(w, h, colorMode); + } + splash = new Splash(bitmap); + switch (colorMode) { + case splashModeMono1: color.mono1 = 0; break; + case splashModeMono8: color.mono8 = 0; break; + case splashModeRGB8: color.rgb8 = splashMakeRGB8(0, 0, 0); break; + case splashModeBGR8Packed: color.bgr8 = splashMakeBGR8(0, 0, 0); break; + } + splash->setStrokePattern(new SplashSolidColor(color)); + splash->setFillPattern(new SplashSolidColor(color)); + splash->setLineCap(splashLineCapButt); + splash->setLineJoin(splashLineJoinMiter); + splash->setLineDash(NULL, 0, 0); + splash->setMiterLimit(10); + splash->setFlatness(1); + splash->clear(paperColor); + + if (underlayCbk) { + (*underlayCbk)(underlayCbkData); + } +} + +void SplashOutputDev::endPage() { +} + +void SplashOutputDev::drawLink(Link *link, Catalog *catalog) { + double x1, y1, x2, y2; + LinkBorderStyle *borderStyle; + GfxRGB rgb; + double gray; + double *dash; + int dashLength; + SplashCoord dashList[20]; + SplashPath *path; + int x, y, i; + + link->getRect(&x1, &y1, &x2, &y2); + borderStyle = link->getBorderStyle(); + if (borderStyle->getWidth() > 0) { + borderStyle->getColor(&rgb.r, &rgb.g, &rgb.b); + gray = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b; + if (gray > 1) { + gray = 1; + } + splash->setStrokePattern(getColor(gray, &rgb)); + splash->setLineWidth((SplashCoord)borderStyle->getWidth()); + borderStyle->getDash(&dash, &dashLength); + if (borderStyle->getType() == linkBorderDashed && dashLength > 0) { + if (dashLength > 20) { + dashLength = 20; + } + for (i = 0; i < dashLength; ++i) { + dashList[i] = (SplashCoord)dash[i]; + } + splash->setLineDash(dashList, dashLength, 0); + } + path = new SplashPath(); + if (borderStyle->getType() == linkBorderUnderlined) { + cvtUserToDev(x1, y1, &x, &y); + path->moveTo((SplashCoord)x, (SplashCoord)y); + cvtUserToDev(x2, y1, &x, &y); + path->lineTo((SplashCoord)x, (SplashCoord)y); + } else { + cvtUserToDev(x1, y1, &x, &y); + path->moveTo((SplashCoord)x, (SplashCoord)y); + cvtUserToDev(x2, y1, &x, &y); + path->lineTo((SplashCoord)x, (SplashCoord)y); + cvtUserToDev(x2, y2, &x, &y); + path->lineTo((SplashCoord)x, (SplashCoord)y); + cvtUserToDev(x1, y2, &x, &y); + path->lineTo((SplashCoord)x, (SplashCoord)y); + path->close(); + } + splash->stroke(path); + delete path; + } +} + +void SplashOutputDev::saveState(GfxState *state) { + splash->saveState(); +} + +void SplashOutputDev::restoreState(GfxState *state) { + splash->restoreState(); + needFontUpdate = gTrue; +} + +void SplashOutputDev::updateAll(GfxState *state) { + updateLineDash(state); + updateLineJoin(state); + updateLineCap(state); + updateLineWidth(state); + updateFlatness(state); + updateMiterLimit(state); + updateFillColor(state); + updateStrokeColor(state); + needFontUpdate = gTrue; +} + +void SplashOutputDev::updateCTM(GfxState *state, double m11, double m12, + double m21, double m22, + double m31, double m32) { + updateLineDash(state); + updateLineJoin(state); + updateLineCap(state); + updateLineWidth(state); +} + +void SplashOutputDev::updateLineDash(GfxState *state) { + double *dashPattern; + int dashLength; + double dashStart; + SplashCoord dash[20]; + SplashCoord phase; + int i; + + state->getLineDash(&dashPattern, &dashLength, &dashStart); + if (dashLength > 20) { + dashLength = 20; + } + for (i = 0; i < dashLength; ++i) { + dash[i] = (SplashCoord)state->transformWidth(dashPattern[i]); + if (dash[i] < 1) { + dash[i] = 1; + } + } + phase = (SplashCoord)state->transformWidth(dashStart); + splash->setLineDash(dash, dashLength, phase); +} + +void SplashOutputDev::updateFlatness(GfxState *state) { + splash->setFlatness(state->getFlatness()); +} + +void SplashOutputDev::updateLineJoin(GfxState *state) { + splash->setLineJoin(state->getLineJoin()); +} + +void SplashOutputDev::updateLineCap(GfxState *state) { + splash->setLineCap(state->getLineCap()); +} + +void SplashOutputDev::updateMiterLimit(GfxState *state) { + splash->setMiterLimit(state->getMiterLimit()); +} + +void SplashOutputDev::updateLineWidth(GfxState *state) { + splash->setLineWidth(state->getTransformedLineWidth()); +} + +void SplashOutputDev::updateFillColor(GfxState *state) { + double gray; + GfxRGB rgb; + + state->getFillGray(&gray); + state->getFillRGB(&rgb); + splash->setFillPattern(getColor(gray, &rgb)); +} + +void SplashOutputDev::updateStrokeColor(GfxState *state) { + double gray; + GfxRGB rgb; + + state->getStrokeGray(&gray); + state->getStrokeRGB(&rgb); + splash->setStrokePattern(getColor(gray, &rgb)); +} + +SplashPattern *SplashOutputDev::getColor(double gray, GfxRGB *rgb) { + SplashPattern *pattern; + SplashColor color0, color1; + double r, g, b; + + if (reverseVideo) { + gray = 1 - gray; + r = 1 - rgb->r; + g = 1 - rgb->g; + b = 1 - rgb->b; + } else { + r = rgb->r; + g = rgb->g; + b = rgb->b; + } + + pattern = NULL; // make gcc happy + switch (colorMode) { + case splashModeMono1: + color0.mono1 = 0; + color1.mono1 = 1; + pattern = new SplashHalftone(color0, color1, + splash->getScreen()->copy(), + (SplashCoord)gray); + break; + case splashModeMono8: + color1.mono8 = soutRound(255 * gray); + pattern = new SplashSolidColor(color1); + break; + case splashModeRGB8: + color1.rgb8 = splashMakeRGB8(soutRound(255 * r), + soutRound(255 * g), + soutRound(255 * b)); + pattern = new SplashSolidColor(color1); + break; + case splashModeBGR8Packed: + color1.bgr8 = splashMakeBGR8(soutRound(255 * r), + soutRound(255 * g), + soutRound(255 * b)); + pattern = new SplashSolidColor(color1); + break; + } + + return pattern; +} + +void SplashOutputDev::updateFont(GfxState *state) { + GfxFont *gfxFont; + GfxFontType fontType; + SplashOutFontFileID *id; + SplashFontFile *fontFile; + FoFiTrueType *ff; + Ref embRef; + Object refObj, strObj; + GString *tmpFileName, *fileName, *substName; + FILE *tmpFile; + Gushort *codeToGID; + DisplayFontParam *dfp; + double m11, m12, m21, m22, w1, w2; + SplashCoord mat[4]; + char *name; + int c, substIdx, n, code; + + needFontUpdate = gFalse; + font = NULL; + tmpFileName = NULL; + substIdx = -1; + + if (!(gfxFont = state->getFont())) { + goto err1; + } + fontType = gfxFont->getType(); + if (fontType == fontType3) { + goto err1; + } + + // check the font file cache + id = new SplashOutFontFileID(gfxFont->getID()); + if ((fontFile = fontEngine->getFontFile(id))) { + delete id; + + } else { + + // if there is an embedded font, write it to disk + if (gfxFont->getEmbeddedFontID(&embRef)) { + if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) { + error(-1, "Couldn't create temporary font file"); + goto err2; + } + refObj.initRef(embRef.num, embRef.gen); + refObj.fetch(xref, &strObj); + refObj.free(); + strObj.streamReset(); + while ((c = strObj.streamGetChar()) != EOF) { + fputc(c, tmpFile); + } + strObj.streamClose(); + strObj.free(); + fclose(tmpFile); + fileName = tmpFileName; + + // if there is an external font file, use it + } else if (!(fileName = gfxFont->getExtFontFile())) { + + // look for a display font mapping or a substitute font + dfp = NULL; + if (gfxFont->isCIDFont()) { + if (((GfxCIDFont *)gfxFont)->getCollection()) { + dfp = globalParams-> + getDisplayCIDFont(gfxFont->getName(), + ((GfxCIDFont *)gfxFont)->getCollection()); + } + } else { + if (gfxFont->getName()) { + dfp = globalParams->getDisplayFont(gfxFont->getName()); + } + if (!dfp) { + // 8-bit font substitution + if (gfxFont->isFixedWidth()) { + substIdx = 8; + } else if (gfxFont->isSerif()) { + substIdx = 4; + } else { + substIdx = 0; + } + if (gfxFont->isBold()) { + substIdx += 2; + } + if (gfxFont->isItalic()) { + substIdx += 1; + } + substName = new GString(splashOutSubstFonts[substIdx].name); + dfp = globalParams->getDisplayFont(substName); + delete substName; + id->setSubstIdx(substIdx); + } + } + if (!dfp) { + error(-1, "Couldn't find a font for '%s'", + gfxFont->getName() ? gfxFont->getName()->getCString() + : "(unnamed)"); + goto err2; + } + switch (dfp->kind) { + case displayFontT1: + fileName = dfp->t1.fileName; + fontType = gfxFont->isCIDFont() ? fontCIDType0 : fontType1; + break; + case displayFontTT: + fileName = dfp->tt.fileName; + fontType = gfxFont->isCIDFont() ? fontCIDType2 : fontTrueType; + break; + } + } + + // load the font file + switch (fontType) { + case fontType1: + if (!(fontFile = fontEngine->loadType1Font( + id, + fileName->getCString(), + fileName == tmpFileName, + ((Gfx8BitFont *)gfxFont)->getEncoding()))) { + error(-1, "Couldn't create a font for '%s'", + gfxFont->getName() ? gfxFont->getName()->getCString() + : "(unnamed)"); + goto err2; + } + break; + case fontType1C: + if (!(fontFile = fontEngine->loadType1CFont( + id, + fileName->getCString(), + fileName == tmpFileName, + ((Gfx8BitFont *)gfxFont)->getEncoding()))) { + error(-1, "Couldn't create a font for '%s'", + gfxFont->getName() ? gfxFont->getName()->getCString() + : "(unnamed)"); + goto err2; + } + break; + case fontTrueType: + if (!(ff = FoFiTrueType::load(fileName->getCString()))) { + goto err2; + } + codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff); + delete ff; + if (!(fontFile = fontEngine->loadTrueTypeFont( + id, + fileName->getCString(), + fileName == tmpFileName, + codeToGID, 256))) { + error(-1, "Couldn't create a font for '%s'", + gfxFont->getName() ? gfxFont->getName()->getCString() + : "(unnamed)"); + goto err2; + } + break; + case fontCIDType0: + case fontCIDType0C: + if (!(fontFile = fontEngine->loadCIDFont( + id, + fileName->getCString(), + fileName == tmpFileName))) { + error(-1, "Couldn't create a font for '%s'", + gfxFont->getName() ? gfxFont->getName()->getCString() + : "(unnamed)"); + goto err2; + } + break; + case fontCIDType2: + n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen(); + codeToGID = (Gushort *)gmalloc(n * sizeof(Gushort)); + memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), + n * sizeof(Gushort)); + if (!(fontFile = fontEngine->loadTrueTypeFont( + id, + fileName->getCString(), + fileName == tmpFileName, + codeToGID, n))) { + error(-1, "Couldn't create a font for '%s'", + gfxFont->getName() ? gfxFont->getName()->getCString() + : "(unnamed)"); + goto err2; + } + break; + default: + // this shouldn't happen + goto err2; + } + } + + // get the font matrix + state->getFontTransMat(&m11, &m12, &m21, &m22); + m11 *= state->getHorizScaling(); + m12 *= state->getHorizScaling(); + + // for substituted fonts: adjust the font matrix -- compare the + // width of 'm' in the original font and the substituted font + substIdx = ((SplashOutFontFileID *)fontFile->getID())->getSubstIdx(); + if (substIdx >= 0) { + for (code = 0; code < 256; ++code) { + if ((name = ((Gfx8BitFont *)gfxFont)->getCharName(code)) && + name[0] == 'm' && name[1] == '\0') { + break; + } + } + if (code < 256) { + w1 = ((Gfx8BitFont *)gfxFont)->getWidth(code); + w2 = splashOutSubstFonts[substIdx].mWidth; + if (!gfxFont->isSymbolic()) { + // if real font is substantially narrower than substituted + // font, reduce the font size accordingly + if (w1 > 0.01 && w1 < 0.9 * w2) { + w1 /= w2; + m11 *= w1; + m21 *= w1; + } + } + } + } + + // create the scaled font + mat[0] = m11; mat[1] = -m12; + mat[2] = m21; mat[3] = -m22; + font = fontEngine->getFont(fontFile, mat); + + if (tmpFileName) { + delete tmpFileName; + } + return; + + err2: + delete id; + err1: + if (tmpFileName) { + delete tmpFileName; + } + return; +} + +void SplashOutputDev::stroke(GfxState *state) { + SplashPath *path; + + path = convertPath(state, state->getPath()); + splash->stroke(path); + delete path; +} + +void SplashOutputDev::fill(GfxState *state) { + SplashPath *path; + + path = convertPath(state, state->getPath()); + splash->fill(path, gFalse); + delete path; +} + +void SplashOutputDev::eoFill(GfxState *state) { + SplashPath *path; + + path = convertPath(state, state->getPath()); + splash->fill(path, gTrue); + delete path; +} + +void SplashOutputDev::clip(GfxState *state) { + SplashPath *path; + + path = convertPath(state, state->getPath()); + splash->clipToPath(path, gFalse); + delete path; +} + +void SplashOutputDev::eoClip(GfxState *state) { + SplashPath *path; + + path = convertPath(state, state->getPath()); + splash->clipToPath(path, gTrue); + delete path; +} + +SplashPath *SplashOutputDev::convertPath(GfxState *state, GfxPath *path) { + SplashPath *sPath; + GfxSubpath *subpath; + double x1, y1, x2, y2, x3, y3; + int i, j; + + sPath = new SplashPath(); + for (i = 0; i < path->getNumSubpaths(); ++i) { + subpath = path->getSubpath(i); + if (subpath->getNumPoints() > 0) { + state->transform(subpath->getX(0), subpath->getY(0), &x1, &y1); + sPath->moveTo((SplashCoord)x1, (SplashCoord)y1); + j = 1; + while (j < subpath->getNumPoints()) { + if (subpath->getCurve(j)) { + state->transform(subpath->getX(j), subpath->getY(j), &x1, &y1); + state->transform(subpath->getX(j+1), subpath->getY(j+1), &x2, &y2); + state->transform(subpath->getX(j+2), subpath->getY(j+2), &x3, &y3); + sPath->curveTo((SplashCoord)x1, (SplashCoord)y1, + (SplashCoord)x2, (SplashCoord)y2, + (SplashCoord)x3, (SplashCoord)y3); + j += 3; + } else { + state->transform(subpath->getX(j), subpath->getY(j), &x1, &y1); + sPath->lineTo((SplashCoord)x1, (SplashCoord)y1); + ++j; + } + } + if (subpath->isClosed()) { + sPath->close(); + } + } + } + return sPath; +} + +void SplashOutputDev::drawChar(GfxState *state, double x, double y, + double dx, double dy, + double originX, double originY, + CharCode code, Unicode *u, int uLen) { + double x1, y1; + SplashPath *path; + int render; + + if (needFontUpdate) { + updateFont(state); + } + if (!font) { + return; + } + + // check for invisible text -- this is used by Acrobat Capture + render = state->getRender(); + if (render == 3) { + return; + } + + x -= originX; + y -= originY; + state->transform(x, y, &x1, &y1); + + // fill + if (!(render & 1)) { + splash->fillChar((SplashCoord)x1, (SplashCoord)y1, code, font); + } + + // stroke + if ((render & 3) == 1 || (render & 3) == 2) { + if ((path = font->getGlyphPath(code))) { + path->offset((SplashCoord)x1, (SplashCoord)y1); + splash->stroke(path); + delete path; + } + } + + // clip + if (render & 4) { + path = font->getGlyphPath(code); + path->offset((SplashCoord)x1, (SplashCoord)y1); + if (textClipPath) { + textClipPath->append(path); + delete path; + } else { + textClipPath = path; + } + } +} + +GBool SplashOutputDev::beginType3Char(GfxState *state, double x, double y, + double dx, double dy, + CharCode code, Unicode *u, int uLen) { + GfxFont *gfxFont; + Ref *fontID; + double *ctm, *bbox; + T3FontCache *t3Font; + T3GlyphStack *t3gs; + double x1, y1, xMin, yMin, xMax, yMax, xt, yt; + int i, j; + + if (!(gfxFont = state->getFont())) { + return gFalse; + } + fontID = gfxFont->getID(); + ctm = state->getCTM(); + state->transform(0, 0, &xt, &yt); + + // is it the first (MRU) font in the cache? + if (!(nT3Fonts > 0 && + t3FontCache[0]->matches(fontID, ctm[0], ctm[1], ctm[2], ctm[3]))) { + + // is the font elsewhere in the cache? + for (i = 1; i < nT3Fonts; ++i) { + if (t3FontCache[i]->matches(fontID, ctm[0], ctm[1], ctm[2], ctm[3])) { + t3Font = t3FontCache[i]; + for (j = i; j > 0; --j) { + t3FontCache[j] = t3FontCache[j - 1]; + } + t3FontCache[0] = t3Font; + break; + } + } + if (i >= nT3Fonts) { + + // create new entry in the font cache + if (nT3Fonts == splashOutT3FontCacheSize) { + delete t3FontCache[nT3Fonts - 1]; + --nT3Fonts; + } + for (j = nT3Fonts; j > 0; --j) { + t3FontCache[j] = t3FontCache[j - 1]; + } + ++nT3Fonts; + bbox = gfxFont->getFontBBox(); + if (bbox[0] == 0 && bbox[1] == 0 && bbox[2] == 0 && bbox[3] == 0) { + // broken bounding box -- just take a guess + xMin = xt - 5; + xMax = xMin + 30; + yMax = yt + 15; + yMin = yMax - 45; + } else { + state->transform(bbox[0], bbox[1], &x1, &y1); + xMin = xMax = x1; + yMin = yMax = y1; + state->transform(bbox[0], bbox[3], &x1, &y1); + if (x1 < xMin) { + xMin = x1; + } else if (x1 > xMax) { + xMax = x1; + } + if (y1 < yMin) { + yMin = y1; + } else if (y1 > yMax) { + yMax = y1; + } + state->transform(bbox[2], bbox[1], &x1, &y1); + if (x1 < xMin) { + xMin = x1; + } else if (x1 > xMax) { + xMax = x1; + } + if (y1 < yMin) { + yMin = y1; + } else if (y1 > yMax) { + yMax = y1; + } + state->transform(bbox[2], bbox[3], &x1, &y1); + if (x1 < xMin) { + xMin = x1; + } else if (x1 > xMax) { + xMax = x1; + } + if (y1 < yMin) { + yMin = y1; + } else if (y1 > yMax) { + yMax = y1; + } + } + t3FontCache[0] = new T3FontCache(fontID, ctm[0], ctm[1], ctm[2], ctm[3], + (int)floor(xMin - xt), + (int)floor(yMin - yt), + (int)ceil(xMax) - (int)floor(xMin) + 3, + (int)ceil(yMax) - (int)floor(yMin) + 3, + colorMode != splashModeMono1); + } + } + t3Font = t3FontCache[0]; + + // is the glyph in the cache? + i = (code & (t3Font->cacheSets - 1)) * t3Font->cacheAssoc; + for (j = 0; j < t3Font->cacheAssoc; ++j) { + if ((t3Font->cacheTags[i+j].mru & 0x8000) && + t3Font->cacheTags[i+j].code == code) { + drawType3Glyph(t3Font, &t3Font->cacheTags[i+j], + t3Font->cacheData + (i+j) * t3Font->glyphSize, + xt, yt); + return gTrue; + } + } + + // push a new Type 3 glyph record + t3gs = new T3GlyphStack(); + t3gs->next = t3GlyphStack; + t3GlyphStack = t3gs; + t3GlyphStack->code = code; + t3GlyphStack->x = xt; + t3GlyphStack->y = yt; + t3GlyphStack->cache = t3Font; + t3GlyphStack->cacheTag = NULL; + t3GlyphStack->cacheData = NULL; + + return gFalse; +} + +void SplashOutputDev::endType3Char(GfxState *state) { + T3GlyphStack *t3gs; + double *ctm; + + if (t3GlyphStack->cacheTag) { + memcpy(t3GlyphStack->cacheData, bitmap->getDataPtr().mono8, + t3GlyphStack->cache->glyphSize); + delete bitmap; + delete splash; + bitmap = t3GlyphStack->origBitmap; + splash = t3GlyphStack->origSplash; + ctm = state->getCTM(); + state->setCTM(ctm[0], ctm[1], ctm[2], ctm[3], + t3GlyphStack->origCTM4, t3GlyphStack->origCTM5); + drawType3Glyph(t3GlyphStack->cache, + t3GlyphStack->cacheTag, t3GlyphStack->cacheData, + t3GlyphStack->x, t3GlyphStack->y); + } + t3gs = t3GlyphStack; + t3GlyphStack = t3gs->next; + delete t3gs; +} + +void SplashOutputDev::type3D0(GfxState *state, double wx, double wy) { +} + +void SplashOutputDev::type3D1(GfxState *state, double wx, double wy, + double llx, double lly, double urx, double ury) { + double *ctm; + T3FontCache *t3Font; + SplashColor color; + double xt, yt, xMin, xMax, yMin, yMax, x1, y1; + int i, j; + + t3Font = t3GlyphStack->cache; + + // check for a valid bbox + state->transform(0, 0, &xt, &yt); + state->transform(llx, lly, &x1, &y1); + xMin = xMax = x1; + yMin = yMax = y1; + state->transform(llx, ury, &x1, &y1); + if (x1 < xMin) { + xMin = x1; + } else if (x1 > xMax) { + xMax = x1; + } + if (y1 < yMin) { + yMin = y1; + } else if (y1 > yMax) { + yMax = y1; + } + state->transform(urx, lly, &x1, &y1); + if (x1 < xMin) { + xMin = x1; + } else if (x1 > xMax) { + xMax = x1; + } + if (y1 < yMin) { + yMin = y1; + } else if (y1 > yMax) { + yMax = y1; + } + state->transform(urx, ury, &x1, &y1); + if (x1 < xMin) { + xMin = x1; + } else if (x1 > xMax) { + xMax = x1; + } + if (y1 < yMin) { + yMin = y1; + } else if (y1 > yMax) { + yMax = y1; + } + if (xMin - xt < t3Font->glyphX || + yMin - yt < t3Font->glyphY || + xMax - xt > t3Font->glyphX + t3Font->glyphW || + yMax - yt > t3Font->glyphY + t3Font->glyphH) { + error(-1, "Bad bounding box in Type 3 glyph"); + return; + } + + // allocate a cache entry + i = (t3GlyphStack->code & (t3Font->cacheSets - 1)) * t3Font->cacheAssoc; + for (j = 0; j < t3Font->cacheAssoc; ++j) { + if ((t3Font->cacheTags[i+j].mru & 0x7fff) == t3Font->cacheAssoc - 1) { + t3Font->cacheTags[i+j].mru = 0x8000; + t3Font->cacheTags[i+j].code = t3GlyphStack->code; + t3GlyphStack->cacheTag = &t3Font->cacheTags[i+j]; + t3GlyphStack->cacheData = t3Font->cacheData + (i+j) * t3Font->glyphSize; + } else { + ++t3Font->cacheTags[i+j].mru; + } + } + + // save state + t3GlyphStack->origBitmap = bitmap; + t3GlyphStack->origSplash = splash; + ctm = state->getCTM(); + t3GlyphStack->origCTM4 = ctm[4]; + t3GlyphStack->origCTM5 = ctm[5]; + + // create the temporary bitmap + if (colorMode == splashModeMono1) { + bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, splashModeMono1); + splash = new Splash(bitmap); + color.mono1 = 0; + splash->clear(color); + color.mono1 = 1; + } else { + bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, splashModeMono8); + splash = new Splash(bitmap); + color.mono8 = 0x00; + splash->clear(color); + color.mono8 = 0xff; + } + splash->setFillPattern(new SplashSolidColor(color)); + splash->setStrokePattern(new SplashSolidColor(color)); + //~ this should copy other state from t3GlyphStack->origSplash? + state->setCTM(ctm[0], ctm[1], ctm[2], ctm[3], + -t3Font->glyphX, -t3Font->glyphY); +} + +void SplashOutputDev::drawType3Glyph(T3FontCache *t3Font, + T3FontCacheTag *tag, Guchar *data, + double x, double y) { + SplashGlyphBitmap glyph; + + glyph.x = -t3Font->glyphX; + glyph.y = -t3Font->glyphY; + glyph.w = t3Font->glyphW; + glyph.h = t3Font->glyphH; + glyph.aa = colorMode != splashModeMono1; + glyph.data = data; + glyph.freeData = gFalse; + splash->fillGlyph((SplashCoord)x, (SplashCoord)y, &glyph); +} + +void SplashOutputDev::endTextObject(GfxState *state) { + if (textClipPath) { + splash->clipToPath(textClipPath, gFalse); + delete textClipPath; + textClipPath = NULL; + } +} + +struct SplashOutImageMaskData { + ImageStream *imgStr; + int nPixels, idx; + GBool invert; +}; + +GBool SplashOutputDev::imageMaskSrc(void *data, SplashMono1 *pixel) { + SplashOutImageMaskData *imgMaskData = (SplashOutImageMaskData *)data; + Guchar pix; + + if (imgMaskData->idx >= imgMaskData->nPixels) { + return gFalse; + } + //~ use getLine + imgMaskData->imgStr->getPixel(&pix); + if (!imgMaskData->invert) { + pix ^= 1; + } + *pixel = pix; + ++imgMaskData->idx; + return gTrue; +} + +void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, + int width, int height, GBool invert, + GBool inlineImg) { + double *ctm; + SplashCoord mat[6]; + SplashOutImageMaskData imgMaskData; + Guchar pix; + + ctm = state->getCTM(); + mat[0] = ctm[0]; + mat[1] = ctm[1]; + mat[2] = -ctm[2]; + mat[3] = -ctm[3]; + mat[4] = ctm[2] + ctm[4]; + mat[5] = ctm[3] + ctm[5]; + + imgMaskData.imgStr = new ImageStream(str, width, 1, 1); + imgMaskData.imgStr->reset(); + imgMaskData.nPixels = width * height; + imgMaskData.idx = 0; + imgMaskData.invert = invert; + + splash->fillImageMask(&imageMaskSrc, &imgMaskData, width, height, mat); + if (inlineImg) { + while (imageMaskSrc(&imgMaskData, &pix)) ; + } + + delete imgMaskData.imgStr; +} + +struct SplashOutImageData { + ImageStream *imgStr; + GfxImageColorMap *colorMap; + int *maskColors; + SplashOutputDev *out; + int nPixels, idx; +}; + +GBool SplashOutputDev::imageSrc(void *data, SplashColor *pixel, + Guchar *alpha) { + SplashOutImageData *imgData = (SplashOutImageData *)data; + Guchar pix[gfxColorMaxComps]; + GfxRGB rgb; + double gray; + int i; + + if (imgData->idx >= imgData->nPixels) { + return gFalse; + } + + //~ use getLine + imgData->imgStr->getPixel(pix); + switch (imgData->out->colorMode) { + case splashModeMono1: + case splashModeMono8: + imgData->colorMap->getGray(pix, &gray); + pixel->mono8 = soutRound(255 * gray); + break; + case splashModeRGB8: + imgData->colorMap->getRGB(pix, &rgb); + pixel->rgb8 = splashMakeRGB8(soutRound(255 * rgb.r), + soutRound(255 * rgb.g), + soutRound(255 * rgb.b)); + break; + case splashModeBGR8Packed: + imgData->colorMap->getRGB(pix, &rgb); + pixel->bgr8 = splashMakeBGR8(soutRound(255 * rgb.r), + soutRound(255 * rgb.g), + soutRound(255 * rgb.b)); + break; + } + + if (imgData->maskColors) { + *alpha = 0; + for (i = 0; i < imgData->colorMap->getNumPixelComps(); ++i) { + if (pix[i] < imgData->maskColors[2*i] || + pix[i] > imgData->maskColors[2*i+1]) { + *alpha = 1; + break; + } + } + } else { + *alpha = 1; + } + + ++imgData->idx; + return gTrue; +} + +void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + int *maskColors, GBool inlineImg) { + double *ctm; + SplashCoord mat[6]; + SplashOutImageData imgData; + SplashColor pix; + Guchar alpha; + + ctm = state->getCTM(); + mat[0] = ctm[0]; + mat[1] = ctm[1]; + mat[2] = -ctm[2]; + mat[3] = -ctm[3]; + mat[4] = ctm[2] + ctm[4]; + mat[5] = ctm[3] + ctm[5]; + + imgData.imgStr = new ImageStream(str, width, + colorMap->getNumPixelComps(), + colorMap->getBits()); + imgData.imgStr->reset(); + imgData.colorMap = colorMap; + imgData.maskColors = maskColors; + imgData.out = this; + imgData.nPixels = width * height; + imgData.idx = 0; + + splash->drawImage(&imageSrc, &imgData, + (colorMode == splashModeMono1) ? splashModeMono8 + : colorMode, + width, height, mat); + if (inlineImg) { + while (imageSrc(&imgData, &pix, &alpha)) ; + } + + delete imgData.imgStr; +} + +int SplashOutputDev::getBitmapWidth() { + return bitmap->getWidth(); +} + +int SplashOutputDev::getBitmapHeight() { + return bitmap->getHeight(); +} + +void SplashOutputDev::xorRectangle(int x0, int y0, int x1, int y1, + SplashPattern *pattern) { + SplashPath *path; + + path = new SplashPath(); + path->moveTo((SplashCoord)x0, (SplashCoord)y0); + path->lineTo((SplashCoord)x1, (SplashCoord)y0); + path->lineTo((SplashCoord)x1, (SplashCoord)y1); + path->lineTo((SplashCoord)x0, (SplashCoord)y1); + path->close(); + splash->setFillPattern(pattern); + splash->xorFill(path, gTrue); + delete path; +} + +void SplashOutputDev::setFillColor(int r, int g, int b) { + GfxRGB rgb; + double gray; + + rgb.r = r / 255.0; + rgb.g = g / 255.0; + rgb.b = b / 255.0; + gray = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.g; + splash->setFillPattern(getColor(gray, &rgb)); +} + +SplashFont *SplashOutputDev::getFont(GString *name, double *mat) { + DisplayFontParam *dfp; + Ref ref; + SplashOutFontFileID *id; + SplashFontFile *fontFile; + SplashFont *fontObj; + int i; + + for (i = 0; i < 16; ++i) { + if (!name->cmp(splashOutSubstFonts[i].name)) { + break; + } + } + if (i == 16) { + return NULL; + } + ref.num = i; + ref.gen = -1; + id = new SplashOutFontFileID(&ref); + + // check the font file cache + if ((fontFile = fontEngine->getFontFile(id))) { + delete id; + + // load the font file + } else { + dfp = globalParams->getDisplayFont(name); + if (dfp->kind != displayFontT1) { + return NULL; + } + fontFile = fontEngine->loadType1Font(id, dfp->t1.fileName->getCString(), + gFalse, winAnsiEncoding); + } + + // create the scaled font + fontObj = fontEngine->getFont(fontFile, (SplashCoord *)mat); + + return fontObj; +} |