blob: beb9777bbc49a65d315d3e0cd5246dc3db3a0803 [file] [log] [blame]
//========================================================================
//
// SplashOutputDev.cc
//
// Copyright 2003 Glyph & Cog, LLC
//
//========================================================================
#include <config.h>
#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif
#include <string.h>
#include <math.h>
#include "goo/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 "fofi/FoFiTrueType.h"
#include "splash/SplashBitmap.h"
#include "splash/SplashGlyphBitmap.h"
#include "splash/SplashPattern.h"
#include "splash/SplashScreen.h"
#include "splash/SplashPath.h"
#include "splash/SplashState.h"
#include "splash/SplashErrorCodes.h"
#include "splash/SplashFontEngine.h"
#include "splash/SplashFont.h"
#include "splash/SplashFontFile.h"
#include "splash/SplashFontFileID.h"
#include "splash/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:
case splashModeRGB8Packed: 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:
case splashModeRGB8Packed:
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;
GooString *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 GooString(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()))) {
codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff);
n = 256;
delete ff;
} else {
codeToGID = NULL;
n = 0;
}
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;
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:
case splashModeRGB8Packed:
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(GooString *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;
}