Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
build-document.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Build a capypdf document.
4 *
5 * Authors:
6 * Martin Owens <doctormo@geek-2.com>
7 *
8 * Copyright (C) 2024-2025 Authors
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include "build-document.h"
14
15#include "build-drawing.h"
16#include "build-page.h"
17#include "build-text.h"
18#include "object/sp-anchor.h"
19#include "object/sp-flowtext.h"
20#include "object/sp-image.h"
21#include "object/sp-item.h"
22#include "object/sp-marker.h"
23#include "object/sp-mask.h"
24#include "object/sp-page.h"
25#include "object/sp-root.h"
26#include "object/sp-symbol.h"
27#include "object/sp-text.h"
28#include "object/sp-use.h"
29#include "style.h"
30
32
36std::string get_id(SPObject const *obj)
37{
38 if (auto id = obj->getId()) {
39 return id;
40 }
41 if (auto repr_id = obj->getRepr()->attribute("id")) {
42 return repr_id;
43 }
44 g_error("Object doesn't have any sort of id.");
45}
46
47void Document::set_label(uint32_t page, std::string const &label)
48{
49 _gen.add_page_labeling(page, {}, label, {});
50}
51
53{
54 page.finalize();
55 _gen.add_page(page._ctx);
56}
57
58std::optional<CapyPDF_TransparencyGroupId> Document::add_group(ItemContext &group_ctx)
59{
60 if (!group_ctx.is_valid()) {
61 return {};
62 }
63 auto const cache_key = group_ctx.cache_key();
64
65 if (auto const it = _item_cache.find(cache_key); it != _item_cache.end()) {
66 return it->second;
67 }
68
69 group_ctx.paint();
70
71 auto const item_id = _gen.add_transparency_group(group_ctx._ctx);
72 _item_cache[cache_key] = item_id;
73
74 return item_id;
75}
76
84std::optional<CapyPDF_TransparencyGroupId>
85Document::item_to_transparency_group(SPItem const *item, SPStyle const *context_style, bool is_soft_mask)
86{
87 if (item->isHidden()) {
88 return {};
89 }
90
91 // Items are cached so they can be reused
92 ItemCacheKey cache_key = {get_id(item), "", ""};
93 auto tr = item->transform;
94
95 // Complex caching key modification for when marker styles changes because of context styles
96 if (context_style) {
97 bool fill_used = false, stroke_used = false;
98 get_context_use_recursive(item, fill_used, stroke_used);
99 if (fill_used) {
100 std::optional<double> opacity;
101 if (is_soft_mask) {
102 opacity = context_style->fill_opacity;
103 }
104 cache_key = {std::get<0>(cache_key), paint_to_cache_key(context_style->fill, opacity), std::get<2>(cache_key)};
105 }
106 if (stroke_used) {
107 std::optional<double> opacity;
108 if (is_soft_mask) {
109 opacity = context_style->stroke_opacity;
110 }
111 cache_key = {std::get<0>(cache_key), std::get<1>(cache_key), paint_to_cache_key(context_style->stroke, opacity)};
112 }
113 }
114 if (auto marker = cast<SPMarker>(item)) {
115 tr = marker->c2p * tr;
116 }
117
118 if (auto const it = _item_cache.find(cache_key); it != _item_cache.end()) {
119 return it->second;
120 }
121
122 // Groups require pre-defined clipping regions which must not be transformed
123 auto bbox = item->visualBounds(Geom::identity(), true, false, true);
124 if (!bbox || bbox->width() == 0 || bbox->height() == 0) {
125 return {};
126 }
127
128 // Remember all anchors for later post-processing
129 if (auto anchor = cast<SPAnchor>(item)) {
130 _anchors.insert(anchor);
131 }
132
133 // Draw item on a group so a mask, blend-mode, used-by-clone or opacity can be applied to it globally.
134 auto group_ctx = PdfBuilder::GroupContext(*this, *bbox, is_soft_mask);
135 group_ctx.set_matrix(tr);
136 group_ctx.paint_item(item, Geom::identity(), context_style);
137
138 // We save the group_ctx id so it can be painted in any other contexts (symbols, clones, markers, etc)
139 auto const item_id = _gen.add_transparency_group(group_ctx._ctx);
140 _item_cache[cache_key] = item_id;
141 return item_id;
142}
143
147std::optional<CapyPDF_TransparencyGroupId> Document::mask_to_transparency_group(SPMask const *mask,
148 Geom::Affine const &transform)
149{
150 // Note: This would normally run through item_to_transparency_group, but SPMask isn't an SPItem
151
152 // Items are cached so they can be reused
153 std::string cache_key = get_id(mask);
154
155 if (auto const it = _mask_cache.find(cache_key); it != _mask_cache.end()) {
156 return it->second;
157 }
158
159 auto bbox = mask->visualBounds(transform);
160 if (!bbox) {
161 return {};
162 }
163
164 auto group_ctx = PdfBuilder::GroupContext(*this, *bbox);
165 group_ctx.transform(transform);
166
167 for (auto &obj : mask->children) {
168 if (auto child_item = cast<SPItem>(&obj)) {
169 if (auto item_id = item_to_transparency_group(child_item)) {
170 group_ctx.paint_group(*item_id, child_item->style);
171 }
172 }
173 }
174
175 auto const item_id = _gen.add_transparency_group(group_ctx._ctx);
176 _mask_cache[cache_key] = item_id;
177 return item_id;
178}
179
183std::optional<CapyPDF_TransparencyGroupId> Document::style_to_transparency_mask(SPStyle const *style,
184 SPStyle const *context_style)
185{
186 Geom::OptRect bbox;
187 std::vector<SPObject *> objects;
188 if (style->fill.set && style->fill.href) {
189 if (auto gradient = cast<SPGradient>(style->fill.href->getObject())) {
190 if (gradient_has_transparency(gradient)) {
191 gradient->getLinkedRecursive(objects, SPObject::LinkedObjectNature::DEPENDENT);
192 bbox.unionWith(gradient->getAllItemsBox());
193 }
194 }
195 }
196 if (style->stroke.set && style->stroke.href) {
197 if (auto gradient = cast<SPGradient>(style->stroke.href->getObject())) {
198 if (gradient_has_transparency(gradient)) {
199 gradient->getLinkedRecursive(objects, SPObject::LinkedObjectNature::DEPENDENT);
200 bbox.unionWith(gradient->getAllItemsBox());
201 }
202 }
203 }
204
205 if (!objects.empty() && bbox) {
206 auto gradient_mask = PdfBuilder::GroupContext(*this, *bbox, true);
207 bool painted = false;
208 for (auto obj : objects) {
209 if (auto item = cast<SPItem>(obj)) {
210 auto style_map = _paint_memory.get_ifset(item->style);
211 gradient_mask.set_paint_style(style_map, item->style, context_style);
212 gradient_mask.paint_item(item, Geom::identity(), context_style);
213 painted = true;
214 }
215 }
216 if (painted) {
217 return _gen.add_transparency_group(gradient_mask._ctx);
218 }
219 }
220 return {};
221}
222
229std::vector<CapyPDF_AnnotationId> Document::get_anchors_for_page(SPPage const *page)
230{
231 auto page_tr = PageContext::page_transform(page);
232 std::vector<CapyPDF_AnnotationId> result;
233 for (auto a : _anchors) {
234 auto bbox = a->visualBounds(a->i2doc_affine() * page_tr, true, false, true);
235 if (!bbox || !a->href || !page->itemOnPage(a)) {
236 continue;
237 }
238
239 auto annot = capypdf::Annotation();
240 annot.set_rectangle(bbox->left(), bbox->bottom(), bbox->right(), bbox->top());
241 annot.set_flags(CAPY_ANNOTATION_FLAG_HIDDEN);
242
243 if (a->local_link) {
244 auto obj = a->local_link->getObject();
245 auto dest = capypdf::Destination();
246 if (auto target_page = cast<SPPage>(obj)) {
247 dest.set_page_fit(target_page->getPageIndex());
248 annot.set_destination(dest);
249 } else if (auto item = cast<SPItem>(obj)) {
250 auto target_page = item->document->getPageManager().getPageFor(item, false);
251 auto target_tr = PageContext::page_transform(target_page);
252 auto item_box = item->visualBounds(item->i2doc_affine() * target_tr);
253 dest.set_page_xyz(target_page->getPageIndex(), {}, item_box->bottom(), {});
254 annot.set_destination(dest);
255 } else {
256 // This happens because of an Inkscape bug elsewhere in the code.
257 annot.set_uri(std::string(a->href));
258 }
259 } else {
260 // This pathway is currently not working because of the above bug
261 annot.set_uri(std::string(a->href));
262 }
263 result.push_back(_gen.add_annotation(annot));
264 }
265 return result;
266}
267
268} // namespace Inkscape::Extension::Internal::PdfBuilder
uint64_t page
Definition canvas.cpp:171
3x3 matrix representing an affine transformation.
Definition affine.h:70
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
std::optional< CapyPDF_TransparencyGroupId > item_to_transparency_group(SPItem const *item, SPStyle const *context_style=nullptr, bool is_soft_mask=false)
Render any type of item into a transparency group.
std::map< std::string, CapyPDF_TransparencyGroupId > _mask_cache
std::map< ItemCacheKey, CapyPDF_TransparencyGroupId > _item_cache
std::optional< CapyPDF_TransparencyGroupId > style_to_transparency_mask(SPStyle const *style, SPStyle const *context_style)
Render gradient transparencies into a transparency mask.
std::vector< CapyPDF_AnnotationId > get_anchors_for_page(SPPage const *page)
Load an anchor link and add it to the page.
std::optional< CapyPDF_TransparencyGroupId > mask_to_transparency_group(SPMask const *mask, Geom::Affine const &transform)
Render a mask out to a transparency group context.
std::optional< CapyPDF_TransparencyGroupId > add_group(ItemContext &context)
void set_label(uint32_t page, std::string const &label)
static Geom::Affine page_transform(SPPage const *page)
Get the transformation for the given page.
StyleMap get_ifset(SPStyle const *style) const
Return a StyleMap of all the set styles, filtered in the same way as get_changes.
SPPage * getPageFor(SPItem *item, bool contains) const
Return the first page that contains the given item.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
Inkscape::PageManager & getPageManager()
Definition document.h:162
Base class for visual SVG elements.
Definition sp-item.h:109
bool isHidden() const
Definition sp-item.cpp:242
Geom::Affine transform
Definition sp-item.h:138
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1824
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:924
Geom::OptRect visualBounds(Geom::Affine const &transform) const
Definition sp-mask.cpp:116
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
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
ChildrenList children
Definition sp-object.h:907
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::FILL_OPACITY, SPIScale24 > fill_opacity
fill-opacity
Definition style.h:242
T< SPAttr::STROKE_OPACITY, SPIScale24 > stroke_opacity
stroke-opacity
Definition style.h:261
Css & result
SPItem * item
Glib::ustring label
Affine identity()
Create an identity matrix.
Definition affine.h:210
std::tuple< std::string, std::string, std::string > ItemCacheKey
std::string get_id(SPObject const *obj)
Attempt to get an object's id, even if it's a clone.
bool gradient_has_transparency(SPPaintServer const *paint)
Returns true if the gradient has transparency.
std::string paint_to_cache_key(SPIPaint const &paint, std::optional< double > opacity)
Turn a paint into a string for use in caching keys.
void get_context_use_recursive(SPItem const *item, bool &fill, bool &stroke)
Find out if any of the item, or its decendents use context-fill and context-stroke.
TODO: insert short description here.
SVG <image> implementation.
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
SPPage – a page object.
SPRoot: SVG <svg> implementation.
SPStyle - a style object for SPItem objects.