blob: 1e2d132b5d7123285d7cae6e44b92c2d24f630a7 [file] [log] [blame]
//========================================================================
//
// CairoOutputDev.cc
//
// Copyright 2003 Glyph & Cog, LLC
// Copyright 2004 Red Hat, Inc
//
//========================================================================
#include <config.h>
#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif
#include <string.h>
#include <math.h>
#include <cairo.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 "CairoOutputDev.h"
#include "CairoFontEngine.h"
//------------------------------------------------------------------------
#define soutRound(x) ((int)(x + 0.5))
//#define LOG_CAIRO
#ifdef LOG_CAIRO
#define LOG(x) (x)
#else
#define LOG(x)
#endif
//------------------------------------------------------------------------
// CairoOutputDev
//------------------------------------------------------------------------
CairoOutputDev::CairoOutputDev(void) {
xref = NULL;
FT_Init_FreeType(&ft_lib);
fontEngine = NULL;
}
CairoOutputDev::~CairoOutputDev() {
if (fontEngine) {
delete fontEngine;
}
cairo_destroy (cairo);
FT_Done_FreeType(ft_lib);
}
void CairoOutputDev::startDoc(XRef *xrefA) {
xref = xrefA;
if (fontEngine) {
delete fontEngine;
}
fontEngine = new CairoFontEngine(ft_lib);
}
void CairoOutputDev::startPage(int pageNum, GfxState *state) {
cairo_destroy (cairo);
createCairo (state);
cairo_init_clip (cairo);
cairo_set_rgb_color (cairo, 0, 0, 0);
cairo_set_operator (cairo, CAIRO_OPERATOR_OVER);
cairo_set_line_cap (cairo, CAIRO_LINE_CAP_BUTT);
cairo_set_line_join (cairo, CAIRO_LINE_JOIN_MITER);
cairo_set_dash (cairo, NULL, 0, 0.0);
cairo_set_miter_limit (cairo, 10);
cairo_set_tolerance (cairo, 1);
}
void CairoOutputDev::endPage() {
}
void CairoOutputDev::drawLink(Link *link, Catalog *catalog) {
}
void CairoOutputDev::saveState(GfxState *state) {
LOG(printf ("save\n"));
cairo_save (cairo);
}
void CairoOutputDev::restoreState(GfxState *state) {
LOG(printf ("restore\n"));
cairo_restore (cairo);
/* TODO: Is this really needed for cairo? Maybe not */
needFontUpdate = gTrue;
}
void CairoOutputDev::updateAll(GfxState *state) {
updateLineDash(state);
updateLineJoin(state);
updateLineCap(state);
updateLineWidth(state);
updateFlatness(state);
updateMiterLimit(state);
updateFillColor(state);
updateStrokeColor(state);
needFontUpdate = gTrue;
}
void CairoOutputDev::updateCTM(GfxState *state, double m11, double m12,
double m21, double m22,
double m31, double m32) {
updateLineDash(state);
updateLineJoin(state);
updateLineCap(state);
updateLineWidth(state);
}
void CairoOutputDev::updateLineDash(GfxState *state) {
double *dashPattern;
int dashLength;
double dashStart;
double *transformedDash;
double transformedStart;
int i;
state->getLineDash(&dashPattern, &dashLength, &dashStart);
transformedDash = new double[dashLength];
for (i = 0; i < dashLength; ++i) {
transformedDash[i] = state->transformWidth(dashPattern[i]);
}
transformedStart = state->transformWidth(dashStart);
cairo_set_dash (cairo, transformedDash, dashLength, transformedStart);
delete [] transformedDash;
}
void CairoOutputDev::updateFlatness(GfxState *state) {
cairo_set_tolerance (cairo, state->getFlatness());
}
void CairoOutputDev::updateLineJoin(GfxState *state) {
switch (state->getLineJoin()) {
case 0:
cairo_set_line_join (cairo, CAIRO_LINE_JOIN_MITER);
break;
case 1:
cairo_set_line_join (cairo, CAIRO_LINE_JOIN_ROUND);
break;
case 2:
cairo_set_line_join (cairo, CAIRO_LINE_JOIN_BEVEL);
break;
}
}
void CairoOutputDev::updateLineCap(GfxState *state) {
switch (state->getLineCap()) {
case 0:
cairo_set_line_cap (cairo, CAIRO_LINE_CAP_BUTT);
break;
case 1:
cairo_set_line_cap (cairo, CAIRO_LINE_CAP_ROUND);
break;
case 2:
cairo_set_line_cap (cairo, CAIRO_LINE_CAP_SQUARE);
break;
}
}
void CairoOutputDev::updateMiterLimit(GfxState *state) {
cairo_set_miter_limit (cairo, state->getMiterLimit());
}
void CairoOutputDev::updateLineWidth(GfxState *state) {
LOG(printf ("line width: %f\n", state->getTransformedLineWidth()));
cairo_set_line_width (cairo, state->getTransformedLineWidth());
}
void CairoOutputDev::updateFillColor(GfxState *state) {
state->getFillRGB(&fill_color);
LOG(printf ("fill color: %f %f %f\n", fill_color.r, fill_color.g, fill_color.b));
}
void CairoOutputDev::updateStrokeColor(GfxState *state) {
state->getStrokeRGB(&stroke_color);
LOG(printf ("stroke color: %f %f %f\n", stroke_color.r, stroke_color.g, stroke_color.b));
}
void CairoOutputDev::updateFont(GfxState *state) {
cairo_font_t *font;
double m11, m12, m21, m22;
double w;
LOG(printf ("updateFont() font=%s\n", state->getFont()->getName()->getCString()));
/* Needs to be rethough, since fonts are now handled by cairo */
needFontUpdate = gFalse;
state->getFontTransMat(&m11, &m12, &m21, &m22);
m11 *= state->getHorizScaling();
m12 *= state->getHorizScaling();
/* w = currentFont->getSubstitutionCorrection(state->getFont()); */
m12 *= -1;
m22 *= -1;
LOG(printf ("font matrix: %f %f %f %f\n", m11, m12, m21, m22));
currentFont = fontEngine->getFont (state->getFont(), xref,
m11, m21, m12, m22);
font = currentFont->getFont();
cairo_set_font (cairo, font);
}
void CairoOutputDev::doPath(GfxState *state, GfxPath *path,
GBool snapToGrid) {
GfxSubpath *subpath;
double x1, y1, x2, y2, x3, y3;
int i, j;
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);
if (snapToGrid) {
x1 = round (x1); y1 = round (y1);
}
cairo_move_to (cairo, x1, y1);
LOG (printf ("move_to %f, %f\n", x1, y1));
j = 1;
while (j < subpath->getNumPoints()) {
if (subpath->getCurve(j)) {
if (snapToGrid) {
x1 = round (x1); y1 = round (y1);
x2 = round (x2); y2 = round (y2);
x3 = round (x3); y3 = round (y3);
}
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);
cairo_curve_to (cairo,
x1, y1,
x2, y2,
x3, y3);
LOG (printf ("curve_to %f, %f %f, %f %f, %f\n", x1, y1, x2, y2, x3, y3));
j += 3;
} else {
state->transform(subpath->getX(j), subpath->getY(j), &x1, &y1);
if (snapToGrid) {
x1 = round (x1); y1 = round (y1);
}
cairo_line_to (cairo, x1, y1);
LOG(printf ("line_to %f, %f\n", x1, y1));
++j;
}
}
if (subpath->isClosed()) {
LOG (printf ("close\n"));
cairo_close_path (cairo);
}
}
}
}
void CairoOutputDev::stroke(GfxState *state) {
doPath (state, state->getPath(), gFalse);
cairo_set_rgb_color (cairo,
stroke_color.r, stroke_color.g, stroke_color.b);
LOG(printf ("stroke\n"));
cairo_stroke (cairo);
}
void CairoOutputDev::fill(GfxState *state) {
doPath (state, state->getPath(), gFalse);
cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_WINDING);
cairo_set_rgb_color (cairo,
fill_color.r, fill_color.g, fill_color.b);
LOG(printf ("fill\n"));
cairo_fill (cairo);
}
void CairoOutputDev::eoFill(GfxState *state) {
doPath (state, state->getPath(), gFalse);
cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD);
cairo_set_rgb_color (cairo,
fill_color.r, fill_color.g, fill_color.b);
LOG(printf ("fill-eo\n"));
cairo_fill (cairo);
}
void CairoOutputDev::clip(GfxState *state, GBool snapToGrid) {
doPath (state, state->getPath(), snapToGrid);
cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_WINDING);
cairo_clip (cairo);
cairo_new_path (cairo); /* Consume path */
LOG (printf ("clip\n"));
}
void CairoOutputDev::eoClip(GfxState *state) {
doPath (state, state->getPath(), gFalse);
cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD);
cairo_clip (cairo);
cairo_new_path (cairo); /* Consume path */
LOG (printf ("clip-eo\n"));
}
void CairoOutputDev::drawString(GfxState *state, GooString *s)
{
GfxFont *font;
int wMode;
int render;
// the number of bytes in the string and not the number of glyphs?
int len = s->getLength();
// need at most len glyphs
cairo_glyph_t *glyphs;
char *p = s->getCString();
int count = 0;
double curX, curY;
double riseX, riseY;
font = state->getFont();
wMode = font->getWMode();
if (needFontUpdate) {
updateFont(state);
}
if (!currentFont) {
return;
}
// check for invisible text -- this is used by Acrobat Capture
render = state->getRender();
if (render == 3) {
return;
}
// ignore empty strings
if (len == 0)
return;
glyphs = (cairo_glyph_t *) gmalloc (len * sizeof (cairo_glyph_t));
state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
curX = state->getCurX();
curY = state->getCurY();
while (len > 0) {
double x, y;
double x1, y1;
double dx, dy, tdx, tdy;
double originX, originY, tOriginX, tOriginY;
int n, uLen;
CharCode code;
Unicode u[8];
n = font->getNextChar(p, len, &code,
u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
&dx, &dy, &originX, &originY);
if (wMode) {
dx *= state->getFontSize();
dy = dy * state->getFontSize() + state->getCharSpace();
if (n == 1 && *p == ' ') {
dy += state->getWordSpace();
}
} else {
dx = dx * state->getFontSize() + state->getCharSpace();
if (n == 1 && *p == ' ') {
dx += state->getWordSpace();
}
dx *= state->getHorizScaling();
dy *= state->getFontSize();
}
originX *= state->getFontSize();
originY *= state->getFontSize();
state->textTransformDelta(dx, dy, &tdx, &tdy);
state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
x = curX + riseX;
y = curY + riseY;
x -= tOriginX;
y -= tOriginY;
state->transform(x, y, &x1, &y1);
glyphs[count].index = currentFont->getGlyph (code, u, uLen);
glyphs[count].x = x1;
glyphs[count].y = y1;
curX += tdx;
curY += tdy;
p += n;
len -= n;
count++;
}
// fill
if (!(render & 1)) {
LOG (printf ("fill string\n"));
cairo_set_rgb_color (cairo,
fill_color.r, fill_color.g, fill_color.b);
cairo_show_glyphs (cairo, glyphs, count);
}
// stroke
if ((render & 3) == 1 || (render & 3) == 2) {
LOG (printf ("stroke string\n"));
cairo_set_rgb_color (cairo,
stroke_color.r, stroke_color.g, stroke_color.b);
cairo_glyph_path (cairo, glyphs, count);
cairo_stroke (cairo);
}
// clip
if (render & 4) {
// FIXME: This is quite right yet, we need to accumulate all
// glyphs within one text object before we clip. Right now this
// just add this one string.
LOG (printf ("clip string\n"));
cairo_glyph_path (cairo, glyphs, count);
cairo_clip (cairo);
}
gfree (glyphs);
}
GBool CairoOutputDev::beginType3Char(GfxState *state, double x, double y,
double dx, double dy,
CharCode code, Unicode *u, int uLen) {
return gFalse;
}
void CairoOutputDev::endType3Char(GfxState *state) {
}
void CairoOutputDev::type3D0(GfxState *state, double wx, double wy) {
}
void CairoOutputDev::type3D1(GfxState *state, double wx, double wy,
double llx, double lly, double urx, double ury) {
}
void CairoOutputDev::endTextObject(GfxState *state) {
}
void CairoOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
int width, int height, GBool invert,
GBool inlineImg) {
char *buffer;
char *dest;
cairo_surface_t *image;
int x, y;
ImageStream *imgStr;
Guchar *pix;
double *ctm;
cairo_matrix_t *mat;
int invert_bit;
buffer = (char *)malloc (width * height * 4);
if (buffer == NULL) {
error(-1, "Unable to allocate memory for image.");
return;
}
/* TODO: Do we want to cache these? */
imgStr = new ImageStream(str, width, 1, 1);
imgStr->reset();
invert_bit = invert ? 1 : 0;
for (y = 0; y < height; y++) {
pix = imgStr->getLine();
dest = buffer + y * width * 4;
for (x = 0; x < width; x++) {
*dest++ = soutRound(255 * fill_color.b);
*dest++ = soutRound(255 * fill_color.g);
*dest++ = soutRound(255 * fill_color.r);
if (pix[x] ^ invert_bit)
*dest++ = 0;
else
*dest++ = 255;
}
}
cairo_save (cairo);
ctm = state->getCTM();
mat = cairo_matrix_create ();
LOG (printf ("drawImageMask %dx%d, matrix: %f, %f, %f, %f, %f, %f\n",
width, height,
ctm[0], ctm[1],
ctm[2], ctm[3],
ctm[4], ctm[5]));
cairo_matrix_set_affine (mat,
ctm[0]/width, ctm[1]/width,
-ctm[2]/height, -ctm[3]/height,
ctm[2] + ctm[4], ctm[3] + ctm[5]);
cairo_concat_matrix (cairo, mat);
cairo_matrix_destroy (mat);
image = cairo_surface_create_for_image (
buffer, CAIRO_FORMAT_ARGB32, width, height, width * 4);
cairo_surface_set_filter (image, CAIRO_FILTER_BEST);
cairo_show_surface (cairo, image, width, height);
cairo_restore (cairo);
cairo_surface_destroy (image);
free (buffer);
delete imgStr;
}
void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
int width, int height,
GfxImageColorMap *colorMap,
int *maskColors, GBool inlineImg) {
char *buffer;
char *dest;
cairo_surface_t *image;
int x, y;
ImageStream *imgStr;
Guchar *pix;
GfxRGB rgb;
int alpha, i;
double *ctm;
cairo_matrix_t *mat;
int is_identity_transform;
buffer = (char *)malloc (width * height * 4);
if (buffer == NULL) {
error(-1, "Unable to allocate memory for image.");
return;
}
/* TODO: Do we want to cache these? */
imgStr = new ImageStream(str, width,
colorMap->getNumPixelComps(),
colorMap->getBits());
imgStr->reset();
/* ICCBased color space doesn't do any color correction
* so check its underlying color space as well */
is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB ||
colorMap->getColorSpace()->getMode() == csICCBased &&
((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB;
for (y = 0; y < height; y++) {
dest = buffer + y * 4 * width;
pix = imgStr->getLine();
for (x = 0; x < width; x++, pix += colorMap->getNumPixelComps()) {
if (maskColors) {
alpha = 0;
for (i = 0; i < colorMap->getNumPixelComps(); ++i) {
if (pix[i] < maskColors[2*i] ||
pix[i] > maskColors[2*i+1]) {
alpha = 255;
break;
}
}
} else {
alpha = 255;
}
if (is_identity_transform) {
*dest++ = pix[2];
*dest++ = pix[1];
*dest++ = pix[0];
} else {
colorMap->getRGB(pix, &rgb);
*dest++ = soutRound(255 * rgb.b);
*dest++ = soutRound(255 * rgb.g);
*dest++ = soutRound(255 * rgb.r);
}
*dest++ = alpha;
}
}
cairo_save (cairo);
ctm = state->getCTM();
mat = cairo_matrix_create ();
LOG (printf ("draw image %dx%d, matrix: %f, %f, %f, %f, %f, %f\n",
width, height,
ctm[0], ctm[1],
ctm[2], ctm[3],
ctm[4], ctm[5]));
cairo_matrix_set_affine (mat,
ctm[0]/width, ctm[1]/width,
-ctm[2]/height, -ctm[3]/height,
ctm[2] + ctm[4], ctm[3] + ctm[5]);
cairo_concat_matrix (cairo, mat);
cairo_matrix_destroy (mat);
image = cairo_surface_create_for_image (
buffer, CAIRO_FORMAT_ARGB32, width, height, width * 4);
cairo_surface_set_filter (image, CAIRO_FILTER_BEST);
cairo_show_surface (cairo, image, width, height);
cairo_restore (cairo);
cairo_surface_destroy (image);
free (buffer);
delete imgStr;
}