1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3 * @file
4 * Style information for rendering.
5 *//*
6 * Authors:
7 * Krzysztof Kosiński <tweenk.pl@gmail.com>
8 *
9 * Copyright (C) 2010 Authors
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13 #include "display/nr-style.h"
14 #include "style.h"
15
16 #include "display/drawing-context.h"
17 #include "display/drawing-pattern.h"
18
19 #include "object/sp-paint-server.h"
20
clear()21 void NRStyle::Paint::clear()
22 {
23 if (server) {
24 sp_object_unref(server, nullptr);
25 server = nullptr;
26 }
27 type = PAINT_NONE;
28 }
29
set(SPColor const & c)30 void NRStyle::Paint::set(SPColor const &c)
31 {
32 clear();
33 type = PAINT_COLOR;
34 color = c;
35 }
36
set(SPPaintServer * ps)37 void NRStyle::Paint::set(SPPaintServer *ps)
38 {
39 clear();
40 if (ps) {
41 type = PAINT_SERVER;
42 server = ps;
43 sp_object_ref(server, nullptr);
44 }
45 }
46
set(const SPIPaint * paint)47 void NRStyle::Paint::set(const SPIPaint* paint)
48 {
49 if (paint->isPaintserver()) {
50 SPPaintServer* server = paint->value.href->getObject();
51 if (server && server->isValid()) {
52 set(server);
53 } else if (paint->colorSet) {
54 set(paint->value.color);
55 } else {
56 clear();
57 }
58 } else if (paint->isColor()) {
59 set(paint->value.color);
60 } else if (paint->isNone()) {
61 clear();
62 } else if (paint->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL ||
63 paint->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE) {
64 // A marker in the defs section will result in ending up here.
65 // std::cerr << "NRStyle::Paint::set: Double" << std::endl;
66 } else {
67 g_assert_not_reached();
68 }
69 }
70
71
NRStyle()72 NRStyle::NRStyle()
73 : fill()
74 , stroke()
75 , stroke_width(0.0)
76 , hairline(false)
77 , miter_limit(0.0)
78 , n_dash(0)
79 , dash(nullptr)
80 , dash_offset(0.0)
81 , fill_rule(CAIRO_FILL_RULE_EVEN_ODD)
82 , line_cap(CAIRO_LINE_CAP_BUTT)
83 , line_join(CAIRO_LINE_JOIN_MITER)
84 , fill_pattern(nullptr)
85 , stroke_pattern(nullptr)
86 , text_decoration_fill_pattern(nullptr)
87 , text_decoration_stroke_pattern(nullptr)
88 , text_decoration_line(TEXT_DECORATION_LINE_CLEAR)
89 , text_decoration_style(TEXT_DECORATION_STYLE_CLEAR)
90 , text_decoration_fill()
91 , text_decoration_stroke()
92 , text_decoration_stroke_width(0.0)
93 , phase_length(0.0)
94 , tspan_line_start(false)
95 , tspan_line_end(false)
96 , tspan_width(0)
97 , ascender(0)
98 , descender(0)
99 , underline_thickness(0)
100 , underline_position(0)
101 , line_through_thickness(0)
102 , line_through_position(0)
103 , font_size(0)
104 {
105 paint_order_layer[0] = PAINT_ORDER_NORMAL;
106 }
107
~NRStyle()108 NRStyle::~NRStyle()
109 {
110 if (fill_pattern) cairo_pattern_destroy(fill_pattern);
111 if (stroke_pattern) cairo_pattern_destroy(stroke_pattern);
112 if (text_decoration_fill_pattern) cairo_pattern_destroy(text_decoration_fill_pattern);
113 if (text_decoration_stroke_pattern) cairo_pattern_destroy(text_decoration_stroke_pattern);
114 if (dash){
115 delete [] dash;
116 }
117 fill.clear();
118 stroke.clear();
119 text_decoration_fill.clear();
120 text_decoration_stroke.clear();
121 }
122
set(SPStyle * style,SPStyle * context_style)123 void NRStyle::set(SPStyle *style, SPStyle *context_style)
124 {
125 // Handle 'context-fill' and 'context-stroke': Work in progress
126 const SPIPaint *style_fill = &(style->fill);
127 if( style_fill->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL ) {
128 if( context_style != nullptr ) {
129 style_fill = &(context_style->fill);
130 } else {
131 // A marker in the defs section will result in ending up here.
132 //std::cerr << "NRStyle::set: 'context-fill': 'context_style' is NULL" << std::endl;
133 }
134 } else if ( style_fill->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE ) {
135 if( context_style != nullptr ) {
136 style_fill = &(context_style->stroke);
137 } else {
138 //std::cerr << "NRStyle::set: 'context-stroke': 'context_style' is NULL" << std::endl;
139 }
140 }
141
142 fill.set(style_fill);
143 fill.opacity = SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
144
145 switch (style->fill_rule.computed) {
146 case SP_WIND_RULE_EVENODD:
147 fill_rule = CAIRO_FILL_RULE_EVEN_ODD;
148 break;
149 case SP_WIND_RULE_NONZERO:
150 fill_rule = CAIRO_FILL_RULE_WINDING;
151 break;
152 default:
153 g_assert_not_reached();
154 }
155
156 const SPIPaint *style_stroke = &(style->stroke);
157 if( style_stroke->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL ) {
158 if( context_style != nullptr ) {
159 style_stroke = &(context_style->fill);
160 } else {
161 //std::cerr << "NRStyle::set: 'context-fill': 'context_style' is NULL" << std::endl;
162 }
163 } else if ( style_stroke->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE ) {
164 if( context_style != nullptr ) {
165 style_stroke = &(context_style->stroke);
166 } else {
167 //std::cerr << "NRStyle::set: 'context-stroke': 'context_style' is NULL" << std::endl;
168 }
169 }
170
171 stroke.set(style_stroke);
172 stroke.opacity = SP_SCALE24_TO_FLOAT(style->stroke_opacity.value);
173 stroke_width = style->stroke_width.computed;
174 hairline = style->stroke_extensions.hairline;
175 switch (style->stroke_linecap.computed) {
176 case SP_STROKE_LINECAP_ROUND:
177 line_cap = CAIRO_LINE_CAP_ROUND;
178 break;
179 case SP_STROKE_LINECAP_SQUARE:
180 line_cap = CAIRO_LINE_CAP_SQUARE;
181 break;
182 case SP_STROKE_LINECAP_BUTT:
183 line_cap = CAIRO_LINE_CAP_BUTT;
184 break;
185 default:
186 g_assert_not_reached();
187 }
188 switch (style->stroke_linejoin.computed) {
189 case SP_STROKE_LINEJOIN_ROUND:
190 line_join = CAIRO_LINE_JOIN_ROUND;
191 break;
192 case SP_STROKE_LINEJOIN_BEVEL:
193 line_join = CAIRO_LINE_JOIN_BEVEL;
194 break;
195 case SP_STROKE_LINEJOIN_MITER:
196 line_join = CAIRO_LINE_JOIN_MITER;
197 break;
198 default:
199 g_assert_not_reached();
200 }
201 miter_limit = style->stroke_miterlimit.value;
202
203 if (dash){
204 delete [] dash;
205 }
206
207 n_dash = style->stroke_dasharray.values.size();
208 if (n_dash != 0) {
209 dash_offset = style->stroke_dashoffset.computed;
210 dash = new double[n_dash];
211 for (unsigned int i = 0; i < n_dash; ++i) {
212 dash[i] = style->stroke_dasharray.values[i].computed;
213 }
214 } else {
215 dash_offset = 0.0;
216 dash = nullptr;
217 }
218
219
220 for( unsigned i = 0; i < PAINT_ORDER_LAYERS; ++i) {
221 switch (style->paint_order.layer[i]) {
222 case SP_CSS_PAINT_ORDER_NORMAL:
223 paint_order_layer[i]=PAINT_ORDER_NORMAL;
224 break;
225 case SP_CSS_PAINT_ORDER_FILL:
226 paint_order_layer[i]=PAINT_ORDER_FILL;
227 break;
228 case SP_CSS_PAINT_ORDER_STROKE:
229 paint_order_layer[i]=PAINT_ORDER_STROKE;
230 break;
231 case SP_CSS_PAINT_ORDER_MARKER:
232 paint_order_layer[i]=PAINT_ORDER_MARKER;
233 break;
234 }
235 }
236
237 text_decoration_line = TEXT_DECORATION_LINE_CLEAR;
238 if(style->text_decoration_line.inherit ){ text_decoration_line |= TEXT_DECORATION_LINE_INHERIT; }
239 if(style->text_decoration_line.underline ){ text_decoration_line |= TEXT_DECORATION_LINE_UNDERLINE + TEXT_DECORATION_LINE_SET; }
240 if(style->text_decoration_line.overline ){ text_decoration_line |= TEXT_DECORATION_LINE_OVERLINE + TEXT_DECORATION_LINE_SET; }
241 if(style->text_decoration_line.line_through){ text_decoration_line |= TEXT_DECORATION_LINE_LINETHROUGH + TEXT_DECORATION_LINE_SET; }
242 if(style->text_decoration_line.blink ){ text_decoration_line |= TEXT_DECORATION_LINE_BLINK + TEXT_DECORATION_LINE_SET; }
243
244 text_decoration_style = TEXT_DECORATION_STYLE_CLEAR;
245 if(style->text_decoration_style.inherit ){ text_decoration_style |= TEXT_DECORATION_STYLE_INHERIT; }
246 if(style->text_decoration_style.solid ){ text_decoration_style |= TEXT_DECORATION_STYLE_SOLID + TEXT_DECORATION_STYLE_SET; }
247 if(style->text_decoration_style.isdouble ){ text_decoration_style |= TEXT_DECORATION_STYLE_ISDOUBLE + TEXT_DECORATION_STYLE_SET; }
248 if(style->text_decoration_style.dotted ){ text_decoration_style |= TEXT_DECORATION_STYLE_DOTTED + TEXT_DECORATION_STYLE_SET; }
249 if(style->text_decoration_style.dashed ){ text_decoration_style |= TEXT_DECORATION_STYLE_DASHED + TEXT_DECORATION_STYLE_SET; }
250 if(style->text_decoration_style.wavy ){ text_decoration_style |= TEXT_DECORATION_STYLE_WAVY + TEXT_DECORATION_STYLE_SET; }
251
252 /* FIXME
253 The meaning of text-decoration-color in CSS3 for SVG is ambiguous (2014-05-06). Set
254 it for fill, for stroke, for both? Both would seem like the obvious choice but what happens
255 is that for text which is just fill (very common) it makes the lines fatter because it
256 enables stroke on the decorations when it wasn't present on the text. That contradicts the
257 usual behavior where the text and decorations by default have the same fill/stroke.
258
259 The behavior here is that if color is defined it is applied to text_decoration_fill/stroke
260 ONLY if the corresponding fill/stroke is also present.
261
262 Hopefully the standard will be clarified to resolve this issue.
263 */
264
265 // Unless explicitly set on an element, text decoration is inherited from
266 // closest ancestor where 'text-decoration' was set. That is, setting
267 // 'text-decoration' on an ancestor fixes the fill and stroke of the
268 // decoration to the fill and stroke values of that ancestor.
269 SPStyle* style_td = style;
270 if ( style->text_decoration.style_td ) style_td = style->text_decoration.style_td;
271 text_decoration_stroke.opacity = SP_SCALE24_TO_FLOAT(style_td->stroke_opacity.value);
272 text_decoration_stroke_width = style_td->stroke_width.computed;
273
274 // Priority is given in order:
275 // * text_decoration_fill
276 // * text_decoration_color (only if fill set)
277 // * fill
278 if (style_td->text_decoration_fill.set) {
279 text_decoration_fill.set(&(style_td->text_decoration_fill));
280 } else if (style_td->text_decoration_color.set) {
281 if(style->fill.isPaintserver() || style->fill.isColor()) {
282 // SVG sets color specifically
283 text_decoration_fill.set(style->text_decoration_color.value.color);
284 } else {
285 // No decoration fill because no text fill
286 text_decoration_fill.clear();
287 }
288 } else {
289 // Pick color/pattern from text
290 text_decoration_fill.set(&(style_td->fill));
291 }
292
293 if (style_td->text_decoration_stroke.set) {
294 text_decoration_stroke.set(&(style_td->text_decoration_stroke));
295 } else if (style_td->text_decoration_color.set) {
296 if(style->stroke.isPaintserver() || style->stroke.isColor()) {
297 // SVG sets color specifically
298 text_decoration_stroke.set(style->text_decoration_color.value.color);
299 } else {
300 // No decoration stroke because no text stroke
301 text_decoration_stroke.clear();
302 }
303 } else {
304 // Pick color/pattern from text
305 text_decoration_stroke.set(&(style_td->stroke));
306 }
307
308 if (text_decoration_line != TEXT_DECORATION_LINE_CLEAR) {
309 phase_length = style->text_decoration_data.phase_length;
310 tspan_line_start = style->text_decoration_data.tspan_line_start;
311 tspan_line_end = style->text_decoration_data.tspan_line_end;
312 tspan_width = style->text_decoration_data.tspan_width;
313 ascender = style->text_decoration_data.ascender;
314 descender = style->text_decoration_data.descender;
315 underline_thickness = style->text_decoration_data.underline_thickness;
316 underline_position = style->text_decoration_data.underline_position;
317 line_through_thickness = style->text_decoration_data.line_through_thickness;
318 line_through_position = style->text_decoration_data.line_through_position;
319 font_size = style->font_size.computed;
320 }
321
322 text_direction = style->direction.computed;
323
324 update();
325 }
326
preparePaint(Inkscape::DrawingContext & dc,Geom::OptRect const & paintbox,Inkscape::DrawingPattern * pattern,Paint & paint)327 cairo_pattern_t* NRStyle::preparePaint(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern, Paint& paint)
328 {
329 cairo_pattern_t* cpattern = nullptr;
330
331 switch (paint.type) {
332 case PAINT_SERVER:
333 if (pattern) {
334 cpattern = pattern->renderPattern(paint.opacity);
335 } else {
336 cpattern = paint.server->pattern_new(dc.raw(), paintbox, paint.opacity);
337 }
338 break;
339 case PAINT_COLOR: {
340 SPColor const &c = paint.color;
341 cpattern = cairo_pattern_create_rgba(
342 c.v.c[0], c.v.c[1], c.v.c[2], paint.opacity);
343 double red = 0;
344 double green = 0;
345 double blue = 0;
346 double alpha = 0;
347 cairo_pattern_get_rgba(cpattern, &red, &green, &blue, &alpha);
348 }
349 break;
350 default:
351 break;
352 }
353 return cpattern;
354 }
355
prepareFill(Inkscape::DrawingContext & dc,Geom::OptRect const & paintbox,Inkscape::DrawingPattern * pattern)356 bool NRStyle::prepareFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
357 {
358 if (!fill_pattern) fill_pattern = preparePaint(dc, paintbox, pattern, fill);
359 return fill_pattern != nullptr;
360 }
361
prepareStroke(Inkscape::DrawingContext & dc,Geom::OptRect const & paintbox,Inkscape::DrawingPattern * pattern)362 bool NRStyle::prepareStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
363 {
364 if (!stroke_pattern) stroke_pattern = preparePaint(dc, paintbox, pattern, stroke);
365 return stroke_pattern != nullptr;
366 }
367
prepareTextDecorationFill(Inkscape::DrawingContext & dc,Geom::OptRect const & paintbox,Inkscape::DrawingPattern * pattern)368 bool NRStyle::prepareTextDecorationFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
369 {
370 if (!text_decoration_fill_pattern) text_decoration_fill_pattern = preparePaint(dc, paintbox, pattern, text_decoration_fill);
371 return text_decoration_fill_pattern != nullptr;
372 }
373
prepareTextDecorationStroke(Inkscape::DrawingContext & dc,Geom::OptRect const & paintbox,Inkscape::DrawingPattern * pattern)374 bool NRStyle::prepareTextDecorationStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
375 {
376 if (!text_decoration_stroke_pattern) text_decoration_stroke_pattern = preparePaint(dc, paintbox, pattern, text_decoration_stroke);
377 return text_decoration_stroke_pattern != nullptr;
378 }
379
applyFill(Inkscape::DrawingContext & dc)380 void NRStyle::applyFill(Inkscape::DrawingContext &dc)
381 {
382 dc.setSource(fill_pattern);
383 dc.setFillRule(fill_rule);
384 }
385
applyTextDecorationFill(Inkscape::DrawingContext & dc)386 void NRStyle::applyTextDecorationFill(Inkscape::DrawingContext &dc)
387 {
388 dc.setSource(text_decoration_fill_pattern);
389 // Fill rule does not matter, no intersections.
390 }
391
applyStroke(Inkscape::DrawingContext & dc)392 void NRStyle::applyStroke(Inkscape::DrawingContext &dc)
393 {
394 dc.setSource(stroke_pattern);
395 if (hairline) {
396 dc.setHairline();
397 } else {
398 dc.setLineWidth(stroke_width);
399 }
400 dc.setLineCap(line_cap);
401 dc.setLineJoin(line_join);
402 dc.setMiterLimit(miter_limit);
403 cairo_set_dash(dc.raw(), dash, n_dash, dash_offset); // fixme
404 }
405
applyTextDecorationStroke(Inkscape::DrawingContext & dc)406 void NRStyle::applyTextDecorationStroke(Inkscape::DrawingContext &dc)
407 {
408 dc.setSource(text_decoration_stroke_pattern);
409 if (hairline) {
410 dc.setHairline();
411 } else {
412 dc.setLineWidth(text_decoration_stroke_width);
413 }
414 dc.setLineCap(CAIRO_LINE_CAP_BUTT);
415 dc.setLineJoin(CAIRO_LINE_JOIN_MITER);
416 dc.setMiterLimit(miter_limit);
417 cairo_set_dash(dc.raw(), nullptr, 0, 0.0); // fixme (no dash)
418 }
419
update()420 void NRStyle::update()
421 {
422 // force pattern update
423 if (fill_pattern) cairo_pattern_destroy(fill_pattern);
424 if (stroke_pattern) cairo_pattern_destroy(stroke_pattern);
425 if (text_decoration_fill_pattern) cairo_pattern_destroy(text_decoration_fill_pattern);
426 if (text_decoration_stroke_pattern) cairo_pattern_destroy(text_decoration_stroke_pattern);
427 fill_pattern = nullptr;
428 stroke_pattern = nullptr;
429 text_decoration_fill_pattern = nullptr;
430 text_decoration_stroke_pattern = nullptr;
431 }
432
433 /*
434 Local Variables:
435 mode:c++
436 c-file-style:"stroustrup"
437 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
438 indent-tabs-mode:nil
439 fill-column:99
440 End:
441 */
442 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
443