diff --git a/src/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp
index 13302831e510996e3806b539befbb19c9c0bf9f6..e9ac98df8e18a59c4c0bbaa80a799b11dbe20014 100644 (file)
#include <signal.h>
#include <errno.h>
-
-#include <libnr/n-art-bpath.h>
-#include <libnr/nr-matrix-ops.h>
-#include <libnr/nr-matrix-fns.h>
-#include <libnr/nr-matrix-scale-ops.h>
-#include <libnr/nr-matrix-translate-ops.h>
-#include <libnr/nr-scale-matrix-ops.h>
-#include <libnr/n-art-bpath-2geom.h>
#include <2geom/pathvector.h>
#include <glib/gmem.h>
#include "display/nr-arena-group.h"
#include "display/curve.h"
#include "display/canvas-bpath.h"
+#include "display/inkscape-cairo.h"
#include "sp-item.h"
#include "sp-item-group.h"
#include "style.h"
_dpi(72),
_pdf_level(0),
_ps_level(1),
+ _eps(false),
_is_texttopath(FALSE),
_is_filtertobitmap(FALSE),
_bitmapresolution(72),
_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),
_ps_level = level;
}
+void CairoRenderContext::setEPS(bool eps)
+{
+ _eps = eps;
+}
+
unsigned int CairoRenderContext::getPSLevel(void)
{
return _ps_level;
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:
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) {
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
// 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;
}
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();
}
}
+ // 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);
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);
}
}
_renderer->destroyContext(mask_ctx);
}
} else {
+ // No clip path or mask
cairo_pop_group_to_source(_cr);
if (opacity == 1.0)
cairo_paint(_cr);
}
void
-CairoRenderContext::addClipPath(NArtBpath const *bp, SPIEnum const *fill_rule)
+CairoRenderContext::addClipPath(Geom::PathVector const &pv, SPIEnum const *fill_rule)
{
g_assert( _is_valid );
} else {
cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING);
}
- addBpath(bp);
+ addPathVector(pv);
}
void
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);
#endif
break;
#endif
}
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;
}
_cr = cairo_create(surface);
+ if (ctm)
+ cairo_set_matrix(_cr, ctm);
_surface = surface;
if (_vector_based_target) {
@@ -937,6 +978,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,23 +1002,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
- // 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;
}
- double surface_width = ceil(bbox_width_scaler * width_scaler * width);
- double surface_height = ceil(bbox_height_scaler * height_scaler * height);
- TRACE(("surface size: %f x %f\n", surface_width, surface_height));
+
+ // 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 - 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);
@@ -986,10 +1022,14 @@ 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(1.0 / scale_width, 1.0 / scale_height);
- ps2user *= Geom::Scale(scale_width, 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();
@@ -1248,25 +1288,14 @@ CairoRenderContext::_setStrokeStyle(SPStyle const *style, NRRect const *pbox)
bool
CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle const *style, NRRect const *pbox)
-{
- NArtBpath * bpath = BPath_from_2GeomPath (pathv);
- const_NRBPath *bp;
- bp->path = bpath;
- bool retvalue = renderPath(bp, style, pbox);
- g_free(bpath);
- return retvalue;
-}
-
-bool
-CairoRenderContext::renderPath(const_NRBPath const *bpath, SPStyle const *style, NRRect const *pbox)
{
g_assert( _is_valid );
if (_render_mode == RENDER_MODE_CLIP) {
if (_clip_mode == CLIP_MODE_PATH) {
- addClipPath(bpath->path, &style->fill_rule);
+ addClipPath(pathv, &style->fill_rule);
} else {
- setBpath(bpath->path);
+ setPathVector(pathv);
if (style->fill_rule.value == SP_WIND_RULE_EVENODD) {
cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD);
} else {
@@ -1278,7 +1307,11 @@ CairoRenderContext::renderPath(const_NRBPath const *bpath, SPStyle const *style,
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 &&
@@ -1289,9 +1322,9 @@ CairoRenderContext::renderPath(const_NRBPath const *bpath, SPStyle const *style,
else
pushLayer();
- if (!style->fill.isNone()) {
+ if (!no_fill) {
_setFillStyle(style, pbox);
- setBpath(bpath->path);
+ setPathVector(pathv);
if (style->fill_rule.value == SP_WIND_RULE_EVENODD) {
cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD);
@@ -1299,16 +1332,16 @@ CairoRenderContext::renderPath(const_NRBPath const *bpath, SPStyle const *style,
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())
- setBpath(bpath->path);
+ if (no_fill)
+ setPathVector(pathv);
cairo_stroke(_cr);
}
@@ -1426,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);
@@ -1519,47 +1558,16 @@ CairoRenderContext::renderGlyphtext(PangoFont *font, Geom::Matrix const *font_ma
/* Helper functions */
void
-CairoRenderContext::addBpath(NArtBpath const *bp)
+CairoRenderContext::setPathVector(Geom::PathVector const &pv)
{
- bool closed = false;
- while (bp->code != NR_END) {
- switch (bp->code) {
- case NR_MOVETO:
- if (closed) {
- cairo_close_path(_cr);
- }
- closed = true;
- cairo_move_to(_cr, bp->x3, bp->y3);
- break;
- case NR_MOVETO_OPEN:
- if (closed) {
- cairo_close_path(_cr);
- }
- closed = false;
- cairo_move_to(_cr, bp->x3, bp->y3);
- break;
- case NR_LINETO:
- cairo_line_to(_cr, bp->x3, bp->y3);
- break;
- case NR_CURVETO:
- cairo_curve_to(_cr, bp->x1, bp->y1, bp->x2, bp->y2, bp->x3, bp->y3);
- break;
- default:
- break;
- }
- bp += 1;
- }
- if (closed) {
- cairo_close_path(_cr);
- }
+ cairo_new_path(_cr);
+ addPathVector(pv);
}
void
-CairoRenderContext::setBpath(NArtBpath const *bp)
+CairoRenderContext::addPathVector(Geom::PathVector const &pv)
{
- cairo_new_path(_cr);
- if (bp)
- addBpath(bp);
+ feed_pathvector_to_cairo(_cr, pv);
}
void