Code

BUG 284680 convert text to paths on cairo output
[inkscape.git] / src / extension / internal / cairo-render-context.cpp
index f2ee32c74c8274fcfe10c627aa2d10ab120846bd..e9ac98df8e18a59c4c0bbaa80a799b11dbe20014 100644 (file)
@@ -116,7 +116,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),
@@ -481,8 +481,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:
@@ -538,9 +538,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 +562,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 +586,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,7 +602,7 @@ 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
@@ -593,9 +612,9 @@ CairoRenderContext::popLayer(void)
                 // It must be stored in item_transform of current state after pushState.
                 Geom::Matrix item_transform; 
                 if (_state->parent_has_userspace)
-                    item_transform = getParentState()->transform;
+                    item_transform = getParentState()->transform * _state->item_transform;
                 else
-                    item_transform = _state->transform;
+                    item_transform = _state->item_transform;
 
                 // apply the clip path
                 clip_ctx->pushState();
@@ -614,19 +633,35 @@ 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;
+            }
+            mask_ctx->setupSurface( surface_width, surface_height );
+            TRACE(("mask surface: %f x %f at %i dpi\n", surface_width, surface_height, _dpi ));
 
             // 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);
@@ -646,18 +681,18 @@ CairoRenderContext::popLayer(void)
             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.
+            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);
                 }
             }
 
@@ -671,6 +706,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);
@@ -744,20 +780,20 @@ CairoRenderContext::setupSurface(double width, double height)
 }
 
 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;
@@ -767,6 +803,8 @@ CairoRenderContext::_finishSurfaceSetup(cairo_surface_t *surface)
     }
 
     _cr = cairo_create(surface);
+    if (ctm)
+        cairo_set_matrix(_cr, ctm);
     _surface = surface;
 
     if (_vector_based_target) {
@@ -974,7 +1012,7 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver
     // Multiply by SUBPIX_SCALE to allow for less than a pixel precision
     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(("surface size: %f x %f\n", surface_width, surface_height));
+    TRACE(("pattern surface size: %f x %f\n", surface_width, surface_height));
     // create new rendering context
     CairoRenderContext *pattern_ctx = cloneMe(surface_width, surface_height);
 
@@ -1269,7 +1307,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->fill_opacity.value == 0;
+
+    if (no_fill && no_stroke)
         return true;
 
     bool need_layer = ( !_state->merge_opacity && !_state->need_layer &&
@@ -1280,7 +1322,7 @@ CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle con
     else
         pushLayer();
 
-    if (!style->fill.isNone()) {
+    if (!no_fill) {
         _setFillStyle(style, pbox);
         setPathVector(pathv);
 
@@ -1290,15 +1332,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);
@@ -1417,10 +1459,16 @@ CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont *font, std::vector<CairoG
         i++;
     }
 
-    if (is_stroke || _is_texttopath)
+    if (is_stroke) {
         cairo_glyph_path(cr, glyphs, num_glyphs - num_invalid_glyphs);
-    else
-        cairo_show_glyphs(cr, glyphs, num_glyphs - num_invalid_glyphs);
+    } else {
+        if (_is_texttopath) {
+            cairo_glyph_path(cr, glyphs, num_glyphs - num_invalid_glyphs);
+            cairo_fill_preserve(cr);
+        } else {
+            cairo_show_glyphs(cr, glyphs, num_glyphs - num_invalid_glyphs);
+        }
+    }
 
     if (num_glyphs > GLYPH_ARRAY_SIZE)
         g_free(glyphs);