Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
cairo-renderer.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/*
6 * Author:
7 * Miklos Erdelyi <erdelyim@gmail.com>
8 * Jon A. Cruz <jon@joncruz.org>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 2006 Miklos Erdelyi
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
16#include <locale>
17#include <sstream>
18#ifdef HAVE_CONFIG_H
19# include "config.h" // only include where actually required!
20#endif
21
22#ifndef PANGO_ENABLE_BACKEND
23#define PANGO_ENABLE_BACKEND
24#endif
25
26#ifndef PANGO_ENABLE_ENGINE
27#define PANGO_ENABLE_ENGINE
28#endif
29
30
31#include <csignal>
32#include <cerrno>
33
34#include <2geom/transforms.h>
35#include <2geom/pathvector.h>
36#include <2geom/point.h>
37#include <2geom/rect.h>
38#include <cairo.h>
39#include <glib.h>
40#include <glibmm/i18n.h>
41
42// include support for only the compiled-in surface types
43#ifdef CAIRO_HAS_PDF_SURFACE
44#include <cairo-pdf.h>
45#endif
46#ifdef CAIRO_HAS_PS_SURFACE
47#include <cairo-ps.h>
48#endif
49
51#include "cairo-renderer.h"
52#include "document.h"
53#include "style-internal.h"
54#include "display/cairo-utils.h"
55#include "display/curve.h"
56#include "filter-chemistry.h"
57#include "helper/pixbuf-ops.h"
58#include "helper/png-write.h"
60
61#include "object/sp-anchor.h"
62#include "object/sp-clippath.h"
63#include "object/sp-flowtext.h"
65#include "object/sp-image.h"
67#include "object/sp-item.h"
68#include "object/sp-marker.h"
70#include "object/sp-mask.h"
71#include "object/sp-page.h"
73#include "object/sp-root.h"
74#include "object/sp-shape.h"
75#include "object/sp-symbol.h"
76#include "object/sp-text.h"
77#include "object/sp-use.h"
78
79#include "util/units.h"
80
81//#define TRACE(_args) g_printf _args
82#define TRACE(_args)
83//#define TEST(_args) _args
84#define TEST(_args)
85
86namespace Inkscape {
87namespace Extension {
88namespace Internal {
89
91
93
98
109#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 17, 0)
110#define _CAIRO_1_16
113struct CairoTagNumpunct : std::numpunct<char>
114{
115 protected:
116 char do_decimal_point() const override {
117 return *std::localeconv()->decimal_point;
118 }
119};
120#endif
121
124class CairoTagStringStream : public std::ostringstream
125{
126public:
127 CairoTagStringStream()
128 {
129 #if defined _CAIRO_1_16
130 imbue(std::locale(std::locale::classic(), new CairoTagNumpunct));
131 #else
132 imbue(std::locale::classic());
133 #endif
134 }
135};
136
137/* The below functions are copy&pasted plus slightly modified from *_invoke_print functions. */
138static void sp_item_invoke_render(SPItem const *item, CairoRenderContext *ctx, SPItem const *origin = nullptr, SPPage const *page = nullptr);
139static void sp_group_render(SPGroup const *group, CairoRenderContext *ctx, SPItem const *origin = nullptr, SPPage const *page = nullptr);
140static void sp_anchor_render(SPAnchor const *a, CairoRenderContext *ctx, SPItem const *origin, SPPage const *page);
141static void sp_use_render(SPUse const *use, CairoRenderContext *ctx, SPPage const *page = nullptr);
142static void sp_shape_render(SPShape const *shape, CairoRenderContext *ctx, SPItem const *origin = nullptr);
143static void sp_text_render(SPText const *text, CairoRenderContext *ctx);
144static void sp_flowtext_render(SPFlowtext const *flowtext, CairoRenderContext *ctx);
145static void sp_image_render(SPImage const *image, CairoRenderContext *ctx);
146static void sp_symbol_render(SPSymbol const *symbol, CairoRenderContext *ctx, SPItem const *origin, SPPage const *page);
147static void sp_asbitmap_render(SPItem const *item, CairoRenderContext *ctx, SPPage const *page = nullptr);
148
160 auto const dims = page_rect.dimensions();
161
162 for (auto const axis : {Geom::X, Geom::Y}) {
163 double const floor_size = std::floor(dims[axis]);
164 result[axis] = (dims[axis] > floor_size + Geom::EPSILON) ? floor_size + 1.0 : floor_size;
165 }
166 return result;
167}
168
173class ContextPaintManager
174{
175public:
176 ContextPaintManager(SPStyle *target_style, SPItem const *style_origin)
177 : _managed_style{target_style}
178 , _origin{style_origin}
179 {
180 auto const fill_origin = target_style->fill.paintOrigin;
181 if (fill_origin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL) {
182 _copyPaint(&target_style->fill, *_origin->style->getFillOrStroke(true));
183 } else if (fill_origin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE) {
184 _copyPaint(&target_style->fill, *_origin->style->getFillOrStroke(false));
185 }
186
187 auto const stroke_origin = target_style->stroke.paintOrigin;
188 if (stroke_origin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL) {
189 _copyPaint(&target_style->stroke, *_origin->style->getFillOrStroke(true));
190 } else if (stroke_origin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE) {
191 _copyPaint(&target_style->stroke, *_origin->style->getFillOrStroke(false));
192 }
193 }
194
195 ~ContextPaintManager()
196 {
197 // Restore rewritten paints.
198 if (_rewrote_fill) {
199 _managed_style->fill = _old_fill;
200 }
201 if (_rewrote_stroke) {
202 _managed_style->stroke = _old_stroke;
203 }
204 }
205
206private:
208 template<typename PainT>
209 void _copyPaint(PainT *destination, SPIPaint paint)
210 {
211 // Keep a copy of the old paint
212 if constexpr (std::is_same<PainT, decltype(_old_fill)>::value) {
213 _rewrote_fill = true;
214 _old_fill = *destination;
215 } else if constexpr (std::is_same<PainT, decltype(_old_stroke)>::value) {
216 _rewrote_stroke = true;
217 _old_stroke = *destination;
218 }
219 static_assert(std::is_same_v<PainT, decltype(_old_fill)> || std::is_same_v<PainT, decltype(_old_stroke)>,
220 "ContextPaintManager::_copyPaint() instantiated with neither fill nor stroke type.");
221 PainT new_value;
222 new_value.upcast()->operator=(paint);
223 *destination = new_value;
224 }
225
226 SPStyle *_managed_style;
227 SPItem const *_origin;
228 decltype(_managed_style->fill) _old_fill;
229 decltype(_managed_style->stroke) _old_stroke;
230 bool _rewrote_fill = false;
231 bool _rewrote_stroke = false;
232};
233
234static void sp_shape_render(SPShape const *shape, CairoRenderContext *ctx, SPItem const *origin)
235{
236 if (!shape->curve()) {
237 return;
238 }
239
240 Geom::PathVector const &pathv = shape->curve()->get_pathvector();
241 if (pathv.empty()) {
242 return;
243 }
244
245 Geom::OptRect pbox = shape->geometricBounds();
246 SPStyle* style = shape->style;
247 std::unique_ptr<ContextPaintManager> context_fs_manager;
248
249 if (origin) {
250 context_fs_manager = std::make_unique<ContextPaintManager>(style, origin);
251 }
252
253 if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_NORMAL ||
254 (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_FILL &&
255 style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE)) {
257 } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE &&
258 style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL ) {
260 } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE &&
261 style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
262 ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_ONLY);
263 } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_FILL &&
264 style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
265 ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_ONLY);
266 }
267
268 // Render markers
269 bool has_stroke = style->stroke_width.computed > 0.f;
270 if (shape->hasMarkers() && has_stroke) {
271 for (auto const &[_, marker, tr] : shape->get_markers()) {
272 if (auto marker_item = sp_item_first_item_child(marker)) {
273 auto const old_tr = marker_item->transform;
274 marker_item->transform = old_tr * marker->c2p * tr;
275 // Marker's context-fill/context-stroke always refer to shape.
276 ctx->getRenderer()->renderItem(ctx, marker_item, shape);
277 marker_item->transform = old_tr;
278 }
279 }
280 }
281
282 if (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL &&
283 style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_STROKE) {
285 } else if (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE &&
286 style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL ) {
288 } else if (style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_STROKE &&
289 style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
290 ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_ONLY);
291 } else if (style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL &&
292 style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
293 ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_ONLY);
294 }
295}
296
297static void sp_group_render(SPGroup const *group, CairoRenderContext *ctx, SPItem const *origin, SPPage const *page)
298{
299 CairoRenderer *renderer = ctx->getRenderer();
300 for (auto &obj : group->children) {
301 if (auto item = cast<SPItem>(&obj)) {
302 renderer->renderItem(ctx, item, origin, page);
303 }
304 }
305}
306
307static void sp_use_render(SPUse const *use, CairoRenderContext *ctx, SPPage const *page)
308{
309 bool translated = false;
310 CairoRenderer *renderer = ctx->getRenderer();
311
312 if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) {
313 // FIXME: This translation sometimes isn't in the correct units; e.g.
314 // x="0" y="42" has a different effect than transform="translate(0,42)".
316 ctx->pushState();
317 ctx->transform(tp);
318 translated = true;
319 }
320
321 if (use->child) {
322 // Padding in the use object as the origin here ensures markers
323 // are rendered with their correct context-fill.
324 renderer->renderItem(ctx, use->child, use, page);
325 }
326
327 if (translated) {
328 ctx->popState();
329 }
330}
331
332static void sp_text_render(SPText const *text, CairoRenderContext *ctx)
333{
334 text->layout.showGlyphs(ctx);
335}
336
337static void sp_flowtext_render(SPFlowtext const *flowtext, CairoRenderContext *ctx)
338{
339 flowtext->layout.showGlyphs(ctx);
340}
341
343{
344 if (!image->pixbuf) {
345 return;
346 }
347
348 double width = image->width.computed;
349 double height = image->height.computed;
350 if (width <= 0.0 || height <= 0.0) {
351 return;
352 }
353
354 double const w = static_cast<double>(image->pixbuf->width());
355 double const h = static_cast<double>(image->pixbuf->height());
356 double x = image->x.computed;
357 double y = image->y.computed;
358
359 if (image->aspect_align != SP_ASPECT_NONE) {
360 calculatePreserveAspectRatio(image->aspect_align, image->aspect_clip, w, h, &x, &y, &width, &height);
361 }
362
363 if (image->aspect_clip == SP_ASPECT_SLICE && !ctx->getCurrentState()->has_overflow) {
364 ctx->addClippingRect(image->x.computed, image->y.computed, image->width.computed, image->height.computed);
365 }
366
367 Geom::Affine const transform = Geom::Scale(width / w, height / h) * Geom::Translate(x, y);
368 ctx->renderImage(image->pixbuf.get(), transform, image->style);
369}
370
371static void sp_anchor_render(SPAnchor const *a, CairoRenderContext *ctx, SPItem const *origin, SPPage const *page)
372{
373 if (a->href) {
374 // Raw linking, whatever the user said they wanted
375 auto link = Glib::ustring::compose("uri='%1'", a->href);
376 if (a->local_link) {
377 if (auto obj = a->local_link->getObject()) {
378 // We wanted to use the syntax page=%d here to link to pages, but
379 // cairo has an odd bug that only allows linking to previous pages
380 // So we link everything with a dest link instead.
381 link = Glib::ustring::compose("dest='%1'", obj->getId());
382 }
383 }
384 // Write a box for this hyperlink so it's contained and positioned correctly.
385 if (auto vbox = a->visualBounds()) {
386 CairoTagStringStream os;
387
388 // Apply transforms as we are writing out the box directly.
389 auto bbox = *vbox * ctx->getTransform();
390 os << " rect=[" << bbox.left() << " " << bbox.top() << " " << bbox.width() << " " << bbox.height() << "]";
391 link += os.str();
392 }
393 ctx->tagBegin(link.c_str());
394 }
395
396 CairoRenderer *renderer = ctx->getRenderer();
397 for (auto const &object : a->children) {
398 if (auto item = cast<SPItem>(&object)) {
399 renderer->renderItem(ctx, item, origin, page);
400 }
401 }
402 if (a->href)
403 ctx->tagEnd();
404}
405
406static void sp_symbol_render(SPSymbol const *symbol, CairoRenderContext *ctx, SPItem const *origin, SPPage const *page)
407{
408 if (!symbol->cloned) {
409 return;
410 }
411
412 /* Cloned <symbol> is actually renderable */
413 ctx->pushState();
414 ctx->transform(symbol->c2p);
415
416 // apply viewbox if set
417 if (false /*symbol->viewBox_set*/) {
418 Geom::Affine vb2user;
419 double x, y, width, height;
420 double view_width, view_height;
421 x = 0.0;
422 y = 0.0;
423 width = 1.0;
424 height = 1.0;
425
426 view_width = symbol->viewBox.width();
427 view_height = symbol->viewBox.height();
428
429 calculatePreserveAspectRatio(symbol->aspect_align, symbol->aspect_clip, view_width, view_height,
430 &x, &y,&width, &height);
431
432 // [itemTransform *] translate(x, y) * scale(w/vw, h/vh) * translate(-vx, -vy);
433 vb2user = Geom::identity();
434 vb2user[0] = width / view_width;
435 vb2user[3] = height / view_height;
436 vb2user[4] = x - symbol->viewBox.left() * vb2user[0];
437 vb2user[5] = y - symbol->viewBox.top() * vb2user[3];
438
439 ctx->transform(vb2user);
440 }
441
442 sp_group_render(symbol, ctx, origin, page);
443 ctx->popState();
444}
445
447{
448 if (!ctx->getCurrentState()->has_overflow && root->parent) {
449 ctx->addClippingRect(root->x.computed, root->y.computed, root->width.computed,
450 root->height.computed);
451 }
452 ctx->pushState();
453 ctx->setStateForItem(root);
454 ctx->transform(root->c2p);
455 sp_group_render(root, ctx);
456 ctx->popState();
457}
458
464{
465
466 // The code was adapted from sp_selection_create_bitmap_copy in selection-chemistry.cpp
467
468 // Calculate resolution
471 double res = ctx->getBitmapResolution();
472 if (res == 0) {
473 res = Inkscape::Util::Quantity::convert(1, "in", "px");
474 }
475 TRACE(("sp_asbitmap_render: resolution: %f\n", res ));
476
477 // Get the bounding box of the selection in document coordinates.
479
480 bbox &= (page ? page->getDocumentRect() : item->document->preferredBounds());
481
482 // no bbox, e.g. empty group or item not overlapping its page
483 if (!bbox) {
484 return;
485 }
486
487 // The width and height of the bitmap in pixels
488 unsigned width = ceil(bbox->width() * Inkscape::Util::Quantity::convert(res, "px", "in"));
489 unsigned height = ceil(bbox->height() * Inkscape::Util::Quantity::convert(res, "px", "in"));
490
491 if (width == 0 || height == 0) return;
492
493 // Scale to exactly fit integer bitmap inside bounding box
494 double scale_x = bbox->width() / width;
495 double scale_y = bbox->height() / height;
496
497 // Location of bounding box in document coordinates.
498 double shift_x = bbox->min()[Geom::X];
499 double shift_y = bbox->top();
500
501 // For default 96 dpi, snap bitmap to pixel grid
502 if (res == Inkscape::Util::Quantity::convert(1, "in", "px")) {
503 shift_x = round (shift_x);
504 shift_y = round (shift_y);
505 }
506
507 // Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects
508
509 // Matrix to put bitmap in correct place on document
510 Geom::Affine t_on_document = Geom::Scale(scale_x, scale_y) * Geom::Translate(shift_x, shift_y);
511
512 // ctx matrix already includes item transformation. We must substract.
513 Geom::Affine t_item = item->i2doc_affine();
514 Geom::Affine t = t_on_document * t_item.inverse();
515
516 // Do the export
517 std::unique_ptr<Inkscape::Pixbuf> pb(sp_generate_internal_bitmap(item->document, *bbox, res, {item}, true));
518
519 if (pb) {
520 //TEST(gdk_pixbuf_save( pb, "bitmap.png", "png", NULL, NULL ));
521 ctx->renderImage(pb.get(), t, item->style);
522 }
523}
524
526{
527 bool is_linked = false;
529 is_linked |= is<SPAnchor>(link);
530 }
531
532 // Test to see if the objects would be invisible on this page and hide them if so.
533 if (page && !origin && !page->itemOnPage(item, false, false))
534 return;
535
536 if (is_linked)
537 ctx->destBegin(item->getId());
538
539 if (auto root = cast<SPRoot>(item)) {
540 TRACE(("root\n"));
541 sp_root_render(root, ctx);
542 } else if (auto symbol = cast<SPSymbol>(item)) {
543 TRACE(("symbol\n"));
544 sp_symbol_render(symbol, ctx, origin, page);
545 } else if (auto anchor = cast<SPAnchor>(item)) {
546 TRACE(("<a>\n"));
547 sp_anchor_render(anchor, ctx, origin, page);
548 } else if (auto shape = cast<SPShape>(item)) {
549 TRACE(("shape\n"));
550 sp_shape_render(shape, ctx, origin);
551 } else if (auto use = cast<SPUse>(item)) {
552 TRACE(("use begin---\n"));
553 sp_use_render(use, ctx, page);
554 TRACE(("---use end\n"));
555 } else if (auto text = cast<SPText>(item)) {
556 TRACE(("text\n"));
557 sp_text_render(text, ctx);
558 } else if (auto flowtext = cast<SPFlowtext>(item)) {
559 TRACE(("flowtext\n"));
560 sp_flowtext_render(flowtext, ctx);
561 } else if (auto image = cast<SPImage>(item)) {
562 TRACE(("image\n"));
564 } else if (is<SPMarker>(item)) {
565 // Marker contents shouldn't be rendered, even outside of <defs>.
566 } else if (auto group = cast<SPGroup>(item)) {
567 TRACE(("<g>\n"));
568 sp_group_render(group, ctx, origin, page);
569 }
570
571 if (is_linked)
572 ctx->destEnd();
573}
574
576{
577 // rasterize filtered items as per user setting
578 // however, clipPaths ignore any filters, so do *not* rasterize
579 // TODO: might apply to some degree to masks with filtered elements as well;
580 // we need to figure out where in the stack it would be safe to rasterize
581 if (ctx->getFilterToBitmap() && !item->isInClipPath()) {
582 if (auto const *clone = cast<SPUse>(item)) {
583 return clone->anyInChain([](SPItem const *i) { return i && i->isFiltered(); });
584 } else {
585 return item->isFiltered();
586 }
587 }
588 return false;
589}
590
592{
593 // Check item's visibility
594 if (item->isHidden() || has_hidder_filter(item)) {
595 return;
596 }
597
598 if (_shouldRasterize(ctx, item)) {
600 } else {
602 }
603}
604
606{
607 ctx->pushState();
608 ctx->setStateForItem(item);
609
610 auto *state = ctx->getCurrentState();
611 ctx->setStateNeedsLayer(state->mask || state->clip_path || state->opacity != 1.0);
612 SPStyle* style = item->style;
613 auto group = cast<SPGroup>(item);
614 bool blend = false;
615 if (group && style->mix_blend_mode.set && style->mix_blend_mode.value != SP_CSS_BLEND_NORMAL) {
616 // Force the creation of a new layer
617 ctx->setStateNeedsLayer(true);
618 blend = true;
619 }
620 // Draw item on a temporary surface so a mask, clip-path, or opacity can be applied to it.
621 if (state->need_layer) {
622 ctx->setStateMergeOpacity(false);
623 ctx->pushLayer();
624 }
625
626 ctx->transform(item->transform);
627
628 _doRender(item, ctx, origin, page);
629
630 if (ctx->getCurrentState()->need_layer) {
631 if (blend) {
632 ctx->popLayer(ink_css_blend_to_cairo_operator(style->mix_blend_mode.value)); // This applies clipping/masking
633 } else {
634 ctx->popLayer(); // This applies clipping/masking
635 }
636 }
637 ctx->popState();
638}
639
640void CairoRenderer::renderHatchPath(CairoRenderContext *ctx, SPHatchPath const &hatchPath, unsigned key) {
641 ctx->pushState();
642 ctx->setStateForStyle(hatchPath.style);
643 ctx->transform(Geom::Translate(hatchPath.offset.computed, 0));
644
645 auto curve = hatchPath.calculateRenderCurve(key);
646 Geom::PathVector const & pathv =curve.get_pathvector();
647 if (!pathv.empty()) {
648 ctx->renderPathVector(pathv, hatchPath.style, Geom::OptRect());
649 }
650
651 ctx->popState();
652}
653
655{
656// PLEASE note when making changes to the boundingbox and transform calculation, corresponding changes should be made to LaTeXTextRenderer::setupDocument !!!
657 g_assert(ctx);
658
659 if (!base) {
660 base = doc->getRoot();
661 }
662
663 // Most pages will ignore this setup, but we still want to initialise something useful.
665 double px_to_ctx_units = 1.0;
666 if (ctx->_vector_based_target) {
667 // convert from px to pt
668 px_to_ctx_units = Inkscape::Util::Quantity::convert(1, "px", "pt");
669 }
670
671 auto width = d.width() * px_to_ctx_units;
672 auto height = d.height() * px_to_ctx_units;
673
674 ctx->setMetadata(*doc);
675
676 TRACE(("setupDocument: %f x %f\n", width, height));
677 return ctx->setupSurface(width, height);
678}
679
683bool
685{
686 auto pages = doc->getPageManager().getPages();
687 if (pages.size() == 0) {
688 // Output the page bounding box as already set up in the initial setupDocument.
689 renderItem(ctx, doc->getRoot());
690 return true;
691 }
692
693 for (auto &page : pages) {
694 ctx->pushState();
695 if (!renderPage(ctx, doc, page, stretch_to_fit)) {
696 return false;
697 }
698 // Create a page dest for any anchor tags that link to this page.
699 ctx->destBegin(page->getId());
700 ctx->destEnd();
701
702 if (!ctx->finishPage()) {
703 g_warning("Couldn't render page in output!");
704 return false;
705 }
706 ctx->popState();
707 }
708 return true;
709}
710
711bool
712CairoRenderer::renderPage(CairoRenderContext *ctx, SPDocument *doc, SPPage const *page, bool stretch_to_fit)
713{
714 // Calculate exact page rectangle in PostScript points:
715 auto const scale = doc->getDocumentScale();
716 auto const unit_conversion = Geom::Scale(Inkscape::Util::Quantity::convert(1, "px", "pt"));
717
718 auto const rect = page->getBleed();
719 auto const exact_rect = rect * scale * unit_conversion;
720 auto const [final_width, final_height] = compute_final_page_dimensions(exact_rect);
721
722 if (stretch_to_fit) {
723 // Calculate distortion from rounding (only really matters for small paper sizes):
724 auto distortion = Geom::Scale(final_width / exact_rect.width(),
725 final_height / exact_rect.height());
726
727 // Scale the drawing a tiny bit so that it still fills the rounded page:
728 ctx->transform(scale * distortion);
729 } else {
730 ctx->transform(scale);
731 }
732
733 SPRoot *root = doc->getRoot();
734 ctx->transform(root->transform);
735 ctx->nextPage(final_width, final_height, page->label());
736
737 // Set up page transformation which pushes objects back into the 0,0 location
738 ctx->transform(Geom::Translate(rect.corner(0)).inverse());
739
740 for (auto &child : page->getOverlappingItems(false, true, false)) {
741 ctx->pushState();
742
743 // This process does not return layers, so those affines are added manually.
744 for (auto anc : child->ancestorList(true)) {
745 if (auto layer = cast<SPItem>(anc)) {
746 if (layer != child && layer != root) {
747 ctx->transform(layer->transform);
748 }
749 }
750 }
751
752 // Render the page into the context in the new location.
753 renderItem(ctx, child, nullptr, page);
754 ctx->popState();
755 }
756 return true;
757}
758
759// Apply an SVG clip path
760void
762{
763 g_assert( ctx != nullptr && ctx->_is_valid );
764
765 if (cp == nullptr)
766 return;
767
770
771 // FIXME: the access to the first clippath view to obtain the bbox is completely bogus
772 Geom::Affine saved_ctm;
774 Geom::Rect clip_bbox = *cp->get_last_bbox();
775 Geom::Affine t(Geom::Scale(clip_bbox.dimensions()));
776 t[4] = clip_bbox.left();
777 t[5] = clip_bbox.top();
778 t *= ctx->getCurrentState()->transform;
779 saved_ctm = ctx->getTransform();
780 ctx->setTransform(t);
781 }
782
783 TRACE(("BEGIN clip\n"));
784 SPObject const *co = cp;
785 for (auto& child: co->children) {
786 SPItem const *item = cast<SPItem>(&child);
787 if (item) {
788 // combine transform of the item in clippath and the item using clippath:
790
791 // render this item in clippath
792 ctx->pushState();
793 ctx->transform(tempmat);
794 ctx->setStateForItem(item);
795 _doRender(item, ctx);
796 ctx->popState();
797 }
798 }
799 TRACE(("END clip\n"));
800
801 // do clipping only if this was the first call to applyClipPath
804 cairo_clip(ctx->_cr);
805
807 ctx->setTransform(saved_ctm);
808
809 ctx->setRenderMode(saved_mode);
810}
811
812// Apply an SVG mask
813void
815{
816 g_assert( ctx != nullptr && ctx->_is_valid );
817
818 if (mask == nullptr)
819 return;
820
821 // FIXME: the access to the first mask view to obtain the bbox is completely bogus
822 // TODO: should the bbox be transformed if maskUnits != userSpaceOnUse ?
824 Geom::Rect mask_bbox = *mask->get_last_bbox();
825 Geom::Affine t(Geom::Scale(mask_bbox.dimensions()));
826 t[4] = mask_bbox.left();
827 t[5] = mask_bbox.top();
828 t *= ctx->getCurrentState()->transform;
829 ctx->setTransform(t);
830 }
831
832 // Clip mask contents... but...
833 // The mask's bounding box is the "geometric bounding box" which doesn't allow for
834 // filters which extend outside the bounding box. So don't clip.
835 // ctx->addClippingRect(mask_bbox.x0, mask_bbox.y0, mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0);
836
837 ctx->pushState();
838
839 TRACE(("BEGIN mask\n"));
840 for (auto const &child : mask->children) {
841 if (auto item = cast<SPItem>(&child)) {
842 renderItem(ctx, item);
843 }
844 }
845 TRACE(("END mask\n"));
846
847 ctx->popState();
848}
849
850void
851calculatePreserveAspectRatio(unsigned int aspect_align, unsigned int aspect_clip, double vp_width, double vp_height,
852 double *x, double *y, double *width, double *height)
853{
854 if (aspect_align == SP_ASPECT_NONE)
855 return;
856
857 double scalex, scaley, scale;
858 double new_width, new_height;
859 scalex = *width / vp_width;
860 scaley = *height / vp_height;
861 scale = (aspect_clip == SP_ASPECT_MEET) ? MIN(scalex, scaley) : MAX(scalex, scaley);
862 new_width = vp_width * scale;
863 new_height = vp_height * scale;
864 /* Now place viewbox to requested position */
865 switch (aspect_align) {
867 break;
869 *x -= 0.5 * (new_width - *width);
870 break;
872 *x -= 1.0 * (new_width - *width);
873 break;
875 *y -= 0.5 * (new_height - *height);
876 break;
878 *x -= 0.5 * (new_width - *width);
879 *y -= 0.5 * (new_height - *height);
880 break;
882 *x -= 1.0 * (new_width - *width);
883 *y -= 0.5 * (new_height - *height);
884 break;
886 *y -= 1.0 * (new_height - *height);
887 break;
889 *x -= 0.5 * (new_width - *width);
890 *y -= 1.0 * (new_height - *height);
891 break;
893 *x -= 1.0 * (new_width - *width);
894 *y -= 1.0 * (new_height - *height);
895 break;
896 default:
897 break;
898 }
899 *width = new_width;
900 *height = new_height;
901}
902
903#include "clear-n_.h"
904
905} /* namespace Internal */
906} /* namespace Extension */
907} /* namespace Inkscape */
908
909#undef TRACE
910
911
912/*
913 Local Variables:
914 mode:c++
915 c-file-style:"stroustrup"
916 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
917 indent-tabs-mode:nil
918 fill-column:99
919 End:
920*/
921// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Cartesian point / 2D vector and related operations.
double scale
Definition aa.cpp:228
Point origin
Definition aa.cpp:227
Declaration of CairoRenderContext, a class used for rendering with Cairo.
Declaration of CairoRenderer, a class used for rendering via a CairoRenderContext.
cairo_operator_t ink_css_blend_to_cairo_operator(SPBlendMode css_blend)
Cairo integration helpers.
uint64_t page
Definition canvas.cpp:171
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
static CRect from_xywh(Coord x, Coord y, Coord w, Coord h)
Create rectangle from origin and dimensions.
C top() const
Return top coordinate of the rectangle (+Y is downwards).
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
CPoint dimensions() const
Get rectangle's width and height as a point.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
Translate inverse() const
Get the inverse translation.
Definition transforms.h:133
void setMetadata(SPDocument const &document)
Extract metadata from the document and store it in the context.
bool renderPathVector(Geom::PathVector const &pathv, SPStyle const *style, Geom::OptRect const &pbox, CairoPaintOrder order=STROKE_OVER_FILL)
bool renderImage(Inkscape::Pixbuf const *pb, Geom::Affine const &image_transform, SPStyle const *style)
bool nextPage(double width, double height, char const *label)
When writing multiple pages, resize the next page.
bool finishPage()
Each page that's made should call finishPage to complete it.
bool setupSurface(double width, double height)
Creates the cairo_surface_t for the context with the given width, height and with the currently set t...
void addClippingRect(double x, double y, double width, double height)
void popLayer(cairo_operator_t composite=CAIRO_OPERATOR_CLEAR)
bool renderPage(CairoRenderContext *ctx, SPDocument *doc, SPPage const *page, bool stretch_to_fit)
void renderItem(CairoRenderContext *ctx, SPItem const *item, SPItem const *origin=nullptr, SPPage const *page=nullptr)
Traverses the object tree and invokes the render methods.
void applyMask(CairoRenderContext *ctx, SPMask const *mask)
bool renderPages(CairoRenderContext *ctx, SPDocument *doc, bool stretch_to_fit)
Handle multiple pages, pushing each out to cairo as needed using renderItem()
void renderHatchPath(CairoRenderContext *ctx, SPHatchPath const &hatchPath, unsigned key)
void applyClipPath(CairoRenderContext *ctx, SPClipPath const *cp)
static bool _shouldRasterize(CairoRenderContext *ctx, SPItem const *item)
Decide whether the given item should be rendered as a bitmap.
bool setupDocument(CairoRenderContext *ctx, SPDocument *doc, SPItem const *base=nullptr)
Initializes the CairoRenderContext according to the specified SPDocument.
static void _doRender(SPItem const *item, CairoRenderContext *ctx, SPItem const *origin=nullptr, SPPage const *page=nullptr)
Render a single item in a fully set up context.
const std::vector< SPPage * > & getPages() const
void showGlyphs(CairoRenderContext *ctx) const
Renders all the glyphs to the given Cairo rendering context.
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:525
std::unique_ptr< Inkscape::URIReference > local_link
Definition sp-anchor.h:41
char * href
Definition sp-anchor.h:25
Geom::OptRect get_last_bbox() const
Definition sp-clippath.h:43
bool clippath_units() const
Definition sp-clippath.h:40
Geom::PathVector const & get_pathvector() const
Definition curve.cpp:52
SVGLength y
SVGLength x
Typed SVG document implementation.
Definition document.h:103
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:202
Geom::Point getDimensions() const
Definition document.cpp:973
Geom::OptRect preferredBounds() const
Definition document.cpp:978
Inkscape::PageManager & getPageManager()
Definition document.h:164
Geom::Scale getDocumentScale(bool computed=true) const
Returns document scale as defined by width/height (in pixels) and viewBox (real world to user-units).
Definition document.cpp:773
Inkscape::Text::Layout layout
Definition sp-flowtext.h:56
SPCurve calculateRenderCurve(unsigned key) const
SVGLength offset
Paint type internal to SPStyle.
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::OptRect documentVisualBounds() const
Get item's visual bbox in document coordinate system.
Definition sp-item.cpp:1025
bool isHidden() const
Definition sp-item.cpp:242
Geom::Affine transform
Definition sp-item.h:138
SPObject * isInClipPath() const
Definition sp-item.cpp:1245
Geom::OptRect geometricBounds(Geom::Affine const &transform=Geom::identity()) const
Get item's geometric bounding box in this item's coordinate system.
Definition sp-item.cpp:927
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1823
bool isFiltered() const
Returns true if the item is filtered, false otherwise.
Definition sp-item.cpp:1233
Geom::OptRect visualBounds(Geom::Affine const &transform=Geom::identity(), bool wfilter=true, bool wclip=true, bool wmask=true) const
Get item's visual bounding box in this item's coordinate system.
Definition sp-item.cpp:932
Geom::OptRect get_last_bbox() const
Definition sp-mask.h:43
bool mask_content_units() const
Definition sp-mask.h:40
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPDocument * document
Definition sp-object.h:188
char const * getId() const
Returns the objects current ID string.
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
virtual void getLinked(std::vector< SPObject * > &objects, LinkedObjectNature direction=LinkedObjectNature::ANY) const
Get objects which are linked to this object as either a source or a target.
unsigned int cloned
Definition sp-object.h:180
ChildrenList children
Definition sp-object.h:907
<svg> element
Definition sp-root.h:33
Base class for shapes, including <path> element.
Definition sp-shape.h:38
SPCurve const * curve() const
Return a borrowed pointer to the curve (if any exists) or NULL if there is no curve.
Definition sp-shape.cpp:970
int hasMarkers() const
Definition sp-shape.cpp:753
std::vector< std::tuple< SPMarkerLoc, SPMarker *, Geom::Affine > > get_markers() const
Lists every marker on this shape along with its transform and marker type.
Definition sp-shape.cpp:247
An SVG style object.
Definition style.h:45
T< SPAttr::FILL, SPIPaint > fill
fill
Definition style.h:240
T< SPAttr::STROKE, SPIPaint > stroke
stroke
Definition style.h:247
T< SPAttr::PAINT_ORDER, SPIPaintOrder > paint_order
Definition style.h:222
T< SPAttr::STROKE_WIDTH, SPILength > stroke_width
stroke-width
Definition style.h:249
T< SPAttr::MIX_BLEND_MODE, SPIEnum< SPBlendMode > > mix_blend_mode
Definition style.h:220
Inkscape::Text::Layout layout
Definition sp-text.h:52
Definition sp-use.h:25
SPItem * child
Definition sp-use.h:33
unsigned int aspect_align
Definition viewbox.h:39
Geom::Rect viewBox
Definition viewbox.h:35
Geom::Affine c2p
Definition viewbox.h:43
unsigned int aspect_clip
Definition viewbox.h:40
bool _set
Definition svg-length.h:41
float computed
Definition svg-length.h:50
A way to clear the N_ macro, which is defined as an inline function.
const double w
Definition conic-4.cpp:19
RootCluster root
Css & result
@ SP_ASPECT_XMAX_YMIN
Definition enums.h:45
@ SP_ASPECT_XMID_YMIN
Definition enums.h:44
@ SP_ASPECT_XMIN_YMID
Definition enums.h:46
@ SP_ASPECT_XMIN_YMAX
Definition enums.h:49
@ SP_ASPECT_XMAX_YMID
Definition enums.h:48
@ SP_ASPECT_XMID_YMAX
Definition enums.h:50
@ SP_ASPECT_NONE
Definition enums.h:42
@ SP_ASPECT_XMIN_YMIN
Definition enums.h:43
@ SP_ASPECT_XMAX_YMAX
Definition enums.h:51
@ SP_ASPECT_XMID_YMID
Definition enums.h:47
@ SP_CONTENT_UNITS_OBJECTBOUNDINGBOX
Definition enums.h:64
@ SP_ASPECT_MEET
Definition enums.h:55
@ SP_ASPECT_SLICE
Definition enums.h:56
static bool has_stroke(SPObject *source)
bool has_hidder_filter(SPObject const *item)
constexpr Coord EPSILON
Default "acceptably small" value.
Definition coord.h:84
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
std::unique_ptr< Magick::Image > image
SPItem * item
Affine identity()
Create an identity matrix.
Definition affine.h:210
static void sp_use_render(SPUse const *use, CairoRenderContext *ctx, SPPage const *page=nullptr)
static void sp_group_render(SPGroup const *group, CairoRenderContext *ctx, SPItem const *origin=nullptr, SPPage const *page=nullptr)
static void sp_flowtext_render(SPFlowtext const *flowtext, CairoRenderContext *ctx)
static void sp_symbol_render(SPSymbol const *symbol, CairoRenderContext *ctx, SPItem const *origin, SPPage const *page)
void calculatePreserveAspectRatio(unsigned int aspect_align, unsigned int aspect_clip, double vp_width, double vp_height, double *x, double *y, double *width, double *height)
static void sp_anchor_render(SPAnchor const *a, CairoRenderContext *ctx, SPItem const *origin, SPPage const *page)
static void sp_shape_render(SPShape const *shape, CairoRenderContext *ctx, SPItem const *origin=nullptr)
static void sp_root_render(SPRoot const *root, CairoRenderContext *ctx)
static void sp_item_invoke_render(SPItem const *item, CairoRenderContext *ctx, SPItem const *origin=nullptr, SPPage const *page=nullptr)
static void sp_image_render(SPImage const *image, CairoRenderContext *ctx)
static Geom::Point compute_final_page_dimensions(Geom::Rect const &page_rect)
Compute the final page dimensions in the resulting PS or PDF.
static void sp_text_render(SPText const *text, CairoRenderContext *ctx)
static void sp_asbitmap_render(SPItem const *item, CairoRenderContext *ctx, SPPage const *page=nullptr)
This function converts the item to a raster image and includes the image into the cairo renderer.
Helper class to stream background task notifications as a series of messages.
static cairo_user_data_key_t key
PathVector - a sequence of subpaths.
Inkscape::Pixbuf * sp_generate_internal_bitmap(SPDocument *document, Geom::Rect const &area, double dpi, std::vector< SPItem const * > items, bool opaque, uint32_t const *checkerboard_color, double device_scale, std::optional< Antialiasing > antialias)
Generates a bitmap from given items.
Ocnode * child[8]
Definition quantize.cpp:33
Axis-aligned rectangle.
TODO: insert short description here.
SVG <hatchPath> implementation.
SVG <image> implementation.
SPItem const * sp_item_first_item_child(SPObject const *obj)
Definition sp-item.cpp:1869
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
TODO: insert short description here.
SPPage – a page object.
TODO: insert short description here.
SPRoot: SVG <svg> implementation.
unsigned need_layer
whether object is masked, clipped, and/or has a non-zero opacity
Geom::Affine item_transform
this item's item->transform, for correct clipping
Definition curve.h:24
@ SP_CSS_BLEND_NORMAL
SPStyle internal: classes that are internal to SPStyle.
@ SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE
@ SP_CSS_PAINT_ORIGIN_CONTEXT_FILL
@ SP_CSS_PAINT_ORDER_STROKE
@ SP_CSS_PAINT_ORDER_MARKER
@ SP_CSS_PAINT_ORDER_FILL
@ SP_CSS_PAINT_ORDER_NORMAL
double height
double width
Affine transformation classes.