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