| //======================================================================== |
| // |
| // CairoOutputDev.cc |
| // |
| // Copyright 2003 Glyph & Cog, LLC |
| // Copyright 2004 Red Hat, Inc |
| // |
| //======================================================================== |
| |
| //======================================================================== |
| // |
| // Modified under the Poppler project - http://poppler.freedesktop.org |
| // |
| // All changes made under the Poppler project to this file are licensed |
| // under GPL version 2 or later |
| // |
| // Copyright (C) 2005-2008 Jeff Muizelaar <jeff@infidigm.net> |
| // Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com> |
| // Copyright (C) 2005, 2009, 2012, 2017-2019 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2005 Nickolay V. Shmyrev <nshmyrev@yandex.ru> |
| // Copyright (C) 2006-2011, 2013, 2014, 2017, 2018 Carlos Garcia Campos <carlosgc@gnome.org> |
| // Copyright (C) 2008 Carl Worth <cworth@cworth.org> |
| // Copyright (C) 2008-2018 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu> |
| // Copyright (C) 2008, 2009 Chris Wilson <chris@chris-wilson.co.uk> |
| // Copyright (C) 2008, 2012 Hib Eris <hib@hiberis.nl> |
| // Copyright (C) 2009, 2010 David Benjamin <davidben@mit.edu> |
| // Copyright (C) 2011-2014 Thomas Freitag <Thomas.Freitag@alfa.de> |
| // Copyright (C) 2012 Patrick Pfeifer <p2000@mailinator.com> |
| // Copyright (C) 2012, 2015, 2016 Jason Crain <jason@aquaticape.us> |
| // Copyright (C) 2015 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp> |
| // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich |
| // Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de> |
| // Copyright (C) 2019 Marek Kasik <mkasik@redhat.com> |
| // |
| // To see a description of the changes please see the Changelog file that |
| // came with your tarball or type make ChangeLog if you are building from git |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #include <cstdint> |
| #include <string.h> |
| #include <math.h> |
| #include <assert.h> |
| #include <cairo.h> |
| |
| #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 <fofi/FoFiTrueType.h> |
| #include <splash/SplashBitmap.h> |
| #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; |
| bool CairoOutputDev::ft_lib_initialized = false; |
| |
| CairoOutputDev::CairoOutputDev() { |
| doc = nullptr; |
| |
| if (!ft_lib_initialized) { |
| FT_Init_FreeType(&ft_lib); |
| ft_lib_initialized = true; |
| } |
| |
| 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); |
| |
| cairo_pattern_add_color_stop_rgba(fill_pattern, offset, |
| colToDbl(fill_color.r), |
| colToDbl(fill_color.g), |
| colToDbl(fill_color.b), |
| fill_opacity); |
| LOG(printf ("fill color stop: %f (%d, %d, %d)\n", |
| offset, fill_color.r, fill_color.g, fill_color.b)); |
| } |
| |
| void CairoOutputDev::updateBlendMode(GfxState *state) { |
| switch (state->getBlendMode()) { |
| default: |
| case gfxBlendNormal: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_OVER); |
| break; |
| case gfxBlendMultiply: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_MULTIPLY); |
| break; |
| case gfxBlendScreen: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_SCREEN); |
| break; |
| case gfxBlendOverlay: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_OVERLAY); |
| break; |
| case gfxBlendDarken: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_DARKEN); |
| break; |
| case gfxBlendLighten: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_LIGHTEN); |
| break; |
| case gfxBlendColorDodge: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_COLOR_DODGE); |
| break; |
| case gfxBlendColorBurn: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_COLOR_BURN); |
| break; |
| case gfxBlendHardLight: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_HARD_LIGHT); |
| break; |
| case gfxBlendSoftLight: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_SOFT_LIGHT); |
| break; |
| case gfxBlendDifference: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_DIFFERENCE); |
| break; |
| case gfxBlendExclusion: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_EXCLUSION); |
| break; |
| case gfxBlendHue: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_HUE); |
| break; |
| case gfxBlendSaturation: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_SATURATION); |
| break; |
| case gfxBlendColor: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_COLOR); |
| break; |
| case gfxBlendLuminosity: |
| cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_LUMINOSITY); |
| break; |
| } |
| LOG(printf ("blend mode: %d\n", (int)state->getBlendMode())); |
| } |
| |
| void CairoOutputDev::updateFont(GfxState *state) { |
| cairo_font_face_t *font_face; |
| cairo_matrix_t matrix, invert_matrix; |
| |
| LOG(printf ("updateFont() font=%s\n", state->getFont()->getName()->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(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, GfxPath *path) { |
| GfxSubpath *subpath; |
| int i, j; |
| double x, y; |
| cairo_new_path (c); |
| for (i = 0; i < path->getNumSubpaths(); ++i) { |
| subpath = path->getSubpath(i); |
| if (subpath->getNumPoints() > 0) { |
| if (align_stroke_coords) { |
| alignStrokeCoords(subpath, 0, &x, &y); |
| } else { |
| x = subpath->getX(0); |
| y = subpath->getY(0); |
| } |
| cairo_move_to (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; |
| |
| 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, Unicode *u, int uLen) |
| { |
| if (currentFont) { |
| glyphs[glyphCount].index = currentFont->getGlyph (code, u, uLen); |
| glyphs[glyphCount].x = x - originX; |
| glyphs[glyphCount].y = y - originY; |
| glyphCount++; |
| if (use_show_text_glyphs) { |
| GooString enc("UTF-8"); |
| UnicodeMap *utf8Map = globalParams->getUnicodeMap(&enc); |
| if (utf8Max - utf8Count < uLen*6) { |
| // utf8 encoded characters can be up to 6 bytes |
| if (utf8Max > uLen*6) |
| utf8Max *= 2; |
| else |
| utf8Max += 2*uLen*6; |
| utf8 = (char *) grealloc (utf8, utf8Max); |
| } |
| clusters[clusterCount].num_bytes = 0; |
| for (int i = 0; i < uLen; i++) { |
| int size = utf8Map->mapUnicode(u[i], utf8 + utf8Count, utf8Max - utf8Count); |
| utf8Count += size; |
| clusters[clusterCount].num_bytes += size; |
| } |
| clusters[clusterCount].num_glyphs = 1; |
| clusterCount++; |
| } |
| } |
| |
| if (!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, 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 (0 && forSoftMask) |
| cairo_push_group_with_content (cairo, CAIRO_CONTENT_ALPHA); |
| else |
| cairo_push_group (cairo); |
| |
| /* push_group has an implicit cairo_save() */ |
| if (knockout) { |
| /*XXX: let's hope this matches the semantics needed */ |
| cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); |
| } else { |
| cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); |
| } |
| } |
| |
| void CairoOutputDev::endTransparencyGroup(GfxState * /*state*/) { |
| if (group) |
| cairo_pattern_destroy(group); |
| group = cairo_pop_group (cairo); |
| |
| LOG(printf ("end transparency group\n")); |
| |
| if (groupColorSpaceStack->next && groupColorSpaceStack->next->knockout) { |
| if (shape) |
| cairo_pattern_destroy(shape); |
| shape = cairo_pop_group (cairo_shape); |
| } |
| } |
| |
| void CairoOutputDev::paintTransparencyGroup(GfxState * /*state*/, 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<uint32_t *>(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<unsigned int *>(buffer + y * row_stride); |
| pix = imgStr->getLine(); |
| colorMap->getRGBLine (pix, dest, width); |
| } |
| |
| filter = getFilterForSurface (image, interpolate); |
| |
| cairo_surface_mark_dirty (image); |
| pattern = cairo_pattern_create_for_surface (image); |
| cairo_surface_destroy (image); |
| if (cairo_pattern_status (pattern)) |
| goto cleanup; |
| |
| LOG (printf ("drawMaskedImage %dx%d\n", width, height)); |
| |
| cairo_pattern_set_filter (pattern, filter); |
| cairo_pattern_set_filter (maskPattern, maskFilter); |
| |
| if (!printing) { |
| cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD); |
| cairo_pattern_set_extend (maskPattern, CAIRO_EXTEND_PAD); |
| } |
| |
| cairo_matrix_init_translate (&matrix, 0, height); |
| cairo_matrix_scale (&matrix, width, -height); |
| cairo_pattern_set_matrix (pattern, &matrix); |
| if (cairo_pattern_status (pattern)) { |
| cairo_pattern_destroy (pattern); |
| cairo_pattern_destroy (maskPattern); |
| goto cleanup; |
| } |
| |
| cairo_matrix_init_translate (&maskMatrix, 0, maskHeight); |
| cairo_matrix_scale (&maskMatrix, maskWidth, -maskHeight); |
| cairo_pattern_set_matrix (maskPattern, &maskMatrix); |
| if (cairo_pattern_status (maskPattern)) { |
| cairo_pattern_destroy (maskPattern); |
| cairo_pattern_destroy (pattern); |
| goto cleanup; |
| } |
| |
| if (!printing) { |
| cairo_save (cairo); |
| cairo_set_source (cairo, pattern); |
| cairo_rectangle (cairo, 0., 0., 1., 1.); |
| cairo_clip (cairo); |
| cairo_mask (cairo, maskPattern); |
| cairo_restore (cairo); |
| } else { |
| cairo_set_source (cairo, pattern); |
| cairo_mask (cairo, maskPattern); |
| } |
| |
| if (cairo_shape) { |
| cairo_save (cairo_shape); |
| cairo_set_source (cairo_shape, pattern); |
| if (!printing) { |
| cairo_rectangle (cairo_shape, 0., 0., 1., 1.); |
| cairo_fill (cairo_shape); |
| } else { |
| cairo_mask (cairo_shape, pattern); |
| } |
| cairo_restore (cairo_shape); |
| } |
| |
| cairo_pattern_destroy (maskPattern); |
| cairo_pattern_destroy (pattern); |
| |
| cleanup: |
| imgStr->close(); |
| delete imgStr; |
| } |
| |
| |
| //XXX: is this affect by AIS(alpha is shape)? |
| void CairoOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, |
| int width, int height, |
| GfxImageColorMap *colorMap, |
| 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<unsigned int *>(buffer + y * row_stride); |
| pix = imgStr->getLine(); |
| colorMap->getRGBLine (pix, dest, width); |
| } |
| |
| filter = getFilterForSurface (image, interpolate); |
| |
| cairo_surface_mark_dirty (image); |
| |
| setMimeData(state, str, ref, colorMap, image, 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
|