//======================================================================== // // 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, 2017-2020 Albert Astals Cid // Copyright (C) 2005 Nickolay V. Shmyrev // Copyright (C) 2006-2011, 2013, 2014, 2017, 2018 Carlos Garcia Campos // Copyright (C) 2008 Carl Worth // Copyright (C) 2008-2018 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, 2015, 2016 Jason Crain // Copyright (C) 2015 Suzuki Toshiya // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, . Work sponsored by the LiMux project of the city of Munich // Copyright (C) 2018, 2020 Adam Reichold // Copyright (C) 2019, 2020 Marek Kasik // Copyright (C) 2020 Michal // Copyright (C) 2020 Oliver Sander // // 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 #include #include #include #include #include #include "goo/gfile.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" #include "JBIG2Stream.h" //------------------------------------------------------------------------ // #define LOG_CAIRO // To limit memory usage and improve performance when printing, limit // cairo images to this size. 8192 is sufficient for an A2 sized // 300ppi image. #define MAX_PRINT_IMAGE_SIZE 8192 #ifdef LOG_CAIRO # define LOG(x) (x) #else # define LOG(x) #endif #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MAX(a, b) (((a) > (b)) ? (a) : (b)) //------------------------------------------------------------------------ // CairoImage //------------------------------------------------------------------------ CairoImage::CairoImage(double x1A, double y1A, double x2A, double y2A) { image = nullptr; x1 = x1A; y1 = y1A; x2 = x2A; y2 = y2A; } CairoImage::~CairoImage() { if (image) cairo_surface_destroy(image); } void CairoImage::setImage(cairo_surface_t *i) { if (image) cairo_surface_destroy(image); image = cairo_surface_reference(i); } //------------------------------------------------------------------------ // 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; std::once_flag CairoOutputDev::ft_lib_once_flag; CairoOutputDev::CairoOutputDev() { doc = nullptr; std::call_once(ft_lib_once_flag, FT_Init_FreeType, &ft_lib); fontEngine = nullptr; fontEngine_owner = false; glyphs = nullptr; fill_pattern = nullptr; fill_color.r = fill_color.g = fill_color.b = 0; stroke_pattern = nullptr; stroke_color.r = stroke_color.g = stroke_color.b = 0; stroke_opacity = 1.0; fill_opacity = 1.0; textClipPath = nullptr; strokePathClip = nullptr; cairo = nullptr; currentFont = nullptr; #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) prescaleImages = false; #else prescaleImages = true; #endif printing = true; use_show_text_glyphs = false; inUncoloredPattern = false; inType3Char = false; t3_glyph_has_bbox = false; text_matrix_valid = true; antialias = CAIRO_ANTIALIAS_DEFAULT; groupColorSpaceStack = nullptr; maskStack = nullptr; group = nullptr; mask = nullptr; shape = nullptr; cairo_shape = nullptr; knockoutCount = 0; textPage = nullptr; actualText = nullptr; // the SA parameter supposedly defaults to false, but Acrobat // apparently hardwires it to true stroke_adjust = true; align_stroke_coords = false; adjusted_stroke_width = false; xref = nullptr; } 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 (textPage) textPage->decRefCnt(); if (actualText) delete actualText; } void CairoOutputDev::setCairo(cairo_t *c) { if (cairo != nullptr) { cairo_status_t status = cairo_status(cairo); if (status) { error(errInternal, -1, "cairo context error: {0:s}\n", cairo_status_to_string(status)); } cairo_destroy(cairo); assert(!cairo_shape); } if (c != nullptr) { cairo = cairo_reference(c); /* 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); setContextAntialias(cairo, antialias); } else { cairo = nullptr; cairo_shape = nullptr; } } void CairoOutputDev::setTextPage(TextPage *text) { if (textPage) textPage->decRefCnt(); if (actualText) delete actualText; if (text) { textPage = text; textPage->incRefCnt(); actualText = new ActualText(text); } else { textPage = nullptr; actualText = nullptr; } } void CairoOutputDev::setAntialias(cairo_antialias_t a) { antialias = a; if (cairo) setContextAntialias(cairo, antialias); if (cairo_shape) setContextAntialias(cairo_shape, antialias); } void CairoOutputDev::setContextAntialias(cairo_t *cr, cairo_antialias_t antialias) { cairo_font_options_t *font_options; cairo_set_antialias(cr, antialias); font_options = cairo_font_options_create(); cairo_get_font_options(cr, font_options); cairo_font_options_set_antialias(font_options, antialias); cairo_set_font_options(cr, font_options); cairo_font_options_destroy(font_options); } 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 = true; } 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 (textPage) textPage->startPage(state); if (xrefA != nullptr) { xref = xrefA; } } void CairoOutputDev::endPage() { if (textPage) { textPage->endPage(); textPage->coalesce(true, 0, false); } } 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; if (strokePathClip) strokePathClip->ref_count++; } void CairoOutputDev::restoreState(GfxState *state) { LOG(printf("restore\n")); cairo_restore(cairo); if (cairo_shape) cairo_restore(cairo_shape); text_matrix_valid = true; /* 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; } if (strokePathClip && --strokePathClip->ref_count == 0) { delete strokePathClip->path; if (strokePathClip->dashes) gfree(strokePathClip->dashes); gfree(strokePathClip); strokePathClip = nullptr; } } 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 = true; if (textPage) textPage->updateFont(state); } void CairoOutputDev::setDefaultCTM(const 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 = false; 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 = true; } } 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); // If stroke pattern is set then the current fill is clipped // to a stroke path. In that case, the stroke opacity has to be used // rather than the fill opacity. // See https://gitlab.freedesktop.org/poppler/poppler/issues/178 auto opacity = (state->getStrokePattern()) ? state->getStrokeOpacity() : state->getFillOpacity(); cairo_pattern_add_color_stop_rgba(fill_pattern, offset, colToDbl(fill_color.r), colToDbl(fill_color.g), colToDbl(fill_color.b), opacity); LOG(printf("fill color stop: %f (%d, %d, %d, %d)\n", offset, fill_color.r, fill_color.g, fill_color.b, dblToCol(opacity))); } 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()->c_str())); needFontUpdate = false; // FIXME: use cairo font engine? if (textPage) textPage->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(); const 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 = false; return; } cairo_set_font_matrix(cairo, &matrix); text_matrix_valid = true; } /* 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(const GfxSubpath *subpath, int i, double *x, double *y) { double x1, y1, x2, y2; bool align = false; 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 = true; } // 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 = true; } *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 *c, GfxState *state, const GfxPath *path) { int i, j; double x, y; cairo_new_path(c); for (i = 0; i < path->getNumSubpaths(); ++i) { const GfxSubpath *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(c, 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(c, 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(c, x, y); ++j; } } if (subpath->isClosed()) { LOG(printf("close\n")); cairo_close_path(c); } } } } 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 = true; doPath(cairo, state, state->getPath()); align_stroke_coords = false; cairo_set_source(cairo, stroke_pattern); LOG(printf("stroke\n")); if (strokePathClip) { cairo_push_group(cairo); cairo_stroke(cairo); cairo_pop_group_to_source(cairo); fillToStrokePathClip(state); } else { 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); if (strokePathClip) { cairo_push_group(cairo); fillToStrokePathClip(state); cairo_pop_group_to_source(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")); if (mask) { cairo_save(cairo); cairo_clip(cairo); cairo_set_matrix(cairo, &mask_matrix); cairo_mask(cairo, mask); cairo_restore(cairo); } else { 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); } } bool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat, Object *str, const double *pmat, int paintType, int /*tilingType*/, Dict *resDict, const double *mat, const 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; double scaleX, scaleY; int surface_width, surface_height; StrokePathClip *strokePathTmp; bool adjusted_stroke_width_tmp; cairo_pattern_t *maskTmp; width = bbox[2] - bbox[0]; height = bbox[3] - bbox[1]; if (xStep != width || yStep != height) return false; /* 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)); scaleX = surface_width / width; scaleY = surface_height / height; surface = cairo_surface_create_similar(cairo_get_target(cairo), CAIRO_CONTENT_COLOR_ALPHA, surface_width, surface_height); if (cairo_surface_status(surface)) return false; old_cairo = cairo; cairo = cairo_create(surface); cairo_surface_destroy(surface); setContextAntialias(cairo, antialias); box.x1 = bbox[0]; box.y1 = bbox[1]; box.x2 = bbox[2]; box.y2 = bbox[3]; cairo_scale(cairo, scaleX, scaleY); cairo_translate(cairo, -box.x1, -box.y1); strokePathTmp = strokePathClip; strokePathClip = nullptr; adjusted_stroke_width_tmp = adjusted_stroke_width; maskTmp = mask; mask = nullptr; gfx = new Gfx(doc, this, resDict, &box, nullptr, nullptr, nullptr, gfxA); if (paintType == 2) inUncoloredPattern = true; gfx->display(str); if (paintType == 2) inUncoloredPattern = false; delete gfx; strokePathClip = strokePathTmp; adjusted_stroke_width = adjusted_stroke_width_tmp; mask = maskTmp; pattern = cairo_pattern_create_for_surface(cairo_get_target(cairo)); cairo_destroy(cairo); cairo = old_cairo; if (cairo_pattern_status(pattern)) return false; // Cairo can fail if the pattern translation is too large. Fix by making the // translation smaller. const double det = pmat[0] * pmat[3] - pmat[1] * pmat[2]; // Find the number of repetitions of pattern we need to shift by. Transform // the translation component of pmat (pmat[4] and pmat[5]) into the pattern's // coordinate system by multiplying by inverse of pmat, then divide by // pattern size (xStep and yStep). const double xoffset = round((pmat[3] * pmat[4] - pmat[2] * pmat[5]) / (xStep * det)); const double yoffset = -round((pmat[1] * pmat[4] - pmat[0] * pmat[5]) / (yStep * det)); if (!std::isfinite(xoffset) || !std::isfinite(yoffset)) { error(errSyntaxWarning, -1, "CairoOutputDev: Singular matrix in tilingPatternFill"); return false; } // Shift pattern_matrix by multiples of the pattern size. pattern_matrix.x0 -= xoffset * pattern_matrix.xx * xStep + yoffset * pattern_matrix.xy * yStep; pattern_matrix.y0 -= xoffset * pattern_matrix.yx * xStep + yoffset * pattern_matrix.yy * yStep; state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); cairo_rectangle(cairo, xMin, yMin, xMax - xMin, yMax - yMin); cairo_matrix_init_scale(&matrix, scaleX, scaleY); cairo_matrix_translate(&matrix, -box.x1, -box.y1); 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 true; } #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) bool CairoOutputDev::functionShadedFill(GfxState *state, GfxFunctionShading *shading) { // Function shaded fills are subdivided to rectangles that are the // following size in device space. Note when printing this size is // in points. const int subdivide_pixels = 10; double x_begin, x_end, x1, x2; double y_begin, y_end, y1, y2; double x_step; double y_step; GfxColor color; GfxRGB rgb; cairo_matrix_t mat; const double *matrix = shading->getMatrix(); mat.xx = matrix[0]; mat.yx = matrix[1]; mat.xy = matrix[2]; mat.yy = matrix[3]; mat.x0 = matrix[4]; mat.y0 = matrix[5]; if (cairo_matrix_invert(&mat)) { error(errSyntaxWarning, -1, "matrix not invertible\n"); return false; } // get cell size in pattern space x_step = y_step = subdivide_pixels; cairo_matrix_transform_distance(&mat, &x_step, &y_step); cairo_pattern_destroy(fill_pattern); fill_pattern = cairo_pattern_create_mesh(); cairo_pattern_set_matrix(fill_pattern, &mat); shading->getDomain(&x_begin, &y_begin, &x_end, &y_end); for (x1 = x_begin; x1 < x_end; x1 += x_step) { x2 = x1 + x_step; if (x2 > x_end) x2 = x_end; for (y1 = y_begin; y1 < y_end; y1 += y_step) { y2 = y1 + y_step; if (y2 > y_end) y2 = y_end; cairo_mesh_pattern_begin_patch(fill_pattern); cairo_mesh_pattern_move_to(fill_pattern, x1, y1); cairo_mesh_pattern_line_to(fill_pattern, x2, y1); cairo_mesh_pattern_line_to(fill_pattern, x2, y2); cairo_mesh_pattern_line_to(fill_pattern, x1, y2); shading->getColor(x1, y1, &color); shading->getColorSpace()->getRGB(&color, &rgb); cairo_mesh_pattern_set_corner_color_rgb(fill_pattern, 0, colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b)); shading->getColor(x2, y1, &color); shading->getColorSpace()->getRGB(&color, &rgb); cairo_mesh_pattern_set_corner_color_rgb(fill_pattern, 1, colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b)); shading->getColor(x2, y2, &color); shading->getColorSpace()->getRGB(&color, &rgb); cairo_mesh_pattern_set_corner_color_rgb(fill_pattern, 2, colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b)); shading->getColor(x1, y2, &color); shading->getColorSpace()->getRGB(&color, &rgb); cairo_mesh_pattern_set_corner_color_rgb(fill_pattern, 3, 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 true; } #endif /* CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) */ bool 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 false; } bool CairoOutputDev::axialShadedSupportExtend(GfxState *state, GfxAxialShading *shading) { return (shading->getExtend0() == shading->getExtend1()); } bool CairoOutputDev::radialShadedFill(GfxState *state, GfxRadialShading *shading, double sMin, double sMax) { double x0, y0, r0, x1, y1, r1; double dx, dy, dr; cairo_matrix_t matrix; double scale; shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1); dx = x1 - x0; dy = y1 - y0; dr = r1 - r0; // Cairo/pixman do not work well with a very large or small scaled // matrix. See cairo bug #81657. // // As a workaround, scale the pattern by the average of the vertical // and horizontal scaling of the current transformation matrix. cairo_get_matrix(cairo, &matrix); scale = (sqrt(matrix.xx * matrix.xx + matrix.yx * matrix.yx) + sqrt(matrix.xy * matrix.xy + matrix.yy * matrix.yy)) / 2; cairo_matrix_init_scale(&matrix, scale, scale); cairo_pattern_destroy(fill_pattern); fill_pattern = cairo_pattern_create_radial((x0 + sMin * dx) * scale, (y0 + sMin * dy) * scale, (r0 + sMin * dr) * scale, (x0 + sMax * dx) * scale, (y0 + sMax * dy) * scale, (r0 + sMax * dr) * scale); cairo_pattern_set_matrix(fill_pattern, &matrix); 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 false; } bool CairoOutputDev::radialShadedSupportExtend(GfxState *state, GfxRadialShading *shading) { return (shading->getExtend0() == shading->getExtend1()); } #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) bool 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 true; } bool 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++) { const 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 true; } #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 = nullptr; } strokePathClip->cap = cairo_get_line_cap(cairo); strokePathClip->join = cairo_get_line_join(cairo); strokePathClip->miter = cairo_get_miter_limit(cairo); strokePathClip->ref_count = 1; } void CairoOutputDev::fillToStrokePathClip(GfxState *state) { cairo_save(cairo); cairo_set_matrix(cairo, &strokePathClip->ctm); cairo_set_line_width(cairo, strokePathClip->line_width); 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); } void CairoOutputDev::beginString(GfxState *state, const 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, const 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) { const UnicodeMap *utf8Map = globalParams->getUtf8Map(); 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 (!textPage) 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 = nullptr; if (use_show_text_glyphs) { gfree(clusters); clusters = nullptr; gfree(utf8); utf8 = nullptr; } } bool CairoOutputDev::beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode code, const Unicode *u, int uLen) { cairo_save(cairo); cairo_matrix_t matrix; const double *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 false; } 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 = true; } 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 = nullptr; } } void CairoOutputDev::beginActualText(GfxState *state, const GooString *text) { if (textPage) actualText->begin(state, text); } void CairoOutputDev::endActualText(GfxState *state) { if (textPage) 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) { cairo_pattern_t *pattern; cairo_surface_t *surface = nullptr; cairo_push_group_with_content(cairo, content); pattern = cairo_pop_group(cairo); cairo_pattern_get_surface(pattern, &surface); cairo_surface_reference(surface); cairo_pattern_destroy(pattern); return surface; } void CairoOutputDev::beginTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/, GfxColorSpace *blendingColorSpace, bool /*isolated*/, bool knockout, bool 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); setContextAntialias(cairo_shape, antialias); /* 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); cairo_set_matrix(cairo_shape, &matrix); } } if (groupColorSpaceStack->next && groupColorSpaceStack->next->knockout) { /* we need to track the shape */ cairo_push_group(cairo_shape); } if (false && 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*/, const double * /*bbox*/) { LOG(printf("paint transparency group\n")); cairo_save(cairo); cairo_set_matrix(cairo, &groupColorSpaceStack->group_matrix); if (shape) { /* 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); if (!mask) { 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 = nullptr; } if (shape) { if (cairo_shape) { cairo_set_source(cairo_shape, shape); cairo_paint(cairo_shape); cairo_set_source_rgb(cairo_shape, 0, 0, 0); } cairo_pattern_destroy(shape); shape = nullptr; } 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, const double *bbox, bool 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); setContextAntialias(maskCtx, antialias); // 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 = reinterpret_cast(cairo_image_surface_get_data(source)); /* get stride in units of 32 bits */ ptrdiff_t 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 = nullptr; } } groupColorSpaceStack = css->next; delete css; } void CairoOutputDev::clearSoftMask(GfxState * /*state*/) { if (mask) cairo_pattern_destroy(mask); mask = nullptr; } /* 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-ordinates 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, bool interpolate) { if (interpolate) return CAIRO_FILTER_GOOD; 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_GOOD; } void CairoOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool 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; unsigned char 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, bool invert, bool 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; unsigned char 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, false, inlineImg); } else { drawImageMaskRegular(state, ref, str, width, height, invert, false, 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(), true, false, false); } 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, bool invert, bool interpolate, bool inlineImg) { unsigned char *buffer; unsigned char *dest; cairo_surface_t *image; cairo_pattern_t *pattern; int x, y, i, bit; ImageStream *imgStr; unsigned char *pix; cairo_matrix_t matrix; int invert_bit; ptrdiff_t 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); 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); if (strokePathClip) { cairo_push_group(cairo); fillToStrokePathClip(state); cairo_pop_group_to_source(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, bool invert, bool interpolate, bool inlineImg) { unsigned char *buffer; cairo_surface_t *image; cairo_pattern_t *pattern; ImageStream *imgStr; unsigned char *pix; cairo_matrix_t matrix; int invert_bit; ptrdiff_t 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-ordinates 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_GOOD : CAIRO_FILTER_FAST); 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); if (strokePathClip) { cairo_push_group(cairo); fillToStrokePathClip(state); cairo_pop_group_to_source(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, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert, bool maskInterpolate) { ImageStream *maskImgStr, *imgStr; ptrdiff_t 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; unsigned char *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 = reinterpret_cast(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, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, bool maskInterpolate) { ImageStream *maskImgStr, *imgStr; ptrdiff_t 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; unsigned char *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(); if (likely(pix != nullptr)) { 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 = reinterpret_cast(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, height); 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; } bool 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 false; strBuffer = (char *)gmalloc(len); str->close(); str->reset(); for (i = 0; i < len; ++i) strBuffer[i] = str->getChar(); *buffer = strBuffer; *length = len; return true; } static bool colorMapHasIdentityDecodeMap(GfxImageColorMap *colorMap) { for (int i = 0; i < colorMap->getNumPixelComps(); i++) { if (colorMap->getDecodeLow(i) != 0.0 || colorMap->getDecodeHigh(i) != 1.0) return false; } return true; } #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2) static cairo_status_t setMimeIdFromRef(cairo_surface_t *surface, const char *mime_type, const char *mime_id_prefix, Ref ref) { GooString *mime_id; char *idBuffer; cairo_status_t status; mime_id = new GooString; if (mime_id_prefix) mime_id->append(mime_id_prefix); mime_id->appendf("{0:d}-{1:d}", ref.gen, ref.num); idBuffer = copyString(mime_id->c_str()); status = cairo_surface_set_mime_data(surface, mime_type, (const unsigned char *)idBuffer, mime_id->getLength(), gfree, idBuffer); delete mime_id; if (status) gfree(idBuffer); return status; } #endif #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) bool CairoOutputDev::setMimeDataForJBIG2Globals(Stream *str, cairo_surface_t *image) { JBIG2Stream *jb2Str = static_cast(str); Object *globalsStr = jb2Str->getGlobalsStream(); char *globalsBuffer; int globalsLength; // nothing to do for JBIG2 stream without Globals if (!globalsStr->isStream()) return true; if (setMimeIdFromRef(image, CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID, nullptr, jb2Str->getGlobalsStreamRef())) return false; if (!getStreamData(globalsStr->getStream(), &globalsBuffer, &globalsLength)) return false; if (cairo_surface_set_mime_data(image, CAIRO_MIME_TYPE_JBIG2_GLOBAL, (const unsigned char *)globalsBuffer, globalsLength, gfree, (void *)globalsBuffer)) { gfree(globalsBuffer); return false; } return true; } #endif #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10) bool CairoOutputDev::setMimeDataForCCITTParams(Stream *str, cairo_surface_t *image, int height) { CCITTFaxStream *ccittStr = static_cast(str); GooString params; params.appendf("Columns={0:d}", ccittStr->getColumns()); params.appendf(" Rows={0:d}", height); params.appendf(" K={0:d}", ccittStr->getEncoding()); params.appendf(" EndOfLine={0:d}", ccittStr->getEndOfLine() ? 1 : 0); params.appendf(" EncodedByteAlign={0:d}", ccittStr->getEncodedByteAlign() ? 1 : 0); params.appendf(" EndOfBlock={0:d}", ccittStr->getEndOfBlock() ? 1 : 0); params.appendf(" BlackIs1={0:d}", ccittStr->getBlackIs1() ? 1 : 0); params.appendf(" DamagedRowsBeforeError={0:d}", ccittStr->getDamagedRowsBeforeError()); char *p = strdup(params.c_str()); if (cairo_surface_set_mime_data(image, CAIRO_MIME_TYPE_CCITT_FAX_PARAMS, (const unsigned char *)p, params.getLength(), gfree, (void *)p)) { gfree(p); return false; } return true; } #endif void CairoOutputDev::setMimeData(GfxState *state, Stream *str, Object *ref, GfxImageColorMap *colorMap, cairo_surface_t *image, int height) { char *strBuffer; int len; Object obj; GfxColorSpace *colorSpace; StreamKind strKind = str->getKind(); const char *mime_type; if (!printing) return; switch (strKind) { case strDCT: mime_type = CAIRO_MIME_TYPE_JPEG; break; case strJPX: mime_type = CAIRO_MIME_TYPE_JP2; break; #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) case strJBIG2: mime_type = CAIRO_MIME_TYPE_JBIG2; break; #endif #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10) case strCCITTFax: mime_type = CAIRO_MIME_TYPE_CCITT_FAX; break; #endif default: return; } obj = str->getDict()->lookup("ColorSpace"); colorSpace = GfxColorSpace::parse(nullptr, &obj, this, state); // colorspace in stream dict may be different from colorspace in jpx // data if (strKind == 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 CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) if (strKind == strJBIG2 && !setMimeDataForJBIG2Globals(str, image)) return; #endif #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10) if (strKind == strCCITTFax && !setMimeDataForCCITTParams(str, image, height)) return; #endif if (getStreamData(str->getNextStream(), &strBuffer, &len)) { cairo_status_t status = CAIRO_STATUS_SUCCESS; #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2) // Since 1.5.10 the cairo PS backend stores images with UNIQUE_ID in PS memory so the // image can be re-used multiple times. As we don't know how large the images are or // how many times they are used, there is no benefit in enabling this. Issue #106 if (cairo_surface_get_type(cairo_get_target(cairo)) != CAIRO_SURFACE_TYPE_PS) { if (ref && ref->isRef()) { status = setMimeIdFromRef(image, CAIRO_MIME_TYPE_UNIQUE_ID, "poppler-surface-", ref->getRef()); } } #endif if (!status) { status = cairo_surface_set_mime_data(image, mime_type, (const unsigned char *)strBuffer, len, gfree, strBuffer); } if (status) gfree(strBuffer); } } class RescaleDrawImage : public CairoRescaleBox { private: ImageStream *imgStr; GfxRGB *lookup; int width; GfxImageColorMap *colorMap; const int *maskColors; int current_row; bool imageError; public: ~RescaleDrawImage() override; cairo_surface_t *getSourceImage(Stream *str, int widthA, int height, int scaledWidth, int scaledHeight, bool printing, GfxImageColorMap *colorMapA, const int *maskColorsA) { cairo_surface_t *image = nullptr; int i; lookup = nullptr; colorMap = colorMapA; maskColors = maskColorsA; width = widthA; current_row = -1; imageError = false; /* 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; unsigned char pix; n = 1 << colorMap->getBits(); lookup = (GfxRGB *)gmallocn(n, sizeof(GfxRGB)); for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getRGB(&pix, &lookup[i]); } } #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) bool needsCustomDownscaling = false; #else bool needsCustomDownscaling = true; #endif if (printing) { if (width > MAX_PRINT_IMAGE_SIZE || height > MAX_PRINT_IMAGE_SIZE) { if (width > height) { scaledWidth = MAX_PRINT_IMAGE_SIZE; scaledHeight = MAX_PRINT_IMAGE_SIZE * (double)height / width; } else { scaledHeight = MAX_PRINT_IMAGE_SIZE; scaledWidth = MAX_PRINT_IMAGE_SIZE * (double)width / height; } needsCustomDownscaling = true; if (scaledWidth == 0) { scaledWidth = 1; } if (scaledHeight == 0) { scaledHeight = 1; } } else { needsCustomDownscaling = false; } } if (!needsCustomDownscaling || scaledWidth >= width || scaledHeight >= height) { // No downscaling. Create cairo image containing the source image data. unsigned char *buffer; ptrdiff_t 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 = reinterpret_cast(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) override { unsigned char *pix; if (row_num <= current_row) return; while (current_row < row_num) { pix = imgStr->getLine(); current_row++; } if (unlikely(pix == nullptr)) { memset(row_data, 0, width * 4); if (!imageError) { error(errInternal, -1, "Bad image stream"); imageError = true; } } else if (lookup) { unsigned char *p = pix; GfxRGB rgb; for (int 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(); } } } }; RescaleDrawImage::~RescaleDrawImage() = default; void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int widthA, int heightA, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool 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_GOOD; 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 */ // cairo 1.15.10 allows mime image data to have different size to cairo image // mime image size will be scaled to same size as cairo image #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10) bool requireSameSize = false; #else bool requireSameSize = true; #endif if (!requireSameSize || (width == widthA && height == heightA)) setMimeData(state, str, ref, colorMap, image, heightA); } 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 = nullptr; } 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 = nullptr; numImages = 0; size = 0; imgDrawCbk = nullptr; imgDrawCbkData = nullptr; } 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) { const 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, bool invert, bool interpolate, bool 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(nullptr); cairo_surface_destroy(surface); cairo_destroy(cr); } } void CairoImageOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool 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, false); if (state->getFillColorSpace()->getMode() == csPattern) { cairo_mask(cairo, mask); } image->setImage(surface); setCairo(nullptr); cairo_surface_destroy(surface); cairo_destroy(cr); } } void CairoImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool 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(nullptr); cairo_surface_destroy(surface); cairo_destroy(cr); } } void CairoImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, bool 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(nullptr); cairo_surface_destroy(surface); cairo_destroy(cr); } } void CairoImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert, bool 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(nullptr); cairo_surface_destroy(surface); cairo_destroy(cr); } }