Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
build-drawing.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Provide a capypdf interface that understands 2geom, styles, etc.
4 *
5 * Authors:
6 * Martin Owens <doctormo@geek-2.com>
7 *
8 * Copyright (C) 2024 Authors
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include "build-drawing.h"
14
15#include "build-document.h"
16#include "build-page.h"
17#include "build-patterns.h"
18#include "build-text.h"
19#include "object/sp-anchor.h"
20#include "object/sp-flowtext.h"
21#include "object/sp-image.h"
22#include "object/sp-item.h"
23#include "object/sp-marker.h"
24#include "object/sp-mask.h"
25#include "object/sp-page.h"
26#include "object/sp-pattern.h"
27#include "object/sp-root.h"
28#include "object/sp-symbol.h"
29#include "object/sp-text.h"
30#include "object/sp-use.h"
31#include "object/uri.h"
32#include "style.h"
33
35
40{
41 _ctx.set_group_matrix(tr[0], tr[1], tr[2], tr[3], tr[4], tr[5]);
42}
43
48{
49 if (tr != Geom::identity()) {
50 _ctx.cmd_cm(tr[0], tr[1], tr[2], tr[3], tr[4], tr[5]);
51 }
52}
53
55 : DrawContext(doc,
56 doc.generator().new_transparency_group_context(
57 // CapyPDF is very strict about clipping regions being valid. We want to
58 // be more flexible to allow our painting flow to be less repetative
59 // So we check the clip before constructing the new capypdf context.
60 clip ? clip->left() : 0, clip ? clip->top() : 0,
61 clip ? std::max(clip->right(), clip->left() + 0.0001) : 1,
62 clip ? std::max(clip->bottom(), clip->top() + 0.0001) : 1),
63 soft_mask)
64{
65 capypdf::TransparencyGroupProperties props;
66 if (soft_mask) {
67 props.set_CS(CAPY_DEVICE_CS_GRAY);
68 } else {
69 // Do groups have a color space?
70 }
71 props.set_I(true); // Isolate from the document
72 props.set_K(false); // Do not knock out
73 _ctx.set_transparency_group_properties(props);
74}
75
79void DrawContext::paint_item(SPItem const *item, Geom::Affine const &tr, SPStyle const *context_style)
80{
81 // Special exception, return without drawing anything when using LaTeX
82 if (!_doc.get_text_enabled() && (is<SPText>(item) || is<SPFlowtext>(item))) {
83 return;
84 }
85
86 auto style_map = _doc.paint_memory().get_ifset(item->style);
87 auto style_scope = _doc.paint_memory().remember(style_map);
88 auto resolution = item->isFiltered() ? _doc.get_filter_resolution() : 0;
89
90 bool isolate = tr != Geom::identity() || !style_map.empty() || true; // has_pattern || has_opacity etc etc
91 if (isolate) {
92 // Isolate everything in the item
93 _ctx.cmd_q();
94
95 if (!resolution) {
96 transform(tr);
97 // Set styles for cascading
98 set_paint_style(style_map, item->style, context_style);
99 }
100
101 // This text is not affected by the get_text_enabled option.
102 if (auto text_clip = item->getClipTextObject()) {
103 clip_text_layout(text_clip->layout);
104 } else {
106 }
107 }
108
109 // These styles are never cascaded because of the complexity in PDF transparency groups.
110 if (!resolution && !is<SPGroup>(item) && !_soft_mask) {
111 if (auto gsid = _doc.get_shape_graphics_state(item->style)) {
112 _ctx.cmd_gs(*gsid);
113 }
114 }
115
116 if (resolution) {
117 // Turn the item into a raster for the PDF
118 paint_item_to_raster(item, tr, resolution, true);
119 } else if (auto shape = cast<SPShape>(item)) {
120 if (shape->curve() && !shape->curve()->empty()) {
121 paint_shape(shape, context_style);
122 }
123 } else if (auto use = cast<SPUse>(item)) {
124 paint_item_clone(use, context_style);
125 } else if (auto text = cast<SPText>(item)) {
126 paint_text_layout(text->layout, context_style);
127 } else if (auto flowtext = cast<SPFlowtext>(item)) {
128 // sp_flowtext_render(flowtext);
129 } else if (auto image = cast<SPImage>(item)) {
131 } else if (auto group = cast<SPGroup>(item)) { // SPSymbol, SPRoot, SPMarker
132
133 // Optional Content Group tracks layers
134 bool has_ocg = false;
135 if (group->isLayer()) {
136 if (auto label = group->label()) {
137 auto ocg = capypdf::OptionalContentGroup(label);
138 start_ocg(_doc._gen.add_optional_content_group(ocg));
139 has_ocg = true;
140 }
141 }
142
143 paint_item_group(group, context_style);
144
145 if (has_ocg) {
146 end_ocg();
147 }
148 } else {
149 g_warning("Unknown object: %s", get_id(item).c_str());
150 }
151 if (isolate) {
152 _ctx.cmd_Q();
153 }
154}
155
156void DrawContext::paint_item_group(SPGroup const *group, SPStyle const *context_style)
157{
158 // Render children in the group
159 for (auto &obj : group->children) {
160 if (auto child_item = cast<SPItem>(&obj)) {
161 // Calculate a soft mask
162 // const cast because mask references are not created and tracked properly.
163 std::optional<CapyPDF_TransparencyGroupId> mask_id;
164 if (auto ref = const_cast<SPItem *>(child_item)->getMaskRef().getObject()) {
165 mask_id = _doc.mask_to_transparency_group(ref, child_item->transform);
166 }
167
168 // Find out if this object is a source for a clone
169 std::vector<SPObject *> links;
170 child_item->getLinkedRecursive(links, SPObject::LinkedObjectNature::DEPENDENT);
171
172 // Try not creating groups for *every* shape if they don't need them.
173 if (!is<SPGroup>(child_item) && !mask_id && links.empty() && !style_needs_group(child_item->style)) {
174 // Paint the child-shape directly
175 paint_item(child_item, child_item->transform, context_style);
176
177 } else if (auto item_id = _doc.item_to_transparency_group(child_item, context_style)) {
178 // Each reused transparency group has to re-specify it's transform and opacity settings
179 // since PDF applies properties from the outside of the group being drawn.
180 paint_group(*item_id, child_item->style, Geom::identity(), mask_id);
181 }
182 }
183 }
184}
185
189void DrawContext::paint_item_clone(SPUse const *use, SPStyle const *context_style)
190{
191 // Children contains a copy of the clone with the right context style
192 // if (auto child_item = use->get_original()) {
193 for (auto &child_obj : use->children) {
194 if (auto child_item = cast<SPItem>(&child_obj)) {
195 // Consume the SPUse object as the context style
196 if (auto item_id = _doc.item_to_transparency_group(child_item, use->style)) {
197 paint_group(*item_id, child_item->style, Geom::Translate(use->x.computed, use->y.computed));
198 } else {
199 g_warning("Couldn't paint clone: '%s'", get_id(use).c_str());
200 }
201 }
202 }
203}
204
208void DrawContext::paint_group(CapyPDF_TransparencyGroupId child_id, SPStyle const *style, Geom::Affine const &tr,
209 std::optional<CapyPDF_TransparencyGroupId> soft_mask)
210{
211 auto gsid = _doc.get_group_graphics_state(style, soft_mask);
212
213 if (gsid || tr != Geom::identity()) {
214 _ctx.cmd_q();
215 }
216
217 transform(tr);
218 if (gsid) {
219 _ctx.cmd_gs(*gsid);
220 }
221
222 _ctx.cmd_Do(child_id);
223
224 if (gsid || tr != Geom::identity()) {
225 _ctx.cmd_Q();
226 }
227}
228
234void DrawContext::paint_shape(SPShape const *shape, SPStyle const *context_style)
235{
236 auto const style = shape->style;
237
238 bool evenodd = style->fill_rule.computed == SP_WIND_RULE_EVENODD;
239 for (auto layer : get_paint_layers(style, context_style)) {
240 switch (layer) {
241 case PAINT_FILLSTROKE:
242 if (set_shape(shape)) {
243 if (evenodd) {
244 _ctx.cmd_bstar();
245 } else {
246 _ctx.cmd_b();
247 }
248 } else { // Not closed path
249 if (evenodd) {
250 _ctx.cmd_Bstar();
251 } else {
252 _ctx.cmd_B();
253 }
254 }
255 break;
256 case PAINT_FILL:
257 // Fill only without stroke, either because it's only fill, or not in order
258 set_shape(shape);
259
260 if (evenodd) {
261 _ctx.cmd_fstar();
262 } else {
263 _ctx.cmd_f();
264 }
265 break;
266 case PAINT_STROKE:
267 // Stroke only without fill, either because it's only stroke, or not in order
268 if (set_shape(shape)) {
269 _ctx.cmd_s();
270 } else { // Not closed path
271 _ctx.cmd_S();
272 }
273 break;
274 case PAINT_MARKERS:
275 // Markers can still be visible is no_stroke is true
276 for (auto [loc, marker, tr] : shape->get_markers()) {
277 // Isolate each marker render
278 if (auto item_id = _doc.item_to_transparency_group(marker, style, _soft_mask)) {
279 // We don't pass on the style at this stage
280 paint_group(*item_id, nullptr, tr);
281 }
282 }
283 break;
284 }
285 }
286}
287
291void DrawContext::set_clip_path(std::optional<Geom::PathVector> clip, SPStyle *style)
292{
293 if (clip) {
295 // Default to NONZERO when style is nullptr.
296 if (style && style->clip_rule.computed == SP_WIND_RULE_EVENODD) {
297 _ctx.cmd_W();
298 } else {
299 _ctx.cmd_Wstar();
300 }
301 _ctx.cmd_n();
302 }
303}
304
309{
310 if (rect) {
312 }
313}
314
315void DrawContext::start_ocg(CapyPDF_OptionalContentGroupId ocgid)
316{
317 _ctx.cmd_BDC(ocgid);
318}
319
321{
322 _ctx.cmd_EMC();
323}
324
326 : GroupContext(doc, item ? item->visualBounds(Geom::identity(), true, false, true) : Geom::OptRect(), false)
327 , _item{item}
328{}
329
331{
332 return !_item->isHidden();
333}
334
336{
337 return {get_id(_item), "", ""};
338}
339
341{
343}
344
345} // namespace Inkscape::Extension::Internal::PdfBuilder
static SPStyleProp const props[]
Lookup dictionary for attributes/properties.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
Sequence of contiguous curves, aka spline.
Definition path.h:353
Translation by a vector.
Definition transforms.h:115
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::optional< CapyPDF_GraphicsStateId > get_group_graphics_state(SPStyle const *style, std::optional< CapyPDF_TransparencyGroupId > sm)
Set the style for any graphic from the SVG style.
std::optional< CapyPDF_GraphicsStateId > get_shape_graphics_state(SPStyle const *style)
Like get_graphics_style but for drawing shapes (paths)
std::optional< CapyPDF_TransparencyGroupId > mask_to_transparency_group(SPMask const *mask, Geom::Affine const &transform)
Render a mask out to a transparency group context.
void paint_group(CapyPDF_TransparencyGroupId child_id, SPStyle const *style=nullptr, Geom::Affine const &tr=Geom::identity(), std::optional< CapyPDF_TransparencyGroupId > soft_mask={})
Paint a child group at the requested location.
void paint_item_to_raster(SPItem const *item, Geom::Affine const &tr, double resolution, bool antialias)
Draw an item as a bitmap and return.
void set_matrix(Geom::Affine const &affine)
Set the transformation matrix for this context Group.
void paint_raster(SPImage const *image)
Draw the raster data stored in URI into the PDF context.
void paint_item_clone(SPUse const *use, SPStyle const *context_style)
Paint the given clone object, finding or generating a transparency group from it.
void clip_text_layout(Text::Layout const &layout)
Use the text object as a clipping mask in the PDF.
void start_ocg(CapyPDF_OptionalContentGroupId ocgid)
void set_clip_rectangle(Geom::OptRect const &rect)
Apply the clipping rectangle with a NONZERO fill rule.
bool set_shape_pathvector(Geom::PathVector const &pathv)
void set_clip_path(std::optional< Geom::PathVector > clip, SPStyle *style=nullptr)
Apply the clip path to the existing context.
void paint_item(SPItem const *item, Geom::Affine const &tr=Geom::identity(), SPStyle const *context_style=nullptr)
Paint the given object into the given context, making groups if needed.
void paint_shape(SPShape const *shape, SPStyle const *context_style)
Paint a single shape path.
void set_paint_style(StyleMap const &map, SPStyle const *style, SPStyle const *context_style)
Set the style for drawing shapes from the SVG style, this is all the styles that relate to how vector...
void paint_text_layout(Text::Layout const &layout, SPStyle const *context_style)
Paint the given layout into the PDF document Drawing content.
void transform(Geom::Affine const &affine)
Add a transform to the current context stream.
void paint_item_group(SPGroup const *group, SPStyle const *context_style)
GroupContext(Document &doc, Geom::OptRect const &clip, bool soft_mask=false)
StyleMap get_ifset(SPStyle const *style) const
Return a StyleMap of all the set styles, filtered in the same way as get_changes.
auto remember(StyleMap map)
Add the given map to the stack and return a scoped object.
SVGLength y
SVGLength x
Base class for visual SVG elements.
Definition sp-item.h:109
SPMaskReference & getMaskRef()
Definition sp-item.cpp:182
bool isHidden() const
Definition sp-item.cpp:242
SPText const * getClipTextObject() const
Return the text object, IF and only if this item is clipped by a single SPText object.
Definition sp-item.cpp:126
bool isFiltered() const
Returns true if the item is filtered, false otherwise.
Definition sp-item.cpp:1234
std::optional< Geom::PathVector > getClipPathVector() const
Return the path vector of the clipping region.
Definition sp-item.cpp:112
SPMask * getObject() const
Definition sp-mask.h:83
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
ChildrenList children
Definition sp-object.h:907
Base class for shapes, including <path> element.
Definition sp-shape.h:38
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:246
An SVG style object.
Definition style.h:45
T< SPAttr::CLIP_RULE, SPIEnum< SPWindRule > > clip_rule
clip-rule: 0 nonzero, 1 evenodd
Definition style.h:204
T< SPAttr::FILL_RULE, SPIEnum< SPWindRule > > fill_rule
fill-rule: 0 nonzero, 1 evenodd
Definition style.h:244
Definition sp-use.h:25
float computed
Definition svg-length.h:50
std::unique_ptr< Magick::Image > image
SPItem * item
Glib::ustring label
Various utility functions.
Definition affine.h:22
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.
std::vector< PaintLayer > get_paint_layers(SPStyle const *style, SPStyle const *context_style)
Get a PDF specific layer painting pattern for fill, stroke and markers.
bool style_needs_group(SPStyle const *style)
Return true if this shape's style requires a PDF transparency group.
STL namespace.
static T clip(T const &v, T const &a, T const &b)
Ocnode ** ref
Definition quantize.cpp:32
TODO: insert short description here.
SVG <image> implementation.
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
SPPage – a page object.
SVG <pattern> implementation.
SPRoot: SVG <svg> implementation.
@ SP_WIND_RULE_EVENODD
Definition style-enums.h:26
SPStyle - a style object for SPItem objects.