Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
build-styles.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#include "colors/cms/profile.h"
15#include "colors/color.h"
16#include "colors/spaces/cms.h"
17#include "colors/spaces/cmyk.h"
19#include "style.h"
20
22
26CapyPDF_Blend_Mode get_blendmode(SPBlendMode mode)
27{
28 switch (mode) {
30 return CAPY_BM_MULTIPLY;
32 return CAPY_BM_SCREEN;
34 return CAPY_BM_DARKEN;
36 return CAPY_BM_LIGHTEN;
38 return CAPY_BM_OVERLAY;
40 return CAPY_BM_COLORDODGE;
42 return CAPY_BM_COLORBURN;
44 return CAPY_BM_HARDLIGHT;
46 return CAPY_BM_SOFTLIGHT;
48 return CAPY_BM_DIFFERENCE;
50 return CAPY_BM_EXCLUSION;
52 return CAPY_BM_HUE;
54 return CAPY_BM_SATURATION;
56 return CAPY_BM_COLOR;
58 return CAPY_BM_LUMINOSITY;
59 default:
60 break;
61 }
62 return CAPY_BM_NORMAL;
63}
64
66{
67 switch (mode) {
69 return CAPY_LC_PROJECTION;
71 return CAPY_LC_ROUND;
73 default:
74 break;
75 }
76 return CAPY_LC_BUTT;
77}
78
80{
81 switch (mode) {
83 return CAPY_LJ_ROUND;
85 return CAPY_LJ_BEVEL;
87 default:
88 break;
89 }
90 return CAPY_LJ_MITER;
91}
92
97{
98 if (style->fill.set && style->fill.href) {
99 if (gradient_has_transparency(style->fill.href->getObject())) {
100 return true;
101 }
102 }
103 if (style->stroke.set && style->stroke.href) {
104 if (gradient_has_transparency(style->stroke.href->getObject())) {
105 return true;
106 }
107 }
108 return false;
109}
110
114std::vector<PaintLayer> get_paint_layers(SPStyle const *style, SPStyle const *context_style)
115{
116 std::vector<PaintLayer> output;
117
118 // If context paint is used outside of a marker or clone, we do not output them if not context_style is provided.
119 auto context_paint_is_none = [context_style](SPIPaint const &paint) {
120 return (paint.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL && (!context_style || context_style->fill.isNone())) ||
121 (paint.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE && (!context_style || context_style->stroke.isNone()));
122 };
123 bool no_fill = style->fill.isNone() || style->fill_opacity.value < 1e-9 || context_paint_is_none(style->fill);
124 bool no_stroke = style->stroke.isNone() ||
125 (!style->stroke_extensions.hairline && style->stroke_width.computed < 1e-9) ||
126 style->stroke_opacity.value == 0 ||
127 context_paint_is_none(style->stroke);
128
129 if (no_fill && no_stroke) {
130 return output;
131 }
132
133 auto layers = style->paint_order.get_layers();
134
135 for (auto i = 0; i < 3; i++) {
136 auto layer = layers[i];
137 /* PDF's FillStroke paint operator is Atomic, not two operations like it is in SVG:
138
139 https://github.com/pdf-association/pdf-differences/tree/main/Atomic-Fill%2BStroke
140
141 auto next = i < 2 ? layers[i + 1] : SP_CSS_PAINT_ORDER_NORMAL;
142 if (layer == SP_CSS_PAINT_ORDER_FILL && next == SP_CSS_PAINT_ORDER_STROKE && !no_fill && !no_stroke) {
143 output.push_back(PAINT_FILLSTROKE);
144 i++; // Stroke is already done, skip it.
145 } else */
146 if (layer == SP_CSS_PAINT_ORDER_FILL && !no_fill) {
147 output.push_back(PAINT_FILL);
148 } else if (layer == SP_CSS_PAINT_ORDER_STROKE && !no_stroke) {
149 output.push_back(PAINT_STROKE);
150 } else if (layer == SP_CSS_PAINT_ORDER_MARKER) {
151 output.push_back(PAINT_MARKERS);
152 }
153 }
154 return output;
155}
156
160bool style_needs_group(SPStyle const *style)
161{
162 // These things are in the graphics-state, plus gradients and pattern use.
163 return style->opacity < 1.0 || get_blendmode(style->mix_blend_mode.value) ||
164 (style->fill.set && style->fill.href && style->fill.href->getObject()) ||
165 (style->stroke.set && style->stroke.href && style->stroke.href->getObject());
166}
167
171std::string paint_to_cache_key(SPIPaint const &paint, std::optional<double> opacity)
172{
173 // We don't use SPIPaint::get_value because we need a value from the inherited style.
174 if (paint.isNone()) {
175 return "none";
176 }
177 if (opacity) {
178 return std::to_string(*opacity);
179 } else if (paint.isColor()) {
180 return paint.getColor().toString();
181 }
182 if (paint.isPaintserver()) {
183 return paint.href->getObject()->getId();
184 }
185 return "";
186}
187
192{
193 // Both styles must be checked for both values; four total
198 if (fill && stroke) {
199 return;
200 }
201 for (auto &obj : item->children) {
202 if (auto child_item = cast<SPItem>(&obj)) {
204 if (fill && stroke) {
205 return;
206 }
207 }
208 }
209}
210
218std::optional<CapyPDF_GraphicsStateId>
219Document::get_group_graphics_state(SPStyle const *style, std::optional<CapyPDF_TransparencyGroupId> soft_mask)
220{
221 if (!style) {
222 return {};
223 }
224
225 auto gstate = capypdf::GraphicsState();
226 bool gs_used = false;
227
228 if (soft_mask) {
229 auto smask = capypdf::SoftMask(CAPY_SOFT_MASK_LUMINOSITY, *soft_mask);
230 gstate.set_SMask(_gen.add_soft_mask(smask));
231 gs_used = true;
232 }
233 if (style->mix_blend_mode.set) {
234 gstate.set_BM(get_blendmode(style->mix_blend_mode.value));
235 gs_used = true;
236 }
237 if (style->opacity < 1.0) {
238 gstate.set_ca(style->opacity);
239 gs_used = true;
240 }
241 if (gs_used) {
242 return _gen.add_graphics_state(gstate);
243 }
244
245 return {};
246}
247
255std::optional<CapyPDF_GraphicsStateId> Document::get_shape_graphics_state(SPStyle const *style)
256{
257 // PDF allows a lot more to exist in the graphics state, but capypdf does not allow them
258 // to be added into the gs and instead they get added directly to the draw context obj.
259 auto gstate = capypdf::GraphicsState();
260 bool gs_used = false;
261
262 if (auto soft_mask = style_to_transparency_mask(style, nullptr)) {
263 auto smask = capypdf::SoftMask(CAPY_SOFT_MASK_LUMINOSITY, *soft_mask);
264 gstate.set_SMask(_gen.add_soft_mask(smask));
265 gs_used = true;
266 } else { // The draw opacities can not be set at the same time as a soft mask
267 if (style->fill_opacity < 1.0) {
268 gstate.set_ca(style->fill_opacity);
269 gs_used = true;
270 }
271 if (style->stroke_opacity < 1.0) {
272 gstate.set_CA(style->stroke_opacity);
273 gs_used = true;
274 }
275 }
276 if (gs_used) {
277 return _gen.add_graphics_state(gstate);
278 }
279 return {};
280}
281
289std::optional<CapyPDF_FontId> Document::get_font(std::string const &filename)
290{
291 // TODO: It's possible for the font loading to fail but we don't know how yet.
292 if (!_font_cache.contains(filename)) {
293 try {
294 _font_cache[filename] = _gen.load_font(filename.c_str());
295 } catch (std::exception const &err) {
296 std::cerr << "Can't load font: '" << filename.c_str() << "'\n";
297 return {};
298 }
299 }
300 return _font_cache[filename];
301}
302
306std::optional<capypdf::Color> Document::get_paint(SPIPaint const &paint, SPStyle const *context_style,
307 std::optional<double> opacity)
308{
309 if (context_style) {
311 return get_paint(context_style->fill, nullptr, opacity);
313 return get_paint(context_style->stroke, nullptr, opacity);
314 }
315 }
316
317 if (paint.isNone()) {
318 return {};
319 }
320
321 if (paint.isColor()) {
322 return get_color(paint.getColor(), opacity);
323 }
324
325 capypdf::Color out;
326 if (paint.isPaintserver()) {
327 if (auto pattern_id = get_pattern(paint.href ? paint.href->getObject() : nullptr, opacity)) {
328 out.set_pattern(*pattern_id);
329 } else {
330 g_warning("Couldn't generate pattern for fill '%s'", paint.get_value().c_str());
331 }
332 } else if (!context_style && (paint.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL ||
334 g_warning("Context style requested but no context style available.");
335 out.set_rgb(0, 0, 0);
336 } else {
337 g_warning("Fill style not supported: '%s'", paint.get_value().c_str());
338 out.set_rgb(0, 0, 0); // Black default on error
339 }
340 return out;
341}
342
343capypdf::Color Document::get_color(Colors::Color const &color, std::optional<double> opacity)
344{
345 auto space = color.getSpace();
346
347 capypdf::Color out;
348 if (opacity) {
349 out.set_gray(*opacity * color.getOpacity());
350 } else if (auto cmyk = std::dynamic_pointer_cast<Colors::Space::DeviceCMYK>(space)) {
351 out.set_cmyk(color[0], color[1], color[2], color[3]);
352 } else if (auto cms = std::dynamic_pointer_cast<Colors::Space::CMS>(space)) {
353 if (auto icc_id = get_icc_profile(cms)) {
354 auto vals = color.getValues();
355 out.set_icc(*icc_id, vals.data(), vals.size());
356 } else {
357 g_warning("Couldn't set icc color, icc profile didn't load.");
358 }
359 } else if (auto rgb = color.converted(Colors::Space::Type::RGB)) {
360 out.set_rgb(rgb->get(0), rgb->get(1), rgb->get(2));
361 } else {
362 g_warning("Problem outputting color '%s' to PDF.", color.toString().c_str());
363 out.set_rgb(0, 0, 0); // Black default on error
364 }
365 return out;
366}
367
368std::optional<CapyPDF_IccColorSpaceId> Document::get_icc_profile(std::shared_ptr<Colors::Space::CMS> const &profile)
369{
370 auto key = profile->getName();
371 if (auto it = _icc_cache.find(key); it != _icc_cache.end()) {
372 return it->second;
373 }
374
375 if (auto cms_profile = profile->getProfile()) {
376 auto channels = profile->getComponentCount();
377 auto vec = cms_profile->dumpData();
378 CapyPDF_IccColorSpaceId id =
379 _gen.add_icc_profile(reinterpret_cast<char const *>(vec.data()), vec.size(), channels);
380 _icc_cache[key] = id;
381 return id;
382 }
383 return {};
384}
385
386CapyPDF_Device_Colorspace Document::get_default_colorspace() const
387{
388 // TODO: Make this return the correct color space (icc, etc) for the document
389 return CAPY_DEVICE_CS_RGB;
390}
391
392CapyPDF_Device_Colorspace Document::get_colorspace(std::shared_ptr<Colors::Space::AnySpace> const &space) const
393{
394 if (std::dynamic_pointer_cast<Colors::Space::DeviceCMYK>(space)) {
395 return CAPY_DEVICE_CS_CMYK;
396 } else if (std::dynamic_pointer_cast<Colors::Space::RGB>(space)) {
397 return CAPY_DEVICE_CS_RGB;
398 } else if (auto cms = std::dynamic_pointer_cast<Colors::Space::CMS>(space)) {
399 // TODO: Support icc profiles here, which are missing from capypdf atm
400 g_warning("ICC profile color space expressed as device color space!");
401 switch (cms->getType()) {
403 return CAPY_DEVICE_CS_RGB;
405 return CAPY_DEVICE_CS_CMYK;
406 default:
407 break;
408 }
409 // Return IccColorSpaceId here, somehow.
410 }
411 return CAPY_DEVICE_CS_RGB;
412}
413
414// Because soft masks negate the use of draw opacities, we must fold them in.
415std::optional<double> DrawContext::get_softmask(double opacity) const
416{
417 if (_soft_mask) {
418 return opacity;
419 }
420 return {};
421}
422
431void DrawContext::set_paint_style(StyleMap const &map, SPStyle const *style, SPStyle const *context_style)
432{
433 // NOTE: We might find out that fill_opacity.set is important for style cascading
434 if (map.contains(SPAttr::FILL)) {
435 if (auto color = _doc.get_paint(style->fill, context_style, get_softmask(style->fill_opacity))) {
436 _ctx.set_nonstroke(*color);
437 }
438 }
439 if (map.contains(SPAttr::STROKE)) {
440 if (auto color = _doc.get_paint(style->stroke, context_style, get_softmask(style->stroke_opacity))) {
441 _ctx.set_stroke(*color);
442 }
443 }
444 if (map.contains(SPAttr::STROKE_WIDTH)) {
445 // TODO: if (style->stroke_extensions.hairline) {
446 // ink_cairo_set_hairline(_cr);
447 _ctx.cmd_w(style->stroke_width.computed);
448 }
449 if (map.contains(SPAttr::STROKE_MITERLIMIT)) {
450 _ctx.cmd_M(style->stroke_miterlimit.value);
451 }
452 if (map.contains(SPAttr::STROKE_LINECAP)) {
453 _ctx.cmd_J(get_linecap(style->stroke_linecap.computed));
454 }
455 if (map.contains(SPAttr::STROKE_LINEJOIN)) {
456 _ctx.cmd_j(get_linejoin(style->stroke_linejoin.computed));
457 }
458 if (map.contains(SPAttr::STROKE_DASHARRAY)) {
459 auto values = style->stroke_dasharray.get_computed();
460 if (!values.empty()) {
461 _ctx.cmd_d(values.data(), values.size(), style->stroke_dashoffset.computed);
462 }
463 }
464}
465
466} // namespace Inkscape::Extension::Internal::PdfBuilder
@ STROKE_LINECAP
@ STROKE_WIDTH
@ STROKE_LINEJOIN
@ STROKE_MITERLIMIT
@ STROKE_DASHARRAY
std::string toString(bool opacity=true) const
Format the color as a css string and return it.
Definition color.cpp:106
std::optional< Color > converted(Color const &other) const
Return a copy of this color converted to the same format as the other color.
Definition color.cpp:189
double getOpacity() const
Get the opacity in this color, if it's stored.
Definition color.cpp:407
std::shared_ptr< Space::AnySpace > const & getSpace() const
Definition color.h:39
const std::vector< double > & getValues() const
Definition color.h:40
CapyPDF_Device_Colorspace get_default_colorspace() const
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::map< std::string, CapyPDF_FontId > _font_cache
std::optional< CapyPDF_FontId > get_font(std::string const &filename)
Load a font and cache the results.
std::optional< capypdf::Color > get_paint(SPIPaint const &paint, SPStyle const *context_style, std::optional< double > opacity)
Generate a solid color, gradient or pattern based on the SPIPaint.
std::optional< CapyPDF_GraphicsStateId > get_shape_graphics_state(SPStyle const *style)
Like get_graphics_style but for drawing shapes (paths)
std::optional< CapyPDF_IccColorSpaceId > get_icc_profile(std::shared_ptr< Colors::Space::CMS > const &profile)
std::map< std::string, CapyPDF_IccColorSpaceId > _icc_cache
std::optional< CapyPDF_TransparencyGroupId > style_to_transparency_mask(SPStyle const *style, SPStyle const *context_style)
Render gradient transparencies into a transparency mask.
capypdf::Color get_color(Colors::Color const &color, std::optional< double > opacity)
CapyPDF_Device_Colorspace get_colorspace(std::shared_ptr< Colors::Space::AnySpace > const &space) const
std::optional< CapyPDF_PatternId > get_pattern(SPPaintServer const *paint, std::optional< double > opacity)
Construct a PDF pattern object from the given paintserver.
std::optional< double > get_softmask(double opacity) const
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...
Paint type internal to SPStyle.
bool isPaintserver() const
const Glib::ustring get_value() const override
bool isColor() const
bool isNone() const
std::shared_ptr< SPPaintServerReference > href
SPPaintOrigin paintOrigin
Colors::Color const & getColor() const
Base class for visual SVG elements.
Definition sp-item.h:109
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
An SVG style object.
Definition style.h:45
T< SPAttr::FILL, SPIPaint > fill
fill
Definition style.h:240
T< SPAttr::STROKE_DASHARRAY, SPIDashArray > stroke_dasharray
stroke-dasharray
Definition style.h:257
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::FILL_OPACITY, SPIScale24 > fill_opacity
fill-opacity
Definition style.h:242
T< SPAttr::STROKE_LINEJOIN, SPIEnum< SPStrokeJoinType > > stroke_linejoin
stroke-linejoin
Definition style.h:253
T< SPAttr::STROKE_OPACITY, SPIScale24 > stroke_opacity
stroke-opacity
Definition style.h:261
T< SPAttr::STROKE_MITERLIMIT, SPIFloat > stroke_miterlimit
stroke-miterlimit
Definition style.h:255
T< SPAttr::MIX_BLEND_MODE, SPIEnum< SPBlendMode > > mix_blend_mode
Definition style.h:220
T< SPAttr::OPACITY, SPIScale24 > opacity
opacity
Definition style.h:216
T< SPAttr::STROKE_LINECAP, SPIEnum< SPStrokeCapType > > stroke_linecap
stroke-linecap
Definition style.h:251
T< SPAttr::STROKE_DASHOFFSET, SPILength > stroke_dashoffset
stroke-dashoffset
Definition style.h:259
T< SPAttr::STROKE_EXTENSIONS, SPIStrokeExtensions > stroke_extensions
-inkscape-stroke
Definition style.h:263
std::unordered_map< std::string, std::unique_ptr< SPDocument > > map
Colors::Color fill
Colors::Color stroke
SPItem * item
CapyPDF_Line_Join get_linejoin(SPStrokeJoinType mode)
bool style_has_gradient_transparency(SPStyle const *style)
Returns true if the gradient has transparency.
bool gradient_has_transparency(SPPaintServer const *paint)
Returns true if the gradient has transparency.
CapyPDF_Blend_Mode get_blendmode(SPBlendMode mode)
Get the blend mode for capyPDF output.
std::string paint_to_cache_key(SPIPaint const &paint, std::optional< double > opacity)
Turn a paint into a string for use in caching keys.
CapyPDF_Line_Cap get_linecap(SPStrokeCapType mode)
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.
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.
static void err(const char *fmt,...)
Definition pov-out.cpp:57
std::map< SPAttr, std::string > StyleMap
static cairo_user_data_key_t key
int mode
Authors: see git history.
RGB rgb
Definition quantize.cpp:36
SPBlendMode
@ SP_CSS_BLEND_LUMINOSITY
@ SP_CSS_BLEND_DARKEN
@ SP_CSS_BLEND_LIGHTEN
@ SP_CSS_BLEND_DIFFERENCE
@ SP_CSS_BLEND_COLORBURN
@ SP_CSS_BLEND_HARDLIGHT
@ SP_CSS_BLEND_EXCLUSION
@ SP_CSS_BLEND_COLORDODGE
@ SP_CSS_BLEND_SOFTLIGHT
@ SP_CSS_BLEND_SATURATION
@ SP_CSS_BLEND_SCREEN
@ SP_CSS_BLEND_OVERLAY
@ SP_CSS_BLEND_HUE
@ SP_CSS_BLEND_COLOR
@ SP_CSS_BLEND_MULTIPLY
SPStrokeJoinType
Definition style-enums.h:33
@ SP_STROKE_LINEJOIN_MITER
Definition style-enums.h:34
@ SP_STROKE_LINEJOIN_BEVEL
Definition style-enums.h:36
@ SP_STROKE_LINEJOIN_ROUND
Definition style-enums.h:35
SPStrokeCapType
Definition style-enums.h:40
@ SP_STROKE_LINECAP_SQUARE
Definition style-enums.h:43
@ SP_STROKE_LINECAP_ROUND
Definition style-enums.h:42
@ SP_STROKE_LINECAP_BUTT
Definition style-enums.h:41
@ 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
SPStyle - a style object for SPItem objects.