//======================================================================== // // 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 // Copyright (C) 2005, 2006 Kristian Høgsberg // Copyright (C) 2005, 2009, 2012 Albert Astals Cid // Copyright (C) 2005 Nickolay V. Shmyrev // Copyright (C) 2006-2011, 2013, 2014 Carlos Garcia Campos // Copyright (C) 2008 Carl Worth // Copyright (C) 2008-2014 Adrian Johnson // Copyright (C) 2008 Michael Vrable // Copyright (C) 2008, 2009 Chris Wilson // Copyright (C) 2008, 2012 Hib Eris // Copyright (C) 2009, 2010 David Benjamin // Copyright (C) 2011-2014 Thomas Freitag // Copyright (C) 2012 Patrick Pfeifer // Copyright (C) 2012 Jason Crain // // 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 #ifdef USE_GCC_PRAGMAS #pragma implementation #endif #include #include #include #include #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 #include #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; text_matrix_valid = gTrue; 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); text_matrix_valid = gTrue; /* 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"); text_matrix_valid = gFalse; return; } cairo_set_font_matrix (cairo, &matrix); text_matrix_valid = gTrue; } /* 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_matrix_t pattern_matrix; cairo_t *old_cairo; double xMin, yMin, xMax, yMax; double width, height; int surface_width, surface_height; StrokePathClip *strokePathTmp; GBool adjusted_stroke_width_tmp; width = bbox[2] - bbox[0]; height = bbox[3] - bbox[1]; if (xStep != width || yStep != height) return gFalse; /* TODO: implement the other cases here too */ // Find the width and height of the transformed pattern cairo_get_matrix (cairo, &matrix); cairo_matrix_init (&pattern_matrix, mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); cairo_matrix_multiply (&matrix, &matrix, &pattern_matrix); double widthX = width, widthY = 0; cairo_matrix_transform_distance (&matrix, &widthX, &widthY); surface_width = ceil (sqrt (widthX * widthX + widthY * widthY)); double heightX = 0, heightY = height; cairo_matrix_transform_distance (&matrix, &heightX, &heightY); surface_height = ceil (sqrt (heightX * heightX + heightY * heightY)); 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); cairo_scale (cairo, surface_width / width, surface_height / height); box.x1 = bbox[0]; box.y1 = bbox[1]; box.x2 = bbox[2]; box.y2 = bbox[3]; strokePathTmp = strokePathClip; strokePathClip = NULL; adjusted_stroke_width_tmp = adjusted_stroke_width; 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; adjusted_stroke_width = adjusted_stroke_width_tmp; 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_transform (cairo, &pattern_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 || !text_matrix_valid) { goto finish; } 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); } } finish: 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 && groupColorSpaceStack->cs) { 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; ytransform(&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; igetLine(); for (int j=0; j 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(state, str, ref, colorMap, 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; } static GBool colorMapHasIdentityDecodeMap(GfxImageColorMap *colorMap) { for (int i = 0; i < colorMap->getNumPixelComps(); i++) { if (colorMap->getDecodeLow(i) != 0.0 || colorMap->getDecodeHigh(i) != 1.0) return gFalse; } return gTrue; } void CairoOutputDev::setMimeData(GfxState *state, Stream *str, Object *ref, GfxImageColorMap *colorMap, cairo_surface_t *image) { char *strBuffer; int len; Object obj; GfxColorSpace *colorSpace; if (!printing || !(str->getKind() == strDCT || str->getKind() == strJPX)) return; str->getDict()->lookup("ColorSpace", &obj); colorSpace = GfxColorSpace::parse(NULL, &obj, this, state); obj.free(); // colorspace in stream dict may be different from colorspace in jpx // data if (str->getKind() == strJPX && colorSpace) return; // only embed mime data for gray, rgb, and cmyk colorspaces. if (colorSpace) { GfxColorSpaceMode mode = colorSpace->getMode(); delete colorSpace; switch (mode) { case csDeviceGray: case csCalGray: case csDeviceRGB: case csCalRGB: case csDeviceCMYK: case csICCBased: break; case csLab: case csIndexed: case csSeparation: case csDeviceN: case csPattern: return; } } if (!colorMapHasIdentityDecodeMap(colorMap)) 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(state, str, ref, colorMap, 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); } }