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