//======================================================================== // // SplashOutputDev.cc // // Copyright 2003 Glyph & Cog, LLC // //======================================================================== #include #ifdef USE_GCC_PRAGMAS #pragma implementation #endif #include #include #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; }