Code

remove memory boundries on bitmap renderer, optimize memory usage
[inkscape.git] / src / extension / internal / cairo-render-context.cpp
index 0a12a5c5bba60fa6621a69bab1caea75134de6dc..b68105b30908e275982102adc6ea90dc07208fd5 100644 (file)
@@ -1,11 +1,11 @@
-#define __SP_CAIRO_RENDER_CONTEXT_C__
-
 /** \file
  * Rendering with Cairo.
  */
 /*
  * Author:
  *   Miklos Erdelyi <erdelyim@gmail.com>
+ *   Jon A. Cruz <jon@joncruz.org>
+ *   Abhishek Sharma
  *
  * Copyright (C) 2006 Miklos Erdelyi
  *
@@ -80,6 +80,7 @@
 #include <pango/pangofc-fontmap.h>
 
 //#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<CairoGlyphInfo> const &glyphtext, bool is_stroke)
+CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont *font, std::vector<CairoGlyphInfo> 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<CairoGlyphInfo>::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 :