summaryrefslogtreecommitdiffstats
path: root/src/extension/internal/cairo-renderer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/extension/internal/cairo-renderer.cpp')
-rw-r--r--src/extension/internal/cairo-renderer.cpp1012
1 files changed, 1012 insertions, 0 deletions
diff --git a/src/extension/internal/cairo-renderer.cpp b/src/extension/internal/cairo-renderer.cpp
new file mode 100644
index 0000000..5898b8e
--- /dev/null
+++ b/src/extension/internal/cairo-renderer.cpp
@@ -0,0 +1,1012 @@
+// 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 <csignal>
+#include <cerrno>
+
+
+#include <2geom/transforms.h>
+#include <2geom/pathvector.h>
+#include <cairo.h>
+#include <glib.h>
+#include <glibmm/i18n.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
+
+#include "cairo-render-context.h"
+#include "cairo-renderer.h"
+#include "document.h"
+#include "inkscape-version.h"
+#include "rdf.h"
+#include "style-internal.h"
+#include "display/cairo-utils.h"
+#include "display/curve.h"
+#include "extension/system.h"
+#include "filter-chemistry.h"
+#include "helper/pixbuf-ops.h"
+#include "helper/png-write.h"
+
+#include "io/sys.h"
+
+#include "include/source_date_epoch.h"
+
+#include "libnrtype/Layout-TNG.h"
+
+#include "object/sp-anchor.h"
+#include "object/sp-clippath.h"
+#include "object/sp-defs.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-hatch-path.h"
+#include "object/sp-image.h"
+#include "object/sp-item-group.h"
+#include "object/sp-item.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-marker.h"
+#include "object/sp-mask.h"
+#include "object/sp-page.h"
+#include "object/sp-pattern.h"
+#include "object/sp-radial-gradient.h"
+#include "object/sp-root.h"
+#include "object/sp-shape.h"
+#include "object/sp-symbol.h"
+#include "object/sp-text.h"
+#include "object/sp-use.h"
+
+#include "util/units.h"
+
+//#define TRACE(_args) g_printf _args
+#define TRACE(_args)
+//#define TEST(_args) _args
+#define TEST(_args)
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+CairoRenderer::CairoRenderer(void)
+= default;
+
+CairoRenderer::~CairoRenderer()
+{
+ /* restore default signal handling for SIGPIPE */
+#if !defined(_WIN32) && !defined(__WIN32__)
+ (void) signal(SIGPIPE, SIG_DFL);
+#endif
+
+ return;
+}
+
+CairoRenderContext*
+CairoRenderer::createContext()
+{
+ CairoRenderContext *new_context = new CairoRenderContext(this);
+ g_assert( new_context != nullptr );
+
+ new_context->_state = nullptr;
+
+ // create initial render state
+ CairoRenderState *state = new_context->_createState();
+ state->transform = Geom::identity();
+ new_context->_state_stack.push_back(state);
+ new_context->_state = state;
+
+ return new_context;
+}
+
+void
+CairoRenderer::destroyContext(CairoRenderContext *ctx)
+{
+ delete ctx;
+}
+
+/*
+
+Here comes the rendering part which could be put into the 'render' methods of SPItems'
+
+*/
+
+/* The below functions are copy&pasted plus slightly modified from *_invoke_print functions. */
+static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx, SPItem *origin = nullptr, SPPage *page = nullptr);
+static void sp_group_render(SPGroup *group, CairoRenderContext *ctx, SPItem *origin = nullptr, SPPage *page = nullptr);
+static void sp_anchor_render(SPAnchor *a, CairoRenderContext *ctx);
+static void sp_use_render(SPUse *use, CairoRenderContext *ctx, SPPage *page = nullptr);
+static void sp_shape_render(SPShape *shape, CairoRenderContext *ctx, SPItem *origin = nullptr);
+static void sp_text_render(SPText *text, CairoRenderContext *ctx);
+static void sp_flowtext_render(SPFlowtext *flowtext, CairoRenderContext *ctx);
+static void sp_image_render(SPImage *image, CairoRenderContext *ctx);
+static void sp_symbol_render(SPSymbol *symbol, CairoRenderContext *ctx, SPItem *origin, SPPage *page);
+static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx, SPPage *page = nullptr);
+
+static void sp_shape_render_invoke_marker_rendering(SPMarker* marker, Geom::Affine tr, SPStyle* style, CairoRenderContext *ctx, SPItem *origin)
+{
+ bool render = true;
+ if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
+ if (style->stroke_width.computed > 1e-9) {
+ tr = Geom::Scale(style->stroke_width.computed) * tr;
+ } else {
+ render = false; // stroke width zero and marker is thus scaled down to zero, skip
+ }
+ }
+
+ if (render) {
+ SPItem* marker_item = sp_item_first_item_child(marker);
+ if (marker_item) {
+ tr = (Geom::Affine)marker_item->transform * (Geom::Affine)marker->c2p * tr;
+ Geom::Affine old_tr = marker_item->transform;
+ marker_item->transform = tr;
+ ctx->getRenderer()->renderItem (ctx, marker_item, origin);
+ marker_item->transform = old_tr;
+ }
+ }
+}
+
+static void sp_shape_render(SPShape *shape, CairoRenderContext *ctx, SPItem *origin)
+{
+ if (!shape->curve()) {
+ return;
+ }
+
+ Geom::OptRect pbox = shape->geometricBounds();
+
+ SPStyle* style = shape->style;
+ auto fill_origin = style->fill.paintOrigin;
+ auto stroke_origin = style->stroke.paintOrigin;
+
+ if (origin) {
+ // If the shape is a child of a marker, we must set styles from the origin.
+ SPMarker *marker = nullptr;
+ auto parentobj = shape->parent;
+ while (parentobj) {
+ if (marker = dynamic_cast<SPMarker *>(parentobj)) {
+ break;
+ }
+ parentobj = parentobj->parent;
+ }
+ if (marker) {
+ // Origin will ultimately be either the original object the marker
+ // was added to OR a use tag. See sp_use_render for where this object
+ // comes from when it's a clone.
+ SPStyle *styleorig = origin->style;
+ bool iscolorfill = styleorig->fill.isColor() || (styleorig->fill.isPaintserver() && !styleorig->getFillPaintServer()->isValid());
+ bool iscolorstroke = styleorig->stroke.isColor() || (styleorig->stroke.isPaintserver() && !styleorig->getStrokePaintServer()->isValid());
+ bool fillctxfill = style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL;
+ bool fillctxstroke = style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE;
+ bool strokectxfill = style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL;
+ bool strokectxstroke = style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE;
+
+ if (fillctxfill || fillctxstroke) {
+ if (fillctxfill ? iscolorfill : iscolorstroke) {
+ style->fill.setColor(fillctxfill ? styleorig->fill.value.color : styleorig->stroke.value.color);
+ } else if (fillctxfill ? styleorig->fill.isPaintserver() : styleorig->stroke.isPaintserver()) {
+ style->fill.value.href = fillctxfill ? styleorig->fill.value.href : styleorig->stroke.value.href;
+ } else {
+ style->fill.setNone();
+ }
+ }
+ if (strokectxfill || strokectxstroke) {
+ if (strokectxfill ? iscolorfill : iscolorstroke) {
+ style->stroke.setColor(strokectxfill ? styleorig->fill.value.color : styleorig->stroke.value.color);
+ } else if (strokectxfill ? styleorig->fill.isPaintserver() : styleorig->stroke.isPaintserver()) {
+ style->stroke.value.href = strokectxfill ? styleorig->fill.value.href : styleorig->stroke.value.href;
+ } else {
+ style->stroke.setNone();
+ }
+ }
+ style->fill.paintOrigin = SP_CSS_PAINT_ORIGIN_NORMAL;
+ style->stroke.paintOrigin = SP_CSS_PAINT_ORIGIN_NORMAL;
+ }
+ }
+
+ Geom::PathVector const &pathv = shape->curve()->get_pathvector();
+ if (pathv.empty()) {
+ return;
+ }
+
+ if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_NORMAL ||
+ (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_FILL &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE)) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_OVER_FILL);
+ } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_OVER_STROKE);
+ } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_ONLY);
+ } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_FILL &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_ONLY);
+ }
+
+ // START marker
+ for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START
+ if ( shape->_marker[i] ) {
+ SPMarker* marker = shape->_marker[i];
+ Geom::Affine tr;
+ if (marker->orient_mode == MARKER_ORIENT_AUTO) {
+ tr = sp_shape_marker_get_transform_at_start(pathv.begin()->front());
+ } else if (marker->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) {
+ tr = Geom::Rotate::from_degrees( 180.0 ) * sp_shape_marker_get_transform_at_start(pathv.begin()->front());
+ } else {
+ tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(pathv.begin()->front().pointAt(0));
+ }
+ sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx, origin ? origin : shape);
+ }
+ }
+ // MID marker
+ for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID
+ if ( !shape->_marker[i] ) continue;
+ SPMarker* marker = shape->_marker[i];
+ for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) {
+ // START position
+ if ( path_it != pathv.begin()
+ && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there
+ {
+ Geom::Affine tr;
+ if (marker->orient_mode != MARKER_ORIENT_ANGLE) {
+ tr = sp_shape_marker_get_transform_at_start(path_it->front());
+ } else {
+ tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(path_it->front().pointAt(0));
+ }
+ sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx, origin ? origin : shape);
+ }
+ // MID position
+ if (path_it->size_default() > 1) {
+ Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve
+ Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve
+ while (curve_it2 != path_it->end_default())
+ {
+ /* Put marker between curve_it1 and curve_it2.
+ * Loop to end_default (so including closing segment), because when a path is closed,
+ * there should be a midpoint marker between last segment and closing straight line segment */
+ Geom::Affine tr;
+ if (marker->orient_mode != MARKER_ORIENT_ANGLE) {
+ tr = sp_shape_marker_get_transform(*curve_it1, *curve_it2);
+ } else {
+ tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(curve_it1->pointAt(1));
+ }
+
+ sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx, origin ? origin : shape);
+
+ ++curve_it1;
+ ++curve_it2;
+ }
+ }
+ // END position
+ if ( path_it != (pathv.end()-1) && !path_it->empty()) {
+ Geom::Curve const &lastcurve = path_it->back_default();
+ Geom::Affine tr;
+ if (marker->orient_mode != MARKER_ORIENT_ANGLE) {
+ tr = sp_shape_marker_get_transform_at_end(lastcurve);
+ } else {
+ tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(lastcurve.pointAt(1));
+ }
+ sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx, origin ? origin : shape);
+ }
+ }
+ }
+ // END marker
+ for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END
+ if ( shape->_marker[i] ) {
+ SPMarker* marker = shape->_marker[i];
+
+ /* Get reference to last curve in the path.
+ * For moveto-only path, this returns the "closing line segment". */
+ Geom::Path const &path_last = pathv.back();
+ unsigned int index = path_last.size_default();
+ if (index > 0) {
+ index--;
+ }
+ Geom::Curve const &lastcurve = path_last[index];
+
+ Geom::Affine tr;
+ if (marker->orient_mode != MARKER_ORIENT_ANGLE) {
+ tr = sp_shape_marker_get_transform_at_end(lastcurve);
+ } else {
+ tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(lastcurve.pointAt(1));
+ }
+
+ sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx, origin ? origin : shape);
+ }
+ }
+
+ if (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL &&
+ style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_STROKE) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_OVER_FILL);
+ } else if (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_OVER_STROKE);
+ } else if (style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_ONLY);
+ } else if (style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_ONLY);
+ }
+
+ // Put the style's paint origin back, in case this shape is a marker
+ // which is rendered multple times.
+ style->fill.paintOrigin = fill_origin;
+ style->stroke.paintOrigin = stroke_origin;
+}
+
+static void sp_group_render(SPGroup *group, CairoRenderContext *ctx, SPItem *origin, SPPage *page)
+{
+ CairoRenderer *renderer = ctx->getRenderer();
+ for (auto obj : group->childList(false)) {
+ if (SPItem *item = dynamic_cast<SPItem *>(obj)) {
+ renderer->renderItem(ctx, item, origin, page);
+ }
+ }
+}
+
+static void sp_use_render(SPUse *use, CairoRenderContext *ctx, SPPage *page)
+{
+ bool translated = false;
+ CairoRenderer *renderer = ctx->getRenderer();
+
+ if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) {
+ // FIXME: This translation sometimes isn't in the correct units; e.g.
+ // x="0" y="42" has a different effect than transform="translate(0,42)".
+ Geom::Affine tp(Geom::Translate(use->x.computed, use->y.computed));
+ ctx->pushState();
+ ctx->transform(tp);
+ translated = true;
+ }
+
+ if (use->child) {
+ // Padding in the use object as the origin here ensures markers
+ // are rendered with their correct context-fill.
+ renderer->renderItem(ctx, use->child, use, page);
+ }
+
+ if (translated) {
+ ctx->popState();
+ }
+}
+
+static void sp_text_render(SPText *text, CairoRenderContext *ctx)
+{
+ text->layout.showGlyphs(ctx);
+}
+
+static void sp_flowtext_render(SPFlowtext *flowtext, CairoRenderContext *ctx)
+{
+ flowtext->layout.showGlyphs(ctx);
+}
+
+static void sp_image_render(SPImage *image, CairoRenderContext *ctx)
+{
+ if (!image->pixbuf) {
+ return;
+ }
+ if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) {
+ return;
+ }
+
+ int w = image->pixbuf->width();
+ int h = image->pixbuf->height();
+
+ double x = image->x.computed;
+ double y = image->y.computed;
+ double width = image->width.computed;
+ double height = image->height.computed;
+
+ if (image->aspect_align != SP_ASPECT_NONE) {
+ calculatePreserveAspectRatio (image->aspect_align, image->aspect_clip, (double)w, (double)h,
+ &x, &y, &width, &height);
+ }
+
+ if (image->aspect_clip == SP_ASPECT_SLICE && !ctx->getCurrentState()->has_overflow) {
+ ctx->addClippingRect(image->x.computed, image->y.computed, image->width.computed, image->height.computed);
+ }
+
+ Geom::Translate tp(x, y);
+ Geom::Scale s(width / (double)w, height / (double)h);
+ Geom::Affine t(s * tp);
+
+ ctx->renderImage(image->pixbuf, t, image->style);
+}
+
+static void sp_anchor_render(SPAnchor *a, CairoRenderContext *ctx)
+{
+ CairoRenderer *renderer = ctx->getRenderer();
+
+ std::vector<SPObject*> l(a->childList(false));
+ if (a->href)
+ ctx->tagBegin(a->href);
+ for(auto x : l){
+ SPItem *item = dynamic_cast<SPItem*>(x);
+ if (item) {
+ renderer->renderItem(ctx, item);
+ }
+ }
+ if (a->href)
+ ctx->tagEnd();
+}
+
+static void sp_symbol_render(SPSymbol *symbol, CairoRenderContext *ctx, SPItem *origin, SPPage *page)
+{
+ if (!symbol->cloned) {
+ return;
+ }
+
+ /* Cloned <symbol> is actually renderable */
+ ctx->pushState();
+ ctx->transform(symbol->c2p);
+
+ // apply viewbox if set
+ if (false /*symbol->viewBox_set*/) {
+ Geom::Affine vb2user;
+ double x, y, width, height;
+ double view_width, view_height;
+ x = 0.0;
+ y = 0.0;
+ width = 1.0;
+ height = 1.0;
+
+ view_width = symbol->viewBox.width();
+ view_height = symbol->viewBox.height();
+
+ calculatePreserveAspectRatio(symbol->aspect_align, symbol->aspect_clip, view_width, view_height,
+ &x, &y,&width, &height);
+
+ // [itemTransform *] translate(x, y) * scale(w/vw, h/vh) * translate(-vx, -vy);
+ vb2user = Geom::identity();
+ vb2user[0] = width / view_width;
+ vb2user[3] = height / view_height;
+ vb2user[4] = x - symbol->viewBox.left() * vb2user[0];
+ vb2user[5] = y - symbol->viewBox.top() * vb2user[3];
+
+ ctx->transform(vb2user);
+ }
+
+ sp_group_render(symbol, ctx, origin, page);
+ ctx->popState();
+}
+
+static void sp_root_render(SPRoot *root, CairoRenderContext *ctx)
+{
+ CairoRenderer *renderer = ctx->getRenderer();
+
+ if (!ctx->getCurrentState()->has_overflow && root->parent)
+ ctx->addClippingRect(root->x.computed, root->y.computed, root->width.computed, root->height.computed);
+
+ ctx->pushState();
+ renderer->setStateForItem(ctx, root);
+ ctx->transform(root->c2p);
+ sp_group_render(root, ctx);
+ ctx->popState();
+}
+
+/**
+ This function converts the item to a raster image and includes the image into the cairo renderer.
+ It is only used for filters and then only when rendering filters as bitmaps is requested.
+*/
+static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx, SPPage *page)
+{
+
+ // The code was adapted from sp_selection_create_bitmap_copy in selection-chemistry.cpp
+
+ // Calculate resolution
+ double res;
+ /** @TODO reimplement the resolution stuff (WHY?)
+ */
+ res = ctx->getBitmapResolution();
+ if(res == 0) {
+ res = Inkscape::Util::Quantity::convert(1, "in", "px");
+ }
+ TRACE(("sp_asbitmap_render: resolution: %f\n", res ));
+
+ // Get the bounding box of the selection in document coordinates.
+ Geom::OptRect bbox = item->documentVisualBounds();
+
+ bbox &= (page ? page->getDocumentRect() : item->document->preferredBounds());
+
+ // no bbox, e.g. empty group or item not overlapping its page
+ if (!bbox) {
+ return;
+ }
+
+ // The width and height of the bitmap in pixels
+ unsigned width = ceil(bbox->width() * Inkscape::Util::Quantity::convert(res, "px", "in"));
+ unsigned height = ceil(bbox->height() * Inkscape::Util::Quantity::convert(res, "px", "in"));
+
+ if (width == 0 || height == 0) return;
+
+ // Scale to exactly fit integer bitmap inside bounding box
+ double scale_x = bbox->width() / width;
+ double scale_y = bbox->height() / height;
+
+ // Location of bounding box in document coordinates.
+ double shift_x = bbox->min()[Geom::X];
+ double shift_y = bbox->top();
+
+ // For default 96 dpi, snap bitmap to pixel grid
+ if (res == Inkscape::Util::Quantity::convert(1, "in", "px")) {
+ shift_x = round (shift_x);
+ shift_y = round (shift_y);
+ }
+
+ // Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects
+
+ // Matrix to put bitmap in correct place on document
+ Geom::Affine t_on_document = (Geom::Affine)(Geom::Scale (scale_x, scale_y)) *
+ (Geom::Affine)(Geom::Translate (shift_x, shift_y));
+
+ // ctx matrix already includes item transformation. We must substract.
+ Geom::Affine t_item = item->i2doc_affine();
+ Geom::Affine t = t_on_document * t_item.inverse();
+
+ // Do the export
+ SPDocument *document = item->document;
+
+ std::vector<SPItem*> items;
+ items.push_back(item);
+
+ std::unique_ptr<Inkscape::Pixbuf> pb(sp_generate_internal_bitmap(document, *bbox, res, items, true));
+
+ if (pb) {
+ //TEST(gdk_pixbuf_save( pb, "bitmap.png", "png", NULL, NULL ));
+
+ ctx->renderImage(pb.get(), t, item->style);
+ }
+}
+
+
+static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx, SPItem *origin, SPPage *page)
+{
+ SPRoot *root = dynamic_cast<SPRoot *>(item);
+ if (root) {
+ TRACE(("root\n"));
+ sp_root_render(root, ctx);
+ } else {
+ SPSymbol *symbol = dynamic_cast<SPSymbol *>(item);
+ if (symbol) {
+ TRACE(("symbol\n"));
+ sp_symbol_render(symbol, ctx, origin, page);
+ } else {
+ SPAnchor *anchor = dynamic_cast<SPAnchor *>(item);
+ if (anchor) {
+ TRACE(("<a>\n"));
+ sp_anchor_render(anchor, ctx);
+ } else {
+ SPShape *shape = dynamic_cast<SPShape *>(item);
+ if (shape) {
+ TRACE(("shape\n"));
+ sp_shape_render(shape, ctx, origin);
+ } else {
+ SPUse *use = dynamic_cast<SPUse *>(item);
+ if (use) {
+ TRACE(("use begin---\n"));
+ sp_use_render(use, ctx, page);
+ TRACE(("---use end\n"));
+ } else {
+ SPText *text = dynamic_cast<SPText *>(item);
+ if (text) {
+ TRACE(("text\n"));
+ sp_text_render(text, ctx);
+ } else {
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(item);
+ if (flowtext) {
+ TRACE(("flowtext\n"));
+ sp_flowtext_render(flowtext, ctx);
+ } else {
+ SPImage *image = dynamic_cast<SPImage *>(item);
+ if (image) {
+ TRACE(("image\n"));
+ sp_image_render(image, ctx);
+ } else if (dynamic_cast<SPMarker *>(item)) {
+ // Marker contents shouldn't be rendered, even outside of <defs>.
+ return;
+ } else {
+ SPGroup *group = dynamic_cast<SPGroup *>(item);
+ if (group) {
+ TRACE(("<g>\n"));
+ sp_group_render(group, ctx, origin, page);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+CairoRenderer::setStateForItem(CairoRenderContext *ctx, SPItem const *item)
+{
+ ctx->setStateForStyle(item->style);
+
+ CairoRenderState *state = ctx->getCurrentState();
+ state->clip_path = item->getClipObject();
+ state->mask = item->getMaskObject();
+ state->item_transform = Geom::Affine (item->transform);
+
+ // If parent_has_userspace is true the parent state's transform
+ // has to be used for the mask's/clippath's context.
+ // This is so because we use the image's/(flow)text's transform for positioning
+ // instead of explicitly specifying it and letting the renderer do the
+ // transformation before rendering the item.
+ if (dynamic_cast<SPText const *>(item) || dynamic_cast<SPFlowtext const *>(item) || dynamic_cast<SPImage const *>(item)) {
+ state->parent_has_userspace = TRUE;
+ }
+ TRACE(("setStateForItem opacity: %f\n", state->opacity));
+}
+
+bool CairoRenderer::_shouldRasterize(CairoRenderContext *ctx, SPItem const *item)
+{
+ // rasterize filtered items as per user setting
+ // however, clipPaths ignore any filters, so do *not* rasterize
+ // TODO: might apply to some degree to masks with filtered elements as well;
+ // we need to figure out where in the stack it would be safe to rasterize
+ if (ctx->getFilterToBitmap() && !item->isInClipPath()) {
+ if (auto const *clone = dynamic_cast<SPUse const *>(item)) {
+ return clone->anyInChain([](SPItem const *i) { return i && i->isFiltered(); });
+ } else {
+ return item->isFiltered();
+ }
+ }
+ return false;
+}
+
+void CairoRenderer::_doRender(SPItem *item, CairoRenderContext *ctx, SPItem *origin, SPPage *page)
+{
+ // Check item's visibility
+ if (item->isHidden() || has_hidder_filter(item)) {
+ return;
+ }
+
+ if (_shouldRasterize(ctx, item)) {
+ sp_asbitmap_render(item, ctx, page);
+ } else {
+ sp_item_invoke_render(item, ctx, origin, page);
+ }
+}
+
+// TODO change this to accept a const SPItem:
+void CairoRenderer::renderItem(CairoRenderContext *ctx, SPItem *item, SPItem *origin, SPPage *page)
+{
+ ctx->pushState();
+ setStateForItem(ctx, item);
+
+ CairoRenderState *state = ctx->getCurrentState();
+ state->need_layer = ( state->mask || state->clip_path || state->opacity != 1.0 );
+ SPStyle* style = item->style;
+ SPGroup * group = dynamic_cast<SPGroup *>(item);
+ bool blend = false;
+ if (group && style->mix_blend_mode.set && style->mix_blend_mode.value != SP_CSS_BLEND_NORMAL) {
+ state->need_layer = true;
+ blend = true;
+ }
+ // Draw item on a temporary surface so a mask, clip-path, or opacity can be applied to it.
+ if (state->need_layer) {
+ state->merge_opacity = FALSE;
+ ctx->pushLayer();
+ }
+
+ ctx->transform(item->transform);
+
+ _doRender(item, ctx, origin, page);
+
+ if (state->need_layer) {
+ if (blend) {
+ ctx->popLayer(ink_css_blend_to_cairo_operator(style->mix_blend_mode.value)); // This applies clipping/masking
+ } else {
+ ctx->popLayer(); // This applies clipping/masking
+ }
+ }
+ ctx->popState();
+}
+
+void CairoRenderer::renderHatchPath(CairoRenderContext *ctx, SPHatchPath const &hatchPath, unsigned key) {
+ ctx->pushState();
+ ctx->setStateForStyle(hatchPath.style);
+ ctx->transform(Geom::Translate(hatchPath.offset.computed, 0));
+
+ std::unique_ptr<SPCurve> curve = hatchPath.calculateRenderCurve(key);
+ Geom::PathVector const & pathv =curve->get_pathvector();
+ if (!pathv.empty()) {
+ ctx->renderPathVector(pathv, hatchPath.style, Geom::OptRect());
+ }
+
+ ctx->popState();
+}
+
+void CairoRenderer::setMetadata(CairoRenderContext *ctx, SPDocument *doc) {
+ // title
+ const gchar *title = rdf_get_work_entity(doc, rdf_find_entity("title"));
+ if (title) {
+ ctx->_metadata.title = title;
+ }
+
+ // author
+ const gchar *author = rdf_get_work_entity(doc, rdf_find_entity("creator"));
+ if (author) {
+ ctx->_metadata.author = author;
+ }
+
+ // subject
+ const gchar *subject = rdf_get_work_entity(doc, rdf_find_entity("description"));
+ if (subject) {
+ ctx->_metadata.subject = subject;
+ }
+
+ // keywords
+ const gchar *keywords = rdf_get_work_entity(doc, rdf_find_entity("subject"));
+ if (keywords) {
+ ctx->_metadata.keywords = keywords;
+ }
+
+ // copyright
+ const gchar *copyright = rdf_get_work_entity(doc, rdf_find_entity("rights"));
+ if (copyright) {
+ ctx->_metadata.copyright = copyright;
+ }
+
+ // creator
+ ctx->_metadata.creator = Glib::ustring::compose("Inkscape %1 (https://inkscape.org)",
+ Inkscape::version_string_without_revision);
+
+ // cdate (only used for for reproducible builds hack)
+ Glib::ustring cdate = ReproducibleBuilds::now_iso_8601();
+ if (!cdate.empty()) {
+ ctx->_metadata.cdate = cdate;
+ }
+
+ // mdate (currently unused)
+}
+
+bool
+CairoRenderer::setupDocument(CairoRenderContext *ctx, SPDocument *doc, bool pageBoundingBox, double bleedmargin_px, SPItem *base)
+{
+// PLEASE note when making changes to the boundingbox and transform calculation, corresponding changes should be made to LaTeXTextRenderer::setupDocument !!!
+
+ g_assert( ctx != nullptr );
+
+ if (!base) {
+ base = doc->getRoot();
+ }
+
+ Geom::Rect d;
+ if (pageBoundingBox) {
+ d = Geom::Rect::from_xywh(Geom::Point(0,0), doc->getDimensions());
+ } else {
+ Geom::OptRect bbox = base->documentVisualBounds();
+ if (!bbox) {
+ g_message("CairoRenderer: empty bounding box.");
+ return false;
+ }
+ d = *bbox;
+ }
+ d.expandBy(bleedmargin_px);
+
+ double px_to_ctx_units = 1.0;
+ if (ctx->_vector_based_target) {
+ // convert from px to pt
+ px_to_ctx_units = Inkscape::Util::Quantity::convert(1, "px", "pt");
+ }
+
+ auto width = d.width() * px_to_ctx_units;
+ auto height = d.height() * px_to_ctx_units;
+
+ setMetadata(ctx, doc);
+
+ TRACE(("setupDocument: %f x %f\n", width, height));
+ bool ret = ctx->setupSurface(width, height);
+
+ if (ret) {
+ if (pageBoundingBox) {
+ // translate to set bleed/margin
+ Geom::Affine tp( Geom::Translate( bleedmargin_px, bleedmargin_px ) );
+ ctx->transform(tp);
+ } else {
+ // this transform translates the export drawing to a virtual page (0,0)-(width,height)
+ Geom::Affine tp(Geom::Translate(-d.min()));
+ ctx->transform(tp);
+ }
+ }
+
+ return ret;
+}
+
+// Apply an SVG clip path
+void
+CairoRenderer::applyClipPath(CairoRenderContext *ctx, SPClipPath const *cp)
+{
+ g_assert( ctx != nullptr && ctx->_is_valid );
+
+ if (cp == nullptr)
+ return;
+
+ CairoRenderContext::CairoRenderMode saved_mode = ctx->getRenderMode();
+ ctx->setRenderMode(CairoRenderContext::RENDER_MODE_CLIP);
+
+ // FIXME: the access to the first clippath view to obtain the bbox is completely bogus
+ Geom::Affine saved_ctm;
+ if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && cp->display->bbox) {
+ Geom::Rect clip_bbox = *cp->display->bbox;
+ Geom::Affine t(Geom::Scale(clip_bbox.dimensions()));
+ t[4] = clip_bbox.left();
+ t[5] = clip_bbox.top();
+ t *= ctx->getCurrentState()->transform;
+ saved_ctm = ctx->getTransform();
+ ctx->setTransform(t);
+ }
+
+ TRACE(("BEGIN clip\n"));
+ SPObject const *co = cp;
+ for (auto& child: co->children) {
+ SPItem const *item = dynamic_cast<SPItem const *>(&child);
+ if (item) {
+
+ // combine transform of the item in clippath and the item using clippath:
+ Geom::Affine tempmat = item->transform * ctx->getCurrentState()->item_transform;
+
+ // render this item in clippath
+ ctx->pushState();
+ ctx->transform(tempmat);
+ setStateForItem(ctx, item);
+ // TODO fix this call to accept const items
+ _doRender(const_cast<SPItem *>(item), ctx);
+ ctx->popState();
+ }
+ }
+ TRACE(("END clip\n"));
+
+ // do clipping only if this was the first call to applyClipPath
+ if (ctx->getClipMode() == CairoRenderContext::CLIP_MODE_PATH
+ && saved_mode == CairoRenderContext::RENDER_MODE_NORMAL)
+ cairo_clip(ctx->_cr);
+
+ if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX)
+ ctx->setTransform(saved_ctm);
+
+ ctx->setRenderMode(saved_mode);
+}
+
+// Apply an SVG mask
+void
+CairoRenderer::applyMask(CairoRenderContext *ctx, SPMask const *mask)
+{
+ g_assert( ctx != nullptr && ctx->_is_valid );
+
+ if (mask == nullptr)
+ return;
+
+ // FIXME: the access to the first mask view to obtain the bbox is completely bogus
+ // TODO: should the bbox be transformed if maskUnits != userSpaceOnUse ?
+ if (mask->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && mask->display->bbox) {
+ Geom::Rect mask_bbox = *mask->display->bbox;
+ Geom::Affine t(Geom::Scale(mask_bbox.dimensions()));
+ t[4] = mask_bbox.left();
+ t[5] = mask_bbox.top();
+ t *= ctx->getCurrentState()->transform;
+ ctx->setTransform(t);
+ }
+
+ // Clip mask contents... but...
+ // The mask's bounding box is the "geometric bounding box" which doesn't allow for
+ // filters which extend outside the bounding box. So don't clip.
+ // ctx->addClippingRect(mask_bbox.x0, mask_bbox.y0, mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0);
+
+ ctx->pushState();
+
+ TRACE(("BEGIN mask\n"));
+ SPObject const *co = mask;
+ for (auto& child: co->children) {
+ SPItem const *item = dynamic_cast<SPItem const *>(&child);
+ if (item) {
+ // TODO fix const correctness:
+ renderItem(ctx, const_cast<SPItem*>(item));
+ }
+ }
+ TRACE(("END mask\n"));
+
+ ctx->popState();
+}
+
+void
+calculatePreserveAspectRatio(unsigned int aspect_align, unsigned int aspect_clip, double vp_width, double vp_height,
+ double *x, double *y, double *width, double *height)
+{
+ if (aspect_align == SP_ASPECT_NONE)
+ return;
+
+ double scalex, scaley, scale;
+ double new_width, new_height;
+ scalex = *width / vp_width;
+ scaley = *height / vp_height;
+ scale = (aspect_clip == SP_ASPECT_MEET) ? MIN(scalex, scaley) : MAX(scalex, scaley);
+ new_width = vp_width * scale;
+ new_height = vp_height * scale;
+ /* Now place viewbox to requested position */
+ switch (aspect_align) {
+ case SP_ASPECT_XMIN_YMIN:
+ break;
+ case SP_ASPECT_XMID_YMIN:
+ *x -= 0.5 * (new_width - *width);
+ break;
+ case SP_ASPECT_XMAX_YMIN:
+ *x -= 1.0 * (new_width - *width);
+ break;
+ case SP_ASPECT_XMIN_YMID:
+ *y -= 0.5 * (new_height - *height);
+ break;
+ case SP_ASPECT_XMID_YMID:
+ *x -= 0.5 * (new_width - *width);
+ *y -= 0.5 * (new_height - *height);
+ break;
+ case SP_ASPECT_XMAX_YMID:
+ *x -= 1.0 * (new_width - *width);
+ *y -= 0.5 * (new_height - *height);
+ break;
+ case SP_ASPECT_XMIN_YMAX:
+ *y -= 1.0 * (new_height - *height);
+ break;
+ case SP_ASPECT_XMID_YMAX:
+ *x -= 0.5 * (new_width - *width);
+ *y -= 1.0 * (new_height - *height);
+ break;
+ case SP_ASPECT_XMAX_YMAX:
+ *x -= 1.0 * (new_width - *width);
+ *y -= 1.0 * (new_height - *height);
+ break;
+ default:
+ break;
+ }
+ *width = new_width;
+ *height = new_height;
+}
+
+#include "clear-n_.h"
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#undef TRACE
+
+
+/*
+ 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 :