X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fextension%2Finternal%2Fcairo-render-context.cpp;h=b68105b30908e275982102adc6ea90dc07208fd5;hb=6c28c26c5dc911196633a0c332b56f712085fa3f;hp=0a12a5c5bba60fa6621a69bab1caea75134de6dc;hpb=4db978f936a528ef0a6646c49eaa6eb5978d5ab2;p=inkscape.git diff --git a/src/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp index 0a12a5c5b..b68105b30 100644 --- a/src/extension/internal/cairo-render-context.cpp +++ b/src/extension/internal/cairo-render-context.cpp @@ -1,11 +1,11 @@ -#define __SP_CAIRO_RENDER_CONTEXT_C__ - /** \file * Rendering with Cairo. */ /* * Author: * Miklos Erdelyi + * Jon A. Cruz + * Abhishek Sharma * * Copyright (C) 2006 Miklos Erdelyi * @@ -80,6 +80,7 @@ #include //#define TRACE(_args) g_printf _args +//#define TRACE(_args) g_message _args #define TRACE(_args) //#define TEST(_args) _args #define TEST(_args) @@ -107,7 +108,7 @@ static cairo_status_t _write_callback(void *closure, const unsigned char *data, CairoRenderContext::CairoRenderContext(CairoRenderer *parent) : _dpi(72), - _pdf_level(0), + _pdf_level(1), _ps_level(1), _eps(false), _is_texttopath(FALSE), @@ -116,7 +117,7 @@ CairoRenderContext::CairoRenderContext(CairoRenderer *parent) : _stream(NULL), _is_valid(FALSE), _vector_based_target(FALSE), - _cr(NULL), + _cr(NULL), // Cairo context _surface(NULL), _target(CAIRO_SURFACE_TYPE_IMAGE), _target_format(CAIRO_FORMAT_ARGB32), @@ -125,14 +126,27 @@ CairoRenderContext::CairoRenderContext(CairoRenderer *parent) : _renderer(parent), _render_mode(RENDER_MODE_NORMAL), _clip_mode(CLIP_MODE_MASK) -{} +{ + font_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, font_data_free); +} CairoRenderContext::~CairoRenderContext(void) { + if(font_table != NULL) { + g_hash_table_remove_all(font_table); + } + if (_cr) cairo_destroy(_cr); if (_surface) cairo_surface_destroy(_surface); if (_layout) g_object_unref(_layout); } +void CairoRenderContext::font_data_free(gpointer data) +{ + cairo_font_face_t *font_face = (cairo_font_face_t *)data; + if (font_face) { + cairo_font_face_destroy(font_face); + } +} CairoRenderer* CairoRenderContext::getRenderer(void) const @@ -192,6 +206,8 @@ CairoRenderContext::cloneMe(double width, double height) const (int)ceil(width), (int)ceil(height)); new_context->_cr = cairo_create(surface); new_context->_surface = surface; + new_context->_width = width; + new_context->_height = height; new_context->_is_valid = TRUE; return new_context; @@ -481,8 +497,8 @@ void CairoRenderContext::setClipMode(CairoClipMode mode) { switch (mode) { - case CLIP_MODE_PATH: - case CLIP_MODE_MASK: + case CLIP_MODE_PATH: // Clip is rendered as a path for vector output + case CLIP_MODE_MASK: // Clip is rendered as a bitmap for raster output. _clip_mode = mode; break; default: @@ -500,7 +516,7 @@ CairoRenderContext::getClipMode(void) const CairoRenderState* CairoRenderContext::_createState(void) { - CairoRenderState *state = (CairoRenderState*)g_malloc(sizeof(CairoRenderState)); + CairoRenderState *state = (CairoRenderState*)g_try_malloc(sizeof(CairoRenderState)); g_assert( state != NULL ); state->has_filtereffect = FALSE; @@ -538,9 +554,23 @@ CairoRenderContext::popLayer(void) g_assert( _is_valid ); float opacity = _state->opacity; - TRACE(("--popLayer w/ %f\n", opacity)); + TRACE(("--popLayer w/ opacity %f\n", opacity)); + + /* + At this point, the Cairo source is ready. A Cairo mask must be created if required. + Care must be taken of transformatons as Cairo, like PS and PDF, treats clip paths and + masks independently of the objects they effect while in SVG the clip paths and masks + are defined relative to the objects they are attached to. + Notes: + 1. An SVG object may have both a clip path and a mask! + 2. An SVG clip path can be composed of an object with a clip path. This is not handled properly. + 3. An SVG clipped or masked object may be first drawn off the page and then translated onto + the page (document). This is also not handled properly. + 4. The code converts all SVG masks to bitmaps. This shouldn't be necessary. + 5. Cairo expects a mask to use only the alpha channel. SVG masks combine the RGB luminance with + alpha. This is handled here by doing a pixel by pixel conversion. + */ - // apply clipPath or mask if present SPClipPath *clip_path = _state->clip_path; SPMask *mask = _state->mask; if (clip_path || mask) { @@ -548,15 +578,17 @@ CairoRenderContext::popLayer(void) CairoRenderContext *clip_ctx = 0; cairo_surface_t *clip_mask = 0; + // Apply any clip path first if (clip_path) { + TRACE((" Applying clip\n")); if (_render_mode == RENDER_MODE_CLIP) mask = NULL; // disable mask when performing nested clipping if (_vector_based_target) { - setClipMode(CLIP_MODE_PATH); + setClipMode(CLIP_MODE_PATH); // Vector if (!mask) { cairo_pop_group_to_source(_cr); - _renderer->applyClipPath(this, clip_path); + _renderer->applyClipPath(this, clip_path); // Uses cairo_clip() if (opacity == 1.0) cairo_paint(_cr); else @@ -570,9 +602,12 @@ CairoRenderContext::popLayer(void) // setup a new rendering context clip_ctx = _renderer->createContext(); clip_ctx->setImageTarget(CAIRO_FORMAT_A8); - clip_ctx->setClipMode(CLIP_MODE_MASK); + clip_ctx->setClipMode(CLIP_MODE_MASK); // Raster + // This code ties the clipping to the document coordinates. It doesn't allow + // for a clipped object intially drawn off the page and then translated onto + // the page. if (!clip_ctx->setupSurface(_width, _height)) { - TRACE(("setupSurface failed\n")); + TRACE(("clip: setupSurface failed\n")); _renderer->destroyContext(clip_ctx); return; } @@ -583,20 +618,23 @@ CairoRenderContext::popLayer(void) cairo_paint(clip_ctx->_cr); cairo_restore(clip_ctx->_cr); - // if a mask won't be applied set opacity too + // If a mask won't be applied set opacity too. (The clip is represented by a solid Cairo mask.) if (!mask) cairo_set_source_rgba(clip_ctx->_cr, 1.0, 1.0, 1.0, opacity); else cairo_set_source_rgba(clip_ctx->_cr, 1.0, 1.0, 1.0, 1.0); // copy over the correct CTM + // It must be stored in item_transform of current state after pushState. + Geom::Matrix item_transform; if (_state->parent_has_userspace) - clip_ctx->setTransform(&getParentState()->transform); + item_transform = getParentState()->transform * _state->item_transform; else - clip_ctx->setTransform(&_state->transform); + item_transform = _state->item_transform; // apply the clip path clip_ctx->pushState(); + clip_ctx->getCurrentState()->item_transform = item_transform; _renderer->applyClipPath(clip_ctx, clip_path); clip_ctx->popState(); @@ -611,19 +649,44 @@ CairoRenderContext::popLayer(void) } } + // Apply any mask second if (mask) { + TRACE((" Applying mask\n")); // create rendering context for mask CairoRenderContext *mask_ctx = _renderer->createContext(); - mask_ctx->setupSurface(_width, _height); + + // Fix Me: This is a kludge. PDF and PS output is set to 72 dpi but the + // Cairo surface is expecting the mask to be 90 dpi. + float surface_width = _width; + float surface_height = _height; + if( _vector_based_target ) { + surface_width *= 1.25; + surface_height *= 1.25; + } + if (!mask_ctx->setupSurface( surface_width, surface_height )) { + TRACE(("mask: setupSurface failed\n")); + _renderer->destroyContext(mask_ctx); + return; + } + TRACE(("mask surface: %f x %f at %i dpi\n", surface_width, surface_height, _dpi )); + + // Mask should start black, but it is created white. + cairo_set_source_rgba(mask_ctx->_cr, 0.0, 0.0, 0.0, 1.0); + cairo_rectangle(mask_ctx->_cr, 0, 0, surface_width, surface_height); + cairo_fill(mask_ctx->_cr); // set rendering mode to normal setRenderMode(RENDER_MODE_NORMAL); // copy the correct CTM to mask context + /* if (_state->parent_has_userspace) mask_ctx->setTransform(&getParentState()->transform); else mask_ctx->setTransform(&_state->transform); + */ + // This is probably not correct... but it seems to do the trick. + mask_ctx->setTransform(&_state->item_transform); // render mask contents to mask_ctx _renderer->applyMask(mask_ctx, mask); @@ -642,19 +705,27 @@ CairoRenderContext::popLayer(void) int stride = cairo_image_surface_get_stride(mask_image); unsigned char *pixels = cairo_image_surface_get_data(mask_image); - // premultiply with opacity - if (_state->opacity != 1.0) { - TRACE(("premul w/ %f\n", opacity)); - guint8 int_opacity = (guint8)(255 * opacity); - for (int row = 0 ; row < height; row++) { - unsigned char *row_data = pixels + (row * stride); - for (int i = 0 ; i < width; i++) { - guint32 *pixel = (guint32 *)row_data + i; - *pixel = ((((*pixel & 0x00ff0000) >> 16) * 13817 + - ((*pixel & 0x0000ff00) >> 8) * 46518 + - ((*pixel & 0x000000ff) ) * 4688) * - int_opacity); - } + // In SVG, the rgb channels as well as the alpha channel is used in masking. + // In Cairo, only the alpha channel is used thus requiring this conversion. + // SVG specifies that RGB be converted to alpha using luminance-to-alpha. + // Notes: This calculation assumes linear RGB values. VERIFY COLOR SPACE! + // The incoming pixel values already include alpha, fill-opacity, etc., + // however, opacity must still be applied. + TRACE(("premul w/ %f\n", opacity)); + const float coeff_r = 0.2125 / 255.0; + const float coeff_g = 0.7154 / 255.0; + const float coeff_b = 0.0721 / 255.0; + for (int row = 0 ; row < height; row++) { + unsigned char *row_data = pixels + (row * stride); + for (int i = 0 ; i < width; i++) { + guint32 *pixel = (guint32 *)row_data + i; + float lum_alpha = (((*pixel & 0x00ff0000) >> 16) * coeff_r + + ((*pixel & 0x0000ff00) >> 8) * coeff_g + + ((*pixel & 0x000000ff) ) * coeff_b ); + // lum_alpha can be slightly greater than 1 due to rounding errors... + // but this should be OK since it doesn't matter what the lower + // six hexadecimal numbers of *pixel are. + *pixel = (guint32)(0xff000000 * lum_alpha * opacity); } } @@ -668,6 +739,7 @@ CairoRenderContext::popLayer(void) _renderer->destroyContext(mask_ctx); } } else { + // No clip path or mask cairo_pop_group_to_source(_cr); if (opacity == 1.0) cairo_paint(_cr); @@ -710,7 +782,12 @@ CairoRenderContext::setupSurface(double width, double height) if (_vector_based_target && _stream == NULL) return false; + _width = width; + _height = height; + cairo_surface_t *surface = NULL; + cairo_matrix_t ctm; + cairo_matrix_init_identity (&ctm); switch (_target) { case CAIRO_SURFACE_TYPE_IMAGE: surface = cairo_image_surface_create(_target_format, (int)ceil(width), (int)ceil(height)); @@ -718,17 +795,27 @@ CairoRenderContext::setupSurface(double width, double height) #ifdef CAIRO_HAS_PDF_SURFACE case CAIRO_SURFACE_TYPE_PDF: surface = cairo_pdf_surface_create_for_stream(Inkscape::Extension::Internal::_write_callback, _stream, width, height); +#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 10, 0)) + cairo_pdf_surface_restrict_to_version(surface, (cairo_pdf_version_t)_pdf_level); +#endif break; #endif #ifdef CAIRO_HAS_PS_SURFACE case CAIRO_SURFACE_TYPE_PS: surface = cairo_ps_surface_create_for_stream(Inkscape::Extension::Internal::_write_callback, _stream, width, height); -#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 5, 2)) if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) { return FALSE; } - cairo_ps_surface_restrict_to_level (surface, (cairo_ps_level_t)_ps_level); - cairo_ps_surface_set_eps (surface, (cairo_bool_t) _eps); +#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 5, 2)) + cairo_ps_surface_restrict_to_level(surface, (cairo_ps_level_t)_ps_level); + cairo_ps_surface_set_eps(surface, (cairo_bool_t) _eps); +#endif + // Cairo calculates the bounding box itself, however we want to override this. See Launchpad bug #380501 +#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2)) + if (override_bbox) { + cairo_ps_dsc_comment(surface, "%%BoundingBox: 100 100 200 200"); + cairo_ps_dsc_comment(surface, "%%PageBoundingBox: 100 100 200 200"); + } #endif break; #endif @@ -737,33 +824,38 @@ CairoRenderContext::setupSurface(double width, double height) break; } - return _finishSurfaceSetup (surface); + return _finishSurfaceSetup (surface, &ctm); } bool -CairoRenderContext::setSurfaceTarget(cairo_surface_t *surface, bool is_vector) +CairoRenderContext::setSurfaceTarget(cairo_surface_t *surface, bool is_vector, cairo_matrix_t *ctm) { if (_is_valid || !surface) return false; _vector_based_target = is_vector; - bool ret = _finishSurfaceSetup (surface); + bool ret = _finishSurfaceSetup (surface, ctm); if (ret) cairo_surface_reference (surface); return ret; } bool -CairoRenderContext::_finishSurfaceSetup(cairo_surface_t *surface) +CairoRenderContext::_finishSurfaceSetup(cairo_surface_t *surface, cairo_matrix_t *ctm) { if(surface == NULL) { - return FALSE; + return false; } if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) { - return FALSE; + return false; } _cr = cairo_create(surface); + if(CAIRO_STATUS_SUCCESS != cairo_status(_cr)) { + return false; + } + if (ctm) + cairo_set_matrix(_cr, ctm); _surface = surface; if (_vector_based_target) { @@ -891,14 +983,15 @@ CairoRenderContext::popState(void) g_assert( g_slist_length(_state_stack) > 0 ); } -static bool pattern_hasItemChildren (SPPattern *pat) +static bool pattern_hasItemChildren(SPPattern *pat) { - for (SPObject *child = sp_object_first_child(SP_OBJECT(pat)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + bool hasItems = false; + for ( SPObject *child = pat->firstChild() ; child && !hasItems; child = child->getNext() ) { if (SP_IS_ITEM (child)) { - return true; + hasItems = true; } } - return false; + return hasItems; } cairo_pattern_t* @@ -937,6 +1030,7 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver // apply pattern transformation Geom::Matrix pattern_transform(pattern_patternTransform(pat)); ps2user *= pattern_transform; + Geom::Point ori (ps2user[4], ps2user[5]); // create pattern contents coordinate system if (pat->viewBox_set) { @@ -960,31 +1054,17 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver } else if (pbox && pattern_patternContentUnits(pat) == SP_PATTERN_UNITS_OBJECTBOUNDINGBOX) { pcs2dev[0] = pbox->x1 - pbox->x0; pcs2dev[3] = pbox->y1 - pbox->y0; - } - - // Calculate the size of the surface which has to be created so that the pattern resolution - // matches the output resolution (i.e., if the pattern is scaled up by a factor of two, - // the surface width should be scaled by a factor of two). - // The scaling needs to be taken into account in the ctm after the pattern transformation. - Geom::Matrix temp; - temp = pattern_transform * _state->transform; - double width_scaler = sqrt(temp[0] * temp[0] + temp[2] * temp[2]); - double height_scaler = sqrt(temp[1] * temp[1] + temp[3] * temp[3]); - if (_vector_based_target) { - // eliminate PT_PER_PX mul from these - width_scaler *= 1.25; - height_scaler *= 1.25; } -#define SUBPIX_SCALE 1000 - + // Calculate the size of the surface which has to be created +#define SUBPIX_SCALE 100 // Cairo requires an integer pattern surface width/height. // Subtract 0.5 to prevent small rounding errors from increasing pattern size by one pixel. // Multiply by SUBPIX_SCALE to allow for less than a pixel precision - double surface_width = MAX(ceil(SUBPIX_SCALE * bbox_width_scaler * width_scaler * width - 0.5), 1); - double surface_height = MAX(ceil(SUBPIX_SCALE * bbox_height_scaler * height_scaler * height - 0.5), 1); - TRACE(("surface size: %f x %f\n", surface_width, surface_height)); + double surface_width = MAX(ceil(SUBPIX_SCALE * bbox_width_scaler * width - 0.5), 1); + double surface_height = MAX(ceil(SUBPIX_SCALE * bbox_height_scaler * height - 0.5), 1); + TRACE(("pattern surface size: %f x %f\n", surface_width, surface_height)); // create new rendering context CairoRenderContext *pattern_ctx = cloneMe(surface_width, surface_height); @@ -994,23 +1074,27 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver double scale_height = surface_height / (bbox_height_scaler * height); if (scale_width != 1.0 || scale_height != 1.0 || _vector_based_target) { TRACE(("needed to scale with %f %f\n", scale_width, scale_height)); - pcs2dev *= Geom::Scale(scale_width, scale_height); - ps2user *= Geom::Scale(1.0 / scale_width, 1.0 / scale_height); + pcs2dev *= Geom::Scale(SUBPIX_SCALE,SUBPIX_SCALE); + ps2user *= Geom::Scale(1.0/SUBPIX_SCALE,1.0/SUBPIX_SCALE); } + // despite scaling up/down by subpixel scaler, the origin point of the pattern must be the same + ps2user[4] = ori[Geom::X]; + ps2user[5] = ori[Geom::Y]; + pattern_ctx->setTransform(&pcs2dev); pattern_ctx->pushState(); // create arena and group NRArena *arena = NRArena::create(); - unsigned dkey = sp_item_display_key_new(1); + unsigned dkey = SPItem::display_key_new(1); // show items and render them for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children - for (SPObject *child = sp_object_first_child(SP_OBJECT(pat_i)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + for ( SPObject *child = pat_i->firstChild() ; child; child = child->getNext() ) { if (SP_IS_ITEM (child)) { - sp_item_invoke_show (SP_ITEM (child), arena, dkey, SP_ITEM_REFERENCE_FLAGS); + SP_ITEM (child)->invoke_show (arena, dkey, SP_ITEM_REFERENCE_FLAGS); _renderer->renderItem(pattern_ctx, SP_ITEM (child)); } } @@ -1037,9 +1121,9 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver // hide all items for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children - for (SPObject *child = sp_object_first_child(SP_OBJECT(pat_i)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + for ( SPObject *child = pat_i->firstChild() ; child; child = child->getNext() ) { if (SP_IS_ITEM (child)) { - sp_item_invoke_hide (SP_ITEM (child), dkey); + SP_ITEM (child)->invoke_hide (dkey); } } break; // do not go further up the chain if children are found @@ -1060,11 +1144,11 @@ CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const pain SPLinearGradient *lg=SP_LINEARGRADIENT(paintserver); - sp_gradient_ensure_vector(SP_GRADIENT(lg)); // when exporting from commandline, vector is not built + SP_GRADIENT(lg)->ensureVector(); // when exporting from commandline, vector is not built Geom::Point p1 (lg->x1.computed, lg->y1.computed); Geom::Point p2 (lg->x2.computed, lg->y2.computed); - if (pbox && SP_GRADIENT(lg)->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + if (pbox && SP_GRADIENT(lg)->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { // convert to userspace Geom::Matrix bbox2user(pbox->x1 - pbox->x0, 0, 0, pbox->y1 - pbox->y0, pbox->x0, pbox->y0); p1 *= bbox2user; @@ -1084,12 +1168,12 @@ CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const pain SPRadialGradient *rg=SP_RADIALGRADIENT(paintserver); - sp_gradient_ensure_vector(SP_GRADIENT(rg)); // when exporting from commandline, vector is not built + SP_GRADIENT(rg)->ensureVector(); // when exporting from commandline, vector is not built Geom::Point c (rg->cx.computed, rg->cy.computed); Geom::Point f (rg->fx.computed, rg->fy.computed); double r = rg->r.computed; - if (pbox && SP_GRADIENT(rg)->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) + if (pbox && SP_GRADIENT(rg)->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) apply_bbox2user = true; // create radial gradient pattern @@ -1112,7 +1196,7 @@ CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const pain SPGradient *g = SP_GRADIENT(paintserver); // set extend type - SPGradientSpread spread = sp_gradient_get_spread(g); + SPGradientSpread spread = g->fetchSpread(); switch (spread) { case SP_GRADIENT_SPREAD_REPEAT: { cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); @@ -1159,7 +1243,8 @@ CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const pain void CairoRenderContext::_setFillStyle(SPStyle const *const style, NRRect const *pbox) { - g_return_if_fail( style->fill.isColor() + g_return_if_fail( !style->fill.set + || style->fill.isColor() || style->fill.isPaintserver() ); float alpha = SP_SCALE24_TO_FLOAT(style->fill_opacity.value); @@ -1173,6 +1258,10 @@ CairoRenderContext::_setFillStyle(SPStyle const *const style, NRRect const *pbox sp_color_get_rgb_floatv(&style->fill.value.color, rgb); cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha); + + } else if (!style->fill.set) { // unset fill is black + cairo_set_source_rgba(_cr, 0, 0, 0, alpha); + } else { g_assert( style->fill.isPaintserver() || SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) @@ -1200,7 +1289,7 @@ CairoRenderContext::_setStrokeStyle(SPStyle const *style, NRRect const *pbox) cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha); } else { - g_assert( style->fill.isPaintserver() + g_assert( style->stroke.isPaintserver() || SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style)) || SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style)) ); @@ -1275,7 +1364,11 @@ CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle con return true; } - if (style->fill.isNone() && style->stroke.isNone()) + bool no_fill = style->fill.isNone() || style->fill_opacity.value == 0; + bool no_stroke = style->stroke.isNone() || style->stroke_width.computed < 1e-9 || + style->stroke_opacity.value == 0; + + if (no_fill && no_stroke) return true; bool need_layer = ( !_state->merge_opacity && !_state->need_layer && @@ -1286,7 +1379,7 @@ CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle con else pushLayer(); - if (!style->fill.isNone()) { + if (!no_fill) { _setFillStyle(style, pbox); setPathVector(pathv); @@ -1296,15 +1389,15 @@ CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle con cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING); } - if (style->stroke.isNone()) + if (no_stroke) cairo_fill(_cr); else cairo_fill_preserve(_cr); } - if (!style->stroke.isNone()) { + if (!no_stroke) { _setStrokeStyle(style, pbox); - if (style->fill.isNone()) + if (no_fill) setPathVector(pathv); cairo_stroke(_cr); @@ -1327,9 +1420,20 @@ CairoRenderContext::renderImage(guchar *px, unsigned int w, unsigned int h, unsi if (_render_mode == RENDER_MODE_CLIP) return true; - guchar* px_rgba = (guchar*)g_malloc(4 * w * h); - if (!px_rgba) + guchar* px_rgba = NULL; + guint64 size = 4L * (guint64)w * (guint64)h; + + if(size < (guint64)G_MAXSIZE) { + px_rgba = (guchar*)g_try_malloc(4 * w * h); + if (!px_rgba) { + g_warning ("Could not allocate %lu bytes for pixel buffer!", (long unsigned) size); + return false; + } + } else { + g_warning ("the requested memory exceeds the system limit"); return false; + } + float opacity; if (_state->merge_opacity) @@ -1339,15 +1443,16 @@ CairoRenderContext::renderImage(guchar *px, unsigned int w, unsigned int h, unsi // make a copy of the original pixbuf with premultiplied alpha // if we pass the original pixbuf it will get messed up + /// @todo optimize this code, it costs a lot of time for (unsigned i = 0; i < h; i++) { + guchar const *src = px + i * rs; + guint32 *dst = (guint32 *)(px_rgba + i * rs); for (unsigned j = 0; j < w; j++) { - guchar const *src = px + i * rs + j * 4; - guint32 *dst = (guint32 *)(px_rgba + i * rs + j * 4); guchar r, g, b, alpha_dst; // calculate opacity-modified alpha alpha_dst = src[3]; - if (opacity != 1.0 && _vector_based_target) + if ((opacity != 1.0) && _vector_based_target) alpha_dst = (guchar)ceil((float)alpha_dst * opacity); // premul alpha (needed because this will be undone by cairo-pdf) @@ -1356,6 +1461,9 @@ CairoRenderContext::renderImage(guchar *px, unsigned int w, unsigned int h, unsi b = src[2]*alpha_dst/255; *dst = (((alpha_dst) << 24) | (((r)) << 16) | (((g)) << 8) | (b)); + + dst++; // pointer to 4byte variables + src += 4; // pointer to 1byte variables } } @@ -1399,34 +1507,41 @@ CairoRenderContext::renderImage(guchar *px, unsigned int w, unsigned int h, unsi #define GLYPH_ARRAY_SIZE 64 unsigned int -CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont *font, std::vector const &glyphtext, bool is_stroke) +CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont *font, std::vector const &glyphtext, bool path) { cairo_glyph_t glyph_array[GLYPH_ARRAY_SIZE]; cairo_glyph_t *glyphs = glyph_array; unsigned int num_glyphs = glyphtext.size(); - if (num_glyphs > GLYPH_ARRAY_SIZE) - glyphs = (cairo_glyph_t*)g_malloc(sizeof(cairo_glyph_t) * num_glyphs); + if (num_glyphs > GLYPH_ARRAY_SIZE) { + glyphs = (cairo_glyph_t*)g_try_malloc(sizeof(cairo_glyph_t) * num_glyphs); + if(glyphs == NULL) { + g_warning("CairorenderContext::_showGlyphs: can not allocate memory for %d glyphs.", num_glyphs); + return 0; + } + } unsigned int num_invalid_glyphs = 0; - unsigned int i = 0; + unsigned int i = 0; // is a counter for indexing the glyphs array, only counts the valid glyphs for (std::vector::const_iterator it_info = glyphtext.begin() ; it_info != glyphtext.end() ; it_info++) { // skip glyphs which are PANGO_GLYPH_EMPTY (0x0FFFFFFF) // or have the PANGO_GLYPH_UNKNOWN_FLAG (0x10000000) set if (it_info->index == 0x0FFFFFFF || it_info->index & 0x10000000) { TRACE(("INVALID GLYPH found\n")); + g_message("Invalid glyph found, continuing..."); num_invalid_glyphs++; continue; } - glyphs[i - num_invalid_glyphs].index = it_info->index; - glyphs[i - num_invalid_glyphs].x = it_info->x; - glyphs[i - num_invalid_glyphs].y = it_info->y; + glyphs[i].index = it_info->index; + glyphs[i].x = it_info->x; + glyphs[i].y = it_info->y; i++; } - if (is_stroke || _is_texttopath) + if (path) { cairo_glyph_path(cr, glyphs, num_glyphs - num_invalid_glyphs); - else + } else { cairo_show_glyphs(cr, glyphs, num_glyphs - num_invalid_glyphs); + } if (num_glyphs > GLYPH_ARRAY_SIZE) g_free(glyphs); @@ -1440,10 +1555,11 @@ CairoRenderContext::renderGlyphtext(PangoFont *font, Geom::Matrix const *font_ma { // create a cairo_font_face from PangoFont double size = style->font_size.computed; - cairo_font_face_t *font_face = NULL; + gpointer fonthash = (gpointer)font; + cairo_font_face_t *font_face = (cairo_font_face_t *)g_hash_table_lookup(font_table, fonthash); FcPattern *fc_pattern = NULL; - + #ifdef USE_PANGO_WIN32 # ifdef CAIRO_HAS_WIN32_FONT LOGFONTA *lfa = pango_win32_font_logfont(font); @@ -1452,17 +1568,23 @@ CairoRenderContext::renderGlyphtext(PangoFont *font, Geom::Matrix const *font_ma ZeroMemory(&lfw, sizeof(LOGFONTW)); memcpy(&lfw, lfa, sizeof(LOGFONTA)); MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, lfa->lfFaceName, LF_FACESIZE, lfw.lfFaceName, LF_FACESIZE); - - font_face = cairo_win32_font_face_create_for_logfontw(&lfw); + + if(font_face == NULL) { + font_face = cairo_win32_font_face_create_for_logfontw(&lfw); + g_hash_table_insert(font_table, fonthash, font_face); + } # endif #else # ifdef CAIRO_HAS_FT_FONT PangoFcFont *fc_font = PANGO_FC_FONT(font); fc_pattern = fc_font->font_pattern; - font_face = cairo_ft_font_face_create_for_pattern(fc_pattern); + if(font_face == NULL) { + font_face = cairo_ft_font_face_create_for_pattern(fc_pattern); + g_hash_table_insert(font_table, fonthash, font_face); + } # endif #endif - + cairo_save(_cr); cairo_set_font_face(_cr, font_face); @@ -1487,28 +1609,36 @@ CairoRenderContext::renderGlyphtext(PangoFont *font, Geom::Matrix const *font_ma _showGlyphs(_cr, font, glyphtext, TRUE); } } else { - + bool fill = false, stroke = false, have_path = false; if (style->fill.isColor() || style->fill.isPaintserver()) { - // set fill style - _setFillStyle(style, NULL); - - _showGlyphs(_cr, font, glyphtext, FALSE); + fill = true; } if (style->stroke.isColor() || style->stroke.isPaintserver()) { - // set stroke style + stroke = true; + } + if (fill) { + _setFillStyle(style, NULL); + if (_is_texttopath) { + _showGlyphs(_cr, font, glyphtext, true); + have_path = true; + if (stroke) cairo_fill_preserve(_cr); + else cairo_fill(_cr); + } else { + _showGlyphs(_cr, font, glyphtext, false); + } + } + if (stroke) { _setStrokeStyle(style, NULL); - - // paint stroke - _showGlyphs(_cr, font, glyphtext, TRUE); + if (!have_path) _showGlyphs(_cr, font, glyphtext, true); cairo_stroke(_cr); } } cairo_restore(_cr); - if (font_face) - cairo_font_face_destroy(font_face); +// if (font_face) +// cairo_font_face_destroy(font_face); return true; } @@ -1591,4 +1721,4 @@ _write_callback(void *closure, const unsigned char *data, unsigned int length) fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :