diff options
Diffstat (limited to 'Build/source/libs/poppler/poppler-0.26.5/poppler/CairoOutputDev.cc')
-rw-r--r-- | Build/source/libs/poppler/poppler-0.26.5/poppler/CairoOutputDev.cc | 3197 |
1 files changed, 3197 insertions, 0 deletions
diff --git a/Build/source/libs/poppler/poppler-0.26.5/poppler/CairoOutputDev.cc b/Build/source/libs/poppler/poppler-0.26.5/poppler/CairoOutputDev.cc new file mode 100644 index 00000000000..1c67b5c1c8d --- /dev/null +++ b/Build/source/libs/poppler/poppler-0.26.5/poppler/CairoOutputDev.cc @@ -0,0 +1,3197 @@ +//======================================================================== +// +// 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, 2013, 2014 Carlos Garcia Campos <carlosgc@gnome.org> +// Copyright (C) 2008 Carl Worth <cworth@cworth.org> +// Copyright (C) 2008-2014 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, 2012 Hib Eris <hib@hiberis.nl> +// Copyright (C) 2009, 2010 David Benjamin <davidben@mit.edu> +// Copyright (C) 2011-2013 Thomas Freitag <Thomas.Freitag@alfa.de> +// Copyright (C) 2012 Patrick Pfeifer <p2000@mailinator.com> +// Copyright (C) 2012 Jason Crain <jason@aquaticape.us> +// +// 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 "UnicodeMap.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; + inUncoloredPattern = 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; + xref = NULL; +} + +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; + } + xref = doc->getXRef(); +} + +void CairoOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefA) { + /* 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.); + fill_color.r = fill_color.g = fill_color.b = 0; + stroke_pattern = cairo_pattern_reference(fill_pattern); + stroke_color.r = stroke_color.g = stroke_color.b = 0; + + if (text) + text->startPage(state); + if (xrefA != NULL) { + xref = xrefA; + } +} + +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; + + if (inUncoloredPattern) + return; + + 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; + + if (inUncoloredPattern) + return; + + 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; + + if (inUncoloredPattern) + return; + + 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; + + if (inUncoloredPattern) + return; + + 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) { + if (inUncoloredPattern) + return; + + 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, xref); + + 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 *gfxA, 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, NULL, NULL, gfxA->getXRef()); + if (paintType == 2) + inUncoloredPattern = gTrue; + gfx->display(str); + if (paintType == 2) + inUncoloredPattern = gFalse; + 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++) { + if (shading->isParameterized()) { + double color0, color1, color2; + shading->getTriangle(i, &x0, &y0, &color0, + &x1, &y1, &color1, + &x2, &y2, &color2); + shading->getParameterizedColor(color0, &color[0]); + shading->getParameterizedColor(color1, &color[1]); + shading->getParameterizedColor(color2, &color[2]); + } else { + 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) { + GooString enc("UTF-8"); + UnicodeMap *utf8Map = globalParams->getUnicodeMap(&enc); + 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 = utf8Map->mapUnicode(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 || transferFunc) { + /* 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 */ + if (!alpha) { + 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 = alpha ? fill_opacity : 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 if (alpha) { + 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(const cairo_matrix_t *matrix, + int orig_width, + int orig_height, + int *scaledWidth, + int *scaledHeight) +{ + 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_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; + + /* When printing, don't change the interpolation. */ + if (printing) + return CAIRO_FILTER_NEAREST; + + cairo_matrix_t matrix; + cairo_get_matrix(cairo, &matrix); + int scaled_width, scaled_height; + getScaledSize (&matrix, 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)) { + 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); + } + } + } else { + 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., 1., 1.); + 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., 1., 1.); + 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., 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; +} + +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); + } +} + +class RescaleDrawImage : public CairoRescaleBox { +private: + ImageStream *imgStr; + GfxRGB *lookup; + int width; + GfxImageColorMap *colorMap; + int *maskColors; + int current_row; + GBool imageError; + +public: + cairo_surface_t *getSourceImage(Stream *str, + int widthA, int height, + int scaledWidth, int scaledHeight, + GBool printing, + GfxImageColorMap *colorMapA, + int *maskColorsA) { + cairo_surface_t *image = NULL; + int i; + + lookup = NULL; + colorMap = colorMapA; + maskColors = maskColorsA; + width = widthA; + current_row = -1; + imageError = gFalse; + + /* 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 + + // 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]); + } + } + + if (printing || scaledWidth >= width || scaledHeight >= height) { + // No downscaling. Create cairo image containing the source image data. + unsigned char *buffer; + int stride; + + image = cairo_image_surface_create (maskColors ? + CAIRO_FORMAT_ARGB32 : + CAIRO_FORMAT_RGB24, + width, height); + if (cairo_surface_status (image)) + goto cleanup; + + 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); + getRow(y, dest); + } + } else { + // // Downscaling required. Create cairo image the size of the + // rescaled image and // downscale the source image data into + // the cairo image. downScaleImage() will call getRow() to read + // source image data from the image stream. This avoids having + // to create an image the size of the source image which may + // exceed cairo's 32676x32767 image size limit (and also saves a + // lot of memory). + image = cairo_image_surface_create (maskColors ? + CAIRO_FORMAT_ARGB32 : + CAIRO_FORMAT_RGB24, + scaledWidth, scaledHeight); + if (cairo_surface_status (image)) + goto cleanup; + + downScaleImage(width, height, + scaledWidth, scaledHeight, + 0, 0, scaledWidth, scaledHeight, + image); + } + cairo_surface_mark_dirty (image); + + cleanup: + gfree(lookup); + imgStr->close(); + delete imgStr; + return image; + } + + void getRow(int row_num, uint32_t *row_data) { + int i; + Guchar *pix; + + if (row_num <= current_row) + return; + + while (current_row < row_num) { + pix = imgStr->getLine(); + current_row++; + } + + if (unlikely(pix == NULL)) { + memset(row_data, 0, width*4); + if (!imageError) { + error(errInternal, -1, "Bad image stream"); + imageError = gTrue; + } + } else if (lookup) { + Guchar *p = pix; + GfxRGB rgb; + + for (i = 0; i < width; i++) { + rgb = lookup[*p]; + row_data[i] = + ((int) colToByte(rgb.r) << 16) | + ((int) colToByte(rgb.g) << 8) | + ((int) colToByte(rgb.b) << 0); + p++; + } + } else { + colorMap->getRGBLine (pix, row_data, 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) + *row_data |= 0xff000000; + else + *row_data = 0; + row_data++; + pix += colorMap->getNumPixelComps(); + } + } + } + +}; + +void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, + int widthA, int heightA, + GfxImageColorMap *colorMap, + GBool interpolate, + int *maskColors, GBool inlineImg) +{ + cairo_surface_t *image; + cairo_pattern_t *pattern, *maskPattern; + cairo_matrix_t matrix; + int width, height; + int scaledWidth, scaledHeight; + cairo_filter_t filter = CAIRO_FILTER_BILINEAR; + RescaleDrawImage rescale; + + LOG (printf ("drawImage %dx%d\n", widthA, heightA)); + + cairo_get_matrix(cairo, &matrix); + getScaledSize (&matrix, widthA, heightA, &scaledWidth, &scaledHeight); + image = rescale.getSourceImage(str, widthA, heightA, scaledWidth, scaledHeight, printing, colorMap, maskColors); + if (!image) + return; + + width = cairo_image_surface_get_width (image); + height = cairo_image_surface_get_height (image); + if (width == widthA && height == heightA) + filter = getFilterForSurface (image, interpolate); + + 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)) + return; + + 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); + return; + } + + 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); + if (mask) + 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); +} + + +//------------------------------------------------------------------------ +// 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::getBBox(GfxState *state, int width, int height, + double *x1, double *y1, double *x2, double *y2) +{ + double *ctm = state->getCTM(); + cairo_matrix_t matrix; + cairo_matrix_init(&matrix, + ctm[0], ctm[1], + -ctm[2], -ctm[3], + ctm[2] + ctm[4], ctm[3] + ctm[5]); + + int scaledWidth, scaledHeight; + getScaledSize (&matrix, width, height, &scaledWidth, &scaledHeight); + + if (matrix.xx >= 0) { + *x1 = matrix.x0; + } else { + *x1 = matrix.x0 - scaledWidth; + } + *x2 = *x1 + scaledWidth; + + if (matrix.yy >= 0) { + *y1 = matrix.y0; + } else { + *y1 = matrix.y0 - scaledHeight; + } + *y2 = *y1 + scaledHeight; +} + +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; + CairoImage *image; + + getBBox(state, width, height, &x1, &y1, &x2, &y2); + + 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::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, + int width, int height, GBool invert, + GBool inlineImg, double *baseMatrix) +{ + cairo_t *cr; + cairo_surface_t *surface; + double x1, y1, x2, y2; + CairoImage *image; + + getBBox(state, width, height, &x1, &y1, &x2, &y2); + + 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, inlineImg, gFalse); + if (state->getFillColorSpace()->getMode() == csPattern) { + cairo_mask (cairo, mask); + } + 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; + CairoImage *image; + + getBBox(state, width, height, &x1, &y1, &x2, &y2); + + 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; + CairoImage *image; + + getBBox(state, width, height, &x1, &y1, &x2, &y2); + + 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; + CairoImage *image; + + getBBox(state, width, height, &x1, &y1, &x2, &y2); + + 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); + } +} |