summaryrefslogtreecommitdiffstats
path: root/src/extension/internal/cairo-render-context.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/extension/internal/cairo-render-context.cpp2040
1 files changed, 2040 insertions, 0 deletions
diff --git a/src/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp
new file mode 100644
index 0000000..063938b
--- /dev/null
+++ b/src/extension/internal/cairo-render-context.cpp
@@ -0,0 +1,2040 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Rendering with Cairo.
+ */
+/*
+ * Author:
+ * Miklos Erdelyi <erdelyim@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006 Miklos Erdelyi
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifndef PANGO_ENABLE_BACKEND
+#define PANGO_ENABLE_BACKEND
+#endif
+
+#ifndef PANGO_ENABLE_ENGINE
+#define PANGO_ENABLE_ENGINE
+#endif
+
+#include "cairo-render-context.h"
+
+#include <csignal>
+#include <cerrno>
+#include <2geom/pathvector.h>
+
+#include <glib.h>
+#include <glibmm/i18n.h>
+
+#include "display/drawing.h"
+#include "display/curve.h"
+#include "display/cairo-utils.h"
+
+#include "object/sp-item.h"
+#include "object/sp-item-group.h"
+#include "object/sp-hatch.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-radial-gradient.h"
+#include "object/sp-mesh-gradient.h"
+#include "object/sp-pattern.h"
+#include "object/sp-mask.h"
+#include "object/sp-clippath.h"
+
+#include "util/units.h"
+#ifdef _WIN32
+#include "libnrtype/FontFactory.h" // USE_PANGO_WIN32
+#endif
+
+#include "cairo-renderer.h"
+#include "extension/system.h"
+
+#include "io/sys.h"
+
+#include "svg/stringstream.h"
+
+#include <cairo.h>
+
+// include support for only the compiled-in surface types
+#ifdef CAIRO_HAS_PDF_SURFACE
+#include <cairo-pdf.h>
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+#include <cairo-ps.h>
+#endif
+
+
+#ifdef CAIRO_HAS_FT_FONT
+#include <cairo-ft.h>
+#endif
+#ifdef CAIRO_HAS_WIN32_FONT
+#include <pango/pangowin32.h>
+#include <cairo-win32.h>
+#endif
+
+#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)
+
+// FIXME: expose these from sp-clippath/mask.cpp
+/*struct SPClipPathView {
+ SPClipPathView *next;
+ unsigned int key;
+ Inkscape::DrawingItem *arenaitem;
+ Geom::OptRect bbox;
+};
+
+struct SPMaskView {
+ SPMaskView *next;
+ unsigned int key;
+ Inkscape::DrawingItem *arenaitem;
+ Geom::OptRect bbox;
+};*/
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+static cairo_status_t _write_callback(void *closure, const unsigned char *data, unsigned int length);
+
+CairoRenderContext::CairoRenderContext(CairoRenderer *parent) :
+ _dpi(72),
+ _pdf_level(1),
+ _ps_level(1),
+ _eps(false),
+ _is_texttopath(FALSE),
+ _is_omittext(FALSE),
+ _is_filtertobitmap(FALSE),
+ _is_show_page(false),
+ _bitmapresolution(72),
+ _stream(nullptr),
+ _is_valid(FALSE),
+ _vector_based_target(FALSE),
+ _cr(nullptr), // Cairo context
+ _surface(nullptr),
+ _target(CAIRO_SURFACE_TYPE_IMAGE),
+ _target_format(CAIRO_FORMAT_ARGB32),
+ _layout(nullptr),
+ _state(nullptr),
+ _renderer(parent),
+ _render_mode(RENDER_MODE_NORMAL),
+ _clip_mode(CLIP_MODE_MASK),
+ _omittext_state(EMPTY)
+{
+}
+
+CairoRenderContext::~CairoRenderContext()
+{
+ for (std::map<gpointer, cairo_font_face_t *>::const_iterator iter = font_table.begin(); iter != font_table.end(); ++iter)
+ font_data_free(iter->second);
+
+ 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() const
+{
+ return _renderer;
+}
+
+CairoRenderState* CairoRenderContext::getCurrentState() const
+{
+ return _state;
+}
+
+CairoRenderState* CairoRenderContext::getParentState() const
+{
+ // if this is the root node just return it
+ if (_state_stack.size() == 1) {
+ return _state;
+ } else {
+ return _state_stack[_state_stack.size()-2];
+ }
+}
+
+void CairoRenderContext::setStateForStyle(SPStyle const *style)
+{
+ // only opacity & overflow is stored for now
+ _state->opacity = SP_SCALE24_TO_FLOAT(style->opacity.value);
+ _state->has_overflow = (style->overflow.set && style->overflow.value != SP_CSS_OVERFLOW_VISIBLE);
+ _state->has_filtereffect = (style->filter.set != 0) ? TRUE : FALSE;
+
+ if (style->fill.isPaintserver() || style->stroke.isPaintserver())
+ _state->merge_opacity = FALSE;
+
+ // disable rendering of opacity if there's a stroke on the fill
+ if (_state->merge_opacity
+ && !style->fill.isNone()
+ && !style->stroke.isNone())
+ _state->merge_opacity = FALSE;
+}
+
+/**
+ * \brief Creates a new render context which will be compatible with the given context's Cairo surface
+ *
+ * \param width width of the surface to be created
+ * \param height height of the surface to be created
+ */
+CairoRenderContext*
+CairoRenderContext::cloneMe(double width, double height) const
+{
+ g_assert( _is_valid );
+ g_assert( width > 0.0 && height > 0.0 );
+
+ CairoRenderContext *new_context = _renderer->createContext();
+ cairo_surface_t *surface = cairo_surface_create_similar(cairo_get_target(_cr), CAIRO_CONTENT_COLOR_ALPHA,
+ (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;
+}
+
+CairoRenderContext* CairoRenderContext::cloneMe() const
+{
+ g_assert( _is_valid );
+
+ return cloneMe(_width, _height);
+}
+
+bool CairoRenderContext::setImageTarget(cairo_format_t format)
+{
+ // format cannot be set on an already initialized surface
+ if (_is_valid)
+ return false;
+
+ switch (format) {
+ case CAIRO_FORMAT_ARGB32:
+ case CAIRO_FORMAT_RGB24:
+ case CAIRO_FORMAT_A8:
+ case CAIRO_FORMAT_A1:
+ _target_format = format;
+ _target = CAIRO_SURFACE_TYPE_IMAGE;
+ return true;
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool CairoRenderContext::setPdfTarget(gchar const *utf8_fn)
+{
+#ifndef CAIRO_HAS_PDF_SURFACE
+ return false;
+#else
+ _target = CAIRO_SURFACE_TYPE_PDF;
+ _vector_based_target = TRUE;
+#endif
+
+ FILE *osf = nullptr;
+ FILE *osp = nullptr;
+
+ gsize bytesRead = 0;
+ gsize bytesWritten = 0;
+ GError *error = nullptr;
+ gchar *local_fn = g_filename_from_utf8(utf8_fn,
+ -1, &bytesRead, &bytesWritten, &error);
+ gchar const *fn = local_fn;
+
+ /* TODO: Replace the below fprintf's with something that does the right thing whether in
+ * gui or batch mode (e.g. --print=blah). Consider throwing an exception: currently one of
+ * the callers (sp_print_document_to_file, "ret = mod->begin(doc)") wrongly ignores the
+ * return code.
+ */
+ if (fn != nullptr) {
+ if (*fn == '|') {
+ fn += 1;
+ while (isspace(*fn)) fn += 1;
+#ifndef _WIN32
+ osp = popen(fn, "w");
+#else
+ osp = _popen(fn, "w");
+#endif
+ if (!osp) {
+ fprintf(stderr, "inkscape: popen(%s): %s\n",
+ fn, strerror(errno));
+ return false;
+ }
+ _stream = osp;
+ } else if (*fn == '>') {
+ fn += 1;
+ while (isspace(*fn)) fn += 1;
+ Inkscape::IO::dump_fopen_call(fn, "K");
+ osf = Inkscape::IO::fopen_utf8name(fn, "w+");
+ if (!osf) {
+ fprintf(stderr, "inkscape: fopen(%s): %s\n",
+ fn, strerror(errno));
+ return false;
+ }
+ _stream = osf;
+ } else {
+ /* put cwd stuff in here */
+ gchar *qn = ( *fn
+ ? g_strdup_printf("lpr -P %s", fn) /* FIXME: quote fn */
+ : g_strdup("lpr") );
+#ifndef _WIN32
+ osp = popen(qn, "w");
+#else
+ osp = _popen(qn, "w");
+#endif
+ if (!osp) {
+ fprintf(stderr, "inkscape: popen(%s): %s\n",
+ qn, strerror(errno));
+ return false;
+ }
+ g_free(qn);
+ _stream = osp;
+ }
+ }
+
+ g_free(local_fn);
+
+ if (_stream) {
+ /* fixme: this is kinda icky */
+#if !defined(_WIN32) && !defined(__WIN32__)
+ (void) signal(SIGPIPE, SIG_IGN);
+#endif
+ }
+
+ return true;
+}
+
+bool CairoRenderContext::setPsTarget(gchar const *utf8_fn)
+{
+#ifndef CAIRO_HAS_PS_SURFACE
+ return false;
+#else
+ _target = CAIRO_SURFACE_TYPE_PS;
+ _vector_based_target = TRUE;
+#endif
+
+ FILE *osf = nullptr;
+ FILE *osp = nullptr;
+
+ gsize bytesRead = 0;
+ gsize bytesWritten = 0;
+ GError *error = nullptr;
+ gchar *local_fn = g_filename_from_utf8(utf8_fn,
+ -1, &bytesRead, &bytesWritten, &error);
+ gchar const *fn = local_fn;
+
+ /* TODO: Replace the below fprintf's with something that does the right thing whether in
+ * gui or batch mode (e.g. --print=blah). Consider throwing an exception: currently one of
+ * the callers (sp_print_document_to_file, "ret = mod->begin(doc)") wrongly ignores the
+ * return code.
+ */
+ if (fn != nullptr) {
+ if (*fn == '|') {
+ fn += 1;
+ while (isspace(*fn)) fn += 1;
+#ifndef _WIN32
+ osp = popen(fn, "w");
+#else
+ osp = _popen(fn, "w");
+#endif
+ if (!osp) {
+ fprintf(stderr, "inkscape: popen(%s): %s\n",
+ fn, strerror(errno));
+ return false;
+ }
+ _stream = osp;
+ } else if (*fn == '>') {
+ fn += 1;
+ while (isspace(*fn)) fn += 1;
+ Inkscape::IO::dump_fopen_call(fn, "K");
+ osf = Inkscape::IO::fopen_utf8name(fn, "w+");
+ if (!osf) {
+ fprintf(stderr, "inkscape: fopen(%s): %s\n",
+ fn, strerror(errno));
+ return false;
+ }
+ _stream = osf;
+ } else {
+ /* put cwd stuff in here */
+ gchar *qn = ( *fn
+ ? g_strdup_printf("lpr -P %s", fn) /* FIXME: quote fn */
+ : g_strdup("lpr") );
+#ifndef _WIN32
+ osp = popen(qn, "w");
+#else
+ osp = _popen(qn, "w");
+#endif
+ if (!osp) {
+ fprintf(stderr, "inkscape: popen(%s): %s\n",
+ qn, strerror(errno));
+ return false;
+ }
+ g_free(qn);
+ _stream = osp;
+ }
+ }
+
+ g_free(local_fn);
+
+ if (_stream) {
+ /* fixme: this is kinda icky */
+#if !defined(_WIN32) && !defined(__WIN32__)
+ (void) signal(SIGPIPE, SIG_IGN);
+#endif
+ }
+
+ return true;
+}
+
+void CairoRenderContext::setPSLevel(unsigned int level)
+{
+ _ps_level = level;
+}
+
+void CairoRenderContext::setEPS(bool eps)
+{
+ _eps = eps;
+}
+
+unsigned int CairoRenderContext::getPSLevel()
+{
+ return _ps_level;
+}
+
+void CairoRenderContext::setPDFLevel(unsigned int level)
+{
+ _pdf_level = level;
+}
+
+void CairoRenderContext::setTextToPath(bool texttopath)
+{
+ _is_texttopath = texttopath;
+}
+
+void CairoRenderContext::setOmitText(bool omittext)
+{
+ _is_omittext = omittext;
+}
+
+bool CairoRenderContext::getOmitText()
+{
+ return _is_omittext;
+}
+
+void CairoRenderContext::setFilterToBitmap(bool filtertobitmap)
+{
+ _is_filtertobitmap = filtertobitmap;
+}
+
+bool CairoRenderContext::getFilterToBitmap()
+{
+ return _is_filtertobitmap;
+}
+
+void CairoRenderContext::setBitmapResolution(int resolution)
+{
+ _bitmapresolution = resolution;
+}
+
+int CairoRenderContext::getBitmapResolution()
+{
+ return _bitmapresolution;
+}
+
+cairo_surface_t*
+CairoRenderContext::getSurface()
+{
+ g_assert( _is_valid );
+
+ return _surface;
+}
+
+bool
+CairoRenderContext::saveAsPng(const char *file_name)
+{
+ cairo_status_t status = cairo_surface_write_to_png(_surface, file_name);
+ if (status)
+ return false;
+ else
+ return true;
+}
+
+void
+CairoRenderContext::setRenderMode(CairoRenderMode mode)
+{
+ switch (mode) {
+ case RENDER_MODE_NORMAL:
+ case RENDER_MODE_CLIP:
+ _render_mode = mode;
+ break;
+ default:
+ _render_mode = RENDER_MODE_NORMAL;
+ break;
+ }
+}
+
+CairoRenderContext::CairoRenderMode
+CairoRenderContext::getRenderMode() const
+{
+ return _render_mode;
+}
+
+void
+CairoRenderContext::setClipMode(CairoClipMode mode)
+{
+ switch (mode) {
+ 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:
+ _clip_mode = CLIP_MODE_PATH;
+ break;
+ }
+}
+
+CairoRenderContext::CairoClipMode
+CairoRenderContext::getClipMode() const
+{
+ return _clip_mode;
+}
+
+CairoRenderState* CairoRenderContext::_createState()
+{
+ CairoRenderState *state = static_cast<CairoRenderState*>(g_try_malloc(sizeof(CairoRenderState)));
+ g_assert( state != nullptr );
+
+ state->has_filtereffect = FALSE;
+ state->merge_opacity = TRUE;
+ state->opacity = 1.0;
+ state->need_layer = FALSE;
+ state->has_overflow = FALSE;
+ state->parent_has_userspace = FALSE;
+ state->clip_path = nullptr;
+ state->mask = nullptr;
+
+ return state;
+}
+
+void CairoRenderContext::pushLayer()
+{
+ g_assert( _is_valid );
+
+ TRACE(("--pushLayer\n"));
+ cairo_push_group(_cr);
+
+ // clear buffer
+ if (!_vector_based_target) {
+ cairo_save(_cr);
+ cairo_set_operator(_cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint(_cr);
+ cairo_restore(_cr);
+ }
+}
+
+void
+CairoRenderContext::popLayer(cairo_operator_t composite)
+{
+ g_assert( _is_valid );
+
+ float opacity = _state->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.
+ */
+
+ SPClipPath *clip_path = _state->clip_path;
+ SPMask *mask = _state->mask;
+ if (clip_path || mask) {
+
+ CairoRenderContext *clip_ctx = nullptr;
+ cairo_surface_t *clip_mask = nullptr;
+
+ // Apply any clip path first
+ if (clip_path) {
+ TRACE((" Applying clip\n"));
+ if (_render_mode == RENDER_MODE_CLIP)
+ mask = nullptr; // disable mask when performing nested clipping
+
+ if (_vector_based_target) {
+ setClipMode(CLIP_MODE_PATH); // Vector
+ if (!mask) {
+ cairo_pop_group_to_source(_cr);
+ _renderer->applyClipPath(this, clip_path); // Uses cairo_clip()
+ if (opacity == 1.0)
+ cairo_paint(_cr);
+ else
+ cairo_paint_with_alpha(_cr, opacity);
+
+ } else {
+ // the clipPath will be applied before masking
+ }
+ } else {
+
+ // setup a new rendering context
+ clip_ctx = _renderer->createContext();
+ clip_ctx->setImageTarget(CAIRO_FORMAT_A8);
+ clip_ctx->setClipMode(CLIP_MODE_MASK); // Raster
+ // This code ties the clipping to the document coordinates. It doesn't allow
+ // for a clipped object initially drawn off the page and then translated onto
+ // the page.
+ if (!clip_ctx->setupSurface(_width, _height)) {
+ TRACE(("clip: setupSurface failed\n"));
+ _renderer->destroyContext(clip_ctx);
+ return;
+ }
+
+ // clear buffer
+ cairo_save(clip_ctx->_cr);
+ cairo_set_operator(clip_ctx->_cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint(clip_ctx->_cr);
+ cairo_restore(clip_ctx->_cr);
+
+ // 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::Affine item_transform;
+ if (_state->parent_has_userspace)
+ item_transform = getParentState()->transform * _state->item_transform;
+ else
+ 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();
+
+ clip_mask = clip_ctx->getSurface();
+ TEST(clip_ctx->saveAsPng("clip_mask.png"));
+
+ if (!mask) {
+ cairo_pop_group_to_source(_cr);
+ if (composite != CAIRO_OPERATOR_CLEAR){
+ cairo_set_operator(_cr, composite);
+ }
+ cairo_mask_surface(_cr, clip_mask, 0, 0);
+ _renderer->destroyContext(clip_ctx);
+ }
+ }
+ }
+
+ // Apply any mask second
+ if (mask) {
+ TRACE((" Applying mask\n"));
+ // create rendering context for mask
+ CairoRenderContext *mask_ctx = _renderer->createContext();
+
+ // 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 96 dpi.
+ float surface_width = _width;
+ float surface_height = _height;
+ if( _vector_based_target ) {
+ surface_width *= 4.0/3.0;
+ surface_height *= 4.0/3.0;
+ }
+ 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);
+
+ TEST(mask_ctx->saveAsPng("mask.png"));
+
+ // composite with clip mask
+ if (clip_path && _clip_mode == CLIP_MODE_MASK) {
+ cairo_mask_surface(mask_ctx->_cr, clip_mask, 0, 0);
+ _renderer->destroyContext(clip_ctx);
+ }
+
+ cairo_surface_t *mask_image = mask_ctx->getSurface();
+ int width = cairo_image_surface_get_width(mask_image);
+ int height = cairo_image_surface_get_height(mask_image);
+ int stride = cairo_image_surface_get_stride(mask_image);
+ unsigned char *pixels = cairo_image_surface_get_data(mask_image);
+
+ // 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 = reinterpret_cast<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);
+ }
+ }
+
+ cairo_pop_group_to_source(_cr);
+ if (composite != CAIRO_OPERATOR_CLEAR){
+ cairo_set_operator(_cr, composite);
+ }
+ if (_clip_mode == CLIP_MODE_PATH) {
+ // we have to do the clipping after cairo_pop_group_to_source
+ _renderer->applyClipPath(this, clip_path);
+ }
+ // apply the mask onto the layer
+ cairo_mask_surface(_cr, mask_image, 0, 0);
+ _renderer->destroyContext(mask_ctx);
+ }
+ } else {
+ // No clip path or mask
+ cairo_pop_group_to_source(_cr);
+ if (composite != CAIRO_OPERATOR_CLEAR){
+ cairo_set_operator(_cr, composite);
+ }
+ if (opacity == 1.0)
+ cairo_paint(_cr);
+ else
+ cairo_paint_with_alpha(_cr, opacity);
+ }
+}
+void CairoRenderContext::tagBegin(const char* l){
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 4)
+ char* link = g_strdup_printf("uri='%s'", l);
+ cairo_tag_begin(_cr, CAIRO_TAG_LINK, link);
+ g_free(link);
+#endif
+}
+
+void CairoRenderContext::tagEnd(){
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 4)
+ cairo_tag_end(_cr, CAIRO_TAG_LINK);
+#endif
+}
+
+
+void
+CairoRenderContext::addClipPath(Geom::PathVector const &pv, SPIEnum<SPWindRule> const *fill_rule)
+{
+ g_assert( _is_valid );
+
+ // here it should be checked whether the current clip winding changed
+ // so we could switch back to masked clipping
+ if (fill_rule->value == SP_WIND_RULE_EVENODD) {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD);
+ } else {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING);
+ }
+ addPathVector(pv);
+}
+
+void
+CairoRenderContext::addClippingRect(double x, double y, double width, double height)
+{
+ g_assert( _is_valid );
+
+ cairo_rectangle(_cr, x, y, width, height);
+ cairo_clip(_cr);
+}
+
+bool
+CairoRenderContext::setupSurface(double width, double height)
+{
+ // Is the surface already set up?
+ if (_is_valid)
+ return true;
+
+ if (_vector_based_target && _stream == nullptr)
+ return false;
+
+ _width = width;
+ _height = height;
+
+ cairo_surface_t *surface = nullptr;
+ 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));
+ break;
+#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);
+ cairo_pdf_surface_restrict_to_version(surface, (cairo_pdf_version_t)_pdf_level);
+ 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_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);
+ break;
+#endif
+ default:
+ return false;
+ break;
+ }
+
+ _setSurfaceMetadata(surface);
+
+ return _finishSurfaceSetup (surface, &ctm);
+}
+
+bool
+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, ctm);
+ if (ret)
+ cairo_surface_reference (surface);
+ return ret;
+}
+
+bool
+CairoRenderContext::_finishSurfaceSetup(cairo_surface_t *surface, cairo_matrix_t *ctm)
+{
+ if(surface == nullptr) {
+ return false;
+ }
+ if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) {
+ 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) {
+ cairo_scale(_cr, Inkscape::Util::Quantity::convert(1, "px", "pt"), Inkscape::Util::Quantity::convert(1, "px", "pt"));
+ } else if (cairo_surface_get_content(_surface) != CAIRO_CONTENT_ALPHA) {
+ // set background color on non-alpha surfaces
+ // TODO: bgcolor should be derived from SPDocument (see IconImpl)
+ cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0);
+ cairo_rectangle(_cr, 0, 0, _width, _height);
+ cairo_fill(_cr);
+ }
+
+ _is_valid = TRUE;
+
+ return true;
+}
+
+void
+CairoRenderContext::_setSurfaceMetadata(cairo_surface_t *surface)
+{
+ switch (_target) {
+#if defined CAIRO_HAS_PDF_SURFACE && CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 4)
+ case CAIRO_SURFACE_TYPE_PDF:
+ if (!_metadata.title.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_TITLE, _metadata.title.c_str());
+ }
+ if (!_metadata.author.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_AUTHOR, _metadata.author.c_str());
+ }
+ if (!_metadata.subject.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_SUBJECT, _metadata.subject.c_str());
+ }
+ if (!_metadata.keywords.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_KEYWORDS, _metadata.keywords.c_str());
+ }
+ if (!_metadata.creator.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_CREATOR, _metadata.creator.c_str());
+ }
+ if (!_metadata.cdate.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_CREATE_DATE, _metadata.cdate.c_str());
+ }
+ if (!_metadata.mdate.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_MOD_DATE, _metadata.mdate.c_str());
+ }
+ break;
+#endif
+#if defined CAIRO_HAS_PS_SURFACE
+ case CAIRO_SURFACE_TYPE_PS:
+ if (!_metadata.title.empty()) {
+ cairo_ps_surface_dsc_comment(surface, (Glib::ustring("%%Title: ") + _metadata.title).c_str());
+ }
+ if (!_metadata.copyright.empty()) {
+ cairo_ps_surface_dsc_comment(surface, (Glib::ustring("%%Copyright: ") + _metadata.copyright).c_str());
+ }
+ break;
+#endif
+ default:
+ g_warning("unsupported target %d\n", _target);
+ }
+}
+
+/**
+ * Each page that's made should call finishPage to complete it.
+ */
+bool
+CairoRenderContext::finishPage()
+{
+ g_assert(_is_valid);
+ if (!_vector_based_target)
+ return false;
+
+ // Protect against finish() showing one too many pages.
+ if (!_is_show_page) {
+ cairo_show_page(_cr);
+ _is_show_page = true;
+ }
+
+ auto status = cairo_status(_cr);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ g_critical("error while rendering page: %s", cairo_status_to_string(status));
+ return false;
+ }
+ return true;
+}
+
+/**
+ * When writing multiple pages, resize the next page.
+ */
+bool
+CairoRenderContext::nextPage(double width, double height, char const *label)
+{
+ g_assert(_is_valid);
+ if (!_vector_based_target)
+ return false;
+
+ _width = width;
+ _height = height;
+ _is_show_page = false;
+ cairo_pdf_surface_set_size(_surface, width, height);
+
+ if (label) {
+ cairo_pdf_surface_set_page_label(_surface, label);
+ }
+
+ auto status = cairo_surface_status(_surface);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ g_critical("error while sizing page: %s", cairo_status_to_string(status));
+ return false;
+ }
+ return true;
+}
+
+
+bool
+CairoRenderContext::finish(bool finish_surface)
+{
+ g_assert( _is_valid );
+
+ if (_vector_based_target && !_is_show_page && finish_surface)
+ cairo_show_page(_cr);
+
+ cairo_status_t status = cairo_status(_cr);
+ if (status != CAIRO_STATUS_SUCCESS)
+ g_critical("error while rendering output: %s", cairo_status_to_string(status));
+
+ cairo_destroy(_cr);
+ _cr = nullptr;
+
+ if (finish_surface)
+ cairo_surface_finish(_surface);
+ status = cairo_surface_status(_surface);
+ cairo_surface_destroy(_surface);
+ _surface = nullptr;
+
+ if (_layout)
+ g_object_unref(_layout);
+
+ _is_valid = FALSE;
+
+ if (_vector_based_target && _stream) {
+ /* Flush stream to be sure. */
+ (void) fflush(_stream);
+
+ fclose(_stream);
+ _stream = nullptr;
+ }
+
+ if (status == CAIRO_STATUS_SUCCESS)
+ return true;
+ else
+ return false;
+}
+
+void
+CairoRenderContext::transform(Geom::Affine const &transform)
+{
+ g_assert( _is_valid );
+
+ cairo_matrix_t matrix;
+ _initCairoMatrix(&matrix, transform);
+ cairo_transform(_cr, &matrix);
+
+ // store new CTM
+ _state->transform = getTransform();
+}
+
+void
+CairoRenderContext::setTransform(Geom::Affine const &transform)
+{
+ g_assert( _is_valid );
+
+ cairo_matrix_t matrix;
+ _initCairoMatrix(&matrix, transform);
+ cairo_set_matrix(_cr, &matrix);
+ _state->transform = transform;
+}
+
+Geom::Affine CairoRenderContext::getTransform() const
+{
+ g_assert( _is_valid );
+
+ cairo_matrix_t ctm;
+ cairo_get_matrix(_cr, &ctm);
+ Geom::Affine ret;
+ ret[0] = ctm.xx;
+ ret[1] = ctm.yx;
+ ret[2] = ctm.xy;
+ ret[3] = ctm.yy;
+ ret[4] = ctm.x0;
+ ret[5] = ctm.y0;
+ return ret;
+}
+
+Geom::Affine CairoRenderContext::getParentTransform() const
+{
+ g_assert( _is_valid );
+
+ CairoRenderState *parent_state = getParentState();
+ return parent_state->transform;
+}
+
+void CairoRenderContext::pushState()
+{
+ g_assert( _is_valid );
+
+ cairo_save(_cr);
+
+ CairoRenderState *new_state = _createState();
+ // copy current state's transform
+ new_state->transform = _state->transform;
+ _state_stack.push_back(new_state);
+ _state = new_state;
+}
+
+void CairoRenderContext::popState()
+{
+ g_assert( _is_valid );
+
+ cairo_restore(_cr);
+
+ g_free(_state_stack.back());
+ _state_stack.pop_back();
+
+ g_assert( !_state_stack.empty());
+ _state = _state_stack.back();
+}
+
+static bool pattern_hasItemChildren(SPPattern *pat)
+{
+ for (auto& child: pat->children) {
+ if (SP_IS_ITEM (&child)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+cairo_pattern_t*
+CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox)
+{
+ g_assert( SP_IS_PATTERN(paintserver) );
+
+ SPPattern *pat = SP_PATTERN (paintserver);
+
+ Geom::Affine ps2user, pcs2dev;
+ ps2user = Geom::identity();
+ pcs2dev = Geom::identity();
+
+ double x = pat->x();
+ double y = pat->y();
+ double width = pat->width();
+ double height = pat->height();
+ double bbox_width_scaler;
+ double bbox_height_scaler;
+
+ TRACE(("%f x %f pattern\n", width, height));
+
+ if (pbox && pat->patternUnits() == SPPattern::UNITS_OBJECTBOUNDINGBOX) {
+ bbox_width_scaler = pbox->width();
+ bbox_height_scaler = pbox->height();
+ ps2user[4] = x * bbox_width_scaler + pbox->left();
+ ps2user[5] = y * bbox_height_scaler + pbox->top();
+ } else {
+ bbox_width_scaler = 1.0;
+ bbox_height_scaler = 1.0;
+ ps2user[4] = x;
+ ps2user[5] = y;
+ }
+
+ // apply pattern transformation
+ Geom::Affine pattern_transform(pat->getTransform());
+ ps2user *= pattern_transform;
+ Geom::Point ori (ps2user[4], ps2user[5]);
+
+ // create pattern contents coordinate system
+ if (pat->viewBox_set) {
+ Geom::Rect view_box = *pat->viewbox();
+
+ double x, y, w, h;
+ x = 0;
+ y = 0;
+ w = width * bbox_width_scaler;
+ h = height * bbox_height_scaler;
+
+ //calculatePreserveAspectRatio(pat->aspect_align, pat->aspect_clip, view_width, view_height, &x, &y, &w, &h);
+ pcs2dev[0] = w / view_box.width();
+ pcs2dev[3] = h / view_box.height();
+ pcs2dev[4] = x - view_box.left() * pcs2dev[0];
+ pcs2dev[5] = y - view_box.top() * pcs2dev[3];
+ } else if (pbox && pat->patternContentUnits() == SPPattern::UNITS_OBJECTBOUNDINGBOX) {
+ pcs2dev[0] = pbox->width();
+ pcs2dev[3] = pbox->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);
+
+ // adjust the size of the painted pattern to fit exactly the created surface
+ // this has to be done because of the rounding to obtain an integer pattern surface width/height
+ double scale_width = surface_width / (bbox_width_scaler * width);
+ 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(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 drawing and group
+ Inkscape::Drawing drawing;
+ unsigned dkey = SPItem::display_key_new(1);
+
+ // show items and render them
+ for (SPPattern *pat_i = pat; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
+ if (pat_i && pattern_hasItemChildren(pat_i)) { // find the first one with item children
+ for (auto& child: pat_i->children) {
+ if (SP_IS_ITEM(&child)) {
+ SP_ITEM(&child)->invoke_show(drawing, dkey, SP_ITEM_REFERENCE_FLAGS);
+ _renderer->renderItem(pattern_ctx, SP_ITEM(&child));
+ }
+ }
+ break; // do not go further up the chain if children are found
+ }
+ }
+
+ pattern_ctx->popState();
+
+ // setup a cairo_pattern_t
+ cairo_surface_t *pattern_surface = pattern_ctx->getSurface();
+ TEST(pattern_ctx->saveAsPng("pattern.png"));
+ cairo_pattern_t *result = cairo_pattern_create_for_surface(pattern_surface);
+ cairo_pattern_set_extend(result, CAIRO_EXTEND_REPEAT);
+
+ // set pattern transformation
+ cairo_matrix_t pattern_matrix;
+ _initCairoMatrix(&pattern_matrix, ps2user);
+ cairo_matrix_invert(&pattern_matrix);
+ cairo_pattern_set_matrix(result, &pattern_matrix);
+
+ delete pattern_ctx;
+
+ // hide all items
+ for (SPPattern *pat_i = pat; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
+ if (pat_i && pattern_hasItemChildren(pat_i)) { // find the first one with item children
+ for (auto& child: pat_i->children) {
+ if (SP_IS_ITEM(&child)) {
+ SP_ITEM(&child)->invoke_hide(dkey);
+ }
+ }
+ break; // do not go further up the chain if children are found
+ }
+ }
+
+ return result;
+}
+
+cairo_pattern_t*
+CairoRenderContext::_createHatchPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox) {
+ SPHatch const *hatch = dynamic_cast<SPHatch const *>(paintserver);
+ g_assert( hatch );
+
+ g_assert(hatch->pitch() > 0);
+
+ // create drawing and group
+ Inkscape::Drawing drawing;
+ unsigned dkey = SPItem::display_key_new(1);
+
+ // TODO need to refactor 'evil' referenced code for const correctness.
+ SPHatch *evil = const_cast<SPHatch *>(hatch);
+ evil->show(drawing, dkey, pbox);
+
+ SPHatch::RenderInfo render_info = hatch->calculateRenderInfo(dkey);
+ Geom::Rect tile_rect = render_info.tile_rect;
+
+ // 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
+ const int subpix_scale = 10;
+ double surface_width = MAX(ceil(subpix_scale * tile_rect.width() - 0.5), 1);
+ double surface_height = MAX(ceil(subpix_scale * tile_rect.height() - 0.5), 1);
+ Geom::Affine drawing_scale = Geom::Scale(surface_width / tile_rect.width(), surface_height / tile_rect.height());
+ Geom::Affine drawing_transform = Geom::Translate(-tile_rect.min()) * drawing_scale;
+
+ Geom::Affine child_transform = render_info.child_transform;
+ child_transform *= drawing_transform;
+
+ //The rendering of hatch overflow is implemented by repeated drawing
+ //of hatch paths over one strip. Within each iteration paths are moved by pitch value.
+ //The movement progresses from right to left. This gives the same result
+ //as drawing whole strips in left-to-right order.
+ gdouble overflow_right_strip = 0.0;
+ int overflow_steps = 1;
+ Geom::Affine overflow_transform;
+ if (hatch->style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) {
+ Geom::Interval bounds = hatch->bounds();
+ overflow_right_strip = floor(bounds.max() / hatch->pitch()) * hatch->pitch();
+ overflow_steps = ceil((overflow_right_strip - bounds.min()) / hatch->pitch()) + 1;
+ overflow_transform = Geom::Translate(hatch->pitch(), 0.0);
+ }
+
+ CairoRenderContext *pattern_ctx = cloneMe(surface_width, surface_height);
+ pattern_ctx->setTransform(child_transform);
+ pattern_ctx->transform(Geom::Translate(-overflow_right_strip, 0.0));
+ pattern_ctx->pushState();
+
+ std::vector<SPHatchPath *> children(evil->hatchPaths());
+
+ for (int i = 0; i < overflow_steps; i++) {
+ for (auto path : children) {
+ _renderer->renderHatchPath(pattern_ctx, *path, dkey);
+ }
+ pattern_ctx->transform(overflow_transform);
+ }
+
+ pattern_ctx->popState();
+
+ // setup a cairo_pattern_t
+ cairo_surface_t *pattern_surface = pattern_ctx->getSurface();
+ TEST(pattern_ctx->saveAsPng("hatch.png"));
+ cairo_pattern_t *result = cairo_pattern_create_for_surface(pattern_surface);
+ cairo_pattern_set_extend(result, CAIRO_EXTEND_REPEAT);
+
+ Geom::Affine pattern_transform;
+ pattern_transform = render_info.pattern_to_user_transform.inverse() * drawing_transform;
+ ink_cairo_pattern_set_matrix(result, pattern_transform);
+
+ evil->hide(dkey);
+
+ delete pattern_ctx;
+ return result;
+}
+
+cairo_pattern_t*
+CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const paintserver,
+ Geom::OptRect const &pbox, float alpha)
+{
+ cairo_pattern_t *pattern = nullptr;
+ bool apply_bbox2user = FALSE;
+
+ auto const paintserver_mutable = const_cast<SPPaintServer *>(paintserver);
+
+ if (auto lg = dynamic_cast<SPLinearGradient *>(paintserver_mutable)) {
+
+ 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 && lg->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) {
+ // convert to userspace
+ Geom::Affine bbox2user(pbox->width(), 0, 0, pbox->height(), pbox->left(), pbox->top());
+ p1 *= bbox2user;
+ p2 *= bbox2user;
+ }
+
+ // create linear gradient pattern
+ pattern = cairo_pattern_create_linear(p1[Geom::X], p1[Geom::Y], p2[Geom::X], p2[Geom::Y]);
+
+ // add stops
+ for (gint i = 0; unsigned(i) < lg->vector.stops.size(); i++) {
+ float rgb[3];
+ lg->vector.stops[i].color.get_rgb_floatv(rgb);
+ cairo_pattern_add_color_stop_rgba(pattern, lg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], lg->vector.stops[i].opacity * alpha);
+ }
+ } else if (auto rg = dynamic_cast<SPRadialGradient *>(paintserver_mutable)) {
+
+ 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;
+ double fr = rg->fr.computed;
+ if (pbox && rg->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX)
+ apply_bbox2user = true;
+
+ // create radial gradient pattern
+ pattern = cairo_pattern_create_radial(f[Geom::X], f[Geom::Y], fr, c[Geom::X], c[Geom::Y], r);
+
+ // add stops
+ for (gint i = 0; unsigned(i) < rg->vector.stops.size(); i++) {
+ float rgb[3];
+ rg->vector.stops[i].color.get_rgb_floatv(rgb);
+ cairo_pattern_add_color_stop_rgba(pattern, rg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], rg->vector.stops[i].opacity * alpha);
+ }
+ } else if (auto mg = dynamic_cast<SPMeshGradient *>(paintserver_mutable)) {
+ pattern = mg->pattern_new(_cr, pbox, 1.0);
+ } else if (SP_IS_PATTERN (paintserver)) {
+ pattern = _createPatternPainter(paintserver, pbox);
+ } else if ( dynamic_cast<SPHatch const *>(paintserver) ) {
+ pattern = _createHatchPainter(paintserver, pbox);
+ } else {
+ return nullptr;
+ }
+
+ if (pattern && SP_IS_GRADIENT(paintserver)) {
+ auto g = dynamic_cast<SPGradient *>(paintserver_mutable);
+
+ // set extend type
+ SPGradientSpread spread = g->fetchSpread();
+ switch (spread) {
+ case SP_GRADIENT_SPREAD_REPEAT: {
+ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
+ break;
+ }
+ case SP_GRADIENT_SPREAD_REFLECT: { // not supported by cairo-pdf yet
+ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REFLECT);
+ break;
+ }
+ case SP_GRADIENT_SPREAD_PAD: { // not supported by cairo-pdf yet
+ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD);
+ break;
+ }
+ default: {
+ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_NONE);
+ break;
+ }
+ }
+
+ cairo_matrix_t pattern_matrix;
+ if (g->gradientTransform_set) {
+ // apply gradient transformation
+ cairo_matrix_init(&pattern_matrix,
+ g->gradientTransform[0], g->gradientTransform[1],
+ g->gradientTransform[2], g->gradientTransform[3],
+ g->gradientTransform[4], g->gradientTransform[5]);
+ } else {
+ cairo_matrix_init_identity (&pattern_matrix);
+ }
+
+ if (apply_bbox2user) {
+ // convert to userspace
+ cairo_matrix_t bbox2user;
+ cairo_matrix_init (&bbox2user, pbox->width(), 0, 0, pbox->height(), pbox->left(), pbox->top());
+ cairo_matrix_multiply (&pattern_matrix, &bbox2user, &pattern_matrix);
+ }
+ cairo_matrix_invert(&pattern_matrix); // because Cairo expects a userspace->patternspace matrix
+ cairo_pattern_set_matrix(pattern, &pattern_matrix);
+ }
+
+ return pattern;
+}
+
+void
+CairoRenderContext::_setFillStyle(SPStyle const *const style, Geom::OptRect const &pbox)
+{
+ g_return_if_fail( !style->fill.set
+ || style->fill.isColor()
+ || style->fill.isPaintserver() );
+
+ float alpha = SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
+ if (_state->merge_opacity) {
+ alpha *= _state->opacity;
+ TRACE(("merged op=%f\n", alpha));
+ }
+
+ SPPaintServer const *paint_server = style->getFillPaintServer();
+ if (paint_server && paint_server->isValid()) {
+
+ g_assert(SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))
+ || SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style))
+ || dynamic_cast<SPHatch *>(SP_STYLE_FILL_SERVER(style)));
+
+ cairo_pattern_t *pattern = _createPatternForPaintServer(paint_server, pbox, alpha);
+ if (pattern) {
+ cairo_set_source(_cr, pattern);
+ cairo_pattern_destroy(pattern);
+ }
+ } else if (style->fill.colorSet) {
+ float rgb[3];
+ style->fill.value.color.get_rgb_floatv(rgb);
+
+ cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha);
+
+ } else { // unset fill is black
+ g_assert(!style->fill.set
+ || (paint_server && !paint_server->isValid()));
+
+ cairo_set_source_rgba(_cr, 0, 0, 0, alpha);
+ }
+}
+
+void
+CairoRenderContext::_setStrokeStyle(SPStyle const *style, Geom::OptRect const &pbox)
+{
+ float alpha = SP_SCALE24_TO_FLOAT(style->stroke_opacity.value);
+ if (_state->merge_opacity)
+ alpha *= _state->opacity;
+
+ if (style->stroke.isColor() || (style->stroke.isPaintserver() && !style->getStrokePaintServer()->isValid())) {
+ float rgb[3];
+ style->stroke.value.color.get_rgb_floatv(rgb);
+
+ cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha);
+ } else {
+ g_assert( style->stroke.isPaintserver()
+ || SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style))
+ || SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style))
+ || dynamic_cast<SPHatch *>(SP_STYLE_STROKE_SERVER(style)));
+
+ cairo_pattern_t *pattern = _createPatternForPaintServer(SP_STYLE_STROKE_SERVER(style), pbox, alpha);
+
+ if (pattern) {
+ cairo_set_source(_cr, pattern);
+ cairo_pattern_destroy(pattern);
+ }
+ }
+
+ if (!style->stroke_dasharray.values.empty())
+ {
+ size_t ndashes = style->stroke_dasharray.values.size();
+ double* dashes =(double*)malloc(ndashes*sizeof(double));
+ for( unsigned i = 0; i < ndashes; ++i ) {
+ dashes[i] = style->stroke_dasharray.values[i].value;
+ }
+ cairo_set_dash(_cr, dashes, ndashes, style->stroke_dashoffset.value);
+ free(dashes);
+ } else {
+ cairo_set_dash(_cr, nullptr, 0, 0.0); // disable dashing
+ }
+
+ // This allows hairlines to be drawn properly in PDF, PS, Win32-Print, etc.
+ // It requires the following pull request in Cairo:
+ // https://gitlab.freedesktop.org/cairo/cairo/merge_requests/21
+ if (style->stroke_extensions.hairline) {
+ ink_cairo_set_hairline(_cr);
+ } else {
+ cairo_set_line_width(_cr, style->stroke_width.computed);
+ }
+
+ // set line join type
+ cairo_line_join_t join = CAIRO_LINE_JOIN_MITER;
+ switch (style->stroke_linejoin.computed) {
+ case SP_STROKE_LINEJOIN_MITER:
+ join = CAIRO_LINE_JOIN_MITER;
+ break;
+ case SP_STROKE_LINEJOIN_ROUND:
+ join = CAIRO_LINE_JOIN_ROUND;
+ break;
+ case SP_STROKE_LINEJOIN_BEVEL:
+ join = CAIRO_LINE_JOIN_BEVEL;
+ break;
+ }
+ cairo_set_line_join(_cr, join);
+
+ // set line cap type
+ cairo_line_cap_t cap = CAIRO_LINE_CAP_BUTT;
+ switch (style->stroke_linecap.computed) {
+ case SP_STROKE_LINECAP_BUTT:
+ cap = CAIRO_LINE_CAP_BUTT;
+ break;
+ case SP_STROKE_LINECAP_ROUND:
+ cap = CAIRO_LINE_CAP_ROUND;
+ break;
+ case SP_STROKE_LINECAP_SQUARE:
+ cap = CAIRO_LINE_CAP_SQUARE;
+ break;
+ }
+ cairo_set_line_cap(_cr, cap);
+ cairo_set_miter_limit(_cr, MAX(1, style->stroke_miterlimit.value));
+}
+
+void
+CairoRenderContext::_prepareRenderGraphic()
+{
+ // Only PDFLaTeX supports importing a single page of a graphics file,
+ // so only PDF backend gets interleaved text/graphics
+ if (_is_omittext && _target == CAIRO_SURFACE_TYPE_PDF && _render_mode != RENDER_MODE_CLIP) {
+ if (_omittext_state == NEW_PAGE_ON_GRAPHIC) {
+ // better set this immediately (not sure if masks applied during "popLayer" could call
+ // this function, too, triggering the same code again in error
+ _omittext_state = GRAPHIC_ON_TOP;
+
+ // As we can not emit the page in the middle of a layer (aka group) - it will not be fully painted yet! -
+ // the following basically mirrors the calls in CairoRenderer::renderItem (but in reversed order)
+ // - first traverse all saved states in reversed order (i.e. from deepest nesting to the top)
+ // and apply clipping / masking to layers on the way (this is done in popLayer)
+ // - then emit the page using cairo_show_page()
+ // - finally restore the previous state with proper transforms and appropriate layers again
+ //
+ // TODO: While this appears to be an ugly hack it seems to work
+ // Somebody with a more intimate understanding of cairo and the renderer implementation might
+ // be able to implement this in a cleaner way, though.
+ int stack_size = _state_stack.size();
+ for (int i = stack_size-1; i > 0; i--) {
+ if (_state_stack[i]->need_layer)
+ popLayer();
+ cairo_restore(_cr);
+ _state = _state_stack[i-1];
+ }
+
+ cairo_show_page(_cr);
+
+ for (int i = 1; i < stack_size; i++) {
+ cairo_save(_cr);
+ _state = _state_stack[i];
+ if (_state->need_layer)
+ pushLayer();
+ setTransform(_state->transform);
+ }
+ }
+ _omittext_state = GRAPHIC_ON_TOP;
+ }
+}
+
+void
+CairoRenderContext::_prepareRenderText()
+{
+ // Only PDFLaTeX supports importing a single page of a graphics file,
+ // so only PDF backend gets interleaved text/graphics
+ if (_is_omittext && _target == CAIRO_SURFACE_TYPE_PDF) {
+ if (_omittext_state == GRAPHIC_ON_TOP)
+ _omittext_state = NEW_PAGE_ON_GRAPHIC;
+ }
+}
+
+/* We need CairoPaintOrder as markers are rendered in a separate step and may be rendered
+ * in between fill and stroke.
+ */
+bool
+CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle const *style, Geom::OptRect const &pbox, CairoPaintOrder order)
+{
+ g_assert( _is_valid );
+
+ _prepareRenderGraphic();
+
+ if (_render_mode == RENDER_MODE_CLIP) {
+ if (_clip_mode == CLIP_MODE_PATH) {
+ addClipPath(pathv, &style->fill_rule);
+ } else {
+ setPathVector(pathv);
+ if (style->fill_rule.computed == SP_WIND_RULE_EVENODD) {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD);
+ } else {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING);
+ }
+ if (style->mix_blend_mode.set && style->mix_blend_mode.value) {
+ cairo_set_operator(_cr, ink_css_blend_to_cairo_operator(style->mix_blend_mode.value));
+ }
+ cairo_fill(_cr);
+ TEST(cairo_surface_write_to_png (_surface, "pathmask.png"));
+ }
+ return true;
+ }
+
+ bool no_fill = style->fill.isNone() || style->fill_opacity.value == 0 ||
+ order == STROKE_ONLY;
+ bool no_stroke = style->stroke.isNone() || (!style->stroke_extensions.hairline && style->stroke_width.computed < 1e-9) ||
+ style->stroke_opacity.value == 0 || order == FILL_ONLY;
+
+ if (no_fill && no_stroke)
+ return true;
+
+ bool need_layer = ( !_state->merge_opacity && !_state->need_layer &&
+ ( _state->opacity != 1.0 || _state->clip_path != nullptr || _state->mask != nullptr ) );
+ bool blend = false;
+ if (style->mix_blend_mode.set && style->mix_blend_mode.value != SP_CSS_BLEND_NORMAL) {
+ need_layer = true;
+ blend = true;
+ }
+ if (!need_layer)
+ cairo_save(_cr);
+ else
+ pushLayer();
+
+ if (!no_fill) {
+ if (style->fill_rule.computed == SP_WIND_RULE_EVENODD) {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD);
+ } else {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING);
+ }
+ }
+
+ setPathVector(pathv);
+
+ if (!no_fill && (order == STROKE_OVER_FILL || order == FILL_ONLY)) {
+ _setFillStyle(style, pbox);
+
+ if (no_stroke)
+ cairo_fill(_cr);
+ else
+ cairo_fill_preserve(_cr);
+ }
+
+ if (!no_stroke) {
+ _setStrokeStyle(style, pbox);
+
+ if (no_fill || order == STROKE_OVER_FILL)
+ cairo_stroke(_cr);
+ else
+ cairo_stroke_preserve(_cr);
+ }
+
+ if (!no_fill && order == FILL_OVER_STROKE) {
+ _setFillStyle(style, pbox);
+
+ cairo_fill(_cr);
+ }
+
+ if (need_layer) {
+ if (blend) {
+ popLayer(ink_css_blend_to_cairo_operator(style->mix_blend_mode.value));
+ } else {
+ popLayer();
+ }
+ } else {
+ cairo_restore(_cr);
+ }
+
+ return true;
+}
+
+bool CairoRenderContext::renderImage(Inkscape::Pixbuf *pb,
+ Geom::Affine const &image_transform, SPStyle const *style)
+{
+ g_assert( _is_valid );
+
+ if (_render_mode == RENDER_MODE_CLIP) {
+ return true;
+ }
+
+ _prepareRenderGraphic();
+
+ int w = pb->width();
+ int h = pb->height();
+
+ // TODO: reenable merge_opacity if useful
+
+ cairo_surface_t *image_surface = pb->getSurfaceRaw();
+ if (cairo_surface_status(image_surface)) {
+ TRACE(("Image surface creation failed:\n%s\n", cairo_status_to_string(cairo_surface_status(image_surface))));
+ return false;
+ }
+
+ cairo_save(_cr);
+
+ // scaling by width & height is not needed because it will be done by Cairo
+ transform(image_transform);
+
+ cairo_set_source_surface(_cr, image_surface, 0.0, 0.0);
+
+ // set clip region so that the pattern will not be repeated (bug in Cairo-PDF)
+ if (_vector_based_target) {
+ cairo_new_path(_cr);
+ cairo_rectangle(_cr, 0, 0, w, h);
+ cairo_clip(_cr);
+ }
+
+ // Cairo filter method will be mapped to PS/PDF 'interpolate' true/false).
+ // See cairo-pdf-surface.c
+ if (style) {
+ // See: http://www.w3.org/TR/SVG/painting.html#ImageRenderingProperty
+ // https://drafts.csswg.org/css-images-3/#the-image-rendering
+ // style.h/style.cpp, drawing-image.cpp
+ //
+ // CSS 3 defines:
+ // 'optimizeSpeed' as alias for "pixelated"
+ // 'optimizeQuality' as alias for "smooth"
+ switch (style->image_rendering.computed) {
+ case SP_CSS_IMAGE_RENDERING_OPTIMIZESPEED:
+ case SP_CSS_IMAGE_RENDERING_PIXELATED:
+ // we don't have an implementation for crisp-edges, but it should *not* smooth or blur
+ case SP_CSS_IMAGE_RENDERING_CRISPEDGES:
+ cairo_pattern_set_filter(cairo_get_source(_cr), CAIRO_FILTER_NEAREST);
+ break;
+ case SP_CSS_IMAGE_RENDERING_OPTIMIZEQUALITY:
+ case SP_CSS_IMAGE_RENDERING_AUTO:
+ default:
+ cairo_pattern_set_filter(cairo_get_source(_cr), CAIRO_FILTER_BEST);
+ break;
+ }
+ }
+
+ if (style->mix_blend_mode.set && style->mix_blend_mode.value) {
+ cairo_set_operator(_cr, ink_css_blend_to_cairo_operator(style->mix_blend_mode.value));
+ }
+
+ cairo_paint(_cr);
+
+ cairo_restore(_cr);
+ return true;
+}
+
+#define GLYPH_ARRAY_SIZE 64
+
+// TODO investigate why the font is being ignored:
+unsigned int 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_try_malloc(sizeof(cairo_glyph_t) * num_glyphs);
+ if(glyphs == nullptr) {
+ 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; // is a counter for indexing the glyphs array, only counts the valid glyphs
+ for (const auto & it_info : glyphtext) {
+ // 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].index = it_info.index;
+ glyphs[i].x = it_info.x;
+ glyphs[i].y = it_info.y;
+ i++;
+ }
+
+ if (path) {
+ cairo_glyph_path(cr, glyphs, num_glyphs - num_invalid_glyphs);
+ } else {
+ cairo_show_glyphs(cr, glyphs, num_glyphs - num_invalid_glyphs);
+ }
+
+ if (num_glyphs > GLYPH_ARRAY_SIZE) {
+ g_free(glyphs);
+ }
+
+ return num_glyphs - num_invalid_glyphs;
+}
+
+bool
+CairoRenderContext::renderGlyphtext(PangoFont *font, Geom::Affine const &font_matrix,
+ std::vector<CairoGlyphInfo> const &glyphtext, SPStyle const *style)
+{
+
+ _prepareRenderText();
+ if (_is_omittext)
+ return true;
+
+ // create a cairo_font_face from PangoFont
+ // double size = style->font_size.computed; /// \fixme why is this variable never used?
+ gpointer fonthash = (gpointer)font;
+ cairo_font_face_t *font_face = nullptr;
+ if(font_table.find(fonthash)!=font_table.end())
+ font_face = font_table[fonthash];
+
+ FcPattern *fc_pattern = nullptr;
+
+#ifdef USE_PANGO_WIN32
+# ifdef CAIRO_HAS_WIN32_FONT
+ LOGFONTA *lfa = pango_win32_font_logfont(font);
+ LOGFONTW lfw;
+
+ ZeroMemory(&lfw, sizeof(LOGFONTW));
+ memcpy(&lfw, lfa, sizeof(LOGFONTA));
+ MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, lfa->lfFaceName, LF_FACESIZE, lfw.lfFaceName, LF_FACESIZE);
+
+ if(font_face == NULL) {
+ font_face = cairo_win32_font_face_create_for_logfontw(&lfw);
+ 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;
+ if(font_face == nullptr) {
+ font_face = cairo_ft_font_face_create_for_pattern(fc_pattern);
+ font_table[fonthash] = font_face;
+ }
+# endif
+#endif
+
+ cairo_save(_cr);
+ cairo_set_font_face(_cr, font_face);
+
+ //if (fc_pattern && FcPatternGetDouble(fc_pattern, FC_PIXEL_SIZE, 0, &size) != FcResultMatch)
+ // size = 12.0;
+
+ // set the given font matrix
+ cairo_matrix_t matrix;
+ _initCairoMatrix(&matrix, font_matrix);
+ cairo_set_font_matrix(_cr, &matrix);
+
+ if (_render_mode == RENDER_MODE_CLIP) {
+ if (_clip_mode == CLIP_MODE_MASK) {
+ if (style->fill_rule.computed == SP_WIND_RULE_EVENODD) {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD);
+ } else {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING);
+ }
+ _showGlyphs(_cr, font, glyphtext, FALSE);
+ } else {
+ // just add the glyph paths to the current context
+ _showGlyphs(_cr, font, glyphtext, TRUE);
+ }
+ } else {
+
+ bool fill = false;
+ if (style->fill.isColor() || style->fill.isPaintserver()) {
+ fill = true;
+ }
+
+ bool stroke = false;
+ if (style->stroke.isColor() || style->stroke.isPaintserver()) {
+ stroke = true;
+ }
+
+ if (style->mix_blend_mode.set && style->mix_blend_mode.value) {
+ cairo_set_operator(_cr, ink_css_blend_to_cairo_operator(style->mix_blend_mode.value));
+ }
+
+ // Text never has markers
+ bool stroke_over_fill = true;
+ if ( (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL) ||
+
+ (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL) ||
+
+ (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL) ) {
+ stroke_over_fill = false;
+ }
+
+ bool have_path = false;
+ if (fill && stroke_over_fill) {
+ _setFillStyle(style, Geom::OptRect());
+ if (_is_texttopath) {
+ _showGlyphs(_cr, font, glyphtext, true);
+ if (stroke) {
+ cairo_fill_preserve(_cr);
+ have_path = true;
+ } else {
+ cairo_fill(_cr);
+ }
+ } else {
+ _showGlyphs(_cr, font, glyphtext, false);
+ }
+ }
+
+ if (stroke) {
+ _setStrokeStyle(style, Geom::OptRect());
+ if (!have_path) {
+ _showGlyphs(_cr, font, glyphtext, true);
+ }
+ if (fill && _is_texttopath && !stroke_over_fill) {
+ cairo_stroke_preserve(_cr);
+ have_path = true;
+ } else {
+ cairo_stroke(_cr);
+ }
+ }
+
+ if (fill && !stroke_over_fill) {
+ _setFillStyle(style, Geom::OptRect());
+ if (_is_texttopath) {
+ if (!have_path) {
+ // Could happen if both 'stroke' and 'stroke_over_fill' are false
+ _showGlyphs(_cr, font, glyphtext, true);
+ }
+ cairo_fill(_cr);
+ } else {
+ _showGlyphs(_cr, font, glyphtext, false);
+ }
+ }
+
+ }
+
+ cairo_restore(_cr);
+
+// if (font_face)
+// cairo_font_face_destroy(font_face);
+
+ return true;
+}
+
+/* Helper functions */
+
+void
+CairoRenderContext::setPathVector(Geom::PathVector const &pv)
+{
+ cairo_new_path(_cr);
+ addPathVector(pv);
+}
+
+void
+CairoRenderContext::addPathVector(Geom::PathVector const &pv)
+{
+ feed_pathvector_to_cairo(_cr, pv);
+}
+
+void
+CairoRenderContext::_concatTransform(cairo_t *cr, double xx, double yx, double xy, double yy, double x0, double y0)
+{
+ cairo_matrix_t matrix;
+
+ cairo_matrix_init(&matrix, xx, yx, xy, yy, x0, y0);
+ cairo_transform(cr, &matrix);
+}
+
+void
+CairoRenderContext::_initCairoMatrix(cairo_matrix_t *matrix, Geom::Affine const &transform)
+{
+ matrix->xx = transform[0];
+ matrix->yx = transform[1];
+ matrix->xy = transform[2];
+ matrix->yy = transform[3];
+ matrix->x0 = transform[4];
+ matrix->y0 = transform[5];
+}
+
+void
+CairoRenderContext::_concatTransform(cairo_t *cr, Geom::Affine const &transform)
+{
+ _concatTransform(cr, transform[0], transform[1],
+ transform[2], transform[3],
+ transform[4], transform[5]);
+}
+
+static cairo_status_t
+_write_callback(void *closure, const unsigned char *data, unsigned int length)
+{
+ size_t written;
+ FILE *file = (FILE*)closure;
+
+ written = fwrite (data, 1, length, file);
+
+ if (written == length)
+ return CAIRO_STATUS_SUCCESS;
+ else
+ return CAIRO_STATUS_WRITE_ERROR;
+}
+
+#include "clear-n_.h"
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#undef TRACE
+#undef TEST
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :