blob: b70183e15f4ccfca63b790ad14ed93586ee1c461 [file] [log] [blame]
//========================================================================
//
// CairoOutputDev.cc
//
// Copyright 2003 Glyph & Cog, LLC
// Copyright 2004 Red Hat, Inc
//
//========================================================================
//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2005-2008 Jeff Muizelaar <jeff@infidigm.net>
// Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com>
// Copyright (C) 2005, 2009, 2012 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2005 Nickolay V. Shmyrev <nshmyrev@yandex.ru>
// Copyright (C) 2006-2011 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2008 Carl Worth <cworth@cworth.org>
// Copyright (C) 2008-2012 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu>
// Copyright (C) 2008, 2009 Chris Wilson <chris@chris-wilson.co.uk>
// Copyright (C) 2008 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2009, 2010 David Benjamin <davidben@mit.edu>
// Copyright (C) 2011, 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2012 Patrick Pfeifer <p2000@mailinator.com>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================
#include <config.h>
#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif
#include <string.h>
#include <math.h>
#include <assert.h>
#include <cairo.h>
#include "goo/gfile.h"
#include "goo/gtypes_p.h"
#include "GlobalParams.h"
#include "Error.h"
#include "Object.h"
#include "Gfx.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "Page.h"
#include "Link.h"
#include "FontEncodingTables.h"
#include "PDFDocEncoding.h"
#include <fofi/FoFiTrueType.h>
#include <splash/SplashBitmap.h>
#include "CairoOutputDev.h"
#include "CairoFontEngine.h"
#include "CairoRescaleBox.h"
#include "UTF8.h"
//------------------------------------------------------------------------
// #define LOG_CAIRO
#ifdef LOG_CAIRO
#define LOG(x) (x)
#else
#define LOG(x)
#endif
static inline void printMatrix(cairo_matrix_t *matrix){
printf("%f %f, %f %f (%f %f)\n", matrix->xx, matrix->yx,
matrix->xy, matrix->yy,
matrix->x0, matrix->y0);
}
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
//------------------------------------------------------------------------
// CairoImage
//------------------------------------------------------------------------
CairoImage::CairoImage (double x1, double y1, double x2, double y2) {
this->image = NULL;
this->x1 = x1;
this->y1 = y1;
this->x2 = x2;
this->y2 = y2;
}
CairoImage::~CairoImage () {
if (image)
cairo_surface_destroy (image);
}
void CairoImage::setImage (cairo_surface_t *image) {
if (this->image)
cairo_surface_destroy (this->image);
this->image = cairo_surface_reference (image);
}
//------------------------------------------------------------------------
// CairoOutputDev
//------------------------------------------------------------------------
// We cannot tie the lifetime of an FT_Library object to that of
// CairoOutputDev, since any FT_Faces created with it may end up with a
// reference by Cairo which can be held long after the CairoOutputDev is
// deleted. The simplest way to avoid problems is to never tear down the
// FT_Library instance; to avoid leaks, just use a single global instance
// initialized the first time it is needed.
FT_Library CairoOutputDev::ft_lib;
GBool CairoOutputDev::ft_lib_initialized = gFalse;
CairoOutputDev::CairoOutputDev() {
doc = NULL;
if (!ft_lib_initialized) {
FT_Init_FreeType(&ft_lib);
ft_lib_initialized = gTrue;
}
fontEngine = NULL;
fontEngine_owner = gFalse;
glyphs = NULL;
fill_pattern = NULL;
fill_color.r = fill_color.g = fill_color.b = 0;
stroke_pattern = NULL;
stroke_color.r = stroke_color.g = stroke_color.b = 0;
stroke_opacity = 1.0;
fill_opacity = 1.0;
textClipPath = NULL;
strokePathClip = NULL;
cairo = NULL;
currentFont = NULL;
prescaleImages = gTrue;
printing = gTrue;
use_show_text_glyphs = gFalse;
inType3Char = gFalse;
t3_glyph_has_bbox = gFalse;
groupColorSpaceStack = NULL;
maskStack = NULL;
group = NULL;
mask = NULL;
shape = NULL;
cairo_shape = NULL;
knockoutCount = 0;
text = NULL;
actualText = NULL;
// the SA parameter supposedly defaults to false, but Acrobat
// apparently hardwires it to true
stroke_adjust = globalParams->getStrokeAdjust();
align_stroke_coords = gFalse;
adjusted_stroke_width = gFalse;
}
CairoOutputDev::~CairoOutputDev() {
if (fontEngine_owner && fontEngine) {
delete fontEngine;
}
if (cairo)
cairo_destroy (cairo);
cairo_pattern_destroy (stroke_pattern);
cairo_pattern_destroy (fill_pattern);
if (group)
cairo_pattern_destroy (group);
if (mask)
cairo_pattern_destroy (mask);
if (shape)
cairo_pattern_destroy (shape);
if (text)
text->decRefCnt();
if (actualText)
delete actualText;
}
void CairoOutputDev::setCairo(cairo_t *cairo)
{
if (this->cairo != NULL) {
cairo_status_t status = cairo_status (this->cairo);
if (status) {
error(errInternal, -1, "cairo context error: {0:s}\n", cairo_status_to_string(status));
}
cairo_destroy (this->cairo);
assert(!cairo_shape);
}
if (cairo != NULL) {
this->cairo = cairo_reference (cairo);
/* save the initial matrix so that we can use it for type3 fonts. */
//XXX: is this sufficient? could we miss changes to the matrix somehow?
cairo_get_matrix(cairo, &orig_matrix);
} else {
this->cairo = NULL;
this->cairo_shape = NULL;
}
}
void CairoOutputDev::setTextPage(TextPage *text)
{
if (this->text)
this->text->decRefCnt();
if (actualText)
delete actualText;
if (text) {
this->text = text;
this->text->incRefCnt();
actualText = new ActualText(text);
} else {
this->text = NULL;
actualText = NULL;
}
}
void CairoOutputDev::startDoc(PDFDoc *docA,
CairoFontEngine *parentFontEngine) {
doc = docA;
if (parentFontEngine) {
fontEngine = parentFontEngine;
} else {
if (fontEngine) {
delete fontEngine;
}
fontEngine = new CairoFontEngine(ft_lib);
fontEngine_owner = gTrue;
}
}
void CairoOutputDev::startPage(int pageNum, GfxState *state) {
/* set up some per page defaults */
cairo_pattern_destroy(fill_pattern);
cairo_pattern_destroy(stroke_pattern);
fill_pattern = cairo_pattern_create_rgb(0., 0., 0.);
stroke_pattern = cairo_pattern_reference(fill_pattern);
if (text)
text->startPage(state);
}
void CairoOutputDev::endPage() {
if (text) {
text->endPage();
text->coalesce(gTrue, 0, gFalse);
}
}
void CairoOutputDev::saveState(GfxState *state) {
LOG(printf ("save\n"));
cairo_save (cairo);
if (cairo_shape)
cairo_save (cairo_shape);
MaskStack *ms = new MaskStack;
ms->mask = cairo_pattern_reference(mask);
ms->mask_matrix = mask_matrix;
ms->next = maskStack;
maskStack = ms;
}
void CairoOutputDev::restoreState(GfxState *state) {
LOG(printf ("restore\n"));
cairo_restore (cairo);
if (cairo_shape)
cairo_restore (cairo_shape);
/* These aren't restored by cairo_restore() since we keep them in
* the output device. */
updateFillColor(state);
updateStrokeColor(state);
updateFillOpacity(state);
updateStrokeOpacity(state);
updateBlendMode(state);
MaskStack* ms = maskStack;
if (ms) {
if (mask)
cairo_pattern_destroy(mask);
mask = ms->mask;
mask_matrix = ms->mask_matrix;
maskStack = ms->next;
delete ms;
}
}
void CairoOutputDev::updateAll(GfxState *state) {
updateLineDash(state);
updateLineJoin(state);
updateLineCap(state);
updateLineWidth(state);
updateFlatness(state);
updateMiterLimit(state);
updateFillColor(state);
updateStrokeColor(state);
updateFillOpacity(state);
updateStrokeOpacity(state);
updateBlendMode(state);
needFontUpdate = gTrue;
if (text)
text->updateFont(state);
}
void CairoOutputDev::setDefaultCTM(double *ctm) {
cairo_matrix_t matrix;
matrix.xx = ctm[0];
matrix.yx = ctm[1];
matrix.xy = ctm[2];
matrix.yy = ctm[3];
matrix.x0 = ctm[4];
matrix.y0 = ctm[5];
cairo_transform (cairo, &matrix);
if (cairo_shape)
cairo_transform (cairo_shape, &matrix);
OutputDev::setDefaultCTM(ctm);
}
void CairoOutputDev::updateCTM(GfxState *state, double m11, double m12,
double m21, double m22,
double m31, double m32) {
cairo_matrix_t matrix, invert_matrix;
matrix.xx = m11;
matrix.yx = m12;
matrix.xy = m21;
matrix.yy = m22;
matrix.x0 = m31;
matrix.y0 = m32;
/* Make sure the matrix is invertible before setting it.
* cairo will blow up if we give it a matrix that's not
* invertible, so we need to check before passing it
* to cairo_transform. Ignoring it is likely to give better
* results than not rendering anything at all. See #14398
*
* Ideally, we could do the cairo_transform
* and then check if anything went wrong and fix it then
* instead of having to invert the matrix. */
invert_matrix = matrix;
if (cairo_matrix_invert(&invert_matrix)) {
error(errSyntaxWarning, -1, "matrix not invertible\n");
return;
}
cairo_transform (cairo, &matrix);
if (cairo_shape)
cairo_transform (cairo_shape, &matrix);
updateLineDash(state);
updateLineJoin(state);
updateLineCap(state);
updateLineWidth(state);
}
void CairoOutputDev::updateLineDash(GfxState *state) {
double *dashPattern;
int dashLength;
double dashStart;
state->getLineDash(&dashPattern, &dashLength, &dashStart);
cairo_set_dash (cairo, dashPattern, dashLength, dashStart);
if (cairo_shape)
cairo_set_dash (cairo_shape, dashPattern, dashLength, dashStart);
}
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;
}
if (cairo_shape)
cairo_set_line_join (cairo_shape, cairo_get_line_join(cairo));
}
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;
}
if (cairo_shape)
cairo_set_line_cap (cairo_shape, cairo_get_line_cap(cairo));
}
void CairoOutputDev::updateMiterLimit(GfxState *state) {
cairo_set_miter_limit (cairo, state->getMiterLimit());
if (cairo_shape)
cairo_set_miter_limit (cairo_shape, state->getMiterLimit());
}
void CairoOutputDev::updateLineWidth(GfxState *state) {
LOG(printf ("line width: %f\n", state->getLineWidth()));
adjusted_stroke_width = gFalse;
double width = state->getLineWidth();
if (stroke_adjust && !printing) {
double x, y;
x = y = width;
/* find out line width in device units */
cairo_user_to_device_distance(cairo, &x, &y);
if (fabs(x) <= 1.0 && fabs(y) <= 1.0) {
/* adjust width to at least one device pixel */
x = y = 1.0;
cairo_device_to_user_distance(cairo, &x, &y);
width = MIN(fabs(x),fabs(y));
adjusted_stroke_width = gTrue;
}
} else if (width == 0.0) {
/* Cairo does not support 0 line width == 1 device pixel. Find out
* how big pixels (device unit) are in the x and y
* directions. Choose the smaller of the two as our line width.
*/
double x = 1.0, y = 1.0;
if (printing) {
// assume printer pixel size is 1/600 inch
x = 72.0/600;
y = 72.0/600;
}
cairo_device_to_user_distance(cairo, &x, &y);
width = MIN(fabs(x),fabs(y));
}
cairo_set_line_width (cairo, width);
if (cairo_shape)
cairo_set_line_width (cairo_shape, cairo_get_line_width (cairo));
}
void CairoOutputDev::updateFillColor(GfxState *state) {
GfxRGB color = fill_color;
state->getFillRGB(&fill_color);
if (cairo_pattern_get_type (fill_pattern) != CAIRO_PATTERN_TYPE_SOLID ||
color.r != fill_color.r ||
color.g != fill_color.g ||
color.b != fill_color.b)
{
cairo_pattern_destroy(fill_pattern);
fill_pattern = cairo_pattern_create_rgba(colToDbl(fill_color.r),
colToDbl(fill_color.g),
colToDbl(fill_color.b),
fill_opacity);
LOG(printf ("fill color: %d %d %d\n",
fill_color.r, fill_color.g, fill_color.b));
}
}
void CairoOutputDev::updateStrokeColor(GfxState *state) {
GfxRGB color = stroke_color;
state->getStrokeRGB(&stroke_color);
if (cairo_pattern_get_type (fill_pattern) != CAIRO_PATTERN_TYPE_SOLID ||
color.r != stroke_color.r ||
color.g != stroke_color.g ||
color.b != stroke_color.b)
{
cairo_pattern_destroy(stroke_pattern);
stroke_pattern = cairo_pattern_create_rgba(colToDbl(stroke_color.r),
colToDbl(stroke_color.g),
colToDbl(stroke_color.b),
stroke_opacity);
LOG(printf ("stroke color: %d %d %d\n",
stroke_color.r, stroke_color.g, stroke_color.b));
}
}
void CairoOutputDev::updateFillOpacity(GfxState *state) {
double opacity = fill_opacity;
fill_opacity = state->getFillOpacity();
if (opacity != fill_opacity) {
cairo_pattern_destroy(fill_pattern);
fill_pattern = cairo_pattern_create_rgba(colToDbl(fill_color.r),
colToDbl(fill_color.g),
colToDbl(fill_color.b),
fill_opacity);
LOG(printf ("fill opacity: %f\n", fill_opacity));
}
}
void CairoOutputDev::updateStrokeOpacity(GfxState *state) {
double opacity = stroke_opacity;
stroke_opacity = state->getStrokeOpacity();
if (opacity != stroke_opacity) {
cairo_pattern_destroy(stroke_pattern);
stroke_pattern = cairo_pattern_create_rgba(colToDbl(stroke_color.r),
colToDbl(stroke_color.g),
colToDbl(stroke_color.b),
stroke_opacity);
LOG(printf ("stroke opacity: %f\n", stroke_opacity));
}
}
void CairoOutputDev::updateFillColorStop(GfxState *state, double offset) {
state->getFillRGB(&fill_color);
cairo_pattern_add_color_stop_rgba(fill_pattern, offset,
colToDbl(fill_color.r),
colToDbl(fill_color.g),
colToDbl(fill_color.b),
fill_opacity);
LOG(printf ("fill color stop: %f (%d, %d, %d)\n",
offset, fill_color.r, fill_color.g, fill_color.b));
}
void CairoOutputDev::updateBlendMode(GfxState *state) {
switch (state->getBlendMode()) {
default:
case gfxBlendNormal:
cairo_set_operator (cairo, CAIRO_OPERATOR_OVER);
break;
case gfxBlendMultiply:
cairo_set_operator (cairo, CAIRO_OPERATOR_MULTIPLY);
break;
case gfxBlendScreen:
cairo_set_operator (cairo, CAIRO_OPERATOR_SCREEN);
break;
case gfxBlendOverlay:
cairo_set_operator (cairo, CAIRO_OPERATOR_OVERLAY);
break;
case gfxBlendDarken:
cairo_set_operator (cairo, CAIRO_OPERATOR_DARKEN);
break;
case gfxBlendLighten:
cairo_set_operator (cairo, CAIRO_OPERATOR_LIGHTEN);
break;
case gfxBlendColorDodge:
cairo_set_operator (cairo, CAIRO_OPERATOR_COLOR_DODGE);
break;
case gfxBlendColorBurn:
cairo_set_operator (cairo, CAIRO_OPERATOR_COLOR_BURN);
break;
case gfxBlendHardLight:
cairo_set_operator (cairo, CAIRO_OPERATOR_HARD_LIGHT);
break;
case gfxBlendSoftLight:
cairo_set_operator (cairo, CAIRO_OPERATOR_SOFT_LIGHT);
break;
case gfxBlendDifference:
cairo_set_operator (cairo, CAIRO_OPERATOR_DIFFERENCE);
break;
case gfxBlendExclusion:
cairo_set_operator (cairo, CAIRO_OPERATOR_EXCLUSION);
break;
case gfxBlendHue:
cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_HUE);
break;
case gfxBlendSaturation:
cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_SATURATION);
break;
case gfxBlendColor:
cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_COLOR);
break;
case gfxBlendLuminosity:
cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_LUMINOSITY);
break;
}
LOG(printf ("blend mode: %d\n", (int)state->getBlendMode()));
}
void CairoOutputDev::updateFont(GfxState *state) {
cairo_font_face_t *font_face;
cairo_matrix_t matrix, invert_matrix;
LOG(printf ("updateFont() font=%s\n", state->getFont()->getName()->getCString()));
needFontUpdate = gFalse;
//FIXME: use cairo font engine?
if (text)
text->updateFont(state);
currentFont = fontEngine->getFont (state->getFont(), doc, printing);
if (!currentFont)
return;
font_face = currentFont->getFontFace();
cairo_set_font_face (cairo, font_face);
use_show_text_glyphs = state->getFont()->hasToUnicodeCMap() &&
cairo_surface_has_show_text_glyphs (cairo_get_target (cairo));
double fontSize = state->getFontSize();
double *m = state->getTextMat();
/* NOTE: adjusting by a constant is hack. The correct solution
* is probably to use user-fonts and compute the scale on a per
* glyph basis instead of for the entire font */
double w = currentFont->getSubstitutionCorrection(state->getFont());
matrix.xx = m[0] * fontSize * state->getHorizScaling() * w;
matrix.yx = m[1] * fontSize * state->getHorizScaling() * w;
matrix.xy = -m[2] * fontSize;
matrix.yy = -m[3] * fontSize;
matrix.x0 = 0;
matrix.y0 = 0;
LOG(printf ("font matrix: %f %f %f %f\n", matrix.xx, matrix.yx, matrix.xy, matrix.yy));
/* Make sure the font matrix is invertible before setting it. cairo
* will blow up if we give it a matrix that's not invertible, so we
* need to check before passing it to cairo_set_font_matrix. Ignoring it
* is likely to give better results than not rendering anything at
* all. See #18254.
*/
invert_matrix = matrix;
if (cairo_matrix_invert(&invert_matrix)) {
error(errSyntaxWarning, -1, "font matrix not invertible\n");
return;
}
cairo_set_font_matrix (cairo, &matrix);
}
/* Tolerance in pixels for checking if strokes are horizontal or vertical
* lines in device space */
#define STROKE_COORD_TOLERANCE 0.5
/* Align stroke coordinate i if the point is the start or end of a
* horizontal or vertical line */
void CairoOutputDev::alignStrokeCoords(GfxSubpath *subpath, int i, double *x, double *y)
{
double x1, y1, x2, y2;
GBool align = gFalse;
x1 = subpath->getX(i);
y1 = subpath->getY(i);
cairo_user_to_device (cairo, &x1, &y1);
// Does the current coord and prev coord form a horiz or vert line?
if (i > 0 && !subpath->getCurve(i - 1)) {
x2 = subpath->getX(i - 1);
y2 = subpath->getY(i - 1);
cairo_user_to_device (cairo, &x2, &y2);
if (fabs(x2 - x1) < STROKE_COORD_TOLERANCE || fabs(y2 - y1) < STROKE_COORD_TOLERANCE)
align = gTrue;
}
// Does the current coord and next coord form a horiz or vert line?
if (i < subpath->getNumPoints() - 1 && !subpath->getCurve(i + 1)) {
x2 = subpath->getX(i + 1);
y2 = subpath->getY(i + 1);
cairo_user_to_device (cairo, &x2, &y2);
if (fabs(x2 - x1) < STROKE_COORD_TOLERANCE || fabs(y2 - y1) < STROKE_COORD_TOLERANCE)
align = gTrue;
}
*x = subpath->getX(i);
*y = subpath->getY(i);
if (align) {
/* see http://www.cairographics.org/FAQ/#sharp_lines */
cairo_user_to_device (cairo, x, y);
*x = floor(*x) + 0.5;
*y = floor(*y) + 0.5;
cairo_device_to_user (cairo, x, y);
}
}
#undef STROKE_COORD_TOLERANCE
void CairoOutputDev::doPath(cairo_t *cairo, GfxState *state, GfxPath *path) {
GfxSubpath *subpath;
int i, j;
double x, y;
cairo_new_path (cairo);
for (i = 0; i < path->getNumSubpaths(); ++i) {
subpath = path->getSubpath(i);
if (subpath->getNumPoints() > 0) {
if (align_stroke_coords) {
alignStrokeCoords(subpath, 0, &x, &y);
} else {
x = subpath->getX(0);
y = subpath->getY(0);
}
cairo_move_to (cairo, x, y);
j = 1;
while (j < subpath->getNumPoints()) {
if (subpath->getCurve(j)) {
if (align_stroke_coords) {
alignStrokeCoords(subpath, j + 2, &x, &y);
} else {
x = subpath->getX(j+2);
y = subpath->getY(j+2);
}
cairo_curve_to( cairo,
subpath->getX(j), subpath->getY(j),
subpath->getX(j+1), subpath->getY(j+1),
x, y);
j += 3;
} else {
if (align_stroke_coords) {
alignStrokeCoords(subpath, j, &x, &y);
} else {
x = subpath->getX(j);
y = subpath->getY(j);
}
cairo_line_to (cairo, x, y);
++j;
}
}
if (subpath->isClosed()) {
LOG (printf ("close\n"));
cairo_close_path (cairo);
}
}
}
}
void CairoOutputDev::stroke(GfxState *state) {
if (inType3Char) {
GfxGray gray;
state->getFillGray(&gray);
if (colToDbl(gray) > 0.5)
return;
}
if (adjusted_stroke_width)
align_stroke_coords = gTrue;
doPath (cairo, state, state->getPath());
align_stroke_coords = gFalse;
cairo_set_source (cairo, stroke_pattern);
LOG(printf ("stroke\n"));
cairo_stroke (cairo);
if (cairo_shape) {
doPath (cairo_shape, state, state->getPath());
cairo_stroke (cairo_shape);
}
}
void CairoOutputDev::fill(GfxState *state) {
if (inType3Char) {
GfxGray gray;
state->getFillGray(&gray);
if (colToDbl(gray) > 0.5)
return;
}
doPath (cairo, state, state->getPath());
cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_WINDING);
cairo_set_source (cairo, fill_pattern);
LOG(printf ("fill\n"));
//XXX: how do we get the path
if (mask) {
cairo_save (cairo);
cairo_clip (cairo);
cairo_set_matrix (cairo, &mask_matrix);
cairo_mask (cairo, mask);
cairo_restore (cairo);
} else if (strokePathClip) {
fillToStrokePathClip(state);
} else {
cairo_fill (cairo);
}
if (cairo_shape) {
cairo_set_fill_rule (cairo_shape, CAIRO_FILL_RULE_WINDING);
doPath (cairo_shape, state, state->getPath());
cairo_fill (cairo_shape);
}
}
void CairoOutputDev::eoFill(GfxState *state) {
doPath (cairo, state, state->getPath());
cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD);
cairo_set_source (cairo, fill_pattern);
LOG(printf ("fill-eo\n"));
cairo_fill (cairo);
if (cairo_shape) {
cairo_set_fill_rule (cairo_shape, CAIRO_FILL_RULE_EVEN_ODD);
doPath (cairo_shape, state, state->getPath());
cairo_fill (cairo_shape);
}
}
GBool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx1, Catalog *cat, Object *str,
double *pmat, int paintType, int /*tilingType*/, Dict *resDict,
double *mat, double *bbox,
int x0, int y0, int x1, int y1,
double xStep, double yStep)
{
PDFRectangle box;
Gfx *gfx;
cairo_pattern_t *pattern;
cairo_surface_t *surface;
cairo_matrix_t matrix;
cairo_t *old_cairo;
double xMin, yMin, xMax, yMax;
double width, height;
int surface_width, surface_height;
StrokePathClip *strokePathTmp;
width = bbox[2] - bbox[0];
height = bbox[3] - bbox[1];
if (xStep != width || yStep != height)
return gFalse;
/* TODO: implement the other cases here too */
surface_width = (int) ceil (width);
surface_height = (int) ceil (height);
surface = cairo_surface_create_similar (cairo_get_target (cairo),
CAIRO_CONTENT_COLOR_ALPHA,
surface_width, surface_height);
if (cairo_surface_status (surface))
return gFalse;
old_cairo = cairo;
cairo = cairo_create (surface);
cairo_surface_destroy (surface);
box.x1 = bbox[0]; box.y1 = bbox[1];
box.x2 = bbox[2]; box.y2 = bbox[3];
strokePathTmp = strokePathClip;
strokePathClip = NULL;
gfx = new Gfx(doc, this, resDict, &box, NULL);
gfx->display(str);
delete gfx;
strokePathClip = strokePathTmp;
pattern = cairo_pattern_create_for_surface (cairo_get_target (cairo));
cairo_destroy (cairo);
cairo = old_cairo;
if (cairo_pattern_status (pattern))
return gFalse;
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
cairo_rectangle (cairo, xMin, yMin, xMax - xMin, yMax - yMin);
cairo_matrix_init_scale (&matrix, surface_width / width, surface_height / height);
cairo_pattern_set_matrix (pattern, &matrix);
cairo_matrix_init (&matrix, mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
cairo_transform (cairo, &matrix);
cairo_set_source (cairo, pattern);
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
if (strokePathClip) {
fillToStrokePathClip(state);
} else {
cairo_fill (cairo);
}
cairo_pattern_destroy (pattern);
return gTrue;
}
GBool CairoOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax) {
double x0, y0, x1, y1;
double dx, dy;
shading->getCoords(&x0, &y0, &x1, &y1);
dx = x1 - x0;
dy = y1 - y0;
cairo_pattern_destroy(fill_pattern);
fill_pattern = cairo_pattern_create_linear (x0 + tMin * dx, y0 + tMin * dy,
x0 + tMax * dx, y0 + tMax * dy);
if (!shading->getExtend0() && !shading->getExtend1())
cairo_pattern_set_extend (fill_pattern, CAIRO_EXTEND_NONE);
else
cairo_pattern_set_extend (fill_pattern, CAIRO_EXTEND_PAD);
LOG (printf ("axial-sh\n"));
// TODO: use the actual stops in the shading in the case
// of linear interpolation (Type 2 Exponential functions with N=1)
return gFalse;
}
GBool CairoOutputDev::axialShadedSupportExtend(GfxState *state, GfxAxialShading *shading)
{
return (shading->getExtend0() == shading->getExtend1());
}
GBool CairoOutputDev::radialShadedFill(GfxState *state, GfxRadialShading *shading, double sMin, double sMax) {
double x0, y0, r0, x1, y1, r1;
double dx, dy, dr;
shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
dx = x1 - x0;
dy = y1 - y0;
dr = r1 - r0;
cairo_pattern_destroy(fill_pattern);
fill_pattern = cairo_pattern_create_radial (x0 + sMin * dx,
y0 + sMin * dy,
r0 + sMin * dr,
x0 + sMax * dx,
y0 + sMax * dy,
r0 + sMax * dr);
if (shading->getExtend0() && shading->getExtend1())
cairo_pattern_set_extend (fill_pattern, CAIRO_EXTEND_PAD);
else
cairo_pattern_set_extend (fill_pattern, CAIRO_EXTEND_NONE);
LOG (printf ("radial-sh\n"));
return gFalse;
}
GBool CairoOutputDev::radialShadedSupportExtend(GfxState *state, GfxRadialShading *shading)
{
return (shading->getExtend0() == shading->getExtend1());
}
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
GBool CairoOutputDev::gouraudTriangleShadedFill(GfxState *state, GfxGouraudTriangleShading *shading)
{
double x0, y0, x1, y1, x2, y2;
GfxColor color[3];
int i, j;
GfxRGB rgb;
cairo_pattern_destroy(fill_pattern);
fill_pattern = cairo_pattern_create_mesh ();
for (i = 0; i < shading->getNTriangles(); i++) {
shading->getTriangle(i,
&x0, &y0, &color[0],
&x1, &y1, &color[1],
&x2, &y2, &color[2]);
cairo_mesh_pattern_begin_patch (fill_pattern);
cairo_mesh_pattern_move_to (fill_pattern, x0, y0);
cairo_mesh_pattern_line_to (fill_pattern, x1, y1);
cairo_mesh_pattern_line_to (fill_pattern, x2, y2);
for (j = 0; j < 3; j++) {
shading->getColorSpace()->getRGB(&color[j], &rgb);
cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, j,
colToDbl(rgb.r),
colToDbl(rgb.g),
colToDbl(rgb.b));
}
cairo_mesh_pattern_end_patch (fill_pattern);
}
double xMin, yMin, xMax, yMax;
// get the clip region bbox
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
state->moveTo(xMin, yMin);
state->lineTo(xMin, yMax);
state->lineTo(xMax, yMax);
state->lineTo(xMax, yMin);
state->closePath();
fill(state);
state->clearPath();
return gTrue;
}
GBool CairoOutputDev::patchMeshShadedFill(GfxState *state, GfxPatchMeshShading *shading)
{
int i, j, k;
cairo_pattern_destroy(fill_pattern);
fill_pattern = cairo_pattern_create_mesh ();
for (i = 0; i < shading->getNPatches(); i++) {
GfxPatch *patch = shading->getPatch(i);
GfxColor color;
GfxRGB rgb;
cairo_mesh_pattern_begin_patch (fill_pattern);
cairo_mesh_pattern_move_to (fill_pattern, patch->x[0][0], patch->y[0][0]);
cairo_mesh_pattern_curve_to (fill_pattern,
patch->x[0][1], patch->y[0][1],
patch->x[0][2], patch->y[0][2],
patch->x[0][3], patch->y[0][3]);
cairo_mesh_pattern_curve_to (fill_pattern,
patch->x[1][3], patch->y[1][3],
patch->x[2][3], patch->y[2][3],
patch->x[3][3], patch->y[3][3]);
cairo_mesh_pattern_curve_to (fill_pattern,
patch->x[3][2], patch->y[3][2],
patch->x[3][1], patch->y[3][1],
patch->x[3][0], patch->y[3][0]);
cairo_mesh_pattern_curve_to (fill_pattern,
patch->x[2][0], patch->y[2][0],
patch->x[1][0], patch->y[1][0],
patch->x[0][0], patch->y[0][0]);
cairo_mesh_pattern_set_control_point (fill_pattern, 0, patch->x[1][1], patch->y[1][1]);
cairo_mesh_pattern_set_control_point (fill_pattern, 1, patch->x[1][2], patch->y[1][2]);
cairo_mesh_pattern_set_control_point (fill_pattern, 2, patch->x[2][2], patch->y[2][2]);
cairo_mesh_pattern_set_control_point (fill_pattern, 3, patch->x[2][1], patch->y[2][1]);
for (j = 0; j < 4; j++) {
int u, v;
switch (j) {
case 0:
u = 0; v = 0;
break;
case 1:
u = 0; v = 1;
break;
case 2:
u = 1; v = 1;
break;
case 3:
u = 1; v = 0;
break;
}
if (shading->isParameterized()) {
shading->getParameterizedColor (patch->color[u][v].c[0], &color);
} else {
for (k = 0; k < shading->getColorSpace()->getNComps(); k++) {
// simply cast to the desired type; that's all what is needed.
color.c[k] = GfxColorComp (patch->color[u][v].c[k]);
}
}
shading->getColorSpace()->getRGB(&color, &rgb);
cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, j,
colToDbl(rgb.r),
colToDbl(rgb.g),
colToDbl(rgb.b));
}
cairo_mesh_pattern_end_patch (fill_pattern);
}
double xMin, yMin, xMax, yMax;
// get the clip region bbox
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
state->moveTo(xMin, yMin);
state->lineTo(xMin, yMax);
state->lineTo(xMax, yMax);
state->lineTo(xMax, yMin);
state->closePath();
fill(state);
state->clearPath();
return gTrue;
}
#endif /* CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) */
void CairoOutputDev::clip(GfxState *state) {
doPath (cairo, state, state->getPath());
cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_WINDING);
cairo_clip (cairo);
LOG (printf ("clip\n"));
if (cairo_shape) {
doPath (cairo_shape, state, state->getPath());
cairo_set_fill_rule (cairo_shape, CAIRO_FILL_RULE_WINDING);
cairo_clip (cairo_shape);
}
}
void CairoOutputDev::eoClip(GfxState *state) {
doPath (cairo, state, state->getPath());
cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD);
cairo_clip (cairo);
LOG (printf ("clip-eo\n"));
if (cairo_shape) {
doPath (cairo_shape, state, state->getPath());
cairo_set_fill_rule (cairo_shape, CAIRO_FILL_RULE_EVEN_ODD);
cairo_clip (cairo_shape);
}
}
void CairoOutputDev::clipToStrokePath(GfxState *state) {
LOG(printf("clip-to-stroke-path\n"));
strokePathClip = (StrokePathClip*)gmalloc (sizeof(*strokePathClip));
strokePathClip->path = state->getPath()->copy();
cairo_get_matrix (cairo, &strokePathClip->ctm);
strokePathClip->line_width = cairo_get_line_width (cairo);
strokePathClip->dash_count = cairo_get_dash_count (cairo);
if (strokePathClip->dash_count) {
strokePathClip->dashes = (double*) gmallocn (sizeof(double), strokePathClip->dash_count);
cairo_get_dash (cairo, strokePathClip->dashes, &strokePathClip->dash_offset);
} else {
strokePathClip->dashes = NULL;
}
strokePathClip->cap = cairo_get_line_cap (cairo);
strokePathClip->join = cairo_get_line_join (cairo);
strokePathClip->miter = cairo_get_miter_limit (cairo);
}
void CairoOutputDev::fillToStrokePathClip(GfxState *state) {
cairo_save (cairo);
cairo_set_matrix (cairo, &strokePathClip->ctm);
cairo_set_line_width (cairo, strokePathClip->line_width);
strokePathClip->dash_count = cairo_get_dash_count (cairo);
cairo_set_dash (cairo, strokePathClip->dashes, strokePathClip->dash_count, strokePathClip->dash_offset);
cairo_set_line_cap (cairo, strokePathClip->cap);
cairo_set_line_join (cairo, strokePathClip->join);
cairo_set_miter_limit (cairo, strokePathClip->miter);
doPath (cairo, state, strokePathClip->path);
cairo_stroke (cairo);
cairo_restore (cairo);
delete strokePathClip->path;
if (strokePathClip->dashes)
gfree (strokePathClip->dashes);
gfree (strokePathClip);
strokePathClip = NULL;
}
void CairoOutputDev::beginString(GfxState *state, GooString *s)
{
int len = s->getLength();
if (needFontUpdate)
updateFont(state);
if (!currentFont)
return;
glyphs = (cairo_glyph_t *) gmallocn (len, sizeof (cairo_glyph_t));
glyphCount = 0;
if (use_show_text_glyphs) {
clusters = (cairo_text_cluster_t *) gmallocn (len, sizeof (cairo_text_cluster_t));
clusterCount = 0;
utf8Max = len*2; // start with twice the number of glyphs. we will realloc if we need more.
utf8 = (char *) gmalloc (utf8Max);
utf8Count = 0;
}
}
void CairoOutputDev::drawChar(GfxState *state, double x, double y,
double dx, double dy,
double originX, double originY,
CharCode code, int nBytes, Unicode *u, int uLen)
{
if (currentFont) {
glyphs[glyphCount].index = currentFont->getGlyph (code, u, uLen);
glyphs[glyphCount].x = x - originX;
glyphs[glyphCount].y = y - originY;
glyphCount++;
if (use_show_text_glyphs) {
if (utf8Max - utf8Count < uLen*6) {
// utf8 encoded characters can be up to 6 bytes
if (utf8Max > uLen*6)
utf8Max *= 2;
else
utf8Max += 2*uLen*6;
utf8 = (char *) grealloc (utf8, utf8Max);
}
clusters[clusterCount].num_bytes = 0;
for (int i = 0; i < uLen; i++) {
int size = mapUTF8 (u[i], utf8 + utf8Count, utf8Max - utf8Count);
utf8Count += size;
clusters[clusterCount].num_bytes += size;
}
clusters[clusterCount].num_glyphs = 1;
clusterCount++;
}
}
if (!text)
return;
actualText->addChar (state, x, y, dx, dy, code, nBytes, u, uLen);
}
void CairoOutputDev::endString(GfxState *state)
{
int render;
if (!currentFont)
return;
// endString can be called without a corresponding beginString. If this
// happens glyphs will be null so don't draw anything, just return.
// XXX: OutputDevs should probably not have to deal with this...
if (!glyphs)
return;
// ignore empty strings and invisible text -- this is used by
// Acrobat Capture
render = state->getRender();
if (render == 3 || glyphCount == 0) {
gfree(glyphs);
glyphs = NULL;
return;
}
if (!(render & 1)) {
LOG (printf ("fill string\n"));
cairo_set_source (cairo, fill_pattern);
if (use_show_text_glyphs)
cairo_show_text_glyphs (cairo, utf8, utf8Count, glyphs, glyphCount, clusters, clusterCount, (cairo_text_cluster_flags_t)0);
else
cairo_show_glyphs (cairo, glyphs, glyphCount);
if (cairo_shape)
cairo_show_glyphs (cairo_shape, glyphs, glyphCount);
}
// stroke
if ((render & 3) == 1 || (render & 3) == 2) {
LOG (printf ("stroke string\n"));
cairo_set_source (cairo, stroke_pattern);
cairo_glyph_path (cairo, glyphs, glyphCount);
cairo_stroke (cairo);
if (cairo_shape) {
cairo_glyph_path (cairo_shape, glyphs, glyphCount);
cairo_stroke (cairo_shape);
}
}
// clip
if ((render & 4)) {
LOG (printf ("clip string\n"));
// append the glyph path to textClipPath.
// set textClipPath as the currentPath
if (textClipPath) {
cairo_append_path (cairo, textClipPath);
if (cairo_shape) {
cairo_append_path (cairo_shape, textClipPath);
}
cairo_path_destroy (textClipPath);
}
// append the glyph path
cairo_glyph_path (cairo, glyphs, glyphCount);
// move the path back into textClipPath
// and clear the current path
textClipPath = cairo_copy_path (cairo);
cairo_new_path (cairo);
if (cairo_shape) {
cairo_new_path (cairo_shape);
}
}
gfree (glyphs);
glyphs = NULL;
if (use_show_text_glyphs) {
gfree (clusters);
clusters = NULL;
gfree (utf8);
utf8 = NULL;
}
}
GBool CairoOutputDev::beginType3Char(GfxState *state, double x, double y,
double dx, double dy,
CharCode code, Unicode *u, int uLen) {
cairo_save (cairo);
double *ctm;
cairo_matrix_t matrix;
ctm = state->getCTM();
matrix.xx = ctm[0];
matrix.yx = ctm[1];
matrix.xy = ctm[2];
matrix.yy = ctm[3];
matrix.x0 = ctm[4];
matrix.y0 = ctm[5];
/* Restore the original matrix and then transform to matrix needed for the
* type3 font. This is ugly but seems to work. Perhaps there is a better way to do it?*/
cairo_set_matrix(cairo, &orig_matrix);
cairo_transform(cairo, &matrix);
if (cairo_shape) {
cairo_save (cairo_shape);
cairo_set_matrix(cairo_shape, &orig_matrix);
cairo_transform(cairo_shape, &matrix);
}
cairo_pattern_destroy(stroke_pattern);
cairo_pattern_reference(fill_pattern);
stroke_pattern = fill_pattern;
return gFalse;
}
void CairoOutputDev::endType3Char(GfxState *state) {
cairo_restore (cairo);
if (cairo_shape) {
cairo_restore (cairo_shape);
}
}
void CairoOutputDev::type3D0(GfxState *state, double wx, double wy) {
t3_glyph_wx = wx;
t3_glyph_wy = wy;
}
void CairoOutputDev::type3D1(GfxState *state, double wx, double wy,
double llx, double lly, double urx, double ury) {
t3_glyph_wx = wx;
t3_glyph_wy = wy;
t3_glyph_bbox[0] = llx;
t3_glyph_bbox[1] = lly;
t3_glyph_bbox[2] = urx;
t3_glyph_bbox[3] = ury;
t3_glyph_has_bbox = gTrue;
}
void CairoOutputDev::beginTextObject(GfxState *state) {
}
void CairoOutputDev::endTextObject(GfxState *state) {
if (textClipPath) {
// clip the accumulated text path
cairo_append_path (cairo, textClipPath);
cairo_clip (cairo);
if (cairo_shape) {
cairo_append_path (cairo_shape, textClipPath);
cairo_clip (cairo_shape);
}
cairo_path_destroy (textClipPath);
textClipPath = NULL;
}
}
void CairoOutputDev::beginActualText(GfxState *state, GooString *text)
{
if (this->text)
actualText->begin(state, text);
}
void CairoOutputDev::endActualText(GfxState *state)
{
if (text)
actualText->end(state);
}
static inline int splashRound(SplashCoord x) {
return (int)floor(x + 0.5);
}
static inline int splashCeil(SplashCoord x) {
return (int)ceil(x);
}
static inline int splashFloor(SplashCoord x) {
return (int)floor(x);
}
static
cairo_surface_t *cairo_surface_create_similar_clip (cairo_t *cairo, cairo_content_t content)
{
double x1, y1, x2, y2;
int width, height;
cairo_clip_extents (cairo, &x1, &y1, &x2, &y2);
cairo_matrix_t matrix;
cairo_get_matrix (cairo, &matrix);
//cairo_matrix_transform_point(&matrix, &x1, &y1);
//cairo_matrix_transform_point(&matrix, &x2, &y2);*/
cairo_user_to_device(cairo, &x1, &y1);
cairo_user_to_device(cairo, &x2, &y2);
width = splashCeil(x2) - splashFloor(x1);
//XXX: negative matrix
////height = splashCeil(y2) - splashFloor(y1);
height = splashFloor(y1) - splashCeil(y2);
cairo_surface_t *target = cairo_get_target (cairo);
cairo_surface_t *result;
result = cairo_surface_create_similar (target, content, width, height);
double x_offset, y_offset;
cairo_surface_get_device_offset(target, &x_offset, &y_offset);
cairo_surface_set_device_offset(result, x_offset, y_offset);
return result;
}
void CairoOutputDev::beginTransparencyGroup(GfxState * /*state*/, double * /*bbox*/,
GfxColorSpace * blendingColorSpace,
GBool /*isolated*/, GBool knockout,
GBool forSoftMask) {
/* push color space */
ColorSpaceStack* css = new ColorSpaceStack;
css->cs = blendingColorSpace;
css->knockout = knockout;
cairo_get_matrix(cairo, &css->group_matrix);
css->next = groupColorSpaceStack;
groupColorSpaceStack = css;
LOG(printf ("begin transparency group. knockout: %s\n", knockout ? "yes":"no"));
if (knockout) {
knockoutCount++;
if (!cairo_shape) {
/* create a surface for tracking the shape */
cairo_surface_t *cairo_shape_surface = cairo_surface_create_similar_clip (cairo, CAIRO_CONTENT_ALPHA);
cairo_shape = cairo_create (cairo_shape_surface);
cairo_surface_destroy (cairo_shape_surface);
/* the color doesn't matter as long as it is opaque */
cairo_set_source_rgb (cairo_shape, 0, 0, 0);
cairo_matrix_t matrix;
cairo_get_matrix (cairo, &matrix);
//printMatrix(&matrix);
cairo_set_matrix (cairo_shape, &matrix);
} else {
cairo_reference (cairo_shape);
}
}
if (groupColorSpaceStack->next && groupColorSpaceStack->next->knockout) {
/* we need to track the shape */
cairo_push_group (cairo_shape);
}
if (0 && forSoftMask)
cairo_push_group_with_content (cairo, CAIRO_CONTENT_ALPHA);
else
cairo_push_group (cairo);
/* push_group has an implicit cairo_save() */
if (knockout) {
/*XXX: let's hope this matches the semantics needed */
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
} else {
cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
}
}
void CairoOutputDev::endTransparencyGroup(GfxState * /*state*/) {
if (group)
cairo_pattern_destroy(group);
group = cairo_pop_group (cairo);
LOG(printf ("end transparency group\n"));
if (groupColorSpaceStack->next && groupColorSpaceStack->next->knockout) {
if (shape)
cairo_pattern_destroy(shape);
shape = cairo_pop_group (cairo_shape);
}
}
void CairoOutputDev::paintTransparencyGroup(GfxState * /*state*/, double * /*bbox*/) {
LOG(printf ("paint transparency group\n"));
cairo_save (cairo);
cairo_set_matrix (cairo, &groupColorSpaceStack->group_matrix);
cairo_set_source (cairo, group);
if (!mask) {
//XXX: deal with mask && shape case
if (shape) {
cairo_save (cairo);
/* OPERATOR_SOURCE w/ a mask is defined as (src IN mask) ADD (dest OUT mask)
* however our source has already been clipped to mask so we only need to
* do ADD and OUT */
/* clear the shape mask */
cairo_set_source (cairo, shape);
cairo_set_operator (cairo, CAIRO_OPERATOR_DEST_OUT);
cairo_paint (cairo);
cairo_set_operator (cairo, CAIRO_OPERATOR_ADD);
cairo_set_source (cairo, group);
cairo_paint (cairo);
cairo_restore (cairo);
cairo_pattern_destroy (shape);
shape = NULL;
} else {
cairo_paint_with_alpha (cairo, fill_opacity);
}
cairo_status_t status = cairo_status(cairo);
if (status)
printf("BAD status: %s\n", cairo_status_to_string(status));
} else {
if (fill_opacity < 1.0) {
cairo_push_group(cairo);
}
cairo_save(cairo);
cairo_set_matrix(cairo, &mask_matrix);
cairo_mask(cairo, mask);
cairo_restore(cairo);
if (fill_opacity < 1.0) {
cairo_pop_group_to_source(cairo);
cairo_paint_with_alpha (cairo, fill_opacity);
}
cairo_pattern_destroy(mask);
mask = NULL;
}
popTransparencyGroup();
cairo_restore(cairo);
}
static int luminocity(uint32_t x)
{
int r = (x >> 16) & 0xff;
int g = (x >> 8) & 0xff;
int b = (x >> 0) & 0xff;
// an arbitrary integer approximation of .3*r + .59*g + .11*b
int y = (r*19661+g*38666+b*7209 + 32829)>>16;
return y;
}
/* XXX: do we need to deal with shape here? */
void CairoOutputDev::setSoftMask(GfxState * state, double * bbox, GBool alpha,
Function * transferFunc, GfxColor * backdropColor) {
cairo_pattern_destroy(mask);
LOG(printf ("set softMask\n"));
if (alpha == false) {
/* We need to mask according to the luminocity of the group.
* So we paint the group to an image surface convert it to a luminocity map
* and then use that as the mask. */
/* Get clip extents in device space */
double x1, y1, x2, y2, x_min, y_min, x_max, y_max;
cairo_clip_extents(cairo, &x1, &y1, &x2, &y2);
cairo_user_to_device(cairo, &x1, &y1);
cairo_user_to_device(cairo, &x2, &y2);
x_min = MIN(x1, x2);
y_min = MIN(y1, y2);
x_max = MAX(x1, x2);
y_max = MAX(y1, y2);
cairo_clip_extents(cairo, &x1, &y1, &x2, &y2);
cairo_user_to_device(cairo, &x1, &y2);
cairo_user_to_device(cairo, &x2, &y1);
x_min = MIN(x_min,MIN(x1, x2));
y_min = MIN(y_min,MIN(y1, y2));
x_max = MAX(x_max,MAX(x1, x2));
y_max = MAX(y_max,MAX(y1, y2));
int width = (int)(ceil(x_max) - floor(x_min));
int height = (int)(ceil(y_max) - floor(y_min));
/* Get group device offset */
double x_offset, y_offset;
if (cairo_get_group_target(cairo) == cairo_get_target(cairo)) {
cairo_surface_get_device_offset(cairo_get_group_target(cairo), &x_offset, &y_offset);
} else {
cairo_surface_t *pats;
cairo_pattern_get_surface(group, &pats);
cairo_surface_get_device_offset(pats, &x_offset, &y_offset);
}
/* Adjust extents by group offset */
x_min += x_offset;
y_min += y_offset;
cairo_surface_t *source = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cairo_t *maskCtx = cairo_create(source);
//XXX: hopefully this uses the correct color space */
GfxRGB backdropColorRGB;
groupColorSpaceStack->cs->getRGB(backdropColor, &backdropColorRGB);
/* paint the backdrop */
cairo_set_source_rgb(maskCtx,
colToDbl(backdropColorRGB.r),
colToDbl(backdropColorRGB.g),
colToDbl(backdropColorRGB.b));
cairo_paint(maskCtx);
/* Copy source ctm to mask ctm and translate origin so that the
* mask appears it the same location on the source surface. */
cairo_matrix_t mat, tmat;
cairo_matrix_init_translate(&tmat, -x_min, -y_min);
cairo_get_matrix(cairo, &mat);
cairo_matrix_multiply(&mat, &mat, &tmat);
cairo_set_matrix(maskCtx, &mat);
/* make the device offset of the new mask match that of the group */
cairo_surface_set_device_offset(source, x_offset, y_offset);
/* paint the group */
cairo_set_source(maskCtx, group);
cairo_paint(maskCtx);
/* XXX status = cairo_status(maskCtx); */
cairo_destroy(maskCtx);
/* convert to a luminocity map */
uint32_t *source_data = (uint32_t*)cairo_image_surface_get_data(source);
/* get stride in units of 32 bits */
int stride = cairo_image_surface_get_stride(source)/4;
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
int lum;
lum = luminocity(source_data[y*stride + x]);
if (transferFunc) {
double lum_in, lum_out;
lum_in = lum/256.0;
transferFunc->transform(&lum_in, &lum_out);
lum = (int)(lum_out * 255.0 + 0.5);
}
source_data[y*stride + x] = lum << 24;
}
}
cairo_surface_mark_dirty (source);
/* setup the new mask pattern */
mask = cairo_pattern_create_for_surface(source);
cairo_get_matrix(cairo, &mask_matrix);
if (cairo_get_group_target(cairo) == cairo_get_target(cairo)) {
cairo_pattern_set_matrix(mask, &mat);
} else {
cairo_matrix_t patMatrix;
cairo_pattern_get_matrix(group, &patMatrix);
/* Apply x_min, y_min offset to it appears in the same location as source. */
cairo_matrix_multiply(&patMatrix, &patMatrix, &tmat);
cairo_pattern_set_matrix(mask, &patMatrix);
}
cairo_surface_destroy(source);
} else {
mask = cairo_pattern_reference(group);
cairo_get_matrix(cairo, &mask_matrix);
}
popTransparencyGroup();
}
void CairoOutputDev::popTransparencyGroup() {
/* pop color space */
ColorSpaceStack *css = groupColorSpaceStack;
if (css->knockout) {
knockoutCount--;
if (!knockoutCount) {
/* we don't need to track the shape anymore because
* we are not above any knockout groups */
cairo_destroy(cairo_shape);
cairo_shape = NULL;
}
}
groupColorSpaceStack = css->next;
delete css;
}
void CairoOutputDev::clearSoftMask(GfxState * /*state*/) {
if (mask)
cairo_pattern_destroy(mask);
mask = NULL;
}
/* Taken from cairo/doc/tutorial/src/singular.c */
static void
get_singular_values (const cairo_matrix_t *matrix,
double *major,
double *minor)
{
double xx = matrix->xx, xy = matrix->xy;
double yx = matrix->yx, yy = matrix->yy;
double a = xx*xx+yx*yx;
double b = xy*xy+yy*yy;
double k = xx*xy+yx*yy;
double f = (a+b) * .5;
double g = (a-b) * .5;
double delta = sqrt (g*g + k*k);
if (major)
*major = sqrt (f + delta);
if (minor)
*minor = sqrt (f - delta);
}
void CairoOutputDev::getScaledSize(int orig_width,
int orig_height,
int *scaledWidth,
int *scaledHeight) {
cairo_matrix_t matrix;
cairo_get_matrix(cairo, &matrix);
double xScale;
double yScale;
if (orig_width > orig_height)
get_singular_values (&matrix, &xScale, &yScale);
else
get_singular_values (&matrix, &yScale, &xScale);
int tx, tx2, ty, ty2; /* the integer co-oridinates of the resulting image */
if (xScale >= 0) {
tx = splashRound(matrix.x0 - 0.01);
tx2 = splashRound(matrix.x0 + xScale + 0.01) - 1;
} else {
tx = splashRound(matrix.x0 + 0.01) - 1;
tx2 = splashRound(matrix.x0 + xScale - 0.01);
}
*scaledWidth = abs(tx2 - tx) + 1;
//scaledWidth = splashRound(fabs(xScale));
if (*scaledWidth == 0) {
// technically, this should draw nothing, but it generally seems
// better to draw a one-pixel-wide stripe rather than throwing it
// away
*scaledWidth = 1;
}
if (yScale >= 0) {
ty = splashFloor(matrix.y0 + 0.01);
ty2 = splashCeil(matrix.y0 + yScale - 0.01);
} else {
ty = splashCeil(matrix.y0 - 0.01);
ty2 = splashFloor(matrix.y0 + yScale + 0.01);
}
*scaledHeight = abs(ty2 - ty);
if (*scaledHeight == 0) {
*scaledHeight = 1;
}
}
cairo_surface_t *CairoOutputDev::downscaleSurface(cairo_surface_t *orig_surface) {
cairo_surface_t *dest_surface;
unsigned char *dest_buffer;
int dest_stride;
unsigned char *orig_buffer;
int orig_width, orig_height;
int orig_stride;
int scaledHeight;
int scaledWidth;
GBool res;
if (printing)
return NULL;
orig_width = cairo_image_surface_get_width (orig_surface);
orig_height = cairo_image_surface_get_height (orig_surface);
getScaledSize (orig_width, orig_height, &scaledWidth, &scaledHeight);
if (scaledWidth >= orig_width || scaledHeight >= orig_height)
return NULL;
dest_surface = cairo_surface_create_similar (orig_surface,
cairo_surface_get_content (orig_surface),
scaledWidth, scaledHeight);
dest_buffer = cairo_image_surface_get_data (dest_surface);
dest_stride = cairo_image_surface_get_stride (dest_surface);
orig_buffer = cairo_image_surface_get_data (orig_surface);
orig_stride = cairo_image_surface_get_stride (orig_surface);
res = downscale_box_filter((uint32_t *)orig_buffer,
orig_stride, orig_width, orig_height,
scaledWidth, scaledHeight, 0, 0,
scaledWidth, scaledHeight,
(uint32_t *)dest_buffer, dest_stride);
if (!res) {
cairo_surface_destroy (dest_surface);
return NULL;
}
return dest_surface;
}
cairo_filter_t
CairoOutputDev::getFilterForSurface(cairo_surface_t *image,
GBool interpolate)
{
if (interpolate)
return CAIRO_FILTER_BILINEAR;
int orig_width = cairo_image_surface_get_width (image);
int orig_height = cairo_image_surface_get_height (image);
if (orig_width == 0 || orig_height == 0)
return CAIRO_FILTER_NEAREST;
int scaled_width, scaled_height;
getScaledSize (orig_width, orig_height, &scaled_width, &scaled_height);
/* When scale factor is >= 400% we don't interpolate. See bugs #25268, #9860 */
if (scaled_width / orig_width >= 4 || scaled_height / orig_height >= 4)
return CAIRO_FILTER_NEAREST;
return CAIRO_FILTER_BILINEAR;
}
void CairoOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
int width, int height, GBool invert,
GBool interpolate, GBool inlineImg) {
/* FIXME: Doesn't the image mask support any colorspace? */
cairo_set_source (cairo, fill_pattern);
/* work around a cairo bug when scaling 1x1 surfaces */
if (width == 1 && height == 1) {
ImageStream *imgStr;
Guchar pix;
int invert_bit;
imgStr = new ImageStream(str, width, 1, 1);
imgStr->reset();
imgStr->getPixel(&pix);
imgStr->close();
delete imgStr;
invert_bit = invert ? 1 : 0;
if (pix ^ invert_bit)
return;
cairo_save (cairo);
cairo_rectangle (cairo, 0., 0., width, height);
cairo_fill (cairo);
cairo_restore (cairo);
if (cairo_shape) {
cairo_save (cairo_shape);
cairo_rectangle (cairo_shape, 0., 0., width, height);
cairo_fill (cairo_shape);
cairo_restore (cairo_shape);
}
return;
}
/* shape is 1.0 for painted areas, 0.0 for unpainted ones */
cairo_matrix_t matrix;
cairo_get_matrix (cairo, &matrix);
//XXX: it is possible that we should only do sub pixel positioning if
// we are rendering fonts */
if (!printing && prescaleImages
/* not rotated */
&& matrix.xy == 0 && matrix.yx == 0
/* axes not flipped / not 180 deg rotated */
&& matrix.xx > 0 && (upsideDown() ? -1 : 1) * matrix.yy > 0) {
drawImageMaskPrescaled(state, ref, str, width, height, invert, interpolate, inlineImg);
} else {
drawImageMaskRegular(state, ref, str, width, height, invert, interpolate, inlineImg);
}
}
void CairoOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str,
int width, int height, GBool invert,
GBool inlineImg, double *baseMatrix) {
/* FIXME: Doesn't the image mask support any colorspace? */
cairo_set_source (cairo, fill_pattern);
/* work around a cairo bug when scaling 1x1 surfaces */
if (width == 1 && height == 1) {
ImageStream *imgStr;
Guchar pix;
int invert_bit;
imgStr = new ImageStream(str, width, 1, 1);
imgStr->reset();
imgStr->getPixel(&pix);
imgStr->close();
delete imgStr;
invert_bit = invert ? 1 : 0;
if (pix ^ invert_bit)
return;
cairo_save (cairo);
cairo_rectangle (cairo, 0., 0., width, height);
cairo_fill (cairo);
cairo_restore (cairo);
if (cairo_shape) {
cairo_save (cairo_shape);
cairo_rectangle (cairo_shape, 0., 0., width, height);
cairo_fill (cairo_shape);
cairo_restore (cairo_shape);
}
return;
}
cairo_push_group_with_content (cairo, CAIRO_CONTENT_ALPHA);
/* shape is 1.0 for painted areas, 0.0 for unpainted ones */
cairo_matrix_t matrix;
cairo_get_matrix (cairo, &matrix);
//XXX: it is possible that we should only do sub pixel positioning if
// we are rendering fonts */
if (!printing && prescaleImages && matrix.xy == 0.0 && matrix.yx == 0.0) {
drawImageMaskPrescaled(state, ref, str, width, height, invert, gFalse, inlineImg);
} else {
drawImageMaskRegular(state, ref, str, width, height, invert, gFalse, inlineImg);
}
if (state->getFillColorSpace()->getMode() == csPattern) {
cairo_set_source_rgb (cairo, 1, 1, 1);
cairo_set_matrix (cairo, &mask_matrix);
cairo_mask (cairo, mask);
}
if (mask)
cairo_pattern_destroy (mask);
mask = cairo_pop_group (cairo);
saveState(state);
double bbox[4] = {0,0,1,1}; // dummy
beginTransparencyGroup(state, bbox, state->getFillColorSpace(),
gTrue, gFalse, gFalse);
}
void CairoOutputDev::unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix) {
double bbox[4] = {0,0,1,1}; // dummy
endTransparencyGroup(state);
restoreState(state);
paintTransparencyGroup(state, bbox);
clearSoftMask(state);
}
void CairoOutputDev::drawImageMaskRegular(GfxState *state, Object *ref, Stream *str,
int width, int height, GBool invert,
GBool interpolate, GBool inlineImg) {
unsigned char *buffer;
unsigned char *dest;
cairo_surface_t *image;
cairo_pattern_t *pattern;
int x, y, i, bit;
ImageStream *imgStr;
Guchar *pix;
cairo_matrix_t matrix;
int invert_bit;
int row_stride;
cairo_filter_t filter;
/* TODO: Do we want to cache these? */
imgStr = new ImageStream(str, width, 1, 1);
imgStr->reset();
image = cairo_image_surface_create (CAIRO_FORMAT_A1, width, height);
if (cairo_surface_status (image))
goto cleanup;
buffer = cairo_image_surface_get_data (image);
row_stride = cairo_image_surface_get_stride (image);
invert_bit = invert ? 1 : 0;
for (y = 0; y < height; y++) {
pix = imgStr->getLine();
dest = buffer + y * row_stride;
i = 0;
bit = 0;
for (x = 0; x < width; x++) {
if (bit == 0)
dest[i] = 0;
if (!(pix[x] ^ invert_bit)) {
#ifdef WORDS_BIGENDIAN
dest[i] |= (1 << (7 - bit));
#else
dest[i] |= (1 << bit);
#endif
}
bit++;
if (bit > 7) {
bit = 0;
i++;
}
}
}
filter = getFilterForSurface (image, interpolate);
cairo_surface_mark_dirty (image);
pattern = cairo_pattern_create_for_surface (image);
cairo_surface_destroy (image);
if (cairo_pattern_status (pattern))
goto cleanup;
LOG (printf ("drawImageMask %dx%d\n", width, height));
cairo_pattern_set_filter (pattern, filter);
if (!printing)
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
cairo_matrix_init_translate (&matrix, 0, height);
cairo_matrix_scale (&matrix, width, -height);
cairo_pattern_set_matrix (pattern, &matrix);
if (cairo_pattern_status (pattern)) {
cairo_pattern_destroy (pattern);
goto cleanup;
}
if (state->getFillColorSpace()->getMode() == csPattern) {
mask = cairo_pattern_reference (pattern);
cairo_get_matrix (cairo, &mask_matrix);
} else if (!printing) {
cairo_save (cairo);
cairo_rectangle (cairo, 0., 0., 1., 1.);
cairo_clip (cairo);
cairo_mask (cairo, pattern);
cairo_restore (cairo);
} else {
cairo_mask (cairo, pattern);
}
if (cairo_shape) {
cairo_save (cairo_shape);
cairo_set_source (cairo_shape, pattern);
if (!printing) {
cairo_rectangle (cairo_shape, 0., 0., 1., 1.);
cairo_fill (cairo_shape);
} else {
cairo_mask (cairo_shape, pattern);
}
cairo_restore (cairo_shape);
}
cairo_pattern_destroy (pattern);
cleanup:
imgStr->close();
delete imgStr;
}
void CairoOutputDev::drawImageMaskPrescaled(GfxState *state, Object *ref, Stream *str,
int width, int height, GBool invert,
GBool interpolate, GBool inlineImg) {
unsigned char *buffer;
cairo_surface_t *image;
cairo_pattern_t *pattern;
ImageStream *imgStr;
Guchar *pix;
cairo_matrix_t matrix;
int invert_bit;
int row_stride;
/* cairo does a very poor job of scaling down images so we scale them ourselves */
LOG (printf ("drawImageMaskPrescaled %dx%d\n", width, height));
/* this scaling code is adopted from the splash image scaling code */
cairo_get_matrix(cairo, &matrix);
#if 0
printf("[%f %f], [%f %f], %f %f\n", matrix.xx, matrix.xy, matrix.yx, matrix.yy, matrix.x0, matrix.y0);
#endif
/* this whole computation should be factored out */
double xScale = matrix.xx;
double yScale = matrix.yy;
int tx, tx2, ty, ty2; /* the integer co-oridinates of the resulting image */
int scaledHeight;
int scaledWidth;
if (xScale >= 0) {
tx = splashRound(matrix.x0 - 0.01);
tx2 = splashRound(matrix.x0 + xScale + 0.01) - 1;
} else {
tx = splashRound(matrix.x0 + 0.01) - 1;
tx2 = splashRound(matrix.x0 + xScale - 0.01);
}
scaledWidth = abs(tx2 - tx) + 1;
//scaledWidth = splashRound(fabs(xScale));
if (scaledWidth == 0) {
// technically, this should draw nothing, but it generally seems
// better to draw a one-pixel-wide stripe rather than throwing it
// away
scaledWidth = 1;
}
if (yScale >= 0) {
ty = splashFloor(matrix.y0 + 0.01);
ty2 = splashCeil(matrix.y0 + yScale - 0.01);
} else {
ty = splashCeil(matrix.y0 - 0.01);
ty2 = splashFloor(matrix.y0 + yScale + 0.01);
}
scaledHeight = abs(ty2 - ty);
if (scaledHeight == 0) {
scaledHeight = 1;
}
#if 0
printf("xscale: %g, yscale: %g\n", xScale, yScale);
printf("width: %d, height: %d\n", width, height);
printf("scaledWidth: %d, scaledHeight: %d\n", scaledWidth, scaledHeight);
#endif
/* compute the required padding */
/* Padding is used to preserve the aspect ratio.
We compute total_pad to make (height+total_pad)/scaledHeight as close to height/yScale as possible */
int head_pad = 0;
int tail_pad = 0;
int total_pad = splashRound(height*(scaledHeight/fabs(yScale)) - height);
/* compute the two pieces of padding */
if (total_pad > 0) {
//XXX: i'm not positive fabs() is correct
float tail_error = fabs(matrix.y0 - ty);
float head_error = fabs(ty2 - (matrix.y0 + yScale));
float tail_fraction = tail_error/(tail_error + head_error);
tail_pad = splashRound(total_pad*tail_fraction);
head_pad = total_pad - tail_pad;
} else {
tail_pad = 0;
head_pad = 0;
}
int origHeight = height;
height += tail_pad;
height += head_pad;
#if 0
printf("head_pad: %d tail_pad: %d\n", head_pad, tail_pad);
printf("origHeight: %d height: %d\n", origHeight, height);
printf("ty: %d, ty2: %d\n", ty, ty2);
#endif
/* TODO: Do we want to cache these? */
imgStr = new ImageStream(str, width, 1, 1);
imgStr->reset();
invert_bit = invert ? 1 : 0;
image = cairo_image_surface_create (CAIRO_FORMAT_A8, scaledWidth, scaledHeight);
if (cairo_surface_status (image)) {
imgStr->close();
delete imgStr;
return;
}
buffer = cairo_image_surface_get_data (image);
row_stride = cairo_image_surface_get_stride (image);
int yp = height / scaledHeight;
int yq = height % scaledHeight;
int xp = width / scaledWidth;
int xq = width % scaledWidth;
int yt = 0;
int origHeight_c = origHeight;
/* use MIN() because yp might be > origHeight because of padding */
unsigned char *pixBuf = (unsigned char *)malloc(MIN(yp+1, origHeight)*width);
int lastYStep = 1;
int total = 0;
for (int y = 0; y < scaledHeight; y++) {
// y scale Bresenham
int yStep = yp;
yt += yq;
if (yt >= scaledHeight) {
yt -= scaledHeight;
++yStep;
}
// read row (s) from image ignoring the padding as appropriate
{
int n = (yp > 0) ? yStep : lastYStep;
total += n;
if (n > 0) {
unsigned char *p = pixBuf;
int head_pad_count = head_pad;
int origHeight_count = origHeight;
int tail_pad_count = tail_pad;
for (int i=0; i<n; i++) {
// get row
if (head_pad_count) {
head_pad_count--;
} else if (origHeight_count) {
pix = imgStr->getLine();
for (int j=0; j<width; j++) {
if (pix[j] ^ invert_bit)
p[j] = 0;
else
p[j] = 255;
}
origHeight_count--;
p += width;
} else if (tail_pad_count) {
tail_pad_count--;
} else {
printf("%d %d\n", n, total);
assert(0 && "over run\n");
}
}
}
}
lastYStep = yStep;
int k1 = y;
int xt = 0;
int xSrc = 0;
int x1 = k1;
int n = yStep > 0 ? yStep : 1;
int origN = n;
/* compute the size of padding and pixels that will be used for this row */
int head_pad_size = MIN(n, head_pad);
n -= head_pad_size;
head_pad -= MIN(head_pad_size, yStep);
int pix_size = MIN(n, origHeight);
n -= pix_size;
origHeight -= MIN(pix_size, yStep);
int tail_pad_size = MIN(n, tail_pad);
n -= tail_pad_size;
tail_pad -= MIN(tail_pad_size, yStep);
if (n != 0) {
printf("n = %d (%d %d %d)\n", n, head_pad_size, pix_size, tail_pad_size);
assert(n == 0);
}
for (int x = 0; x < scaledWidth; ++x) {
int xStep = xp;
xt += xq;
if (xt >= scaledWidth) {
xt -= scaledWidth;
++xStep;
}
int m = xStep > 0 ? xStep : 1;
float pixAcc0 = 0;
/* could m * head_pad_size * tail_pad_size overflow? */
if (invert_bit) {
pixAcc0 += m * head_pad_size * tail_pad_size * 255;
} else {
pixAcc0 += m * head_pad_size * tail_pad_size * 0;
}
/* Accumulate all of the source pixels for the destination pixel */
for (int i = 0; i < pix_size; ++i) {
for (int j = 0; j< m; ++j) {
if (xSrc + i*width + j > MIN(yp + 1, origHeight_c)*width) {
printf("%d > %d (%d %d %d %d) (%d %d %d)\n", xSrc + i*width + j, MIN(yp + 1, origHeight_c)*width, xSrc, i , width, j, yp, origHeight_c, width);
printf("%d %d %d\n", head_pad_size, pix_size, tail_pad_size);
assert(0 && "bad access\n");
}
pixAcc0 += pixBuf[xSrc + i*width + j];
}
}
buffer[y * row_stride + x] = splashFloor(pixAcc0 / (origN*m));
xSrc += xStep;
x1 += 1;
}
}
free(pixBuf);
cairo_surface_mark_dirty (image);
pattern = cairo_pattern_create_for_surface (image);
cairo_surface_destroy (image);
if (cairo_pattern_status (pattern)) {
imgStr->close();
delete imgStr;
return;
}
/* we should actually be using CAIRO_FILTER_NEAREST here. However,
* cairo doesn't yet do minifaction filtering causing scaled down
* images with CAIRO_FILTER_NEAREST to look really bad */
cairo_pattern_set_filter (pattern,
interpolate ? CAIRO_FILTER_BEST : CAIRO_FILTER_FAST);
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
if (state->getFillColorSpace()->getMode() == csPattern) {
cairo_matrix_init_translate (&matrix, 0, scaledHeight);
cairo_matrix_scale (&matrix, scaledWidth, -scaledHeight);
cairo_pattern_set_matrix (pattern, &matrix);
if (cairo_pattern_status (pattern)) {
cairo_pattern_destroy (pattern);
imgStr->close();
delete imgStr;
return;
}
mask = cairo_pattern_reference (pattern);
cairo_get_matrix (cairo, &mask_matrix);
} else {
cairo_save (cairo);
/* modify our current transformation so that the prescaled image
* goes where it is supposed to */
cairo_get_matrix(cairo, &matrix);
cairo_scale(cairo, 1.0/matrix.xx, 1.0/matrix.yy);
// get integer co-ords
cairo_translate (cairo, tx - matrix.x0, ty2 - matrix.y0);
if (yScale > 0)
cairo_scale(cairo, 1, -1);
cairo_rectangle (cairo, 0., 0., scaledWidth, scaledHeight);
cairo_clip (cairo);
cairo_mask (cairo, pattern);
//cairo_get_matrix(cairo, &matrix);
//printf("mask at: [%f %f], [%f %f], %f %f\n\n", matrix.xx, matrix.xy, matrix.yx, matrix.yy, matrix.x0, matrix.y0);
cairo_restore(cairo);
}
if (cairo_shape) {
cairo_save (cairo_shape);
/* modify our current transformation so that the prescaled image
* goes where it is supposed to */
cairo_get_matrix(cairo_shape, &matrix);
cairo_scale(cairo_shape, 1.0/matrix.xx, 1.0/matrix.yy);
// get integer co-ords
cairo_translate (cairo_shape, tx - matrix.x0, ty2 - matrix.y0);
if (yScale > 0)
cairo_scale(cairo_shape, 1, -1);
cairo_rectangle (cairo_shape, 0., 0., scaledWidth, scaledHeight);
cairo_fill (cairo_shape);
cairo_restore(cairo_shape);
}
cairo_pattern_destroy (pattern);
imgStr->close();
delete imgStr;
}
void CairoOutputDev::drawMaskedImage(GfxState *state, Object *ref,
Stream *str, int width, int height,
GfxImageColorMap *colorMap,
GBool interpolate,
Stream *maskStr, int maskWidth,
int maskHeight, GBool maskInvert,
GBool maskInterpolate)
{
ImageStream *maskImgStr, *imgStr;
int row_stride;
unsigned char *maskBuffer, *buffer;
unsigned char *maskDest;
unsigned int *dest;
cairo_surface_t *maskImage, *image;
cairo_pattern_t *maskPattern, *pattern;
cairo_matrix_t matrix;
cairo_matrix_t maskMatrix;
Guchar *pix;
int x, y;
int invert_bit;
cairo_filter_t filter;
cairo_filter_t maskFilter;
maskImgStr = new ImageStream(maskStr, maskWidth, 1, 1);
maskImgStr->reset();
maskImage = cairo_image_surface_create (CAIRO_FORMAT_A8, maskWidth, maskHeight);
if (cairo_surface_status (maskImage)) {
maskImgStr->close();
delete maskImgStr;
return;
}
maskBuffer = cairo_image_surface_get_data (maskImage);
row_stride = cairo_image_surface_get_stride (maskImage);
invert_bit = maskInvert ? 1 : 0;
for (y = 0; y < maskHeight; y++) {
pix = maskImgStr->getLine();
maskDest = maskBuffer + y * row_stride;
for (x = 0; x < maskWidth; x++) {
if (pix[x] ^ invert_bit)
*maskDest++ = 0;
else
*maskDest++ = 255;
}
}
maskImgStr->close();
delete maskImgStr;
maskFilter = getFilterForSurface (maskImage, maskInterpolate);
cairo_surface_mark_dirty (maskImage);
maskPattern = cairo_pattern_create_for_surface (maskImage);
cairo_surface_destroy (maskImage);
if (cairo_pattern_status (maskPattern))
return;
#if 0
/* ICCBased color space doesn't do any color correction
* so check its underlying color space as well */
int is_identity_transform;
is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB ||
(colorMap->getColorSpace()->getMode() == csICCBased &&
((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB);
#endif
/* TODO: Do we want to cache these? */
imgStr = new ImageStream(str, width,
colorMap->getNumPixelComps(),
colorMap->getBits());
imgStr->reset();
image = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
if (cairo_surface_status (image))
goto cleanup;
buffer = cairo_image_surface_get_data (image);
row_stride = cairo_image_surface_get_stride (image);
for (y = 0; y < height; y++) {
dest = (unsigned int *) (buffer + y * row_stride);
pix = imgStr->getLine();
colorMap->getRGBLine (pix, dest, width);
}
filter = getFilterForSurface (image, interpolate);
cairo_surface_mark_dirty (image);
pattern = cairo_pattern_create_for_surface (image);
cairo_surface_destroy (image);
if (cairo_pattern_status (pattern))
goto cleanup;
LOG (printf ("drawMaskedImage %dx%d\n", width, height));
cairo_pattern_set_filter (pattern, filter);
cairo_pattern_set_filter (maskPattern, maskFilter);
if (!printing) {
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
cairo_pattern_set_extend (maskPattern, CAIRO_EXTEND_PAD);
}
cairo_matrix_init_translate (&matrix, 0, height);
cairo_matrix_scale (&matrix, width, -height);
cairo_pattern_set_matrix (pattern, &matrix);
if (cairo_pattern_status (pattern)) {
cairo_pattern_destroy (pattern);
cairo_pattern_destroy (maskPattern);
goto cleanup;
}
cairo_matrix_init_translate (&maskMatrix, 0, maskHeight);
cairo_matrix_scale (&maskMatrix, maskWidth, -maskHeight);
cairo_pattern_set_matrix (maskPattern, &maskMatrix);
if (cairo_pattern_status (maskPattern)) {
cairo_pattern_destroy (maskPattern);
cairo_pattern_destroy (pattern);
goto cleanup;
}
if (!printing) {
cairo_save (cairo);
cairo_set_source (cairo, pattern);
cairo_rectangle (cairo, 0., 0., 1., 1.);
cairo_clip (cairo);
cairo_mask (cairo, maskPattern);
cairo_restore (cairo);
} else {
cairo_set_source (cairo, pattern);
cairo_mask (cairo, maskPattern);
}
if (cairo_shape) {
cairo_save (cairo_shape);
cairo_set_source (cairo_shape, pattern);
if (!printing) {
cairo_rectangle (cairo_shape, 0., 0., 1., 1.);
cairo_fill (cairo_shape);
} else {
cairo_mask (cairo_shape, pattern);
}
cairo_restore (cairo_shape);
}
cairo_pattern_destroy (maskPattern);
cairo_pattern_destroy (pattern);
cleanup:
imgStr->close();
delete imgStr;
}
//XXX: is this affect by AIS(alpha is shape)?
void CairoOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
int width, int height,
GfxImageColorMap *colorMap,
GBool interpolate,
Stream *maskStr,
int maskWidth, int maskHeight,
GfxImageColorMap *maskColorMap,
GBool maskInterpolate)
{
ImageStream *maskImgStr, *imgStr;
int row_stride;
unsigned char *maskBuffer, *buffer;
unsigned char *maskDest;
unsigned int *dest;
cairo_surface_t *maskImage, *image;
cairo_pattern_t *maskPattern, *pattern;
cairo_matrix_t maskMatrix, matrix;
Guchar *pix;
int y;
cairo_filter_t filter;
cairo_filter_t maskFilter;
maskImgStr = new ImageStream(maskStr, maskWidth,
maskColorMap->getNumPixelComps(),
maskColorMap->getBits());
maskImgStr->reset();
maskImage = cairo_image_surface_create (CAIRO_FORMAT_A8, maskWidth, maskHeight);
if (cairo_surface_status (maskImage)) {
maskImgStr->close();
delete maskImgStr;
return;
}
maskBuffer = cairo_image_surface_get_data (maskImage);
row_stride = cairo_image_surface_get_stride (maskImage);
for (y = 0; y < maskHeight; y++) {
maskDest = (unsigned char *) (maskBuffer + y * row_stride);
pix = maskImgStr->getLine();
maskColorMap->getGrayLine (pix, maskDest, maskWidth);
}
maskImgStr->close();
delete maskImgStr;
maskFilter = getFilterForSurface (maskImage, maskInterpolate);
cairo_surface_mark_dirty (maskImage);
maskPattern = cairo_pattern_create_for_surface (maskImage);
cairo_surface_destroy (maskImage);
if (cairo_pattern_status (maskPattern))
return;
#if 0
/* ICCBased color space doesn't do any color correction
* so check its underlying color space as well */
int is_identity_transform;
is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB ||
(colorMap->getColorSpace()->getMode() == csICCBased &&
((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB);
#endif
/* TODO: Do we want to cache these? */
imgStr = new ImageStream(str, width,
colorMap->getNumPixelComps(),
colorMap->getBits());
imgStr->reset();
image = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
if (cairo_surface_status (image))
goto cleanup;
buffer = cairo_image_surface_get_data (image);
row_stride = cairo_image_surface_get_stride (image);
for (y = 0; y < height; y++) {
dest = (unsigned int *) (buffer + y * row_stride);
pix = imgStr->getLine();
colorMap->getRGBLine (pix, dest, width);
}
filter = getFilterForSurface (image, interpolate);
cairo_surface_mark_dirty (image);
setMimeData(str, ref, image);
pattern = cairo_pattern_create_for_surface (image);
cairo_surface_destroy (image);
if (cairo_pattern_status (pattern))
goto cleanup;
LOG (printf ("drawSoftMaskedImage %dx%d\n", width, height));
cairo_pattern_set_filter (pattern, filter);
cairo_pattern_set_filter (maskPattern, maskFilter);
if (!printing) {
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
cairo_pattern_set_extend (maskPattern, CAIRO_EXTEND_PAD);
}
cairo_matrix_init_translate (&matrix, 0, height);
cairo_matrix_scale (&matrix, width, -height);
cairo_pattern_set_matrix (pattern, &matrix);
if (cairo_pattern_status (pattern)) {
cairo_pattern_destroy (pattern);
cairo_pattern_destroy (maskPattern);
goto cleanup;
}
cairo_matrix_init_translate (&maskMatrix, 0, maskHeight);
cairo_matrix_scale (&maskMatrix, maskWidth, -maskHeight);
cairo_pattern_set_matrix (maskPattern, &maskMatrix);
if (cairo_pattern_status (maskPattern)) {
cairo_pattern_destroy (maskPattern);
cairo_pattern_destroy (pattern);
goto cleanup;
}
if (fill_opacity != 1.0)
cairo_push_group (cairo);
else
cairo_save (cairo);
cairo_set_source (cairo, pattern);
if (!printing) {
cairo_rectangle (cairo, 0., 0.,
MIN (width, maskWidth) / (double)width,
MIN (height, maskHeight) / (double)height);
cairo_clip (cairo);
}
cairo_mask (cairo, maskPattern);
if (fill_opacity != 1.0) {
cairo_pop_group_to_source (cairo);
cairo_save (cairo);
if (!printing) {
cairo_rectangle (cairo, 0., 0.,
MIN (width, maskWidth) / (double)width,
MIN (height, maskHeight) / (double)height);
cairo_clip (cairo);
}
cairo_paint_with_alpha (cairo, fill_opacity);
}
cairo_restore (cairo);
if (cairo_shape) {
cairo_save (cairo_shape);
cairo_set_source (cairo_shape, pattern);
if (!printing) {
cairo_rectangle (cairo_shape, 0., 0.,
MIN (width, maskWidth) / (double)width,
MIN (height, maskHeight) / (double)height);
cairo_fill (cairo_shape);
} else {
cairo_mask (cairo_shape, pattern);
}
cairo_restore (cairo_shape);
}
cairo_pattern_destroy (maskPattern);
cairo_pattern_destroy (pattern);
cleanup:
imgStr->close();
delete imgStr;
}
GBool CairoOutputDev::getStreamData (Stream *str, char **buffer, int *length)
{
int len, i;
char *strBuffer;
len = 0;
str->close();
str->reset();
while (str->getChar() != EOF) len++;
if (len == 0)
return gFalse;
strBuffer = (char *)gmalloc (len);
str->close();
str->reset();
for (i = 0; i < len; ++i)
strBuffer[i] = str->getChar();
*buffer = strBuffer;
*length = len;
return gTrue;
}
void CairoOutputDev::setMimeData(Stream *str, Object *ref, cairo_surface_t *image)
{
char *strBuffer;
int len;
Object obj;
if (!printing || !(str->getKind() == strDCT || str->getKind() == strJPX))
return;
// colorspace in stream dict may be different from colorspace in jpx
// data
if (str->getKind() == strJPX) {
GBool hasColorSpace = !str->getDict()->lookup("ColorSpace", &obj)->isNull();
obj.free();
if (hasColorSpace)
return;
}
if (getStreamData (str->getNextStream(), &strBuffer, &len)) {
cairo_status_t st;
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2)
if (ref && ref->isRef()) {
Ref imgRef = ref->getRef();
GooString *surfaceId = new GooString("poppler-surface-");
surfaceId->appendf("{0:d}-{1:d}", imgRef.gen, imgRef.num);
char *idBuffer = copyString(surfaceId->getCString());
st = cairo_surface_set_mime_data (image, CAIRO_MIME_TYPE_UNIQUE_ID,
(const unsigned char *)idBuffer,
surfaceId->getLength(),
gfree, idBuffer);
if (st)
gfree(idBuffer);
delete surfaceId;
}
#endif
st = cairo_surface_set_mime_data (image,
str->getKind() == strDCT ?
CAIRO_MIME_TYPE_JPEG : CAIRO_MIME_TYPE_JP2,
(const unsigned char *)strBuffer, len,
gfree, strBuffer);
if (st)
gfree (strBuffer);
}
}
void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
int width, int height,
GfxImageColorMap *colorMap,
GBool interpolate,
int *maskColors, GBool inlineImg)
{
cairo_surface_t *image;
cairo_pattern_t *pattern, *maskPattern;
ImageStream *imgStr;
cairo_matrix_t matrix;
unsigned char *buffer;
int stride, i;
GfxRGB *lookup = NULL;
cairo_filter_t filter = CAIRO_FILTER_BILINEAR;
/* TODO: Do we want to cache these? */
imgStr = new ImageStream(str, width,
colorMap->getNumPixelComps(),
colorMap->getBits());
imgStr->reset();
#if 0
/* ICCBased color space doesn't do any color correction
* so check its underlying color space as well */
int is_identity_transform;
is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB ||
(colorMap->getColorSpace()->getMode() == csICCBased &&
((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB);
#endif
image = cairo_image_surface_create (maskColors ?
CAIRO_FORMAT_ARGB32 :
CAIRO_FORMAT_RGB24,
width, height);
if (cairo_surface_status (image))
goto cleanup;
// special case for one-channel (monochrome/gray/separation) images:
// build a lookup table here
if (colorMap->getNumPixelComps() == 1) {
int n;
Guchar pix;
n = 1 << colorMap->getBits();
lookup = (GfxRGB *)gmallocn(n, sizeof(GfxRGB));
for (i = 0; i < n; ++i) {
pix = (Guchar)i;
colorMap->getRGB(&pix, &lookup[i]);
}
}
buffer = cairo_image_surface_get_data (image);
stride = cairo_image_surface_get_stride (image);
for (int y = 0; y < height; y++) {
uint32_t *dest = (uint32_t *) (buffer + y * stride);
Guchar *pix = imgStr->getLine();
if (lookup) {
Guchar *p = pix;
GfxRGB rgb;
for (i = 0; i < width; i++) {
rgb = lookup[*p];
dest[i] =
((int) colToByte(rgb.r) << 16) |
((int) colToByte(rgb.g) << 8) |
((int) colToByte(rgb.b) << 0);
p++;
}
} else {
colorMap->getRGBLine (pix, dest, width);
}
if (maskColors) {
for (int x = 0; x < width; x++) {
bool is_opaque = false;
for (int i = 0; i < colorMap->getNumPixelComps(); ++i) {
if (pix[i] < maskColors[2*i] ||
pix[i] > maskColors[2*i+1]) {
is_opaque = true;
break;
}
}
if (is_opaque)
*dest |= 0xff000000;
else
*dest = 0;
dest++;
pix += colorMap->getNumPixelComps();
}
}
}
gfree(lookup);
LOG (printf ("drawImage %dx%d\n", width, height));
cairo_surface_t *scaled_surface;
scaled_surface = downscaleSurface (image);
if (scaled_surface) {
if (cairo_surface_status (scaled_surface))
goto cleanup;
cairo_surface_destroy (image);
image = scaled_surface;
width = cairo_image_surface_get_width (image);
height = cairo_image_surface_get_height (image);
} else {
filter = getFilterForSurface (image, interpolate);
}
cairo_surface_mark_dirty (image);
if (!inlineImg) /* don't read stream twice if it is an inline image */
setMimeData(str, ref, image);
pattern = cairo_pattern_create_for_surface (image);
cairo_surface_destroy (image);
if (cairo_pattern_status (pattern))
goto cleanup;
cairo_pattern_set_filter (pattern, filter);
if (!printing)
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
cairo_matrix_init_translate (&matrix, 0, height);
cairo_matrix_scale (&matrix, width, -height);
cairo_pattern_set_matrix (pattern, &matrix);
if (cairo_pattern_status (pattern)) {
cairo_pattern_destroy (pattern);
goto cleanup;
}
if (!mask && fill_opacity != 1.0) {
maskPattern = cairo_pattern_create_rgba (1., 1., 1., fill_opacity);
} else if (mask) {
maskPattern = cairo_pattern_reference (mask);
} else {
maskPattern = NULL;
}
cairo_save (cairo);
cairo_set_source (cairo, pattern);
if (!printing)
cairo_rectangle (cairo, 0., 0., 1., 1.);
if (maskPattern) {
if (!printing)
cairo_clip (cairo);
cairo_set_matrix (cairo, &mask_matrix);
cairo_mask (cairo, maskPattern);
} else {
if (printing)
cairo_paint (cairo);
else
cairo_fill (cairo);
}
cairo_restore (cairo);
cairo_pattern_destroy (maskPattern);
if (cairo_shape) {
cairo_save (cairo_shape);
cairo_set_source (cairo_shape, pattern);
if (printing) {
cairo_paint (cairo_shape);
} else {
cairo_rectangle (cairo_shape, 0., 0., 1., 1.);
cairo_fill (cairo_shape);
}
cairo_restore (cairo_shape);
}
cairo_pattern_destroy (pattern);
cleanup:
imgStr->close();
delete imgStr;
}
//------------------------------------------------------------------------
// ImageOutputDev
//------------------------------------------------------------------------
CairoImageOutputDev::CairoImageOutputDev()
{
images = NULL;
numImages = 0;
size = 0;
imgDrawCbk = NULL;
imgDrawCbkData = NULL;
}
CairoImageOutputDev::~CairoImageOutputDev()
{
int i;
for (i = 0; i < numImages; i++)
delete images[i];
gfree (images);
}
void CairoImageOutputDev::saveImage(CairoImage *image)
{
if (numImages >= size) {
size += 16;
images = (CairoImage **) greallocn (images, size, sizeof (CairoImage *));
}
images[numImages++] = image;
}
void CairoImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
int width, int height, GBool invert,
GBool interpolate, GBool inlineImg)
{
cairo_t *cr;
cairo_surface_t *surface;
double x1, y1, x2, y2;
double *ctm;
double mat[6];
CairoImage *image;
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];
x1 = mat[4];
y1 = mat[5];
x2 = x1 + width;
y2 = y1 + height;
image = new CairoImage (x1, y1, x2, y2);
saveImage (image);
if (imgDrawCbk && imgDrawCbk (numImages - 1, imgDrawCbkData)) {
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
cr = cairo_create (surface);
setCairo (cr);
cairo_translate (cr, 0, height);
cairo_scale (cr, width, -height);
CairoOutputDev::drawImageMask(state, ref, str, width, height, invert, interpolate, inlineImg);
image->setImage (surface);
setCairo (NULL);
cairo_surface_destroy (surface);
cairo_destroy (cr);
}
}
void CairoImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
int width, int height, GfxImageColorMap *colorMap,
GBool interpolate, int *maskColors, GBool inlineImg)
{
cairo_t *cr;
cairo_surface_t *surface;
double x1, y1, x2, y2;
double *ctm;
double mat[6];
CairoImage *image;
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];
x1 = mat[4];
y1 = mat[5];
x2 = x1 + width;
y2 = y1 + height;
image = new CairoImage (x1, y1, x2, y2);
saveImage (image);
if (imgDrawCbk && imgDrawCbk (numImages - 1, imgDrawCbkData)) {
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
cr = cairo_create (surface);
setCairo (cr);
cairo_translate (cr, 0, height);
cairo_scale (cr, width, -height);
CairoOutputDev::drawImage(state, ref, str, width, height, colorMap, interpolate, maskColors, inlineImg);
image->setImage (surface);
setCairo (NULL);
cairo_surface_destroy (surface);
cairo_destroy (cr);
}
}
void CairoImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
int width, int height,
GfxImageColorMap *colorMap,
GBool interpolate,
Stream *maskStr,
int maskWidth, int maskHeight,
GfxImageColorMap *maskColorMap,
GBool maskInterpolate)
{
cairo_t *cr;
cairo_surface_t *surface;
double x1, y1, x2, y2;
double *ctm;
double mat[6];
CairoImage *image;
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];
x1 = mat[4];
y1 = mat[5];
x2 = x1 + width;
y2 = y1 + height;
image = new CairoImage (x1, y1, x2, y2);
saveImage (image);
if (imgDrawCbk && imgDrawCbk (numImages - 1, imgDrawCbkData)) {
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
cr = cairo_create (surface);
setCairo (cr);
cairo_translate (cr, 0, height);
cairo_scale (cr, width, -height);
CairoOutputDev::drawSoftMaskedImage(state, ref, str, width, height, colorMap, interpolate,
maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate);
image->setImage (surface);
setCairo (NULL);
cairo_surface_destroy (surface);
cairo_destroy (cr);
}
}
void CairoImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
int width, int height,
GfxImageColorMap *colorMap,
GBool interpolate,
Stream *maskStr,
int maskWidth, int maskHeight,
GBool maskInvert, GBool maskInterpolate)
{
cairo_t *cr;
cairo_surface_t *surface;
double x1, y1, x2, y2;
double *ctm;
double mat[6];
CairoImage *image;
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];
x1 = mat[4];
y1 = mat[5];
x2 = x1 + width;
y2 = y1 + height;
image = new CairoImage (x1, y1, x2, y2);
saveImage (image);
if (imgDrawCbk && imgDrawCbk (numImages - 1, imgDrawCbkData)) {
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
cr = cairo_create (surface);
setCairo (cr);
cairo_translate (cr, 0, height);
cairo_scale (cr, width, -height);
CairoOutputDev::drawMaskedImage(state, ref, str, width, height, colorMap, interpolate,
maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate);
image->setImage (surface);
setCairo (NULL);
cairo_surface_destroy (surface);
cairo_destroy (cr);
}
}