1 #define __SP_STYLE_C__
3 /** \file
4 * SVG stylesheets implementation.
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Peter Moulder <pmoulder@mail.csse.monash.edu.au>
9 * bulia byak <buliabyak@users.sf.net>
10 *
11 * Copyright (C) 2001-2002 Lauris Kaplinski
12 * Copyright (C) 2001 Ximian, Inc.
13 * Copyright (C) 2005 Monash University
14 *
15 * Released under GNU GPL, read the file 'COPYING' for more information
16 */
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
22 #include "libcroco/cr-sel-eng.h"
23 #include "xml/croco-node-iface.h"
25 #include "svg/svg.h"
26 #include "svg/svg-color.h"
27 #include "svg/svg-icc-color.h"
29 #include "display/canvas-bpath.h"
30 #include "attributes.h"
31 #include "document.h"
32 #include "extract-uri.h"
33 #include "marker-status.h"
34 #include "uri-references.h"
35 #include "sp-paint-server.h"
36 #include "streq.h"
37 #include "strneq.h"
38 #include "style.h"
39 #include "svg/css-ostringstream.h"
40 #include "xml/repr.h"
41 #include "unit-constants.h"
42 #include "isnan.h"
44 using Inkscape::CSSOStringStream;
45 using std::vector;
47 namespace Inkscape {
49 /**
50 * Parses a CSS url() specification; temporary hack until
51 * style stuff is redone.
52 * \param string the CSS string to parse
53 * \return a newly-allocated URL string (or NULL); free with g_free()
54 */
55 gchar *parse_css_url(gchar const *string) {
56 if (!string)
57 return NULL;
59 gchar const *iter = string;
60 for ( ; g_ascii_isspace(*iter) ; iter = g_utf8_next_char(iter) );
61 if (strncmp(iter, "url(", 4))
62 return NULL;
63 iter += 4;
65 gchar const end_char = *iter;
66 if ( *iter == '"' || *iter == '\'' ) {
67 iter += 1;
68 }
70 GString *temp = g_string_new(NULL);
71 for ( ; *iter ; iter = g_utf8_next_char(iter) ) {
72 if ( *iter == '(' || *iter == ')' ||
73 *iter == '"' || *iter == '\'' ||
74 g_ascii_isspace(*iter) ||
75 g_ascii_iscntrl(*iter) )
76 {
77 break;
78 }
79 if ( *iter == '\\' ) {
80 iter = g_utf8_next_char(iter);
81 }
82 if ( *iter & (gchar)0x80 ) {
83 break;
84 } else {
85 g_string_append_c(temp, *iter);
86 }
87 }
89 if ( *iter == end_char && end_char != ')' ) {
90 iter = g_utf8_next_char(iter);
91 }
92 gchar *result;
93 if ( *iter == ')' ) {
94 result = temp->str;
95 g_string_free(temp, FALSE);
96 } else {
97 result = NULL;
98 g_string_free(temp, TRUE);
99 }
101 return result;
102 }
104 }
106 #define BMAX 8192
108 class SPStyleEnum;
110 /*#########################
111 ## FORWARD DECLARATIONS
112 #########################*/
113 static void sp_style_clear(SPStyle *style);
115 static void sp_style_merge_property(SPStyle *style, gint id, gchar const *val);
117 static void sp_style_merge_ipaint(SPStyle *style, SPIPaint *paint, SPIPaint const *parent);
118 static void sp_style_merge_ifilter(SPIFilter *child, SPIFilter const *parent);
119 static void sp_style_read_dash(SPStyle *style, gchar const *str);
121 static SPTextStyle *sp_text_style_new(void);
122 static void sp_text_style_clear(SPTextStyle *ts);
123 static SPTextStyle *sp_text_style_unref(SPTextStyle *st);
124 static SPTextStyle *sp_text_style_duplicate_unset(SPTextStyle *st);
125 static guint sp_text_style_write(gchar *p, guint len, SPTextStyle const *st, guint flags = SP_STYLE_FLAG_IFSET);
126 static void sp_style_privatize_text(SPStyle *style);
128 static void sp_style_read_ifloat(SPIFloat *val, gchar const *str);
129 static void sp_style_read_iscale24(SPIScale24 *val, gchar const *str);
130 static void sp_style_read_ienum(SPIEnum *val, gchar const *str, SPStyleEnum const *dict, bool can_explicitly_inherit);
131 static void sp_style_read_istring(SPIString *val, gchar const *str);
132 static void sp_style_read_ilength(SPILength *val, gchar const *str);
133 static void sp_style_read_ilengthornormal(SPILengthOrNormal *val, gchar const *str);
134 static void sp_style_read_itextdecoration(SPITextDecoration *val, gchar const *str);
135 static void sp_style_read_icolor(SPIPaint *paint, gchar const *str, SPStyle *style, SPDocument *document);
136 static void sp_style_read_ipaint(SPIPaint *paint, gchar const *str, SPStyle *style, SPDocument *document);
137 static void sp_style_read_ifontsize(SPIFontSize *val, gchar const *str);
138 static void sp_style_read_ifilter(SPIFilter *f, gchar const *str, SPDocument *document);
140 static void sp_style_read_penum(SPIEnum *val, Inkscape::XML::Node *repr, gchar const *key, SPStyleEnum const *dict, bool can_explicitly_inherit);
141 static void sp_style_read_plength(SPILength *val, Inkscape::XML::Node *repr, gchar const *key);
142 static void sp_style_read_pfontsize(SPIFontSize *val, Inkscape::XML::Node *repr, gchar const *key);
143 static void sp_style_read_pfloat(SPIFloat *val, Inkscape::XML::Node *repr, gchar const *key);
145 static gint sp_style_write_ifloat(gchar *p, gint len, gchar const *key, SPIFloat const *val, SPIFloat const *base, guint flags);
146 static gint sp_style_write_iscale24(gchar *p, gint len, gchar const *key, SPIScale24 const *val, SPIScale24 const *base, guint flags);
147 static gint sp_style_write_ienum(gchar *p, gint len, gchar const *key, SPStyleEnum const *dict, SPIEnum const *val, SPIEnum const *base, guint flags);
148 static gint sp_style_write_istring(gchar *p, gint len, gchar const *key, SPIString const *val, SPIString const *base, guint flags);
149 static gint sp_style_write_ilength(gchar *p, gint len, gchar const *key, SPILength const *val, SPILength const *base, guint flags);
150 static gint sp_style_write_ipaint(gchar *b, gint len, gchar const *key, SPIPaint const *paint, SPIPaint const *base, guint flags);
151 static gint sp_style_write_ifontsize(gchar *p, gint len, gchar const *key, SPIFontSize const *val, SPIFontSize const *base, guint flags);
152 static gint sp_style_write_ilengthornormal(gchar *p, gint const len, gchar const *const key, SPILengthOrNormal const *const val, SPILengthOrNormal const *const base, guint const flags);
153 static gint sp_style_write_itextdecoration(gchar *p, gint const len, gchar const *const key, SPITextDecoration const *const val, SPITextDecoration const *const base, guint const flags);
154 static gint sp_style_write_ifilter(gchar *b, gint len, gchar const *key, SPIFilter const *filter, SPIFilter const *base, guint flags);
156 static void css2_unescape_unquote(SPIString *val);
158 static void sp_style_paint_clear(SPStyle *style, SPIPaint *paint);
160 #define SPS_READ_IENUM_IF_UNSET(v,s,d,i) if (!(v)->set) {sp_style_read_ienum((v), (s), (d), (i));}
161 #define SPS_READ_PENUM_IF_UNSET(v,r,k,d,i) if (!(v)->set) {sp_style_read_penum((v), (r), (k), (d), (i));}
163 #define SPS_READ_ILENGTH_IF_UNSET(v,s) if (!(v)->set) {sp_style_read_ilength((v), (s));}
164 #define SPS_READ_PLENGTH_IF_UNSET(v,r,k) if (!(v)->set) {sp_style_read_plength((v), (r), (k));}
166 #define SPS_READ_PFLOAT_IF_UNSET(v,r,k) if (!(v)->set) {sp_style_read_pfloat((v), (r), (k));}
168 #define SPS_READ_IFONTSIZE_IF_UNSET(v,s) if (!(v)->set) {sp_style_read_ifontsize((v), (s));}
169 #define SPS_READ_PFONTSIZE_IF_UNSET(v,r,k) if (!(v)->set) {sp_style_read_pfontsize((v), (r), (k));}
171 static void sp_style_merge_from_object_stylesheet(SPStyle *, SPObject const *);
173 struct SPStyleEnum {
174 gchar const *key;
175 gint value;
176 };
178 static SPStyleEnum const enum_fill_rule[] = {
179 {"nonzero", SP_WIND_RULE_NONZERO},
180 {"evenodd", SP_WIND_RULE_EVENODD},
181 {NULL, -1}
182 };
184 static SPStyleEnum const enum_stroke_linecap[] = {
185 {"butt", SP_STROKE_LINECAP_BUTT},
186 {"round", SP_STROKE_LINECAP_ROUND},
187 {"square", SP_STROKE_LINECAP_SQUARE},
188 {NULL, -1}
189 };
191 static SPStyleEnum const enum_stroke_linejoin[] = {
192 {"miter", SP_STROKE_LINEJOIN_MITER},
193 {"round", SP_STROKE_LINEJOIN_ROUND},
194 {"bevel", SP_STROKE_LINEJOIN_BEVEL},
195 {NULL, -1}
196 };
198 static SPStyleEnum const enum_font_style[] = {
199 {"normal", SP_CSS_FONT_STYLE_NORMAL},
200 {"italic", SP_CSS_FONT_STYLE_ITALIC},
201 {"oblique", SP_CSS_FONT_STYLE_OBLIQUE},
202 {NULL, -1}
203 };
205 static SPStyleEnum const enum_font_size[] = {
206 {"xx-small", SP_CSS_FONT_SIZE_XX_SMALL},
207 {"x-small", SP_CSS_FONT_SIZE_X_SMALL},
208 {"small", SP_CSS_FONT_SIZE_SMALL},
209 {"medium", SP_CSS_FONT_SIZE_MEDIUM},
210 {"large", SP_CSS_FONT_SIZE_LARGE},
211 {"x-large", SP_CSS_FONT_SIZE_X_LARGE},
212 {"xx-large", SP_CSS_FONT_SIZE_XX_LARGE},
213 {"smaller", SP_CSS_FONT_SIZE_SMALLER},
214 {"larger", SP_CSS_FONT_SIZE_LARGER},
215 {NULL, -1}
216 };
218 static SPStyleEnum const enum_font_variant[] = {
219 {"normal", SP_CSS_FONT_VARIANT_NORMAL},
220 {"small-caps", SP_CSS_FONT_VARIANT_SMALL_CAPS},
221 {NULL, -1}
222 };
224 static SPStyleEnum const enum_font_weight[] = {
225 {"100", SP_CSS_FONT_WEIGHT_100},
226 {"200", SP_CSS_FONT_WEIGHT_200},
227 {"300", SP_CSS_FONT_WEIGHT_300},
228 {"400", SP_CSS_FONT_WEIGHT_400},
229 {"500", SP_CSS_FONT_WEIGHT_500},
230 {"600", SP_CSS_FONT_WEIGHT_600},
231 {"700", SP_CSS_FONT_WEIGHT_700},
232 {"800", SP_CSS_FONT_WEIGHT_800},
233 {"900", SP_CSS_FONT_WEIGHT_900},
234 {"normal", SP_CSS_FONT_WEIGHT_NORMAL},
235 {"bold", SP_CSS_FONT_WEIGHT_BOLD},
236 {"lighter", SP_CSS_FONT_WEIGHT_LIGHTER},
237 {"bolder", SP_CSS_FONT_WEIGHT_BOLDER},
238 {NULL, -1}
239 };
241 static SPStyleEnum const enum_font_stretch[] = {
242 {"ultra-condensed", SP_CSS_FONT_STRETCH_ULTRA_CONDENSED},
243 {"extra-condensed", SP_CSS_FONT_STRETCH_EXTRA_CONDENSED},
244 {"condensed", SP_CSS_FONT_STRETCH_CONDENSED},
245 {"semi-condensed", SP_CSS_FONT_STRETCH_SEMI_CONDENSED},
246 {"normal", SP_CSS_FONT_STRETCH_NORMAL},
247 {"semi-expanded", SP_CSS_FONT_STRETCH_SEMI_EXPANDED},
248 {"expanded", SP_CSS_FONT_STRETCH_EXPANDED},
249 {"extra-expanded", SP_CSS_FONT_STRETCH_EXTRA_EXPANDED},
250 {"ultra-expanded", SP_CSS_FONT_STRETCH_ULTRA_EXPANDED},
251 {"narrower", SP_CSS_FONT_STRETCH_NARROWER},
252 {"wider", SP_CSS_FONT_STRETCH_WIDER},
253 {NULL, -1}
254 };
256 static SPStyleEnum const enum_text_align[] = {
257 {"start", SP_CSS_TEXT_ALIGN_START},
258 {"end", SP_CSS_TEXT_ALIGN_END},
259 {"left", SP_CSS_TEXT_ALIGN_LEFT},
260 {"right", SP_CSS_TEXT_ALIGN_RIGHT},
261 {"center", SP_CSS_TEXT_ALIGN_CENTER},
262 {"justify", SP_CSS_TEXT_ALIGN_JUSTIFY},
263 {NULL, -1}
264 };
266 static SPStyleEnum const enum_text_transform[] = {
267 {"capitalize", SP_CSS_TEXT_TRANSFORM_CAPITALIZE},
268 {"uppercase", SP_CSS_TEXT_TRANSFORM_UPPERCASE},
269 {"lowercase", SP_CSS_TEXT_TRANSFORM_LOWERCASE},
270 {"none", SP_CSS_TEXT_TRANSFORM_NONE},
271 {NULL, -1}
272 };
274 static SPStyleEnum const enum_text_anchor[] = {
275 {"start", SP_CSS_TEXT_ANCHOR_START},
276 {"middle", SP_CSS_TEXT_ANCHOR_MIDDLE},
277 {"end", SP_CSS_TEXT_ANCHOR_END},
278 {NULL, -1}
279 };
281 static SPStyleEnum const enum_direction[] = {
282 {"ltr", SP_CSS_DIRECTION_LTR},
283 {"rtl", SP_CSS_DIRECTION_RTL},
284 {NULL, -1}
285 };
287 static SPStyleEnum const enum_block_progression[] = {
288 {"tb", SP_CSS_BLOCK_PROGRESSION_TB},
289 {"rl", SP_CSS_BLOCK_PROGRESSION_RL},
290 {"lr", SP_CSS_BLOCK_PROGRESSION_LR},
291 {NULL, -1}
292 };
294 static SPStyleEnum const enum_writing_mode[] = {
295 /* Note that using the same enumerator for lr as lr-tb means we write as lr-tb even if the
296 * input file said lr. We prefer writing lr-tb on the grounds that the spec says the initial
297 * value is lr-tb rather than lr.
298 *
299 * ECMA scripts may be surprised to find tb-rl in DOM if they set the attribute to rl, so
300 * sharing enumerators for different strings may be a bug (once we support ecma script).
301 */
302 {"lr-tb", SP_CSS_WRITING_MODE_LR_TB},
303 {"rl-tb", SP_CSS_WRITING_MODE_RL_TB},
304 {"tb-rl", SP_CSS_WRITING_MODE_TB_RL},
305 {"lr", SP_CSS_WRITING_MODE_LR_TB},
306 {"rl", SP_CSS_WRITING_MODE_RL_TB},
307 {"tb", SP_CSS_WRITING_MODE_TB_RL},
308 {NULL, -1}
309 };
311 static SPStyleEnum const enum_visibility[] = {
312 {"hidden", SP_CSS_VISIBILITY_HIDDEN},
313 {"collapse", SP_CSS_VISIBILITY_COLLAPSE},
314 {"visible", SP_CSS_VISIBILITY_VISIBLE},
315 {NULL, -1}
316 };
318 static SPStyleEnum const enum_overflow[] = {
319 {"visible", SP_CSS_OVERFLOW_VISIBLE},
320 {"hidden", SP_CSS_OVERFLOW_HIDDEN},
321 {"scroll", SP_CSS_OVERFLOW_SCROLL},
322 {"auto", SP_CSS_OVERFLOW_AUTO},
323 {NULL, -1}
324 };
326 static SPStyleEnum const enum_display[] = {
327 {"none", SP_CSS_DISPLAY_NONE},
328 {"inline", SP_CSS_DISPLAY_INLINE},
329 {"block", SP_CSS_DISPLAY_BLOCK},
330 {"list-item", SP_CSS_DISPLAY_LIST_ITEM},
331 {"run-in", SP_CSS_DISPLAY_RUN_IN},
332 {"compact", SP_CSS_DISPLAY_COMPACT},
333 {"marker", SP_CSS_DISPLAY_MARKER},
334 {"table", SP_CSS_DISPLAY_TABLE},
335 {"inline-table", SP_CSS_DISPLAY_INLINE_TABLE},
336 {"table-row-group", SP_CSS_DISPLAY_TABLE_ROW_GROUP},
337 {"table-header-group", SP_CSS_DISPLAY_TABLE_HEADER_GROUP},
338 {"table-footer-group", SP_CSS_DISPLAY_TABLE_FOOTER_GROUP},
339 {"table-row", SP_CSS_DISPLAY_TABLE_ROW},
340 {"table-column-group", SP_CSS_DISPLAY_TABLE_COLUMN_GROUP},
341 {"table-column", SP_CSS_DISPLAY_TABLE_COLUMN},
342 {"table-cell", SP_CSS_DISPLAY_TABLE_CELL},
343 {"table-caption", SP_CSS_DISPLAY_TABLE_CAPTION},
344 {NULL, -1}
345 };
347 static SPStyleEnum const enum_shape_rendering[] = {
348 {"auto", 0},
349 {"optimizeSpeed", 0},
350 {"crispEdges", 0},
351 {"geometricPrecision", 0},
352 {NULL, -1}
353 };
355 static SPStyleEnum const enum_color_rendering[] = {
356 {"auto", 0},
357 {"optimizeSpeed", 0},
358 {"optimizeQuality", 0},
359 {NULL, -1}
360 };
362 static SPStyleEnum const *const enum_image_rendering = enum_color_rendering;
364 static SPStyleEnum const enum_text_rendering[] = {
365 {"auto", 0},
366 {"optimizeSpeed", 0},
367 {"optimizeLegibility", 0},
368 {"geometricPrecision", 0},
369 {NULL, -1}
370 };
372 /**
373 * Release callback.
374 */
375 static void
376 sp_style_object_release(SPObject *object, SPStyle *style)
377 {
378 style->object = NULL;
379 }
384 /**
385 * Returns a new SPStyle object with settings as per sp_style_clear().
386 */
387 SPStyle *
388 sp_style_new()
389 {
390 SPStyle *const style = g_new0(SPStyle, 1);
392 style->refcount = 1;
393 style->object = NULL;
394 style->text = sp_text_style_new();
395 style->text_private = TRUE;
397 sp_style_clear(style);
399 style->cloned = false;
401 style->fill_hreffed = false;
402 style->stroke_hreffed = false;
404 style->fill_listening = false;
405 style->stroke_listening = false;
407 return style;
408 }
411 /**
412 * Creates a new SPStyle object, and attaches it to the specified SPObject.
413 */
414 SPStyle *
415 sp_style_new_from_object(SPObject *object)
416 {
417 g_return_val_if_fail(object != NULL, NULL);
418 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
420 SPStyle *style = sp_style_new();
421 style->object = object;
422 g_signal_connect(G_OBJECT(object), "release", G_CALLBACK(sp_style_object_release), style);
424 if (object && SP_OBJECT_IS_CLONED(object)) {
425 style->cloned = true;
426 }
428 return style;
429 }
432 /**
433 * Increase refcount of style.
434 */
435 SPStyle *
436 sp_style_ref(SPStyle *style)
437 {
438 g_return_val_if_fail(style != NULL, NULL);
439 g_return_val_if_fail(style->refcount > 0, NULL);
441 style->refcount += 1;
443 return style;
444 }
447 /**
448 * Decrease refcount of style with possible destruction.
449 */
450 SPStyle *
451 sp_style_unref(SPStyle *style)
452 {
453 g_return_val_if_fail(style != NULL, NULL);
454 g_return_val_if_fail(style->refcount > 0, NULL);
456 style->refcount -= 1;
458 if (style->refcount < 1) {
459 if (style->object)
460 g_signal_handlers_disconnect_matched(G_OBJECT(style->object),
461 G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, style);
462 if (style->text) sp_text_style_unref(style->text);
463 sp_style_paint_clear(style, &style->fill);
464 sp_style_paint_clear(style, &style->stroke);
465 g_free(style->stroke_dash.dash);
466 g_free(style);
467 }
469 return NULL;
470 }
472 /**
473 * Reads the various style parameters for an object from repr.
474 */
475 static void
476 sp_style_read(SPStyle *style, SPObject *object, Inkscape::XML::Node *repr)
477 {
478 g_assert(style != NULL);
479 g_assert(repr != NULL);
480 g_assert(!object || (SP_OBJECT_REPR(object) == repr));
482 sp_style_clear(style);
484 if (object && SP_OBJECT_IS_CLONED(object)) {
485 style->cloned = true;
486 }
488 /* 1. Style itself */
489 gchar const *val = repr->attribute("style");
490 if (val != NULL) {
491 sp_style_merge_from_style_string(style, val);
492 }
494 if (object) {
495 sp_style_merge_from_object_stylesheet(style, object);
496 } else {
497 /** \todo No stylesheet information. Find out under what circumstances
498 * this occurs, and handle accordingly. (If we really wanted to, we
499 * could probably get stylesheets by going through repr->doc.)
500 */
501 }
503 /* 2. Presentation attributes */
504 /* CSS2 */
505 SPS_READ_PENUM_IF_UNSET(&style->visibility, repr, "visibility", enum_visibility, true);
506 SPS_READ_PENUM_IF_UNSET(&style->display, repr, "display", enum_display, true);
507 SPS_READ_PENUM_IF_UNSET(&style->overflow, repr, "overflow", enum_overflow, true);
508 /* Font */
509 SPS_READ_PFONTSIZE_IF_UNSET(&style->font_size, repr, "font-size");
510 SPS_READ_PENUM_IF_UNSET(&style->font_style, repr, "font-style", enum_font_style, true);
511 SPS_READ_PENUM_IF_UNSET(&style->font_variant, repr, "font-variant", enum_font_variant, true);
512 SPS_READ_PENUM_IF_UNSET(&style->font_weight, repr, "font-weight", enum_font_weight, true);
513 SPS_READ_PENUM_IF_UNSET(&style->font_stretch, repr, "font-stretch", enum_font_stretch, true);
514 /* Text (css2 chapter 16) */
515 SPS_READ_PLENGTH_IF_UNSET(&style->text_indent, repr, "text-indent");
516 SPS_READ_PENUM_IF_UNSET(&style->text_align, repr, "text-align", enum_text_align, true);
517 if (!style->text_decoration.set) {
518 val = repr->attribute("text-decoration");
519 if (val) {
520 sp_style_read_itextdecoration(&style->text_decoration, val);
521 }
522 }
523 if (!style->line_height.set) {
524 val = repr->attribute("line-height");
525 if (val) {
526 sp_style_read_ilengthornormal(&style->line_height, val);
527 }
528 }
529 if (!style->letter_spacing.set) {
530 val = repr->attribute("letter-spacing");
531 if (val) {
532 sp_style_read_ilengthornormal(&style->letter_spacing, val);
533 }
534 }
535 if (!style->word_spacing.set) {
536 val = repr->attribute("word-spacing");
537 if (val) {
538 sp_style_read_ilengthornormal(&style->word_spacing, val);
539 }
540 }
541 SPS_READ_PENUM_IF_UNSET(&style->text_transform, repr, "text-transform", enum_text_transform, true);
542 SPS_READ_PENUM_IF_UNSET(&style->direction, repr, "direction", enum_direction, true);
543 SPS_READ_PENUM_IF_UNSET(&style->block_progression, repr, "block_progression", enum_block_progression, true);
545 /* SVG */
546 SPS_READ_PENUM_IF_UNSET(&style->writing_mode, repr, "writing-mode",
547 enum_writing_mode, true);
548 SPS_READ_PENUM_IF_UNSET(&style->text_anchor, repr, "text-anchor",
549 enum_text_anchor, true);
551 /* opacity */
552 if (!style->opacity.set) {
553 val = repr->attribute("opacity");
554 if (val) {
555 sp_style_read_iscale24(&style->opacity, val);
556 }
557 }
558 /* color */
559 if (!style->color.set) {
560 val = repr->attribute("color");
561 if (val) {
562 sp_style_read_icolor(&style->color, val, style, ( object
563 ? SP_OBJECT_DOCUMENT(object)
564 : NULL ));
565 }
566 }
567 /* fill */
568 if (!style->fill.set) {
569 val = repr->attribute("fill");
570 if (val) {
571 sp_style_read_ipaint(&style->fill, val, style, (object) ? SP_OBJECT_DOCUMENT(object) : NULL);
572 }
573 }
574 /* fill-opacity */
575 if (!style->fill_opacity.set) {
576 val = repr->attribute("fill-opacity");
577 if (val) {
578 sp_style_read_iscale24(&style->fill_opacity, val);
579 }
580 }
581 /* fill-rule */
582 SPS_READ_PENUM_IF_UNSET(&style->fill_rule, repr, "fill-rule", enum_fill_rule, true);
583 /* stroke */
584 if (!style->stroke.set) {
585 val = repr->attribute("stroke");
586 if (val) {
587 sp_style_read_ipaint(&style->stroke, val, style, (object) ? SP_OBJECT_DOCUMENT(object) : NULL);
588 }
589 }
590 SPS_READ_PLENGTH_IF_UNSET(&style->stroke_width, repr, "stroke-width");
591 SPS_READ_PENUM_IF_UNSET(&style->stroke_linecap, repr, "stroke-linecap", enum_stroke_linecap, true);
592 SPS_READ_PENUM_IF_UNSET(&style->stroke_linejoin, repr, "stroke-linejoin", enum_stroke_linejoin, true);
593 SPS_READ_PFLOAT_IF_UNSET(&style->stroke_miterlimit, repr, "stroke-miterlimit");
595 /* markers */
596 if (!style->marker[SP_MARKER_LOC].set) {
597 val = repr->attribute("marker");
598 if (val) {
599 sp_style_read_istring(&style->marker[SP_MARKER_LOC], val);
600 }
601 }
602 if (!style->marker[SP_MARKER_LOC_START].set) {
603 val = repr->attribute("marker-start");
604 if (val) {
605 sp_style_read_istring(&style->marker[SP_MARKER_LOC_START], val);
606 }
607 }
608 if (!style->marker[SP_MARKER_LOC_MID].set) {
609 val = repr->attribute("marker-mid");
610 if (val) {
611 sp_style_read_istring(&style->marker[SP_MARKER_LOC_MID], val);
612 }
613 }
614 if (!style->marker[SP_MARKER_LOC_END].set) {
615 val = repr->attribute("marker-end");
616 if (val) {
617 sp_style_read_istring(&style->marker[SP_MARKER_LOC_END], val);
618 }
619 }
621 /* stroke-opacity */
622 if (!style->stroke_opacity.set) {
623 val = repr->attribute("stroke-opacity");
624 if (val) {
625 sp_style_read_iscale24(&style->stroke_opacity, val);
626 }
627 }
628 if (!style->stroke_dasharray_set) {
629 val = repr->attribute("stroke-dasharray");
630 if (val) {
631 sp_style_read_dash(style, val);
632 }
633 }
634 if (!style->stroke_dashoffset_set) {
635 /* fixme */
636 val = repr->attribute("stroke-dashoffset");
637 if (sp_svg_number_read_d(val, &style->stroke_dash.offset)) {
638 style->stroke_dashoffset_set = TRUE;
639 } else {
640 style->stroke_dashoffset_set = FALSE;
641 }
642 }
644 /* font-family */
645 if (!style->text_private || !style->text->font_family.set) {
646 val = repr->attribute("font-family");
647 if (val) {
648 if (!style->text_private) sp_style_privatize_text(style);
649 sp_style_read_istring(&style->text->font_family, val);
650 css2_unescape_unquote(&style->text->font_family);
651 }
652 }
654 /* filter effects */
655 if (!style->filter.set) {
656 val = repr->attribute("filter");
657 if (val) {
658 sp_style_read_ifilter(&style->filter, val, (object) ? SP_OBJECT_DOCUMENT(object) : NULL);
659 }
660 }
662 /* 3. Merge from parent */
663 if (object) {
664 if (object->parent) {
665 sp_style_merge_from_parent(style, SP_OBJECT_STYLE(object->parent));
666 }
667 } else {
668 if (sp_repr_parent(repr)) {
669 /// \todo fixme: This is not the prettiest thing (Lauris)
670 SPStyle *parent = sp_style_new();
671 sp_style_read(parent, NULL, sp_repr_parent(repr));
672 sp_style_merge_from_parent(style, parent);
673 sp_style_unref(parent);
674 }
675 }
676 }
679 /**
680 * Read style properties from object's repr.
681 *
682 * 1. Reset existing object style
683 * 2. Load current effective object style
684 * 3. Load i attributes from immediate parent (which has to be up-to-date)
685 */
686 void
687 sp_style_read_from_object(SPStyle *style, SPObject *object)
688 {
689 g_return_if_fail(style != NULL);
690 g_return_if_fail(object != NULL);
691 g_return_if_fail(SP_IS_OBJECT(object));
693 Inkscape::XML::Node *repr = SP_OBJECT_REPR(object);
694 g_return_if_fail(repr != NULL);
696 sp_style_read(style, object, repr);
697 }
700 /**
701 * Read style properties from repr only.
702 */
703 void
704 sp_style_read_from_repr(SPStyle *style, Inkscape::XML::Node *repr)
705 {
706 g_return_if_fail(style != NULL);
707 g_return_if_fail(repr != NULL);
709 sp_style_read(style, NULL, repr);
710 }
714 /**
715 *
716 */
717 static void
718 sp_style_privatize_text(SPStyle *style)
719 {
720 SPTextStyle *text = style->text;
721 style->text = sp_text_style_duplicate_unset(style->text);
722 sp_text_style_unref(text);
723 style->text_private = TRUE;
724 }
727 /**
728 * Merge property into style.
729 *
730 * Should be called in order of highest to lowest precedence.
731 * E.g. for a single style string, call from the last declaration to the first,
732 * as CSS says that later declarations override earlier ones.
733 *
734 * \pre val != NULL.
735 */
736 static void
737 sp_style_merge_property(SPStyle *style, gint id, gchar const *val)
738 {
739 g_return_if_fail(val != NULL);
741 switch (id) {
742 /* CSS2 */
743 /* Font */
744 case SP_PROP_FONT_FAMILY:
745 if (!style->text_private) sp_style_privatize_text(style);
746 if (!style->text->font_family.set) {
747 sp_style_read_istring(&style->text->font_family, val);
748 css2_unescape_unquote(&style->text->font_family);
749 }
750 break;
751 case SP_PROP_FONT_SIZE:
752 SPS_READ_IFONTSIZE_IF_UNSET(&style->font_size, val);
753 break;
754 case SP_PROP_FONT_SIZE_ADJUST:
755 if (strcmp(val, "none") != 0) {
756 g_warning("Unimplemented style property id SP_PROP_FONT_SIZE_ADJUST: value: %s", val);
757 }
758 break;
759 case SP_PROP_FONT_STYLE:
760 SPS_READ_IENUM_IF_UNSET(&style->font_style, val, enum_font_style, true);
761 break;
762 case SP_PROP_FONT_VARIANT:
763 SPS_READ_IENUM_IF_UNSET(&style->font_variant, val, enum_font_variant, true);
764 break;
765 case SP_PROP_FONT_WEIGHT:
766 SPS_READ_IENUM_IF_UNSET(&style->font_weight, val, enum_font_weight, true);
767 break;
768 case SP_PROP_FONT_STRETCH:
769 SPS_READ_IENUM_IF_UNSET(&style->font_stretch, val, enum_font_stretch, true);
770 break;
771 case SP_PROP_FONT:
772 if (!style->text_private) sp_style_privatize_text(style);
773 if (!style->text->font.set) {
774 g_free(style->text->font.value);
775 style->text->font.value = g_strdup(val);
776 style->text->font.set = TRUE;
777 style->text->font.inherit = (val && !strcmp(val, "inherit"));
778 }
779 break;
780 /* Text */
781 case SP_PROP_TEXT_INDENT:
782 SPS_READ_ILENGTH_IF_UNSET(&style->text_indent, val);
783 break;
784 case SP_PROP_TEXT_ALIGN:
785 SPS_READ_IENUM_IF_UNSET(&style->text_align, val, enum_text_align, true);
786 break;
787 case SP_PROP_TEXT_DECORATION:
788 if (!style->text_decoration.set) {
789 sp_style_read_itextdecoration(&style->text_decoration, val);
790 }
791 break;
792 case SP_PROP_LINE_HEIGHT:
793 if (!style->line_height.set) {
794 sp_style_read_ilengthornormal(&style->line_height, val);
795 }
796 break;
797 case SP_PROP_LETTER_SPACING:
798 if (!style->letter_spacing.set) {
799 sp_style_read_ilengthornormal(&style->letter_spacing, val);
800 }
801 break;
802 case SP_PROP_WORD_SPACING:
803 if (!style->word_spacing.set) {
804 sp_style_read_ilengthornormal(&style->word_spacing, val);
805 }
806 break;
807 case SP_PROP_TEXT_TRANSFORM:
808 SPS_READ_IENUM_IF_UNSET(&style->text_transform, val, enum_text_transform, true);
809 break;
810 /* Text (css3) */
811 case SP_PROP_DIRECTION:
812 SPS_READ_IENUM_IF_UNSET(&style->direction, val, enum_direction, true);
813 break;
814 case SP_PROP_BLOCK_PROGRESSION:
815 SPS_READ_IENUM_IF_UNSET(&style->block_progression, val, enum_block_progression, true);
816 break;
817 case SP_PROP_WRITING_MODE:
818 SPS_READ_IENUM_IF_UNSET(&style->writing_mode, val, enum_writing_mode, true);
819 break;
820 case SP_PROP_TEXT_ANCHOR:
821 SPS_READ_IENUM_IF_UNSET(&style->text_anchor, val, enum_text_anchor, true);
822 break;
823 /* Text (unimplemented) */
824 case SP_PROP_TEXT_RENDERING: {
825 /* Ignore the hint. */
826 SPIEnum dummy;
827 SPS_READ_IENUM_IF_UNSET(&dummy, val, enum_text_rendering, true);
828 break;
829 }
830 case SP_PROP_ALIGNMENT_BASELINE:
831 g_warning("Unimplemented style property SP_PROP_ALIGNMENT_BASELINE: value: %s", val);
832 break;
833 case SP_PROP_BASELINE_SHIFT:
834 g_warning("Unimplemented style property SP_PROP_BASELINE_SHIFT: value: %s", val);
835 break;
836 case SP_PROP_DOMINANT_BASELINE:
837 g_warning("Unimplemented style property SP_PROP_DOMINANT_BASELINE: value: %s", val);
838 break;
839 case SP_PROP_GLYPH_ORIENTATION_HORIZONTAL:
840 g_warning("Unimplemented style property SP_PROP_ORIENTATION_HORIZONTAL: value: %s", val);
841 break;
842 case SP_PROP_GLYPH_ORIENTATION_VERTICAL:
843 g_warning("Unimplemented style property SP_PROP_ORIENTATION_VERTICAL: value: %s", val);
844 break;
845 case SP_PROP_KERNING:
846 g_warning("Unimplemented style property SP_PROP_KERNING: value: %s", val);
847 break;
848 /* Misc */
849 case SP_PROP_CLIP:
850 g_warning("Unimplemented style property SP_PROP_CLIP: value: %s", val);
851 break;
852 case SP_PROP_COLOR:
853 if (!style->color.set) {
854 sp_style_read_icolor(&style->color, val, style, (style->object) ? SP_OBJECT_DOCUMENT(style->object) : NULL);
855 }
856 break;
857 case SP_PROP_CURSOR:
858 g_warning("Unimplemented style property SP_PROP_CURSOR: value: %s", val);
859 break;
860 case SP_PROP_DISPLAY:
861 SPS_READ_IENUM_IF_UNSET(&style->display, val, enum_display, true);
862 break;
863 case SP_PROP_OVERFLOW:
864 /** \todo
865 * FIXME: not supported properly yet, we just read and write it,
866 * but act as if it is always "display".
867 */
868 SPS_READ_IENUM_IF_UNSET(&style->overflow, val, enum_overflow, true);
869 break;
870 case SP_PROP_VISIBILITY:
871 SPS_READ_IENUM_IF_UNSET(&style->visibility, val, enum_visibility, true);
872 break;
873 /* SVG */
874 /* Clip/Mask */
875 case SP_PROP_CLIP_PATH:
876 g_warning("Unimplemented style property SP_PROP_CLIP_PATH: value: %s", val);
877 break;
878 case SP_PROP_CLIP_RULE:
879 g_warning("Unimplemented style property SP_PROP_CLIP_RULE: value: %s", val);
880 break;
881 case SP_PROP_MASK:
882 g_warning("Unimplemented style property SP_PROP_MASK: value: %s", val);
883 break;
884 case SP_PROP_OPACITY:
885 if (!style->opacity.set) {
886 sp_style_read_iscale24(&style->opacity, val);
887 }
888 break;
889 case SP_PROP_ENABLE_BACKGROUND:
890 g_warning("Unimplemented style property SP_PROP_ENABLE_BACKGROUND: value: %s", val);
891 break;
892 /* Filter */
893 case SP_PROP_FILTER:
894 if (!style->filter.set && !style->filter.inherit) {
895 sp_style_read_ifilter(&style->filter, val, (style->object) ? SP_OBJECT_DOCUMENT(style->object) : NULL);
896 }
897 break;
898 case SP_PROP_FLOOD_COLOR:
899 g_warning("Unimplemented style property SP_PROP_FLOOD_COLOR: value: %s", val);
900 break;
901 case SP_PROP_FLOOD_OPACITY:
902 g_warning("Unimplemented style property SP_PROP_FLOOD_OPACITY: value: %s", val);
903 break;
904 case SP_PROP_LIGHTING_COLOR:
905 g_warning("Unimplemented style property SP_PROP_LIGHTING_COLOR: value: %s", val);
906 break;
907 /* Gradient */
908 case SP_PROP_STOP_COLOR:
909 g_warning("Unimplemented style property SP_PROP_STOP_COLOR: value: %s", val);
910 break;
911 case SP_PROP_STOP_OPACITY:
912 g_warning("Unimplemented style property SP_PROP_STOP_OPACITY: value: %s", val);
913 break;
914 /* Interactivity */
915 case SP_PROP_POINTER_EVENTS:
916 g_warning("Unimplemented style property SP_PROP_POINTER_EVENTS: value: %s", val);
917 break;
918 /* Paint */
919 case SP_PROP_COLOR_INTERPOLATION:
920 g_warning("Unimplemented style property SP_PROP_COLOR_INTERPOLATION: value: %s", val);
921 break;
922 case SP_PROP_COLOR_INTERPOLATION_FILTERS:
923 g_warning("Unimplemented style property SP_PROP_INTERPOLATION_FILTERS: value: %s", val);
924 break;
925 case SP_PROP_COLOR_PROFILE:
926 g_warning("Unimplemented style property SP_PROP_COLOR_PROFILE: value: %s", val);
927 break;
928 case SP_PROP_COLOR_RENDERING: {
929 /* Ignore the hint. */
930 SPIEnum dummy;
931 SPS_READ_IENUM_IF_UNSET(&dummy, val, enum_color_rendering, true);
932 break;
933 }
934 case SP_PROP_FILL:
935 if (!style->fill.set) {
936 sp_style_read_ipaint(&style->fill, val, style, (style->object) ? SP_OBJECT_DOCUMENT(style->object) : NULL);
937 }
938 break;
939 case SP_PROP_FILL_OPACITY:
940 if (!style->fill_opacity.set) {
941 sp_style_read_iscale24(&style->fill_opacity, val);
942 }
943 break;
944 case SP_PROP_FILL_RULE:
945 if (!style->fill_rule.set) {
946 sp_style_read_ienum(&style->fill_rule, val, enum_fill_rule, true);
947 }
948 break;
949 case SP_PROP_IMAGE_RENDERING: {
950 /* Ignore the hint. */
951 SPIEnum dummy;
952 SPS_READ_IENUM_IF_UNSET(&dummy, val, enum_image_rendering, true);
953 break;
954 }
955 case SP_PROP_MARKER:
956 /* TODO: Call sp_uri_reference_resolve(SPDocument *document, guchar const *uri) */
957 /* style->marker[SP_MARKER_LOC] = g_quark_from_string(val); */
958 marker_status("Setting SP_PROP_MARKER");
959 if (!style->marker[SP_MARKER_LOC].set) {
960 g_free(style->marker[SP_MARKER_LOC].value);
961 style->marker[SP_MARKER_LOC].value = g_strdup(val);
962 style->marker[SP_MARKER_LOC].set = TRUE;
963 style->marker[SP_MARKER_LOC].inherit = (val && !strcmp(val, "inherit"));
964 }
965 break;
967 case SP_PROP_MARKER_START:
968 /* TODO: Call sp_uri_reference_resolve(SPDocument *document, guchar const *uri) */
969 marker_status("Setting SP_PROP_MARKER_START");
970 if (!style->marker[SP_MARKER_LOC_START].set) {
971 g_free(style->marker[SP_MARKER_LOC_START].value);
972 style->marker[SP_MARKER_LOC_START].value = g_strdup(val);
973 style->marker[SP_MARKER_LOC_START].set = TRUE;
974 style->marker[SP_MARKER_LOC_START].inherit = (val && !strcmp(val, "inherit"));
975 }
976 break;
977 case SP_PROP_MARKER_MID:
978 /* TODO: Call sp_uri_reference_resolve(SPDocument *document, guchar const *uri) */
979 marker_status("Setting SP_PROP_MARKER_MID");
980 if (!style->marker[SP_MARKER_LOC_MID].set) {
981 g_free(style->marker[SP_MARKER_LOC_MID].value);
982 style->marker[SP_MARKER_LOC_MID].value = g_strdup(val);
983 style->marker[SP_MARKER_LOC_MID].set = TRUE;
984 style->marker[SP_MARKER_LOC_MID].inherit = (val && !strcmp(val, "inherit"));
985 }
986 break;
987 case SP_PROP_MARKER_END:
988 /* TODO: Call sp_uri_reference_resolve(SPDocument *document, guchar const *uri) */
989 marker_status("Setting SP_PROP_MARKER_END");
990 if (!style->marker[SP_MARKER_LOC_END].set) {
991 g_free(style->marker[SP_MARKER_LOC_END].value);
992 style->marker[SP_MARKER_LOC_END].value = g_strdup(val);
993 style->marker[SP_MARKER_LOC_END].set = TRUE;
994 style->marker[SP_MARKER_LOC_END].inherit = (val && !strcmp(val, "inherit"));
995 }
996 break;
998 case SP_PROP_SHAPE_RENDERING: {
999 /* Ignore the hint. */
1000 SPIEnum dummy;
1001 SPS_READ_IENUM_IF_UNSET(&dummy, val, enum_shape_rendering, true);
1002 break;
1003 }
1005 case SP_PROP_STROKE:
1006 if (!style->stroke.set) {
1007 sp_style_read_ipaint(&style->stroke, val, style, (style->object) ? SP_OBJECT_DOCUMENT(style->object) : NULL);
1008 }
1009 break;
1010 case SP_PROP_STROKE_WIDTH:
1011 SPS_READ_ILENGTH_IF_UNSET(&style->stroke_width, val);
1012 break;
1013 case SP_PROP_STROKE_DASHARRAY:
1014 if (!style->stroke_dasharray_set) {
1015 sp_style_read_dash(style, val);
1016 }
1017 break;
1018 case SP_PROP_STROKE_DASHOFFSET:
1019 if (!style->stroke_dashoffset_set) {
1020 /* fixme */
1021 if (sp_svg_number_read_d(val, &style->stroke_dash.offset)) {
1022 style->stroke_dashoffset_set = TRUE;
1023 } else {
1024 style->stroke_dashoffset_set = FALSE;
1025 }
1026 }
1027 break;
1028 case SP_PROP_STROKE_LINECAP:
1029 if (!style->stroke_linecap.set) {
1030 sp_style_read_ienum(&style->stroke_linecap, val, enum_stroke_linecap, true);
1031 }
1032 break;
1033 case SP_PROP_STROKE_LINEJOIN:
1034 if (!style->stroke_linejoin.set) {
1035 sp_style_read_ienum(&style->stroke_linejoin, val, enum_stroke_linejoin, true);
1036 }
1037 break;
1038 case SP_PROP_STROKE_MITERLIMIT:
1039 if (!style->stroke_miterlimit.set) {
1040 sp_style_read_ifloat(&style->stroke_miterlimit, val);
1041 }
1042 break;
1043 case SP_PROP_STROKE_OPACITY:
1044 if (!style->stroke_opacity.set) {
1045 sp_style_read_iscale24(&style->stroke_opacity, val);
1046 }
1047 break;
1049 default:
1050 g_warning("Invalid style property id: %d value: %s", id, val);
1051 break;
1052 }
1053 }
1055 static void
1056 sp_style_merge_style_from_decl(SPStyle *const style, CRDeclaration const *const decl)
1057 {
1058 /** \todo Ensure that property is lcased, as per
1059 * http://www.w3.org/TR/REC-CSS2/syndata.html#q4.
1060 * Should probably be done in libcroco.
1061 */
1062 unsigned const prop_idx = sp_attribute_lookup(decl->property->stryng->str);
1063 if (prop_idx != SP_ATTR_INVALID) {
1064 /** \todo
1065 * effic: Test whether the property is already set before trying to
1066 * convert to string. Alternatively, set from CRTerm directly rather
1067 * than converting to string.
1068 */
1069 guchar *const str_value_unsigned = cr_term_to_string(decl->value);
1070 gchar *const str_value = reinterpret_cast<gchar *>(str_value_unsigned);
1071 sp_style_merge_property(style, prop_idx, str_value);
1072 g_free(str_value);
1073 }
1074 }
1076 static void
1077 sp_style_merge_from_props(SPStyle *const style, CRPropList *const props)
1078 {
1079 #if 0 /* forwards */
1080 for (CRPropList const *cur = props; cur; cur = cr_prop_list_get_next(cur)) {
1081 CRDeclaration *decl = NULL;
1082 cr_prop_list_get_decl(cur, &decl);
1083 sp_style_merge_style_from_decl(style, decl);
1084 }
1085 #else /* in reverse order, as we need later declarations to take precedence over earlier ones. */
1086 if (props) {
1087 sp_style_merge_from_props(style, cr_prop_list_get_next(props));
1088 CRDeclaration *decl = NULL;
1089 cr_prop_list_get_decl(props, &decl);
1090 sp_style_merge_style_from_decl(style, decl);
1091 }
1092 #endif
1093 }
1095 /**
1096 * \pre decl_list != NULL
1097 */
1098 static void
1099 sp_style_merge_from_decl_list(SPStyle *const style, CRDeclaration const *const decl_list)
1100 {
1101 if (decl_list->next) {
1102 sp_style_merge_from_decl_list(style, decl_list->next);
1103 }
1104 sp_style_merge_style_from_decl(style, decl_list);
1105 }
1107 static CRSelEng *
1108 sp_repr_sel_eng()
1109 {
1110 CRSelEng *const ret = cr_sel_eng_new();
1111 cr_sel_eng_set_node_iface(ret, &Inkscape::XML::croco_node_iface);
1113 /** \todo
1114 * Check whether we need to register any pseudo-class handlers.
1115 * libcroco has its own default handlers for first-child and lang.
1116 *
1117 * We probably want handlers for link and arguably visited (though
1118 * inkscape can't visit links at the time of writing). hover etc.
1119 * more useful in inkview than the editor inkscape.
1120 *
1121 * http://www.w3.org/TR/SVG11/styling.html#StylingWithCSS says that
1122 * the following should be honoured, at least by inkview:
1123 * :hover, :active, :focus, :visited, :link.
1124 */
1126 g_assert(ret);
1127 return ret;
1128 }
1130 static void
1131 sp_style_merge_from_object_stylesheet(SPStyle *const style, SPObject const *const object)
1132 {
1133 static CRSelEng *sel_eng = NULL;
1134 if (!sel_eng) {
1135 sel_eng = sp_repr_sel_eng();
1136 }
1138 CRPropList *props = NULL;
1139 CRStatus status = cr_sel_eng_get_matched_properties_from_cascade(sel_eng,
1140 object->document->style_cascade,
1141 object->repr,
1142 &props);
1143 g_return_if_fail(status == CR_OK);
1144 /// \todo Check what errors can occur, and handle them properly.
1145 if (props) {
1146 sp_style_merge_from_props(style, props);
1147 cr_prop_list_destroy(props);
1148 }
1149 }
1151 /**
1152 * Parses a style="..." string and merges it with an existing SPStyle.
1153 */
1154 void
1155 sp_style_merge_from_style_string(SPStyle *const style, gchar const *const p)
1156 {
1157 /*
1158 * Reference: http://www.w3.org/TR/SVG11/styling.html#StyleAttribute:
1159 * ``When CSS styling is used, CSS inline style is specified by including
1160 * semicolon-separated property declarations of the form "name : value"
1161 * within the style attribute''.
1162 *
1163 * That's fairly ambiguous. Is a `value' allowed to contain semicolons?
1164 * Why does it say "including", what else is allowed in the style
1165 * attribute value?
1166 */
1168 CRDeclaration *const decl_list
1169 = cr_declaration_parse_list_from_buf(reinterpret_cast<guchar const *>(p), CR_UTF_8);
1170 if (decl_list) {
1171 sp_style_merge_from_decl_list(style, decl_list);
1172 cr_declaration_destroy(decl_list);
1173 }
1174 }
1176 /** Indexed by SP_CSS_FONT_SIZE_blah. */
1177 static float const font_size_table[] = {6.0, 8.0, 10.0, 12.0, 14.0, 18.0, 24.0};
1179 static void
1180 sp_style_merge_font_size_from_parent(SPIFontSize &child, SPIFontSize const &parent)
1181 {
1182 /* 'font-size' */
1183 if (!child.set || child.inherit) {
1184 /* Inherit the computed value. Reference: http://www.w3.org/TR/SVG11/styling.html#Inheritance */
1185 child.computed = parent.computed;
1186 } else if (child.type == SP_FONT_SIZE_LITERAL) {
1187 /** \todo
1188 * fixme: SVG and CSS do not specify clearly, whether we should use
1189 * user or screen coordinates (Lauris)
1190 */
1191 if (child.value < SP_CSS_FONT_SIZE_SMALLER) {
1192 child.computed = font_size_table[child.value];
1193 } else if (child.value == SP_CSS_FONT_SIZE_SMALLER) {
1194 child.computed = parent.computed / 1.2;
1195 } else if (child.value == SP_CSS_FONT_SIZE_LARGER) {
1196 child.computed = parent.computed * 1.2;
1197 } else {
1198 /* Illegal value */
1199 }
1200 } else if (child.type == SP_FONT_SIZE_PERCENTAGE) {
1201 /* Unlike most other lengths, percentage for font size is relative to parent computed value
1202 * rather than viewport. */
1203 child.computed = parent.computed * SP_F8_16_TO_FLOAT(child.value);
1204 }
1205 }
1207 /**
1208 * Sets computed values in \a style, which may involve inheriting from (or in some other way
1209 * calculating from) corresponding computed values of \a parent.
1210 *
1211 * References: http://www.w3.org/TR/SVG11/propidx.html shows what properties inherit by default.
1212 * http://www.w3.org/TR/SVG11/styling.html#Inheritance gives general rules as to what it means to
1213 * inherit a value. http://www.w3.org/TR/REC-CSS2/cascade.html#computed-value is more precise
1214 * about what the computed value is (not obvious for lengths).
1215 *
1216 * \pre \a parent's computed values are already up-to-date.
1217 */
1218 void
1219 sp_style_merge_from_parent(SPStyle *const style, SPStyle const *const parent)
1220 {
1221 g_return_if_fail(style != NULL);
1223 /** \todo
1224 * fixme: Check for existing callers that might pass null parent.
1225 * This should probably be g_return_if_fail, or else we should make a
1226 * best attempt to set computed values correctly without having a parent
1227 * (i.e., by assuming parent has initial values).
1228 */
1229 if (!parent)
1230 return;
1232 /* CSS2 */
1233 /* Font */
1234 sp_style_merge_font_size_from_parent(style->font_size, parent->font_size);
1236 /* 'font-style' */
1237 if (!style->font_style.set || style->font_style.inherit) {
1238 style->font_style.computed = parent->font_style.computed;
1239 }
1241 /* 'font-variant' */
1242 if (!style->font_variant.set || style->font_variant.inherit) {
1243 style->font_variant.computed = parent->font_variant.computed;
1244 }
1246 /* 'font-weight' */
1247 if (!style->font_weight.set || style->font_weight.inherit) {
1248 style->font_weight.computed = parent->font_weight.computed;
1249 } else if (style->font_weight.value == SP_CSS_FONT_WEIGHT_NORMAL) {
1250 /** \todo
1251 * fixme: This is unconditional, i.e., happens even if parent not
1252 * present.
1253 */
1254 style->font_weight.computed = SP_CSS_FONT_WEIGHT_400;
1255 } else if (style->font_weight.value == SP_CSS_FONT_WEIGHT_BOLD) {
1256 style->font_weight.computed = SP_CSS_FONT_WEIGHT_700;
1257 } else if (style->font_weight.value == SP_CSS_FONT_WEIGHT_LIGHTER) {
1258 unsigned const parent_val = parent->font_weight.computed;
1259 g_assert(SP_CSS_FONT_WEIGHT_100 == 0);
1260 // strictly, 'bolder' and 'lighter' should go to the next weight
1261 // expressible in the current font family, but that's difficult to
1262 // find out, so jumping by 3 seems an appropriate approximation
1263 style->font_weight.computed = (parent_val <= SP_CSS_FONT_WEIGHT_100 + 3
1264 ? (unsigned)SP_CSS_FONT_WEIGHT_100
1265 : parent_val - 3);
1266 g_assert(style->font_weight.computed <= (unsigned) SP_CSS_FONT_WEIGHT_900);
1267 } else if (style->font_weight.value == SP_CSS_FONT_WEIGHT_BOLDER) {
1268 unsigned const parent_val = parent->font_weight.computed;
1269 g_assert(parent_val <= SP_CSS_FONT_WEIGHT_900);
1270 style->font_weight.computed = (parent_val >= SP_CSS_FONT_WEIGHT_900 - 3
1271 ? (unsigned)SP_CSS_FONT_WEIGHT_900
1272 : parent_val + 3);
1273 g_assert(style->font_weight.computed <= (unsigned) SP_CSS_FONT_WEIGHT_900);
1274 }
1276 /* 'font-stretch' */
1277 if (!style->font_stretch.set || style->font_stretch.inherit) {
1278 style->font_stretch.computed = parent->font_stretch.computed;
1279 } else if (style->font_stretch.value == SP_CSS_FONT_STRETCH_NARROWER) {
1280 unsigned const parent_val = parent->font_stretch.computed;
1281 style->font_stretch.computed = (parent_val == SP_CSS_FONT_STRETCH_ULTRA_CONDENSED
1282 ? parent_val
1283 : parent_val - 1);
1284 g_assert(style->font_stretch.computed <= (unsigned) SP_CSS_FONT_STRETCH_ULTRA_EXPANDED);
1285 } else if (style->font_stretch.value == SP_CSS_FONT_STRETCH_WIDER) {
1286 unsigned const parent_val = parent->font_stretch.computed;
1287 g_assert(parent_val <= SP_CSS_FONT_STRETCH_ULTRA_EXPANDED);
1288 style->font_stretch.computed = (parent_val == SP_CSS_FONT_STRETCH_ULTRA_EXPANDED
1289 ? parent_val
1290 : parent_val + 1);
1291 g_assert(style->font_stretch.computed <= (unsigned) SP_CSS_FONT_STRETCH_ULTRA_EXPANDED);
1292 }
1294 /* text (css2) */
1295 if (!style->text_indent.set || style->text_indent.inherit) {
1296 style->text_indent.computed = parent->text_indent.computed;
1297 }
1299 if (!style->text_align.set || style->text_align.inherit) {
1300 style->text_align.computed = parent->text_align.computed;
1301 }
1303 if (!style->text_decoration.set || style->text_decoration.inherit) {
1304 style->text_decoration.underline = parent->text_decoration.underline;
1305 style->text_decoration.overline = parent->text_decoration.overline;
1306 style->text_decoration.line_through = parent->text_decoration.line_through;
1307 style->text_decoration.blink = parent->text_decoration.blink;
1308 }
1310 if (!style->line_height.set || style->line_height.inherit) {
1311 style->line_height.computed = parent->line_height.computed;
1312 style->line_height.normal = parent->line_height.normal;
1313 }
1315 if (!style->letter_spacing.set || style->letter_spacing.inherit) {
1316 style->letter_spacing.computed = parent->letter_spacing.computed;
1317 style->letter_spacing.normal = parent->letter_spacing.normal;
1318 }
1320 if (!style->word_spacing.set || style->word_spacing.inherit) {
1321 style->word_spacing.computed = parent->word_spacing.computed;
1322 style->word_spacing.normal = parent->word_spacing.normal;
1323 }
1325 if (!style->text_transform.set || style->text_transform.inherit) {
1326 style->text_transform.computed = parent->text_transform.computed;
1327 }
1329 if (!style->direction.set || style->direction.inherit) {
1330 style->direction.computed = parent->direction.computed;
1331 }
1333 if (!style->block_progression.set || style->block_progression.inherit) {
1334 style->block_progression.computed = parent->block_progression.computed;
1335 }
1337 if (!style->writing_mode.set || style->writing_mode.inherit) {
1338 style->writing_mode.computed = parent->writing_mode.computed;
1339 }
1341 if (!style->text_anchor.set || style->text_anchor.inherit) {
1342 style->text_anchor.computed = parent->text_anchor.computed;
1343 }
1345 if (style->opacity.inherit) {
1346 style->opacity.value = parent->opacity.value;
1347 }
1349 /* Color */
1350 if (!style->color.set || style->color.inherit) {
1351 sp_style_merge_ipaint(style, &style->color, &parent->color);
1352 }
1354 /* Fill */
1355 if (!style->fill.set || style->fill.inherit || style->fill.currentcolor) {
1356 sp_style_merge_ipaint(style, &style->fill, &parent->fill);
1357 }
1359 if (!style->fill_opacity.set || style->fill_opacity.inherit) {
1360 style->fill_opacity.value = parent->fill_opacity.value;
1361 }
1363 if (!style->fill_rule.set || style->fill_rule.inherit) {
1364 style->fill_rule.computed = parent->fill_rule.computed;
1365 }
1367 /* Stroke */
1368 if (!style->stroke.set || style->stroke.inherit || style->stroke.currentcolor) {
1369 sp_style_merge_ipaint(style, &style->stroke, &parent->stroke);
1370 }
1372 if (!style->stroke_width.set || style->stroke_width.inherit) {
1373 style->stroke_width.computed = parent->stroke_width.computed;
1374 } else {
1375 /* Update computed value for any change in font inherited from parent. */
1376 double const em = style->font_size.computed;
1377 if (style->stroke_width.unit == SP_CSS_UNIT_EM) {
1378 style->stroke_width.computed = style->stroke_width.value * em;
1379 } else if (style->stroke_width.unit == SP_CSS_UNIT_EX) {
1380 double const ex = em * 0.5; // fixme: Get x height from libnrtype or pango.
1381 style->stroke_width.computed = style->stroke_width.value * ex;
1382 }
1383 }
1385 if (!style->stroke_linecap.set || style->stroke_linecap.inherit) {
1386 style->stroke_linecap.computed = parent->stroke_linecap.computed;
1387 }
1389 if (!style->stroke_linejoin.set || style->stroke_linejoin.inherit) {
1390 style->stroke_linejoin.computed = parent->stroke_linejoin.computed;
1391 }
1393 if (!style->stroke_miterlimit.set || style->stroke_miterlimit.inherit) {
1394 style->stroke_miterlimit.value = parent->stroke_miterlimit.value;
1395 }
1397 if (!style->stroke_dasharray_set && parent->stroke_dasharray_set) {
1398 /** \todo
1399 * This code looks wrong. Why does the logic differ from the
1400 * above properties? Similarly dashoffset below.
1401 */
1402 style->stroke_dash.n_dash = parent->stroke_dash.n_dash;
1403 if (style->stroke_dash.n_dash > 0) {
1404 style->stroke_dash.dash = g_new(gdouble, style->stroke_dash.n_dash);
1405 memcpy(style->stroke_dash.dash, parent->stroke_dash.dash, style->stroke_dash.n_dash * sizeof(gdouble));
1406 }
1407 }
1409 if (!style->stroke_dashoffset_set && parent->stroke_dashoffset_set) {
1410 style->stroke_dash.offset = parent->stroke_dash.offset;
1411 }
1413 if (!style->stroke_opacity.set || style->stroke_opacity.inherit) {
1414 style->stroke_opacity.value = parent->stroke_opacity.value;
1415 }
1417 if (style->text && parent->text) {
1418 if (!style->text->font_family.set || style->text->font_family.inherit) {
1419 g_free(style->text->font_family.value);
1420 style->text->font_family.value = g_strdup(parent->text->font_family.value);
1421 }
1422 }
1424 /* Markers - Free the old value and make copy of the new */
1425 for (unsigned i = SP_MARKER_LOC; i < SP_MARKER_LOC_QTY; i++) {
1426 if (!style->marker[i].set || style->marker[i].inherit) {
1427 g_free(style->marker[i].value);
1428 style->marker[i].value = g_strdup(parent->marker[i].value);
1429 }
1430 }
1432 /* Filter effects */
1433 if(style->filter.set && style->filter.inherit) {
1434 sp_style_merge_ifilter(&style->filter, &parent->filter);
1435 }
1436 }
1438 template <typename T>
1439 static void
1440 sp_style_merge_prop_from_dying_parent(T &child, T const &parent)
1441 {
1442 if ( ( !(child.set) || child.inherit )
1443 && parent.set && !(parent.inherit) )
1444 {
1445 child = parent;
1446 }
1447 }
1449 /**
1450 * Copy SPIString from parent to child.
1451 */
1452 static void
1453 sp_style_merge_string_prop_from_dying_parent(SPIString &child, SPIString const &parent)
1454 {
1455 if ( ( !(child.set) || child.inherit )
1456 && parent.set && !(parent.inherit) )
1457 {
1458 g_free(child.value);
1459 child.value = g_strdup(parent.value);
1460 child.set = parent.set;
1461 child.inherit = parent.inherit;
1462 }
1463 }
1465 static void
1466 sp_style_merge_paint_prop_from_dying_parent(SPStyle *style,
1467 SPIPaint &child, SPIPaint const &parent)
1468 {
1469 /** \todo
1470 * I haven't given this much attention. See comments below about
1471 * currentColor, colorProfile, and relative URIs.
1472 */
1473 if (!child.set || child.inherit || child.currentcolor) {
1474 sp_style_merge_ipaint(style, &child, &parent);
1475 child.set = parent.set;
1476 child.inherit = parent.inherit;
1477 }
1478 }
1480 static void
1481 sp_style_merge_rel_enum_prop_from_dying_parent(SPIEnum &child, SPIEnum const &parent,
1482 unsigned const max_computed_val,
1483 unsigned const smaller_val)
1484 {
1485 /* We assume that min computed val is 0, contiguous up to max_computed_val,
1486 then zero or more other absolute values, then smaller_val then larger_val. */
1487 unsigned const min_computed_val = 0;
1488 unsigned const larger_val = smaller_val + 1;
1489 g_return_if_fail(min_computed_val < max_computed_val);
1490 g_return_if_fail(max_computed_val < smaller_val);
1492 if (parent.set && !parent.inherit) {
1493 if (!child.set || child.inherit) {
1494 child.value = parent.value;
1495 child.set = parent.set; // i.e. true
1496 child.inherit = parent.inherit; // i.e. false
1497 } else if (child.value < smaller_val) {
1498 /* Child has absolute value: leave as is. */
1499 } else if ( ( child.value == smaller_val
1500 && parent.value == larger_val )
1501 || ( parent.value == smaller_val
1502 && child.value == larger_val ) )
1503 {
1504 child.set = false;
1505 /*
1506 * Note that this can result in a change in computed value in the
1507 * rare case that the parent's setting was a no-op (i.e. if the
1508 * parent's parent's computed value was already ultra-condensed or
1509 * ultra-expanded). However, I'd guess that the change is for the
1510 * better: I'd guess that if the properties were specified
1511 * relatively, then the intent is to counteract parent's effect.
1512 * I.e. I believe this is the best code even in that case.
1513 */
1514 } else if (child.value == parent.value) {
1515 /* Leave as is. */
1516 /** \todo
1517 * It's unclear what to do if style and parent specify the same
1518 * relative directive (narrower or wider). We can either convert
1519 * to absolute specification or coalesce to a single relative
1520 * request (of half the strength of the original pair).
1521 *
1522 * Converting to a single level of relative specification is a
1523 * better choice if the newly-unlinked clone is itself cloned to
1524 * other contexts (inheriting different font stretchiness): it
1525 * would preserve the effect that it will be narrower than
1526 * the inherited context in each case. The disadvantage is that
1527 * it will ~certainly affect the computed value of the
1528 * newly-unlinked clone.
1529 */
1530 } else {
1531 unsigned const parent_val = parent.computed;
1532 child.value = ( child.value == smaller_val
1533 ? ( parent_val == min_computed_val
1534 ? parent_val
1535 : parent_val - 1 )
1536 : ( parent_val == max_computed_val
1537 ? parent_val
1538 : parent_val + 1 ) );
1539 g_assert(child.value <= max_computed_val);
1540 child.inherit = false;
1541 g_assert(child.set);
1542 }
1543 }
1544 }
1546 template <typename LengthT>
1547 static void
1548 sp_style_merge_length_prop_from_dying_parent(LengthT &child, LengthT const &parent,
1549 double const parent_child_em_ratio)
1550 {
1551 if ( ( !(child.set) || child.inherit )
1552 && parent.set && !(parent.inherit) )
1553 {
1554 child = parent;
1555 switch (parent.unit) {
1556 case SP_CSS_UNIT_EM:
1557 case SP_CSS_UNIT_EX:
1558 child.value *= parent_child_em_ratio;
1559 /** \todo
1560 * fixme: Have separate ex ratio parameter.
1561 * Get x height from libnrtype or pango.
1562 */
1563 if (!isFinite(child.value)) {
1564 child.value = child.computed;
1565 child.unit = SP_CSS_UNIT_NONE;
1566 }
1567 break;
1569 default:
1570 break;
1571 }
1572 }
1573 }
1575 static double
1576 get_relative_font_size_frac(SPIFontSize const &font_size)
1577 {
1578 switch (font_size.type) {
1579 case SP_FONT_SIZE_LITERAL: {
1580 switch (font_size.value) {
1581 case SP_CSS_FONT_SIZE_SMALLER:
1582 return 5.0 / 6.0;
1584 case SP_CSS_FONT_SIZE_LARGER:
1585 return 6.0 / 5.0;
1587 default:
1588 g_assert_not_reached();
1589 }
1590 }
1592 case SP_FONT_SIZE_PERCENTAGE:
1593 return SP_F8_16_TO_FLOAT(font_size.value);
1595 case SP_FONT_SIZE_LENGTH:
1596 g_assert_not_reached();
1597 }
1598 g_assert_not_reached();
1599 }
1601 /**
1602 * Combine \a style and \a parent style specifications into a single style specification that
1603 * preserves (as much as possible) the effect of the existing \a style being a child of \a parent.
1604 *
1605 * Called when the parent repr is to be removed (e.g. the parent is a \<use\> element that is being
1606 * unlinked), in which case we copy/adapt property values that are explicitly set in \a parent,
1607 * trying to retain the same visual appearance once the parent is removed. Interesting cases are
1608 * when there is unusual interaction with the parent's value (opacity, display) or when the value
1609 * can be specified as relative to the parent computed value (font-size, font-weight etc.).
1610 *
1611 * Doesn't update computed values of \a style. For correctness, you should subsequently call
1612 * sp_style_merge_from_parent against the new parent (presumably \a parent's parent) even if \a
1613 * style was previously up-to-date wrt \a parent.
1614 *
1615 * \pre \a parent's computed values are already up-to-date.
1616 * (\a style's computed values needn't be up-to-date.)
1617 */
1618 void
1619 sp_style_merge_from_dying_parent(SPStyle *const style, SPStyle const *const parent)
1620 {
1621 /** \note
1622 * The general rule for each property is as follows:
1623 *
1624 * If style is set to an absolute value, then leave it as is.
1625 *
1626 * Otherwise (i.e. if style has a relative value):
1627 *
1628 * If parent is set to an absolute value, then set style to the computed value.
1629 *
1630 * Otherwise, calculate the combined relative value (e.g. multiplying the two percentages).
1631 */
1633 /* We do font-size first, to ensure that em size is up-to-date. */
1634 /** \todo
1635 * fixme: We'll need to have more font-related things up the top once
1636 * we're getting x-height from pango or libnrtype.
1637 */
1639 /* Some things that allow relative specifications. */
1640 {
1641 /* font-size. Note that we update the computed font-size of style,
1642 to assist in em calculations later in this function. */
1643 if (parent->font_size.set && !parent->font_size.inherit) {
1644 if (!style->font_size.set || style->font_size.inherit) {
1645 /* font_size inherits the computed value, so we can use the parent value
1646 * verbatim. */
1647 style->font_size = parent->font_size;
1648 } else if ( style->font_size.type == SP_FONT_SIZE_LENGTH ) {
1649 /* Child already has absolute size (stored in computed value), so do nothing. */
1650 } else if ( style->font_size.type == SP_FONT_SIZE_LITERAL
1651 && style->font_size.value < SP_CSS_FONT_SIZE_SMALLER ) {
1652 /* Child already has absolute size, but we ensure that the computed value
1653 is up-to-date. */
1654 unsigned const ix = style->font_size.value;
1655 g_assert(ix < G_N_ELEMENTS(font_size_table));
1656 style->font_size.computed = font_size_table[ix];
1657 } else {
1658 /* Child has relative size. */
1659 double const child_frac(get_relative_font_size_frac(style->font_size));
1660 style->font_size.set = true;
1661 style->font_size.inherit = false;
1662 style->font_size.computed = parent->font_size.computed * child_frac;
1664 if ( ( parent->font_size.type == SP_FONT_SIZE_LITERAL
1665 && parent->font_size.value < SP_CSS_FONT_SIZE_SMALLER )
1666 || parent->font_size.type == SP_FONT_SIZE_LENGTH )
1667 {
1668 /* Absolute value. */
1669 style->font_size.type = SP_FONT_SIZE_LENGTH;
1670 /* .value is unused for SP_FONT_SIZE_LENGTH. */
1671 } else {
1672 /* Relative value. */
1673 double const parent_frac(get_relative_font_size_frac(parent->font_size));
1674 style->font_size.type = SP_FONT_SIZE_PERCENTAGE;
1675 style->font_size.value = SP_F8_16_FROM_FLOAT(parent_frac * child_frac);
1676 }
1677 }
1678 }
1680 /* 'font-stretch' */
1681 sp_style_merge_rel_enum_prop_from_dying_parent(style->font_stretch,
1682 parent->font_stretch,
1683 SP_CSS_FONT_STRETCH_ULTRA_EXPANDED,
1684 SP_CSS_FONT_STRETCH_NARROWER);
1686 /* font-weight */
1687 sp_style_merge_rel_enum_prop_from_dying_parent(style->font_weight,
1688 parent->font_weight,
1689 SP_CSS_FONT_WEIGHT_900,
1690 SP_CSS_FONT_WEIGHT_LIGHTER);
1691 }
1694 /* Enum values that don't have any relative settings (other than `inherit'). */
1695 {
1696 SPIEnum SPStyle::*const fields[] = {
1697 //nyi: SPStyle::clip_rule,
1698 //nyi: SPStyle::color_interpolation,
1699 //nyi: SPStyle::color_interpolation_filters,
1700 //nyi: SPStyle::color_rendering,
1701 &SPStyle::direction,
1702 &SPStyle::fill_rule,
1703 &SPStyle::font_style,
1704 &SPStyle::font_variant,
1705 //nyi: SPStyle::image_rendering,
1706 //nyi: SPStyle::pointer_events,
1707 //nyi: SPStyle::shape_rendering,
1708 &SPStyle::stroke_linecap,
1709 &SPStyle::stroke_linejoin,
1710 &SPStyle::text_anchor,
1711 //nyi: &SPStyle::text_rendering,
1712 &SPStyle::visibility,
1713 &SPStyle::writing_mode
1714 };
1716 for (unsigned i = 0; i < G_N_ELEMENTS(fields); ++i) {
1717 SPIEnum SPStyle::*const fld = fields[i];
1718 sp_style_merge_prop_from_dying_parent<SPIEnum>(style->*fld, parent->*fld);
1719 }
1720 }
1722 /* A few other simple inheritance properties. */
1723 {
1724 sp_style_merge_prop_from_dying_parent<SPIScale24>(style->fill_opacity, parent->fill_opacity);
1725 sp_style_merge_prop_from_dying_parent<SPIScale24>(style->stroke_opacity, parent->stroke_opacity);
1726 sp_style_merge_prop_from_dying_parent<SPIFloat>(style->stroke_miterlimit, parent->stroke_miterlimit);
1728 /** \todo
1729 * We currently treat text-decoration as if it were a simple inherited
1730 * property (fixme). This code may need changing once we do the
1731 * special fill/stroke inheritance mentioned by the spec.
1732 */
1733 sp_style_merge_prop_from_dying_parent<SPITextDecoration>(style->text_decoration,
1734 parent->text_decoration);
1736 //nyi: font-size-adjust, // <number> | none | inherit
1737 //nyi: glyph-orientation-horizontal,
1738 //nyi: glyph-orientation-vertical,
1739 }
1741 /* Properties that involve length but are easy in other respects. */
1742 {
1743 /* The difficulty with lengths is that font-relative units need adjusting if the font
1744 * varies between parent & child.
1745 *
1746 * Lengths specified in the existing child can stay as they are: its computed font
1747 * specification should stay unchanged, so em & ex lengths should continue to mean the same
1748 * size.
1749 *
1750 * Lengths specified in the dying parent in em or ex need to be scaled according to the
1751 * ratio of em or ex size between parent & child.
1752 */
1753 double const parent_child_em_ratio = parent->font_size.computed / style->font_size.computed;
1755 SPILength SPStyle::*const lfields[] = {
1756 &SPStyle::stroke_width,
1757 &SPStyle::text_indent
1758 };
1759 for (unsigned i = 0; i < G_N_ELEMENTS(lfields); ++i) {
1760 SPILength SPStyle::*fld = lfields[i];
1761 sp_style_merge_length_prop_from_dying_parent<SPILength>(style->*fld,
1762 parent->*fld,
1763 parent_child_em_ratio);
1764 }
1766 SPILengthOrNormal SPStyle::*const nfields[] = {
1767 &SPStyle::letter_spacing,
1768 &SPStyle::line_height,
1769 &SPStyle::word_spacing
1770 };
1771 for (unsigned i = 0; i < G_N_ELEMENTS(nfields); ++i) {
1772 SPILengthOrNormal SPStyle::*fld = nfields[i];
1773 sp_style_merge_length_prop_from_dying_parent<SPILengthOrNormal>(style->*fld,
1774 parent->*fld,
1775 parent_child_em_ratio);
1776 }
1778 //nyi: &SPStyle::kerning: length or `auto'
1780 /* fixme: Move stroke-dash and stroke-dash-offset here once they
1781 can accept units. */
1782 }
1784 /* Properties that involve a URI but are easy in other respects. */
1785 {
1786 /** \todo
1787 * Could cause problems if original object was in another document
1788 * and it used a relative URL. (At the time of writing, we don't
1789 * allow hrefs to other documents, so this isn't a problem yet.)
1790 * Paint properties also allow URIs.
1791 */
1792 //nyi: cursor, // may involve change in effect, but we can't do much better
1793 //nyi: color-profile,
1795 // Markers (marker-start, marker-mid, marker-end).
1796 for (unsigned i = SP_MARKER_LOC; i < SP_MARKER_LOC_QTY; i++) {
1797 sp_style_merge_string_prop_from_dying_parent(style->marker[i], parent->marker[i]);
1798 }
1799 }
1801 /* CSS2 */
1802 /* Font */
1804 if (style->text && parent->text) {
1805 sp_style_merge_string_prop_from_dying_parent(style->text->font_family,
1806 parent->text->font_family);
1807 }
1809 /* Properties that don't inherit by default. Most of these need special handling. */
1810 {
1811 /*
1812 * opacity's effect is cumulative; we set the new value to the combined effect. The
1813 * default value for opacity is 1.0, not inherit. (Note that stroke-opacity and
1814 * fill-opacity are quite different from opacity, and don't need any special handling.)
1815 *
1816 * Cases:
1817 * - parent & child were each previously unset, in which case the effective
1818 * opacity value is 1.0, and style should remain unset.
1819 * - parent was previously unset (so computed opacity value of 1.0)
1820 * and child was set to inherit. The merged child should
1821 * get a value of 1.0, and shouldn't inherit (lest the new parent
1822 * has a different opacity value). Given that opacity's default
1823 * value is 1.0 (rather than inherit), we might as well have the
1824 * merged child's opacity be unset.
1825 * - parent was previously unset (so opacity 1.0), and child was set to a number.
1826 * The merged child should retain its existing settings (though it doesn't matter
1827 * if we make it unset if that number was 1.0).
1828 * - parent was inherit and child was unset. Merged child should be set to inherit.
1829 * - parent was inherit and child was inherit. (We can't in general reproduce this
1830 * effect (short of introducing a new group), but setting opacity to inherit is rare.)
1831 * If the inherited value was strictly between 0.0 and 1.0 (exclusive) then the merged
1832 * child's value should be set to the product of the two, i.e. the square of the
1833 * inherited value, and should not be marked as inherit. (This decision assumes that it
1834 * is more important to retain the effective opacity than to retain the inheriting
1835 * effect, and assumes that the inheriting effect either isn't important enough to create
1836 * a group or isn't common enough to bother maintaining the code to create a group.) If
1837 * the inherited value was 0.0 or 1.0, then marking the merged child as inherit comes
1838 * closer to maintaining the effect.
1839 * - parent was inherit and child was set to a numerical value. If the child's value
1840 * was 1.0, then the merged child should have the same settings as the parent.
1841 * If the child's value was 0, then the merged child should also be set to 0.
1842 * If the child's value was anything else, then we do the same as for the inherit/inherit
1843 * case above: have the merged child set to the product of the two opacities and not
1844 * marked as inherit, for the same reasons as for that case.
1845 * - parent was set to a value, and child was unset. The merged child should have
1846 * parent's settings.
1847 * - parent was set to a value, and child was inherit. The merged child should
1848 * be set to the product, i.e. the square of the parent's value.
1849 * - parent & child are each set to a value. The merged child should be set to the
1850 * product.
1851 */
1852 if ( !style->opacity.set
1853 || ( !style->opacity.inherit
1854 && style->opacity.value == SP_SCALE24_MAX ) )
1855 {
1856 style->opacity = parent->opacity;
1857 } else {
1858 /* Ensure that style's computed value is up-to-date. */
1859 if (style->opacity.inherit) {
1860 style->opacity.value = parent->opacity.value;
1861 }
1863 /* Multiplication of opacities occurs even if a child's opacity is set to inherit. */
1864 style->opacity.value = SP_SCALE24_MUL(style->opacity.value,
1865 parent->opacity.value);
1867 style->opacity.inherit = (parent->opacity.inherit
1868 && style->opacity.inherit
1869 && (parent->opacity.value == 0 ||
1870 parent->opacity.value == SP_SCALE24_MAX));
1871 style->opacity.set = ( style->opacity.inherit
1872 || style->opacity.value < SP_SCALE24_MAX );
1873 }
1875 /* display is in principle similar to opacity, but implementation is easier. */
1876 if ( parent->display.set && !parent->display.inherit
1877 && parent->display.value == SP_CSS_DISPLAY_NONE ) {
1878 style->display.value = SP_CSS_DISPLAY_NONE;
1879 style->display.set = true;
1880 style->display.inherit = false;
1881 } else if (style->display.inherit) {
1882 style->display.value = parent->display.value;
1883 style->display.set = parent->display.set;
1884 style->display.inherit = parent->display.inherit;
1885 } else {
1886 /* Leave as is. (display doesn't inherit by default.) */
1887 }
1889 /** \todo
1890 * fixme: Check that we correctly handle all properties that don't
1891 * inherit by default (as shown in
1892 * http://www.w3.org/TR/SVG11/propidx.html for most SVG 1.1 properties).
1893 */
1894 }
1896 /* SPIPaint properties (including color). */
1897 {
1898 /** \todo
1899 * Think about the issues involved if specified as currentColor or
1900 * if specified relative to colorProfile, and if the currentColor or
1901 * colorProfile differs between parent \& child. See also comments
1902 * elsewhere in this function about URIs.
1903 */
1904 SPIPaint SPStyle::*const fields[] = {
1905 &SPStyle::color,
1906 &SPStyle::fill,
1907 &SPStyle::stroke
1908 };
1909 for (unsigned i = 0; i < G_N_ELEMENTS(fields); ++i) {
1910 SPIPaint SPStyle::*const fld = fields[i];
1911 sp_style_merge_paint_prop_from_dying_parent(style, style->*fld, parent->*fld);
1912 }
1913 }
1915 /* Things from SVG 1.2 or CSS3. */
1916 {
1917 /* Note: If we ever support setting string values for text-align then we'd need strdup
1918 * handling here. */
1919 sp_style_merge_prop_from_dying_parent<SPIEnum>(style->text_align, parent->text_align);
1921 sp_style_merge_prop_from_dying_parent<SPIEnum>(style->text_transform, parent->text_transform);
1922 sp_style_merge_prop_from_dying_parent<SPIEnum>(style->block_progression, parent->block_progression);
1923 }
1925 /* Note: this will need length handling once dasharray supports units. */
1926 if ( ( !style->stroke_dasharray_set || style->stroke_dasharray_inherit )
1927 && parent->stroke_dasharray_set && !parent->stroke_dasharray_inherit )
1928 {
1929 style->stroke_dash.n_dash = parent->stroke_dash.n_dash;
1930 if (style->stroke_dash.n_dash > 0) {
1931 style->stroke_dash.dash = g_new(gdouble, style->stroke_dash.n_dash);
1932 memcpy(style->stroke_dash.dash, parent->stroke_dash.dash, style->stroke_dash.n_dash * sizeof(gdouble));
1933 }
1934 style->stroke_dasharray_set = parent->stroke_dasharray_set;
1935 style->stroke_dasharray_inherit = parent->stroke_dasharray_inherit;
1936 }
1938 /* Note: this will need length handling once dasharray_offset supports units. */
1939 if (!style->stroke_dashoffset_set && parent->stroke_dashoffset_set) {
1940 style->stroke_dash.offset = parent->stroke_dash.offset;
1941 style->stroke_dashoffset_set = parent->stroke_dashoffset_set;
1942 /* fixme: we don't currently allow stroke-dashoffset to be `inherit'. TODO: Try to
1943 * represent it as a normal SPILength; though will need to do something about existing
1944 * users of stroke_dash.offset and stroke_dashoffset_set. */
1945 }
1947 /* TODO: deal with filters */
1948 }
1952 /**
1953 * Disconnects from possible fill and stroke paint servers.
1954 */
1955 static void
1956 sp_style_paint_server_release(SPPaintServer *server, SPStyle *style)
1957 {
1958 if ((style->fill.type == SP_PAINT_TYPE_PAINTSERVER)
1959 && (server == style->fill.value.paint.server))
1960 {
1961 sp_style_paint_clear(style, &style->fill);
1962 }
1964 if ((style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)
1965 && (server == style->stroke.value.paint.server))
1966 {
1967 sp_style_paint_clear(style, &style->stroke);
1968 }
1969 }
1974 /**
1975 * Emit style modified signal on style's object if server is style's fill
1976 * or stroke paint server.
1977 */
1978 static void
1979 sp_style_paint_server_modified(SPPaintServer *server, guint flags, SPStyle *style)
1980 {
1981 if ((style->fill.type == SP_PAINT_TYPE_PAINTSERVER)
1982 && (server == style->fill.value.paint.server))
1983 {
1984 if (style->object) {
1985 /** \todo
1986 * fixme: I do not know, whether it is optimal - we are
1987 * forcing reread of everything (Lauris)
1988 */
1989 /** \todo
1990 * fixme: We have to use object_modified flag, because parent
1991 * flag is only available downstreams.
1992 */
1993 style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1994 }
1995 } else if ((style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)
1996 && (server == style->stroke.value.paint.server))
1997 {
1998 if (style->object) {
1999 /// \todo fixme:
2000 style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
2001 }
2002 } else {
2003 g_assert_not_reached();
2004 }
2005 }
2009 /**
2010 *
2011 */
2012 static void
2013 sp_style_merge_ipaint(SPStyle *style, SPIPaint *paint, SPIPaint const *parent)
2014 {
2015 sp_style_paint_clear(style, paint);
2017 if ((paint->set && paint->currentcolor) || parent->currentcolor) {
2018 paint->currentcolor = TRUE;
2019 paint->type = SP_PAINT_TYPE_COLOR;
2020 sp_color_copy(&paint->value.color, &style->color.value.color);
2021 return;
2022 }
2024 paint->type = parent->type;
2025 switch (paint->type) {
2026 case SP_PAINT_TYPE_COLOR:
2027 sp_color_copy(&paint->value.color, &parent->value.color);
2028 break;
2029 case SP_PAINT_TYPE_PAINTSERVER:
2030 paint->value.paint.server = parent->value.paint.server;
2031 paint->value.paint.uri = parent->value.paint.uri;
2032 if (paint->value.paint.server) {
2033 if (style->object && !style->cloned) { // href paintserver for style of non-clones only
2034 sp_object_href(SP_OBJECT(paint->value.paint.server), style);
2035 if (paint == &style->fill) {
2036 style->fill_hreffed = true;
2037 } else {
2038 assert(paint == &style->stroke);
2039 style->stroke_hreffed = true;
2040 }
2041 }
2042 if (style->object || style->cloned) { // connect to signals for style of real objects or clones (this excludes temp styles)
2043 g_signal_connect(G_OBJECT(paint->value.paint.server), "release",
2044 G_CALLBACK(sp_style_paint_server_release), style);
2045 g_signal_connect(G_OBJECT(paint->value.paint.server), "modified",
2046 G_CALLBACK(sp_style_paint_server_modified), style);
2047 if (paint == &style->fill) {
2048 style->fill_listening = true;
2049 } else {
2050 assert(paint == &style->stroke);
2051 style->stroke_listening = true;
2052 }
2053 }
2054 }
2055 break;
2056 case SP_PAINT_TYPE_NONE:
2057 break;
2058 default:
2059 g_assert_not_reached();
2060 break;
2061 }
2062 }
2065 /**
2066 * Merge filter style from parent.
2067 * Filter effects do not inherit by default
2068 */
2069 static void
2070 sp_style_merge_ifilter(SPIFilter *child, SPIFilter const *parent)
2071 {
2072 child->set = parent->set;
2073 child->inherit = parent->inherit;
2074 child->filter = parent->filter;
2075 child->uri = parent->uri;
2076 }
2078 /**
2079 * Dumps the style to a CSS string, with either SP_STYLE_FLAG_IFSET or
2080 * SP_STYLE_FLAG_ALWAYS flags. Used with Always for copying an object's
2081 * complete cascaded style to style_clipboard. When you need a CSS string
2082 * for an object in the document tree, you normally call
2083 * sp_style_write_difference instead to take into account the object's parent.
2084 *
2085 * \pre style != NULL.
2086 * \pre flags in {IFSET, ALWAYS}.
2087 * \post ret != NULL.
2088 */
2089 gchar *
2090 sp_style_write_string(SPStyle const *const style, guint const flags)
2091 {
2092 /** \todo
2093 * Merge with write_difference, much duplicate code!
2094 */
2095 g_return_val_if_fail(style != NULL, NULL);
2096 g_return_val_if_fail(((flags == SP_STYLE_FLAG_IFSET) ||
2097 (flags == SP_STYLE_FLAG_ALWAYS) ),
2098 NULL);
2100 gchar c[BMAX];
2101 gchar *p = c;
2102 *p = '\0';
2104 p += sp_style_write_ifontsize(p, c + BMAX - p, "font-size", &style->font_size, NULL, flags);
2105 p += sp_style_write_ienum(p, c + BMAX - p, "font-style", enum_font_style, &style->font_style, NULL, flags);
2106 p += sp_style_write_ienum(p, c + BMAX - p, "font-variant", enum_font_variant, &style->font_variant, NULL, flags);
2107 p += sp_style_write_ienum(p, c + BMAX - p, "font-weight", enum_font_weight, &style->font_weight, NULL, flags);
2108 p += sp_style_write_ienum(p, c + BMAX - p, "font-stretch", enum_font_stretch, &style->font_stretch, NULL, flags);
2110 /* Text */
2111 p += sp_style_write_ilength(p, c + BMAX - p, "text-indent", &style->text_indent, NULL, flags);
2112 p += sp_style_write_ienum(p, c + BMAX - p, "text-align", enum_text_align, &style->text_align, NULL, flags);
2113 p += sp_style_write_itextdecoration(p, c + BMAX - p, "text-decoration", &style->text_decoration, NULL, flags);
2114 p += sp_style_write_ilengthornormal(p, c + BMAX - p, "line-height", &style->line_height, NULL, flags);
2115 p += sp_style_write_ilengthornormal(p, c + BMAX - p, "letter-spacing", &style->letter_spacing, NULL, flags);
2116 p += sp_style_write_ilengthornormal(p, c + BMAX - p, "word-spacing", &style->word_spacing, NULL, flags);
2117 p += sp_style_write_ienum(p, c + BMAX - p, "text-transform", enum_text_transform, &style->text_transform, NULL, flags);
2118 p += sp_style_write_ienum(p, c + BMAX - p, "direction", enum_direction, &style->direction, NULL, flags);
2119 p += sp_style_write_ienum(p, c + BMAX - p, "block-progression", enum_block_progression, &style->block_progression, NULL, flags);
2120 p += sp_style_write_ienum(p, c + BMAX - p, "writing-mode", enum_writing_mode, &style->writing_mode, NULL, flags);
2122 p += sp_style_write_ienum(p, c + BMAX - p, "text-anchor", enum_text_anchor, &style->text_anchor, NULL, flags);
2124 /// \todo fixme: Per type methods need default flag too (lauris)
2125 p += sp_style_write_iscale24(p, c + BMAX - p, "opacity", &style->opacity, NULL, flags);
2126 p += sp_style_write_ipaint(p, c + BMAX - p, "color", &style->color, NULL, flags);
2127 p += sp_style_write_ipaint(p, c + BMAX - p, "fill", &style->fill, NULL, flags);
2128 p += sp_style_write_iscale24(p, c + BMAX - p, "fill-opacity", &style->fill_opacity, NULL, flags);
2129 p += sp_style_write_ienum(p, c + BMAX - p, "fill-rule", enum_fill_rule, &style->fill_rule, NULL, flags);
2130 p += sp_style_write_ipaint(p, c + BMAX - p, "stroke", &style->stroke, NULL, flags);
2131 p += sp_style_write_ilength(p, c + BMAX - p, "stroke-width", &style->stroke_width, NULL, flags);
2132 p += sp_style_write_ienum(p, c + BMAX - p, "stroke-linecap", enum_stroke_linecap, &style->stroke_linecap, NULL, flags);
2133 p += sp_style_write_ienum(p, c + BMAX - p, "stroke-linejoin", enum_stroke_linejoin, &style->stroke_linejoin, NULL, flags);
2135 marker_status("sp_style_write_string: Writing markers");
2136 if (style->marker[SP_MARKER_LOC].set) {
2137 p += g_snprintf(p, c + BMAX - p, "marker:%s;", style->marker[SP_MARKER_LOC].value);
2138 } else if (flags == SP_STYLE_FLAG_ALWAYS) {
2139 p += g_snprintf(p, c + BMAX - p, "marker:none;");
2140 }
2141 if (style->marker[SP_MARKER_LOC_START].set) {
2142 p += g_snprintf(p, c + BMAX - p, "marker-start:%s;", style->marker[SP_MARKER_LOC_START].value);
2143 } else if (flags == SP_STYLE_FLAG_ALWAYS) {
2144 p += g_snprintf(p, c + BMAX - p, "marker-start:none;");
2145 }
2146 if (style->marker[SP_MARKER_LOC_MID].set) {
2147 p += g_snprintf(p, c + BMAX - p, "marker-mid:%s;", style->marker[SP_MARKER_LOC_MID].value);
2148 } else if (flags == SP_STYLE_FLAG_ALWAYS) {
2149 p += g_snprintf(p, c + BMAX - p, "marker-mid:none;");
2150 }
2151 if (style->marker[SP_MARKER_LOC_END].set) {
2152 p += g_snprintf(p, c + BMAX - p, "marker-end:%s;", style->marker[SP_MARKER_LOC_END].value);
2153 } else if (flags == SP_STYLE_FLAG_ALWAYS) {
2154 p += g_snprintf(p, c + BMAX - p, "marker-end:none;");
2155 }
2157 p += sp_style_write_ifloat(p, c + BMAX - p, "stroke-miterlimit", &style->stroke_miterlimit, NULL, flags);
2159 /** \todo fixme: */
2160 if ((flags == SP_STYLE_FLAG_ALWAYS)
2161 || style->stroke_dasharray_set)
2162 {
2163 if (style->stroke_dasharray_inherit) {
2164 p += g_snprintf(p, c + BMAX - p, "stroke-dasharray:inherit;");
2165 } else if (style->stroke_dash.n_dash && style->stroke_dash.dash) {
2166 p += g_snprintf(p, c + BMAX - p, "stroke-dasharray:");
2167 gint i;
2168 for (i = 0; i < style->stroke_dash.n_dash; i++) {
2169 Inkscape::CSSOStringStream os;
2170 if (i) {
2171 os << ", ";
2172 }
2173 os << style->stroke_dash.dash[i];
2174 p += g_strlcpy(p, os.str().c_str(), c + BMAX - p);
2175 }
2176 if (p < c + BMAX) {
2177 *p++ = ';';
2178 }
2179 } else {
2180 p += g_snprintf(p, c + BMAX - p, "stroke-dasharray:none;");
2181 }
2182 }
2184 /** \todo fixme: */
2185 if (style->stroke_dashoffset_set) {
2186 Inkscape::CSSOStringStream os;
2187 os << "stroke-dashoffset:" << style->stroke_dash.offset << ";";
2188 p += g_strlcpy(p, os.str().c_str(), c + BMAX - p);
2189 } else if (flags == SP_STYLE_FLAG_ALWAYS) {
2190 p += g_snprintf(p, c + BMAX - p, "stroke-dashoffset:0;");
2191 }
2193 p += sp_style_write_iscale24(p, c + BMAX - p, "stroke-opacity", &style->stroke_opacity, NULL, flags);
2195 p += sp_style_write_ienum(p, c + BMAX - p, "visibility", enum_visibility, &style->visibility, NULL, flags);
2196 p += sp_style_write_ienum(p, c + BMAX - p, "display", enum_display, &style->display, NULL, flags);
2197 p += sp_style_write_ienum(p, c + BMAX - p, "overflow", enum_overflow, &style->overflow, NULL, flags);
2199 /* filter: */
2200 p += sp_style_write_ifilter(p, c + BMAX - p, "filter", &style->filter, NULL, flags);
2202 /* fixme: */
2203 p += sp_text_style_write(p, c + BMAX - p, style->text, flags);
2205 /* Get rid of trailing `;'. */
2206 if (p != c) {
2207 --p;
2208 if (*p == ';') {
2209 *p = '\0';
2210 }
2211 }
2213 return g_strdup(c);
2214 }
2217 #define STYLE_BUF_MAX
2220 /**
2221 * Dumps style to CSS string, see sp_style_write_string()
2222 *
2223 * \pre from != NULL.
2224 * \pre to != NULL.
2225 * \post ret != NULL.
2226 */
2227 gchar *
2228 sp_style_write_difference(SPStyle const *const from, SPStyle const *const to)
2229 {
2230 g_return_val_if_fail(from != NULL, NULL);
2231 g_return_val_if_fail(to != NULL, NULL);
2233 gchar c[BMAX], *p = c;
2234 *p = '\0';
2236 p += sp_style_write_ifontsize(p, c + BMAX - p, "font-size", &from->font_size, &to->font_size, SP_STYLE_FLAG_IFDIFF);
2237 p += sp_style_write_ienum(p, c + BMAX - p, "font-style", enum_font_style, &from->font_style, &to->font_style, SP_STYLE_FLAG_IFDIFF);
2238 p += sp_style_write_ienum(p, c + BMAX - p, "font-variant", enum_font_variant, &from->font_variant, &to->font_variant, SP_STYLE_FLAG_IFDIFF);
2239 p += sp_style_write_ienum(p, c + BMAX - p, "font-weight", enum_font_weight, &from->font_weight, &to->font_weight, SP_STYLE_FLAG_IFDIFF);
2240 p += sp_style_write_ienum(p, c + BMAX - p, "font-stretch", enum_font_stretch, &from->font_stretch, &to->font_stretch, SP_STYLE_FLAG_IFDIFF);
2242 /* Text */
2243 p += sp_style_write_ilength(p, c + BMAX - p, "text-indent", &from->text_indent, &to->text_indent, SP_STYLE_FLAG_IFDIFF);
2244 p += sp_style_write_ienum(p, c + BMAX - p, "text-align", enum_text_align, &from->text_align, &to->text_align, SP_STYLE_FLAG_IFDIFF);
2245 p += sp_style_write_itextdecoration(p, c + BMAX - p, "text-decoration", &from->text_decoration, &to->text_decoration, SP_STYLE_FLAG_IFDIFF);
2246 p += sp_style_write_ilengthornormal(p, c + BMAX - p, "line-height", &from->line_height, &to->line_height, SP_STYLE_FLAG_IFDIFF);
2247 p += sp_style_write_ilengthornormal(p, c + BMAX - p, "letter-spacing", &from->letter_spacing, &to->letter_spacing, SP_STYLE_FLAG_IFDIFF);
2248 p += sp_style_write_ilengthornormal(p, c + BMAX - p, "word-spacing", &from->word_spacing, &to->word_spacing, SP_STYLE_FLAG_IFDIFF);
2249 p += sp_style_write_ienum(p, c + BMAX - p, "text-transform", enum_text_transform, &from->text_transform, &to->text_transform, SP_STYLE_FLAG_IFDIFF);
2250 p += sp_style_write_ienum(p, c + BMAX - p, "direction", enum_direction, &from->direction, &to->direction, SP_STYLE_FLAG_IFDIFF);
2251 p += sp_style_write_ienum(p, c + BMAX - p, "block-progression", enum_block_progression, &from->block_progression, &to->block_progression, SP_STYLE_FLAG_IFDIFF);
2252 p += sp_style_write_ienum(p, c + BMAX - p, "writing-mode", enum_writing_mode, &from->writing_mode, &to->writing_mode, SP_STYLE_FLAG_IFDIFF);
2254 p += sp_style_write_ienum(p, c + BMAX - p, "text-anchor", enum_text_anchor, &from->text_anchor, &to->text_anchor, SP_STYLE_FLAG_IFDIFF);
2256 /// \todo fixme: Per type methods need default flag too
2257 if (from->opacity.set && from->opacity.value != SP_SCALE24_MAX) {
2258 p += sp_style_write_iscale24(p, c + BMAX - p, "opacity", &from->opacity, &to->opacity, SP_STYLE_FLAG_IFSET);
2259 }
2260 p += sp_style_write_ipaint(p, c + BMAX - p, "color", &from->color, &to->color, SP_STYLE_FLAG_IFSET);
2261 p += sp_style_write_ipaint(p, c + BMAX - p, "fill", &from->fill, &to->fill, SP_STYLE_FLAG_IFDIFF);
2262 p += sp_style_write_iscale24(p, c + BMAX - p, "fill-opacity", &from->fill_opacity, &to->fill_opacity, SP_STYLE_FLAG_IFDIFF);
2263 p += sp_style_write_ienum(p, c + BMAX - p, "fill-rule", enum_fill_rule, &from->fill_rule, &to->fill_rule, SP_STYLE_FLAG_IFDIFF);
2264 p += sp_style_write_ipaint(p, c + BMAX - p, "stroke", &from->stroke, &to->stroke, SP_STYLE_FLAG_IFDIFF);
2265 p += sp_style_write_ilength(p, c + BMAX - p, "stroke-width", &from->stroke_width, &to->stroke_width, SP_STYLE_FLAG_IFDIFF);
2266 p += sp_style_write_ienum(p, c + BMAX - p, "stroke-linecap", enum_stroke_linecap,
2267 &from->stroke_linecap, &to->stroke_linecap, SP_STYLE_FLAG_IFDIFF);
2268 p += sp_style_write_ienum(p, c + BMAX - p, "stroke-linejoin", enum_stroke_linejoin,
2269 &from->stroke_linejoin, &to->stroke_linejoin, SP_STYLE_FLAG_IFDIFF);
2270 p += sp_style_write_ifloat(p, c + BMAX - p, "stroke-miterlimit",
2271 &from->stroke_miterlimit, &to->stroke_miterlimit, SP_STYLE_FLAG_IFDIFF);
2272 /** \todo fixme: */
2273 if (from->stroke_dasharray_set) {
2274 if (from->stroke_dasharray_inherit) {
2275 p += g_snprintf(p, c + BMAX - p, "stroke-dasharray:inherit;");
2276 } else if (from->stroke_dash.n_dash && from->stroke_dash.dash) {
2277 p += g_snprintf(p, c + BMAX - p, "stroke-dasharray:");
2278 for (gint i = 0; i < from->stroke_dash.n_dash; i++) {
2279 Inkscape::CSSOStringStream os;
2280 if (i) {
2281 os << ", ";
2282 }
2283 os << from->stroke_dash.dash[i];
2284 p += g_strlcpy(p, os.str().c_str(), c + BMAX - p);
2285 }
2286 p += g_snprintf(p, c + BMAX - p, ";");
2287 }
2288 }
2289 /* fixme: */
2290 if (from->stroke_dashoffset_set) {
2291 Inkscape::CSSOStringStream os;
2292 os << "stroke-dashoffset:" << from->stroke_dash.offset << ";";
2293 p += g_strlcpy(p, os.str().c_str(), c + BMAX - p);
2294 }
2295 p += sp_style_write_iscale24(p, c + BMAX - p, "stroke-opacity", &from->stroke_opacity, &to->stroke_opacity, SP_STYLE_FLAG_IFDIFF);
2297 /* markers */
2298 marker_status("sp_style_write_difference: Writing markers");
2299 if (from->marker[SP_MARKER_LOC].value != NULL) {
2300 p += g_snprintf(p, c + BMAX - p, "marker:%s;", from->marker[SP_MARKER_LOC].value);
2301 }
2302 if (from->marker[SP_MARKER_LOC_START].value != NULL) {
2303 p += g_snprintf(p, c + BMAX - p, "marker-start:%s;", from->marker[SP_MARKER_LOC_START].value);
2304 }
2305 if (from->marker[SP_MARKER_LOC_MID].value != NULL) {
2306 p += g_snprintf(p, c + BMAX - p, "marker-mid:%s;", from->marker[SP_MARKER_LOC_MID].value);
2307 }
2308 if (from->marker[SP_MARKER_LOC_END].value != NULL) {
2309 p += g_snprintf(p, c + BMAX - p, "marker-end:%s;", from->marker[SP_MARKER_LOC_END].value);
2310 }
2312 p += sp_style_write_ienum(p, c + BMAX - p, "visibility", enum_visibility, &from->visibility, &to->visibility, SP_STYLE_FLAG_IFSET);
2313 p += sp_style_write_ienum(p, c + BMAX - p, "display", enum_display, &from->display, &to->display, SP_STYLE_FLAG_IFSET);
2314 p += sp_style_write_ienum(p, c + BMAX - p, "overflow", enum_overflow, &from->overflow, &to->overflow, SP_STYLE_FLAG_IFSET);
2316 /* filter: */
2317 p += sp_style_write_ifilter(p, c + BMAX - p, "filter", &from->filter, &to->filter, SP_STYLE_FLAG_IFDIFF);
2319 p += sp_text_style_write(p, c + BMAX - p, from->text, SP_STYLE_FLAG_IFDIFF);
2321 /** \todo
2322 * The reason we use IFSET rather than IFDIFF is the belief that the IFDIFF
2323 * flag is mainly only for attributes that don't handle explicit unset well.
2324 * We may need to revisit the behaviour of this routine.
2325 */
2327 /* Get rid of trailing `;'. */
2328 if (p != c) {
2329 --p;
2330 if (*p == ';') {
2331 *p = '\0';
2332 }
2333 }
2335 return g_strdup(c);
2336 }
2340 /**
2341 * Reset all style properties.
2342 */
2343 static void
2344 sp_style_clear(SPStyle *style)
2345 {
2346 g_return_if_fail(style != NULL);
2348 sp_style_paint_clear(style, &style->fill);
2349 sp_style_paint_clear(style, &style->stroke);
2350 if (style->stroke_dash.dash) {
2351 g_free(style->stroke_dash.dash);
2352 }
2354 /** \todo fixme: Do that text manipulation via parents */
2355 SPObject *object = style->object;
2356 gint const refcount = style->refcount;
2357 SPTextStyle *text = style->text;
2358 unsigned const text_private = style->text_private;
2359 memset(style, 0, sizeof(SPStyle));
2360 style->refcount = refcount;
2361 style->object = object;
2362 style->text = text;
2363 style->text_private = text_private;
2364 /* fixme: */
2365 style->text->font.set = FALSE;
2366 style->text->font_family.set = FALSE;
2368 style->font_size.set = FALSE;
2369 style->font_size.type = SP_FONT_SIZE_LITERAL;
2370 style->font_size.value = SP_CSS_FONT_SIZE_MEDIUM;
2371 style->font_size.computed = 12.0;
2372 style->font_style.set = FALSE;
2373 style->font_style.value = style->font_style.computed = SP_CSS_FONT_STYLE_NORMAL;
2374 style->font_variant.set = FALSE;
2375 style->font_variant.value = style->font_variant.computed = SP_CSS_FONT_VARIANT_NORMAL;
2376 style->font_weight.set = FALSE;
2377 style->font_weight.value = SP_CSS_FONT_WEIGHT_NORMAL;
2378 style->font_weight.computed = SP_CSS_FONT_WEIGHT_400;
2379 style->font_stretch.set = FALSE;
2380 style->font_stretch.value = style->font_stretch.computed = SP_CSS_FONT_STRETCH_NORMAL;
2382 /* text */
2383 style->text_indent.set = FALSE;
2384 style->text_indent.unit = SP_CSS_UNIT_NONE;
2385 style->text_indent.computed = 0.0;
2387 style->text_align.set = FALSE;
2388 style->text_align.value = style->text_align.computed = SP_CSS_TEXT_ALIGN_START;
2390 style->text_decoration.set = FALSE;
2391 style->text_decoration.underline = FALSE;
2392 style->text_decoration.overline = FALSE;
2393 style->text_decoration.line_through = FALSE;
2394 style->text_decoration.blink = FALSE;
2396 style->line_height.set = FALSE;
2397 style->line_height.unit = SP_CSS_UNIT_PERCENT;
2398 style->line_height.normal = TRUE;
2399 style->line_height.value = style->line_height.computed = 1.0;
2401 style->letter_spacing.set = FALSE;
2402 style->letter_spacing.unit = SP_CSS_UNIT_NONE;
2403 style->letter_spacing.normal = TRUE;
2404 style->letter_spacing.value = style->letter_spacing.computed = 0.0;
2406 style->word_spacing.set = FALSE;
2407 style->word_spacing.unit = SP_CSS_UNIT_NONE;
2408 style->word_spacing.normal = TRUE;
2409 style->word_spacing.value = style->word_spacing.computed = 0.0;
2411 style->text_transform.set = FALSE;
2412 style->text_transform.value = style->text_transform.computed = SP_CSS_TEXT_TRANSFORM_NONE;
2414 style->direction.set = FALSE;
2415 style->direction.value = style->direction.computed = SP_CSS_DIRECTION_LTR;
2417 style->block_progression.set = FALSE;
2418 style->block_progression.value = style->block_progression.computed = SP_CSS_BLOCK_PROGRESSION_TB;
2420 style->writing_mode.set = FALSE;
2421 style->writing_mode.value = style->writing_mode.computed = SP_CSS_WRITING_MODE_LR_TB;
2424 style->text_anchor.set = FALSE;
2425 style->text_anchor.value = style->text_anchor.computed = SP_CSS_TEXT_ANCHOR_START;
2428 style->opacity.value = SP_SCALE24_MAX;
2429 style->visibility.set = FALSE;
2430 style->visibility.value = style->visibility.computed = SP_CSS_VISIBILITY_VISIBLE;
2431 style->display.set = FALSE;
2432 style->display.value = style->display.computed = SP_CSS_DISPLAY_INLINE;
2433 style->overflow.set = FALSE;
2434 style->overflow.value = style->overflow.computed = SP_CSS_OVERFLOW_VISIBLE;
2436 style->color.type = SP_PAINT_TYPE_COLOR;
2437 sp_color_set_rgb_float(&style->color.value.color, 0.0, 0.0, 0.0);
2439 style->fill.type = SP_PAINT_TYPE_COLOR;
2440 sp_color_set_rgb_float(&style->fill.value.color, 0.0, 0.0, 0.0);
2441 style->fill_opacity.value = SP_SCALE24_MAX;
2442 style->fill_rule.value = style->fill_rule.computed = SP_WIND_RULE_NONZERO;
2444 style->stroke.type = SP_PAINT_TYPE_NONE;
2445 style->stroke.set = FALSE;
2446 sp_color_set_rgb_float(&style->stroke.value.color, 0.0, 0.0, 0.0);
2447 style->stroke_opacity.value = SP_SCALE24_MAX;
2449 style->stroke_width.set = FALSE;
2450 style->stroke_width.unit = SP_CSS_UNIT_NONE;
2451 style->stroke_width.computed = 1.0;
2453 style->stroke_linecap.set = FALSE;
2454 style->stroke_linecap.value = style->stroke_linecap.computed = SP_STROKE_LINECAP_BUTT;
2455 style->stroke_linejoin.set = FALSE;
2456 style->stroke_linejoin.value = style->stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER;
2458 style->stroke_miterlimit.set = FALSE;
2459 style->stroke_miterlimit.value = 4.0;
2461 style->stroke_dash.n_dash = 0;
2462 style->stroke_dash.dash = NULL;
2463 style->stroke_dash.offset = 0.0;
2465 for (unsigned i = SP_MARKER_LOC; i < SP_MARKER_LOC_QTY; i++) {
2466 g_free(style->marker[i].value);
2467 style->marker[i].set = FALSE;
2468 }
2470 style->filter.set = FALSE;
2471 //Are these really needed?
2472 style->filter.inherit = FALSE;
2473 style->filter.uri = NULL;
2474 style->filter.filter = FALSE;
2476 }
2480 /**
2481 *
2482 */
2483 static void
2484 sp_style_read_dash(SPStyle *style, gchar const *str)
2485 {
2486 /* Ref: http://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty */
2487 style->stroke_dasharray_set = TRUE;
2489 if (strcmp(str, "inherit") == 0) {
2490 style->stroke_dasharray_inherit = true;
2491 return;
2492 }
2493 style->stroke_dasharray_inherit = false;
2495 NRVpathDash &dash = style->stroke_dash;
2496 g_free(dash.dash);
2497 dash.dash = NULL;
2499 if (strcmp(str, "none") == 0) {
2500 dash.n_dash = 0;
2501 return;
2502 }
2504 gint n_dash = 0;
2505 gdouble d[64];
2506 gchar *e = NULL;
2508 bool LineSolid=true;
2509 while (e != str && n_dash < 64) {
2510 /* TODO: Should allow <length> rather than just a unitless (px) number. */
2511 d[n_dash] = g_ascii_strtod(str, (char **) &e);
2512 if (d[n_dash] > 0.00000001)
2513 LineSolid = false;
2514 if (e != str) {
2515 n_dash += 1;
2516 str = e;
2517 }
2518 while (str && *str && !isalnum(*str)) str += 1;
2519 }
2521 if (LineSolid) {
2522 dash.n_dash = 0;
2523 return;
2524 }
2526 if (n_dash > 0) {
2527 dash.dash = g_new(gdouble, n_dash);
2528 memcpy(dash.dash, d, sizeof(gdouble) * n_dash);
2529 dash.n_dash = n_dash;
2530 }
2531 }
2534 /*#########################
2535 ## SPTextStyle operations
2536 #########################*/
2539 /**
2540 * Return new SPTextStyle object with default settings.
2541 */
2542 static SPTextStyle *
2543 sp_text_style_new()
2544 {
2545 SPTextStyle *ts = g_new0(SPTextStyle, 1);
2546 ts->refcount = 1;
2547 sp_text_style_clear(ts);
2549 ts->font.value = g_strdup("Bitstream Vera Sans");
2550 ts->font_family.value = g_strdup("Bitstream Vera Sans");
2552 return ts;
2553 }
2556 /**
2557 * Clear text style settings.
2558 */
2559 static void
2560 sp_text_style_clear(SPTextStyle *ts)
2561 {
2562 ts->font.set = FALSE;
2563 ts->font_family.set = FALSE;
2564 }
2568 /**
2569 * Reduce refcount of text style and possibly free it.
2570 */
2571 static SPTextStyle *
2572 sp_text_style_unref(SPTextStyle *st)
2573 {
2574 st->refcount -= 1;
2576 if (st->refcount < 1) {
2577 g_free(st->font.value);
2578 g_free(st->font_family.value);
2579 g_free(st);
2580 }
2582 return NULL;
2583 }
2587 /**
2588 * Return duplicate of text style.
2589 */
2590 static SPTextStyle *
2591 sp_text_style_duplicate_unset(SPTextStyle *st)
2592 {
2593 SPTextStyle *nt = g_new0(SPTextStyle, 1);
2594 nt->refcount = 1;
2596 nt->font.value = g_strdup(st->font.value);
2597 nt->font_family.value = g_strdup(st->font_family.value);
2599 return nt;
2600 }
2604 /**
2605 * Write SPTextStyle object into string.
2606 */
2607 static guint
2608 sp_text_style_write(gchar *p, guint const len, SPTextStyle const *const st, guint flags)
2609 {
2610 gint d = 0;
2612 // We do not do diffing for text style
2613 if (flags == SP_STYLE_FLAG_IFDIFF)
2614 flags = SP_STYLE_FLAG_IFSET;
2616 d += sp_style_write_istring(p + d, len - d, "font-family", &st->font_family, NULL, flags);
2617 return d;
2618 }
2622 /* The following sp_tyle_read_* functions ignore invalid values, as per
2623 * http://www.w3.org/TR/REC-CSS2/syndata.html#parsing-errors.
2624 *
2625 * [However, the SVG spec is somewhat unclear as to whether the style attribute should
2626 * be handled as per CSS2 rules or whether it must simply be a set of PROPERTY:VALUE
2627 * pairs, in which case SVG's error-handling rules
2628 * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing should instead be applied.]
2629 */
2632 /**
2633 * Set SPIFloat object from string.
2634 */
2635 static void
2636 sp_style_read_ifloat(SPIFloat *val, gchar const *str)
2637 {
2638 if (!strcmp(str, "inherit")) {
2639 val->set = TRUE;
2640 val->inherit = TRUE;
2641 } else {
2642 gfloat value;
2643 if (sp_svg_number_read_f(str, &value)) {
2644 val->set = TRUE;
2645 val->inherit = FALSE;
2646 val->value = value;
2647 }
2648 }
2649 }
2653 /**
2654 * Set SPIScale24 object from string.
2655 */
2656 static void
2657 sp_style_read_iscale24(SPIScale24 *val, gchar const *str)
2658 {
2659 if (!strcmp(str, "inherit")) {
2660 val->set = TRUE;
2661 val->inherit = TRUE;
2662 } else {
2663 gfloat value;
2664 if (sp_svg_number_read_f(str, &value)) {
2665 val->set = TRUE;
2666 val->inherit = FALSE;
2667 value = CLAMP(value, 0.0f, (gfloat) SP_SCALE24_MAX);
2668 val->value = SP_SCALE24_FROM_FLOAT(value);
2669 }
2670 }
2671 }
2673 /**
2674 * Reads a style value and performs lookup based on the given style value enumerations.
2675 */
2676 static void
2677 sp_style_read_ienum(SPIEnum *val, gchar const *str, SPStyleEnum const *dict,
2678 bool const can_explicitly_inherit)
2679 {
2680 if ( can_explicitly_inherit && !strcmp(str, "inherit") ) {
2681 val->set = TRUE;
2682 val->inherit = TRUE;
2683 } else {
2684 for (unsigned i = 0; dict[i].key; i++) {
2685 if (!strcmp(str, dict[i].key)) {
2686 val->set = TRUE;
2687 val->inherit = FALSE;
2688 val->value = dict[i].value;
2689 /* Save copying for values not needing it */
2690 val->computed = val->value;
2691 break;
2692 }
2693 }
2694 }
2695 }
2699 /**
2700 * Set SPIString object from string.
2701 */
2702 static void
2703 sp_style_read_istring(SPIString *val, gchar const *str)
2704 {
2705 g_free(val->value);
2707 if (!strcmp(str, "inherit")) {
2708 val->set = TRUE;
2709 val->inherit = TRUE;
2710 val->value = NULL;
2711 } else {
2712 val->set = TRUE;
2713 val->inherit = FALSE;
2714 val->value = g_strdup(str);
2715 }
2716 }
2720 /**
2721 * Set SPILength object from string.
2722 */
2723 static void
2724 sp_style_read_ilength(SPILength *val, gchar const *str)
2725 {
2726 if (!strcmp(str, "inherit")) {
2727 val->set = TRUE;
2728 val->inherit = TRUE;
2729 } else {
2730 gdouble value;
2731 gchar *e;
2732 /** \todo fixme: Move this to standard place (Lauris) */
2733 value = g_ascii_strtod(str, &e);
2734 if ((gchar const *) e != str) {
2735 /** \todo
2736 * Allow the number of px per inch to vary (document preferences,
2737 * X server or whatever). E.g. don't fill in computed here, do
2738 * it at the same time as percentage units are done.
2739 */
2740 if (!*e) {
2741 /* Userspace */
2742 val->unit = SP_CSS_UNIT_NONE;
2743 val->computed = value;
2744 } else if (!strcmp(e, "px")) {
2745 /* Userspace */
2746 val->unit = SP_CSS_UNIT_PX;
2747 val->computed = value;
2748 } else if (!strcmp(e, "pt")) {
2749 /* Userspace / DEVICESCALE */
2750 val->unit = SP_CSS_UNIT_PT;
2751 val->computed = value * PX_PER_PT;
2752 } else if (!strcmp(e, "pc")) {
2753 /* 1 pica = 12pt; FIXME: add it to SPUnit */
2754 val->unit = SP_CSS_UNIT_PC;
2755 val->computed = value * PX_PER_PT * 12;
2756 } else if (!strcmp(e, "mm")) {
2757 val->unit = SP_CSS_UNIT_MM;
2758 val->computed = value * PX_PER_MM;
2759 } else if (!strcmp(e, "cm")) {
2760 val->unit = SP_CSS_UNIT_CM;
2761 val->computed = value * PX_PER_CM;
2762 } else if (!strcmp(e, "in")) {
2763 val->unit = SP_CSS_UNIT_IN;
2764 val->computed = value * PX_PER_IN;
2765 } else if (!strcmp(e, "em")) {
2766 /* EM square */
2767 val->unit = SP_CSS_UNIT_EM;
2768 val->value = value;
2769 } else if (!strcmp(e, "ex")) {
2770 /* ex square */
2771 val->unit = SP_CSS_UNIT_EX;
2772 val->value = value;
2773 } else if (!strcmp(e, "%")) {
2774 /* Percentage */
2775 val->unit = SP_CSS_UNIT_PERCENT;
2776 val->value = value * 0.01;
2777 } else {
2778 /* Invalid */
2779 return;
2780 }
2781 val->set = TRUE;
2782 val->inherit = FALSE;
2783 }
2784 }
2785 }
2787 /**
2788 * Set SPILengthOrNormal object from string.
2789 */
2790 static void
2791 sp_style_read_ilengthornormal(SPILengthOrNormal *val, gchar const *str)
2792 {
2793 if (!strcmp(str, "normal")) {
2794 val->set = TRUE;
2795 val->inherit = FALSE;
2796 val->normal = TRUE;
2797 val->unit = SP_CSS_UNIT_NONE;
2798 val->value = val->computed = 0.0;
2799 } else {
2800 SPILength length;
2801 sp_style_read_ilength(&length, str);
2802 val->set = length.set;
2803 val->inherit = length.inherit;
2804 val->normal = FALSE;
2805 val->unit = length.unit;
2806 val->value = length.value;
2807 val->computed = length.computed;
2808 }
2809 }
2811 /**
2812 * Set SPITextDecoration object from string.
2813 */
2814 static void
2815 sp_style_read_itextdecoration(SPITextDecoration *val, gchar const *str)
2816 {
2817 if (!strcmp(str, "inherit")) {
2818 val->set = TRUE;
2819 val->inherit = TRUE;
2820 } else if (!strcmp(str, "none")) {
2821 val->set = TRUE;
2822 val->inherit = FALSE;
2823 val->underline = FALSE;
2824 val->overline = FALSE;
2825 val->line_through = FALSE;
2826 val->blink = FALSE;
2827 } else {
2828 bool found_underline = false;
2829 bool found_overline = false;
2830 bool found_line_through = false;
2831 bool found_blink = false;
2832 for ( ; *str ; str++ ) {
2833 if (*str == ' ') continue;
2834 if (strneq(str, "underline", 9) && (str[9] == ' ' || str[9] == '\0')) {
2835 found_underline = true;
2836 str += 9;
2837 } else if (strneq(str, "overline", 8) && (str[8] == ' ' || str[8] == '\0')) {
2838 found_overline = true;
2839 str += 8;
2840 } else if (strneq(str, "line-through", 12) && (str[12] == ' ' || str[12] == '\0')) {
2841 found_line_through = true;
2842 str += 12;
2843 } else if (strneq(str, "blink", 5) && (str[5] == ' ' || str[5] == '\0')) {
2844 found_blink = true;
2845 str += 5;
2846 } else {
2847 return; // invalid value
2848 }
2849 }
2850 if (!(found_underline || found_overline || found_line_through || found_blink)) {
2851 return; // invalid value: empty
2852 }
2853 val->set = TRUE;
2854 val->inherit = FALSE;
2855 val->underline = found_underline;
2856 val->overline = found_overline;
2857 val->line_through = found_line_through;
2858 val->blink = found_blink;
2859 }
2860 }
2862 /**
2863 * Set SPIPaint object from string containing an integer value.
2864 * \param document Ignored
2865 */
2866 static void
2867 sp_style_read_icolor(SPIPaint *paint, gchar const *str, SPStyle *style, SPDocument *document)
2868 {
2869 paint->currentcolor = FALSE; /* currentColor not a valid <color>. */
2870 if (!strcmp(str, "inherit")) {
2871 paint->set = TRUE;
2872 paint->inherit = TRUE;
2873 } else {
2874 guint32 const rgb0 = sp_svg_read_color(str, 0xff);
2875 if (rgb0 != 0xff) {
2876 paint->type = SP_PAINT_TYPE_COLOR;
2877 sp_color_set_rgb_rgba32(&paint->value.color, rgb0);
2878 paint->set = TRUE;
2879 paint->inherit = FALSE;
2880 }
2881 }
2882 }
2885 /**
2886 * Set SPIPaint object from string.
2887 *
2888 * \pre paint == \&style.fill || paint == \&style.stroke.
2889 */
2890 static void
2891 sp_style_read_ipaint(SPIPaint *paint, gchar const *str, SPStyle *style, SPDocument *document)
2892 {
2893 while (isspace(*str)) {
2894 ++str;
2895 }
2897 if (streq(str, "inherit")) {
2898 paint->set = TRUE;
2899 paint->inherit = TRUE;
2900 paint->currentcolor = FALSE;
2901 } else if (streq(str, "currentColor") && paint != &style->color) {
2902 paint->set = TRUE;
2903 paint->inherit = FALSE;
2904 paint->currentcolor = TRUE;
2905 } else if (streq(str, "none") && paint != &style->color) {
2906 paint->type = SP_PAINT_TYPE_NONE;
2907 paint->set = TRUE;
2908 paint->inherit = FALSE;
2909 paint->currentcolor = FALSE;
2910 } else if (strneq(str, "url", 3) && paint != &style->color) {
2911 // this is alloc'd uri, but seems to be shared with a parent
2912 // potentially, so it's not any easy g_free case...
2913 paint->value.paint.uri = extract_uri(str);
2914 if (paint->value.paint.uri == NULL || *(paint->value.paint.uri) == '\0') {
2915 paint->type = SP_PAINT_TYPE_NONE;
2916 return;
2917 }
2918 paint->type = SP_PAINT_TYPE_PAINTSERVER;
2919 paint->set = TRUE;
2920 paint->inherit = FALSE;
2921 paint->currentcolor = FALSE;
2922 if (document) {
2923 SPObject *ps = sp_uri_reference_resolve(document, str);
2924 if (ps && SP_IS_PAINT_SERVER(ps)) {
2925 paint->value.paint.server = SP_PAINT_SERVER(ps);
2926 if (style->object && !style->cloned) {
2927 sp_object_href(SP_OBJECT(paint->value.paint.server), style);
2928 if (paint == &style->fill) {
2929 style->fill_hreffed = true;
2930 } else {
2931 assert(paint == &style->stroke);
2932 style->stroke_hreffed = true;
2933 }
2934 }
2935 if (style->object || style->cloned) {
2936 g_signal_connect(G_OBJECT(paint->value.paint.server), "release",
2937 G_CALLBACK(sp_style_paint_server_release), style);
2938 g_signal_connect(G_OBJECT(paint->value.paint.server), "modified",
2939 G_CALLBACK(sp_style_paint_server_modified), style);
2940 if (paint == &style->fill) {
2941 style->fill_listening = true;
2942 } else {
2943 assert(paint == &style->stroke);
2944 style->stroke_listening = true;
2945 }
2946 }
2947 } else {
2948 paint->value.paint.server = NULL;
2949 }
2950 }
2951 } else {
2952 guint32 const rgb0 = sp_svg_read_color(str, &str, 0xff);
2953 if (rgb0 != 0xff) {
2954 paint->type = SP_PAINT_TYPE_COLOR;
2955 sp_color_set_rgb_rgba32(&paint->value.color, rgb0);
2956 paint->set = TRUE;
2957 paint->inherit = FALSE;
2958 paint->currentcolor = FALSE;
2960 while (g_ascii_isspace(*str)) {
2961 ++str;
2962 }
2963 if (strneq(str, "icc-color(", 10)) {
2964 SVGICCColor* tmp = new SVGICCColor();
2965 if ( ! sp_svg_read_icc_color( str, &str, tmp ) ) {
2966 delete tmp;
2967 tmp = 0;
2968 }
2969 paint->iccColor = tmp;
2970 }
2971 }
2972 }
2973 }
2977 /**
2978 * Set SPIFontSize object from string.
2979 */
2980 static void
2981 sp_style_read_ifontsize(SPIFontSize *val, gchar const *str)
2982 {
2983 if (!strcmp(str, "inherit")) {
2984 val->set = TRUE;
2985 val->inherit = TRUE;
2986 } else if ((*str == 'x') || (*str == 's') || (*str == 'm') || (*str == 'l')) {
2987 for (unsigned i = 0; enum_font_size[i].key; i++) {
2988 if (!strcmp(str, enum_font_size[i].key)) {
2989 val->set = TRUE;
2990 val->inherit = FALSE;
2991 val->type = SP_FONT_SIZE_LITERAL;
2992 val->value = enum_font_size[i].value;
2993 return;
2994 }
2995 }
2996 /* Invalid */
2997 return;
2998 } else {
2999 gdouble value;
3000 gchar *e;
3001 /* fixme: Move this to standard place (Lauris) */
3002 value = g_ascii_strtod(str, &e);
3003 if ((gchar const *) e != str) {
3004 if (!*e) {
3005 /* Userspace */
3006 } else if (!strcmp(e, "px")) {
3007 /* Userspace */
3008 } else if (!strcmp(e, "pt")) {
3009 /* Userspace * DEVICESCALE */
3010 value *= PX_PER_PT;
3011 } else if (!strcmp(e, "pc")) {
3012 /* 12pt */
3013 value *= PX_PER_PT * 12.0;
3014 } else if (!strcmp(e, "mm")) {
3015 value *= PX_PER_MM;
3016 } else if (!strcmp(e, "cm")) {
3017 value *= PX_PER_CM;
3018 } else if (!strcmp(e, "in")) {
3019 value *= PX_PER_IN;
3020 } else if (!strcmp(e, "%")) {
3021 /* Percentage */
3022 val->set = TRUE;
3023 val->inherit = FALSE;
3024 val->type = SP_FONT_SIZE_PERCENTAGE;
3025 val->value = SP_F8_16_FROM_FLOAT(value / 100.0);
3026 return;
3027 } else {
3028 /* Invalid */
3029 return;
3030 }
3031 /* Length */
3032 val->set = TRUE;
3033 val->inherit = FALSE;
3034 val->type = SP_FONT_SIZE_LENGTH;
3035 val->computed = value;
3036 return;
3037 }
3038 }
3039 }
3043 /**
3044 * Set SPIFilter object from string.
3045 */
3046 static void
3047 sp_style_read_ifilter(SPIFilter *f, gchar const *str, SPDocument *document)
3048 {
3049 /* Try all possible values: inherit, none, uri */
3050 if (streq(str, "inherit")) {
3051 f->set = TRUE;
3052 f->inherit = TRUE;
3053 f->filter = NULL;
3054 } else if(streq(str, "none")) {
3055 f->set = TRUE;
3056 f->inherit = FALSE;
3057 f->filter = NULL;
3058 } else if (strneq(str, "url", 3)) {
3059 f->uri = extract_uri(str);
3060 if(f->uri == NULL || f->uri[0] == '\0') {
3061 g_warning("Specified filter url is empty");
3062 f->set = TRUE;
3063 f->inherit = FALSE;
3064 f->filter = NULL;
3065 return;
3066 }
3067 f->set = TRUE;
3068 f->inherit = FALSE;
3069 f->filter = NULL;
3070 if (document) {
3071 SPObject *obj;
3072 obj = sp_uri_reference_resolve(document, str);
3073 if (SP_IS_FILTER(obj)) {
3074 f->filter = SP_FILTER(obj);
3075 //g_signal_connect(G_OBJECT(f->filter), "release",
3076 // G_CALLBACK(sp_style_filter_release), style);
3077 //g_signal_connect(G_OBJECT(f->filter), "modified",
3078 // G_CALLBACK(sp_style_filter_modified), style);
3079 } else {
3080 g_warning("Element '%s' not found or is not a filter", f->uri);
3081 }
3082 }
3084 } else {
3085 /* We shouldn't reach this if SVG input is well-formed */
3086 f->set = FALSE;
3087 f->inherit = FALSE;
3088 f->filter = NULL;
3089 f->uri = NULL;
3090 }
3091 }
3093 /**
3094 * Set SPIEnum object from repr attribute.
3095 */
3096 static void
3097 sp_style_read_penum(SPIEnum *val, Inkscape::XML::Node *repr,
3098 gchar const *key, SPStyleEnum const *dict,
3099 bool const can_explicitly_inherit)
3100 {
3101 gchar const *str = repr->attribute(key);
3102 if (str) {
3103 sp_style_read_ienum(val, str, dict, can_explicitly_inherit);
3104 }
3105 }
3109 /**
3110 * Set SPILength object from repr attribute.
3111 */
3112 static void
3113 sp_style_read_plength(SPILength *val, Inkscape::XML::Node *repr, gchar const *key)
3114 {
3115 gchar const *str = repr->attribute(key);
3116 if (str) {
3117 sp_style_read_ilength(val, str);
3118 }
3119 }
3121 /**
3122 * Set SPIFontSize object from repr attribute.
3123 */
3124 static void
3125 sp_style_read_pfontsize(SPIFontSize *val, Inkscape::XML::Node *repr, gchar const *key)
3126 {
3127 gchar const *str = repr->attribute(key);
3128 if (str) {
3129 sp_style_read_ifontsize(val, str);
3130 }
3131 }
3134 /**
3135 * Set SPIFloat object from repr attribute.
3136 */
3137 static void
3138 sp_style_read_pfloat(SPIFloat *val, Inkscape::XML::Node *repr, gchar const *key)
3139 {
3140 gchar const *str = repr->attribute(key);
3141 if (str) {
3142 sp_style_read_ifloat(val, str);
3143 }
3144 }
3147 /**
3148 * Write SPIFloat object into string.
3149 */
3150 static gint
3151 sp_style_write_ifloat(gchar *p, gint const len, gchar const *const key,
3152 SPIFloat const *const val, SPIFloat const *const base, guint const flags)
3153 {
3154 Inkscape::CSSOStringStream os;
3156 if ((flags & SP_STYLE_FLAG_ALWAYS)
3157 || ((flags & SP_STYLE_FLAG_IFSET) && val->set)
3158 || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set
3159 && (!base->set || (val->value != base->value))))
3160 {
3161 if (val->inherit) {
3162 return g_snprintf(p, len, "%s:inherit;", key);
3163 } else {
3164 os << key << ":" << val->value << ";";
3165 return g_strlcpy(p, os.str().c_str(), len);
3166 }
3167 }
3168 return 0;
3169 }
3172 /**
3173 * Write SPIScale24 object into string.
3174 */
3175 static gint
3176 sp_style_write_iscale24(gchar *p, gint const len, gchar const *const key,
3177 SPIScale24 const *const val, SPIScale24 const *const base,
3178 guint const flags)
3179 {
3180 Inkscape::CSSOStringStream os;
3182 if ((flags & SP_STYLE_FLAG_ALWAYS)
3183 || ((flags & SP_STYLE_FLAG_IFSET) && val->set)
3184 || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set
3185 && (!base->set || (val->value != base->value))))
3186 {
3187 if (val->inherit) {
3188 return g_snprintf(p, len, "%s:inherit;", key);
3189 } else {
3190 os << key << ":" << SP_SCALE24_TO_FLOAT(val->value) << ";";
3191 return g_strlcpy(p, os.str().c_str(), len);
3192 }
3193 }
3194 return 0;
3195 }
3198 /**
3199 * Write SPIEnum object into string.
3200 */
3201 static gint
3202 sp_style_write_ienum(gchar *p, gint const len, gchar const *const key,
3203 SPStyleEnum const *const dict,
3204 SPIEnum const *const val, SPIEnum const *const base, guint const flags)
3205 {
3206 if ((flags & SP_STYLE_FLAG_ALWAYS)
3207 || ((flags & SP_STYLE_FLAG_IFSET) && val->set)
3208 || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set
3209 && (!base->set || (val->computed != base->computed))))
3210 {
3211 if (val->inherit) {
3212 return g_snprintf(p, len, "%s:inherit;", key);
3213 }
3214 for (unsigned i = 0; dict[i].key; i++) {
3215 if (dict[i].value == static_cast< gint > (val->value) ) {
3216 return g_snprintf(p, len, "%s:%s;", key, dict[i].key);
3217 }
3218 }
3219 }
3220 return 0;
3221 }
3225 /**
3226 * Write SPIString object into string.
3227 */
3228 static gint
3229 sp_style_write_istring(gchar *p, gint const len, gchar const *const key,
3230 SPIString const *const val, SPIString const *const base, guint const flags)
3231 {
3232 if ((flags & SP_STYLE_FLAG_ALWAYS)
3233 || ((flags & SP_STYLE_FLAG_IFSET) && val->set)
3234 || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set
3235 && (!base->set || strcmp(val->value, base->value))))
3236 {
3237 if (val->inherit) {
3238 return g_snprintf(p, len, "%s:inherit;", key);
3239 } else {
3240 return g_snprintf(p, len, "%s:%s;", key, val->value);
3241 }
3242 }
3243 return 0;
3244 }
3247 /**
3248 *
3249 */
3250 static bool
3251 sp_length_differ(SPILength const *const a, SPILength const *const b)
3252 {
3253 if (a->unit != b->unit) {
3254 if (a->unit == SP_CSS_UNIT_EM) return true;
3255 if (a->unit == SP_CSS_UNIT_EX) return true;
3256 if (a->unit == SP_CSS_UNIT_PERCENT) return true;
3257 if (b->unit == SP_CSS_UNIT_EM) return true;
3258 if (b->unit == SP_CSS_UNIT_EX) return true;
3259 if (b->unit == SP_CSS_UNIT_PERCENT) return true;
3260 }
3262 return (a->computed != b->computed);
3263 }
3267 /**
3268 * Write SPILength object into string.
3269 */
3270 static gint
3271 sp_style_write_ilength(gchar *p, gint const len, gchar const *const key,
3272 SPILength const *const val, SPILength const *const base, guint const flags)
3273 {
3274 Inkscape::CSSOStringStream os;
3276 if ((flags & SP_STYLE_FLAG_ALWAYS)
3277 || ((flags & SP_STYLE_FLAG_IFSET) && val->set)
3278 || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set
3279 && (!base->set || sp_length_differ(val, base))))
3280 {
3281 if (val->inherit) {
3282 return g_snprintf(p, len, "%s:inherit;", key);
3283 } else {
3284 switch (val->unit) {
3285 case SP_CSS_UNIT_NONE:
3286 os << key << ":" << val->computed << ";";
3287 return g_strlcpy(p, os.str().c_str(), len);
3288 break;
3289 case SP_CSS_UNIT_PX:
3290 os << key << ":" << val->computed << "px;";
3291 return g_strlcpy(p, os.str().c_str(), len);
3292 break;
3293 case SP_CSS_UNIT_PT:
3294 os << key << ":" << val->computed * PT_PER_PX << "pt;";
3295 return g_strlcpy(p, os.str().c_str(), len);
3296 break;
3297 case SP_CSS_UNIT_PC:
3298 os << key << ":" << val->computed * PT_PER_PX / 12.0 << "pc;";
3299 return g_strlcpy(p, os.str().c_str(), len);
3300 break;
3301 case SP_CSS_UNIT_MM:
3302 os << key << ":" << val->computed * MM_PER_PX << "mm;";
3303 return g_strlcpy(p, os.str().c_str(), len);
3304 break;
3305 case SP_CSS_UNIT_CM:
3306 os << key << ":" << val->computed * CM_PER_PX << "cm;";
3307 return g_strlcpy(p, os.str().c_str(), len);
3308 break;
3309 case SP_CSS_UNIT_IN:
3310 os << key << ":" << val->computed * IN_PER_PX << "in;";
3311 return g_strlcpy(p, os.str().c_str(), len);
3312 break;
3313 case SP_CSS_UNIT_EM:
3314 os << key << ":" << val->value << "em;";
3315 return g_strlcpy(p, os.str().c_str(), len);
3316 break;
3317 case SP_CSS_UNIT_EX:
3318 os << key << ":" << val->value << "ex;";
3319 return g_strlcpy(p, os.str().c_str(), len);
3320 break;
3321 case SP_CSS_UNIT_PERCENT:
3322 os << key << ":" << (val->value * 100.0) << "%;";
3323 return g_strlcpy(p, os.str().c_str(), len);
3324 break;
3325 default:
3326 /* Invalid */
3327 break;
3328 }
3329 }
3330 }
3331 return 0;
3332 }
3335 /**
3336 *
3337 */
3338 static bool
3339 sp_lengthornormal_differ(SPILengthOrNormal const *const a, SPILengthOrNormal const *const b)
3340 {
3341 if (a->normal != b->normal) return true;
3342 if (a->normal) return false;
3344 if (a->unit != b->unit) {
3345 if (a->unit == SP_CSS_UNIT_EM) return true;
3346 if (a->unit == SP_CSS_UNIT_EX) return true;
3347 if (a->unit == SP_CSS_UNIT_PERCENT) return true;
3348 if (b->unit == SP_CSS_UNIT_EM) return true;
3349 if (b->unit == SP_CSS_UNIT_EX) return true;
3350 if (b->unit == SP_CSS_UNIT_PERCENT) return true;
3351 }
3353 return (a->computed != b->computed);
3354 }
3356 /**
3357 * Write SPILengthOrNormal object into string.
3358 */
3359 static gint
3360 sp_style_write_ilengthornormal(gchar *p, gint const len, gchar const *const key,
3361 SPILengthOrNormal const *const val,
3362 SPILengthOrNormal const *const base,
3363 guint const flags)
3364 {
3365 if ((flags & SP_STYLE_FLAG_ALWAYS)
3366 || ((flags & SP_STYLE_FLAG_IFSET) && val->set)
3367 || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set
3368 && (!base->set || sp_lengthornormal_differ(val, base))))
3369 {
3370 if (val->normal) {
3371 return g_snprintf(p, len, "%s:normal;", key);
3372 } else {
3373 SPILength length;
3374 length.set = val->set;
3375 length.inherit = val->inherit;
3376 length.unit = val->unit;
3377 length.value = val->value;
3378 length.computed = val->computed;
3379 return sp_style_write_ilength(p, len, key, &length, NULL, SP_STYLE_FLAG_ALWAYS);
3380 }
3381 }
3382 return 0;
3383 }
3385 /**
3386 *
3387 */
3388 static bool
3389 sp_textdecoration_differ(SPITextDecoration const *const a, SPITextDecoration const *const b)
3390 {
3391 return a->underline != b->underline
3392 || a->overline != b->overline
3393 || a->line_through != b->line_through
3394 || a->blink != b->blink;
3395 }
3397 /**
3398 * Write SPITextDecoration object into string.
3399 */
3400 static gint
3401 sp_style_write_itextdecoration(gchar *p, gint const len, gchar const *const key,
3402 SPITextDecoration const *const val,
3403 SPITextDecoration const *const base,
3404 guint const flags)
3405 {
3406 Inkscape::CSSOStringStream os;
3408 if ((flags & SP_STYLE_FLAG_ALWAYS)
3409 || ((flags & SP_STYLE_FLAG_IFSET) && val->set)
3410 || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set
3411 && (!base->set || sp_textdecoration_differ(val, base))))
3412 {
3413 if (val->inherit) {
3414 return g_snprintf(p, len, "%s:inherit;", key);
3415 } else {
3416 os << key << ":";
3417 if (val->underline || val->overline || val->line_through || val->blink) {
3418 if (val->underline) os << " underline";
3419 if (val->overline) os << " overline";
3420 if (val->line_through) os << " line-through";
3421 if (val->blink) os << " blink";
3422 } else
3423 os << "none";
3424 os << ";";
3425 return g_strlcpy(p, os.str().c_str(), len);
3426 }
3427 }
3428 return 0;
3429 }
3431 /**
3432 *
3433 */
3434 static bool
3435 sp_paint_differ(SPIPaint const *const a, SPIPaint const *const b)
3436 {
3437 if (a->type != b->type)
3438 return true;
3439 if (a->type == SP_PAINT_TYPE_COLOR)
3440 return !(sp_color_is_equal(&a->value.color, &b->value.color)
3441 && ((a->iccColor == b->iccColor)
3442 || (a->iccColor && b->iccColor
3443 && (a->iccColor->colorProfile == b->iccColor->colorProfile)
3444 && (a->iccColor->colors == b->iccColor->colors))));
3445 /* todo: Allow for epsilon differences in iccColor->colors, e.g. changes small enough not to show up
3446 * in the string representation. */
3447 if (a->type == SP_PAINT_TYPE_PAINTSERVER)
3448 return (a->value.paint.server != b->value.paint.server);
3449 return false;
3450 }
3454 /**
3455 * Write SPIPaint object into string.
3456 */
3457 static gint
3458 sp_style_write_ipaint(gchar *b, gint const len, gchar const *const key,
3459 SPIPaint const *const paint, SPIPaint const *const base, guint const flags)
3460 {
3461 if ((flags & SP_STYLE_FLAG_ALWAYS)
3462 || ((flags & SP_STYLE_FLAG_IFSET) && paint->set)
3463 || ((flags & SP_STYLE_FLAG_IFDIFF) && paint->set
3464 && (!base->set || sp_paint_differ(paint, base))))
3465 {
3466 if (paint->inherit) {
3467 return g_snprintf(b, len, "%s:inherit;", key);
3468 } else if (paint->currentcolor) {
3469 return g_snprintf(b, len, "%s:currentColor;", key);
3470 } else {
3471 switch (paint->type) {
3472 case SP_PAINT_TYPE_COLOR: {
3473 char color_buf[8];
3474 sp_svg_write_color(color_buf, sizeof(color_buf), sp_color_get_rgba32_ualpha(&paint->value.color, 0));
3475 if (paint->iccColor) {
3476 CSSOStringStream css;
3477 css << color_buf << " icc-color(" << paint->iccColor->colorProfile;
3478 for (vector<double>::const_iterator i(paint->iccColor->colors.begin()),
3479 iEnd(paint->iccColor->colors.end());
3480 i != iEnd; ++i) {
3481 css << ", " << *i;
3482 }
3483 css << ')';
3484 return g_snprintf(b, len, "%s:%s;", key, css.gcharp());
3485 } else {
3486 return g_snprintf(b, len, "%s:%s;", key, color_buf);
3487 }
3488 }
3489 case SP_PAINT_TYPE_PAINTSERVER:
3490 return g_snprintf(b, len, "%s:url(%s);", key, paint->value.paint.uri);
3491 default:
3492 break;
3493 }
3494 return g_snprintf(b, len, "%s:none;", key);
3495 }
3496 }
3497 return 0;
3498 }
3501 /**
3502 *
3503 */
3504 static bool
3505 sp_fontsize_differ(SPIFontSize const *const a, SPIFontSize const *const b)
3506 {
3507 if (a->type != b->type)
3508 return true;
3509 if (a->type == SP_FONT_SIZE_LENGTH) {
3510 if (a->computed != b->computed)
3511 return true;
3512 } else {
3513 if (a->value != b->value)
3514 return true;
3515 }
3516 return false;
3517 }
3520 /**
3521 * Write SPIFontSize object into string.
3522 */
3523 static gint
3524 sp_style_write_ifontsize(gchar *p, gint const len, gchar const *key,
3525 SPIFontSize const *const val, SPIFontSize const *const base,
3526 guint const flags)
3527 {
3528 if ((flags & SP_STYLE_FLAG_ALWAYS)
3529 || ((flags & SP_STYLE_FLAG_IFSET) && val->set)
3530 || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set
3531 && (!base->set || sp_fontsize_differ(val, base))))
3532 {
3533 if (val->inherit) {
3534 return g_snprintf(p, len, "%s:inherit;", key);
3535 } else if (val->type == SP_FONT_SIZE_LITERAL) {
3536 for (unsigned i = 0; enum_font_size[i].key; i++) {
3537 if (enum_font_size[i].value == static_cast< gint > (val->value) ) {
3538 return g_snprintf(p, len, "%s:%s;", key, enum_font_size[i].key);
3539 }
3540 }
3541 } else if (val->type == SP_FONT_SIZE_LENGTH) {
3542 Inkscape::CSSOStringStream os;
3543 os << key << ":" << val->computed << "px;"; // must specify px, see inkscape bug 1221626, mozilla bug 234789
3544 return g_strlcpy(p, os.str().c_str(), len);
3545 } else if (val->type == SP_FONT_SIZE_PERCENTAGE) {
3546 Inkscape::CSSOStringStream os;
3547 os << key << ":" << (SP_F8_16_TO_FLOAT(val->value) * 100.0) << "%;";
3548 return g_strlcpy(p, os.str().c_str(), len);
3549 }
3550 }
3551 return 0;
3552 }
3555 /**
3556 * Write SPIFilter object into string.
3557 */
3558 static gint
3559 sp_style_write_ifilter(gchar *p, gint const len, gchar const *key,
3560 SPIFilter const *const val, SPIFilter const *const base,
3561 guint const flags)
3562 {
3563 if ((flags & SP_STYLE_FLAG_ALWAYS)
3564 || ((flags & SP_STYLE_FLAG_IFSET) && val->set)
3565 || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set))
3566 {
3567 if (val->inherit) {
3568 return g_snprintf(p, len, "%s:inherit;", key);
3569 } else if (val->uri) {
3570 return g_snprintf(p, len, "%s:url(%s);", key, val->uri);
3571 }
3572 }
3575 return 0;
3576 }
3579 /**
3580 * Clear paint object, and disconnect style from paintserver (if present).
3581 */
3582 static void
3583 sp_style_paint_clear(SPStyle *style, SPIPaint *paint)
3584 {
3585 if ((paint->type == SP_PAINT_TYPE_PAINTSERVER) && paint->value.paint.server) {
3586 if (paint == &style->fill) {
3587 if (style->fill_hreffed) {
3588 sp_object_hunref(SP_OBJECT(paint->value.paint.server), style);
3589 style->fill_hreffed = false;
3590 }
3591 if (style->fill_listening) {
3592 g_signal_handlers_disconnect_matched(G_OBJECT(paint->value.paint.server),
3593 G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, style);
3594 style->fill_listening = false;
3595 }
3596 } else {
3597 assert(paint == &style->stroke); // Only fill & stroke can have a paint server.
3598 if (style->stroke_hreffed) {
3599 sp_object_hunref(SP_OBJECT(paint->value.paint.server), style);
3600 style->stroke_hreffed = false;
3601 }
3602 if (style->stroke_listening) {
3603 g_signal_handlers_disconnect_matched(G_OBJECT(paint->value.paint.server),
3604 G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, style);
3605 style->stroke_listening = false;
3606 }
3607 }
3609 paint->value.paint.server = NULL;
3610 }
3611 paint->value.paint.uri = NULL;
3612 paint->type = SP_PAINT_TYPE_NONE;
3613 delete paint->iccColor;
3614 paint->iccColor = NULL;
3615 }
3617 /**
3618 * Clear all style property attributes in object.
3619 */
3620 void
3621 sp_style_unset_property_attrs(SPObject *o)
3622 {
3623 if (!o) return;
3625 SPStyle *style = SP_OBJECT_STYLE(o);
3626 if (!style) return;
3628 Inkscape::XML::Node *repr = SP_OBJECT_REPR(o);
3629 if (!repr) return;
3631 if (style->opacity.set) {
3632 repr->setAttribute("opacity", NULL);
3633 }
3634 if (style->color.set) {
3635 repr->setAttribute("color", NULL);
3636 }
3637 if (style->fill.set) {
3638 repr->setAttribute("fill", NULL);
3639 }
3640 if (style->fill_opacity.set) {
3641 repr->setAttribute("fill-opacity", NULL);
3642 }
3643 if (style->fill_rule.set) {
3644 repr->setAttribute("fill-rule", NULL);
3645 }
3646 if (style->stroke.set) {
3647 repr->setAttribute("stroke", NULL);
3648 }
3649 if (style->stroke_width.set) {
3650 repr->setAttribute("stroke-width", NULL);
3651 }
3652 if (style->stroke_linecap.set) {
3653 repr->setAttribute("stroke-linecap", NULL);
3654 }
3655 if (style->stroke_linejoin.set) {
3656 repr->setAttribute("stroke-linejoin", NULL);
3657 }
3658 if (style->marker[SP_MARKER_LOC].set) {
3659 repr->setAttribute("marker", NULL);
3660 }
3661 if (style->marker[SP_MARKER_LOC_START].set) {
3662 repr->setAttribute("marker-start", NULL);
3663 }
3664 if (style->marker[SP_MARKER_LOC_MID].set) {
3665 repr->setAttribute("marker-mid", NULL);
3666 }
3667 if (style->marker[SP_MARKER_LOC_END].set) {
3668 repr->setAttribute("marker-end", NULL);
3669 }
3670 if (style->stroke_opacity.set) {
3671 repr->setAttribute("stroke-opacity", NULL);
3672 }
3673 if (style->stroke_dasharray_set) {
3674 repr->setAttribute("stroke-dasharray", NULL);
3675 }
3676 if (style->stroke_dashoffset_set) {
3677 repr->setAttribute("stroke-dashoffset", NULL);
3678 }
3679 if (style->text_private && style->text->font_family.set) {
3680 repr->setAttribute("font-family", NULL);
3681 }
3682 if (style->text_anchor.set) {
3683 repr->setAttribute("text-anchor", NULL);
3684 }
3685 if (style->writing_mode.set) {
3686 repr->setAttribute("writing_mode", NULL);
3687 }
3688 if (style->filter.set) {
3689 repr->setAttribute("filter", NULL);
3690 }
3691 }
3693 /**
3694 * \pre style != NULL.
3695 * \pre flags in {IFSET, ALWAYS}.
3696 */
3697 SPCSSAttr *
3698 sp_css_attr_from_style(SPStyle const *const style, guint const flags)
3699 {
3700 g_return_val_if_fail(style != NULL, NULL);
3701 g_return_val_if_fail(((flags == SP_STYLE_FLAG_IFSET) ||
3702 (flags == SP_STYLE_FLAG_ALWAYS) ),
3703 NULL);
3704 gchar *style_str = sp_style_write_string(style, flags);
3705 SPCSSAttr *css = sp_repr_css_attr_new();
3706 sp_repr_css_attr_add_from_string(css, style_str);
3707 g_free(style_str);
3708 return css;
3709 }
3712 /**
3713 * \pre object != NULL
3714 * \pre flags in {IFSET, ALWAYS}.
3715 */
3716 SPCSSAttr *
3717 sp_css_attr_from_object(SPObject *object, guint const flags)
3718 {
3719 g_return_val_if_fail(((flags == SP_STYLE_FLAG_IFSET) ||
3720 (flags == SP_STYLE_FLAG_ALWAYS) ),
3721 NULL);
3722 SPStyle const *const style = SP_OBJECT_STYLE(object);
3723 if (style == NULL)
3724 return NULL;
3725 return sp_css_attr_from_style(style, flags);
3726 }
3728 /**
3729 * Unset any text-related properties
3730 */
3731 SPCSSAttr *
3732 sp_css_attr_unset_text(SPCSSAttr *css)
3733 {
3734 sp_repr_css_set_property(css, "font", NULL); // not implemented yet
3735 sp_repr_css_set_property(css, "font-size", NULL);
3736 sp_repr_css_set_property(css, "font-size-adjust", NULL); // not implemented yet
3737 sp_repr_css_set_property(css, "font-style", NULL);
3738 sp_repr_css_set_property(css, "font-variant", NULL);
3739 sp_repr_css_set_property(css, "font-weight", NULL);
3740 sp_repr_css_set_property(css, "font-stretch", NULL);
3741 sp_repr_css_set_property(css, "font-family", NULL);
3742 sp_repr_css_set_property(css, "text-indent", NULL);
3743 sp_repr_css_set_property(css, "text-align", NULL);
3744 sp_repr_css_set_property(css, "text-decoration", NULL);
3745 sp_repr_css_set_property(css, "line-height", NULL);
3746 sp_repr_css_set_property(css, "letter-spacing", NULL);
3747 sp_repr_css_set_property(css, "word-spacing", NULL);
3748 sp_repr_css_set_property(css, "text-transform", NULL);
3749 sp_repr_css_set_property(css, "direction", NULL);
3750 sp_repr_css_set_property(css, "block-progression", NULL);
3751 sp_repr_css_set_property(css, "writing-mode", NULL);
3752 sp_repr_css_set_property(css, "text-anchor", NULL);
3753 sp_repr_css_set_property(css, "kerning", NULL); // not implemented yet
3754 sp_repr_css_set_property(css, "dominant-baseline", NULL); // not implemented yet
3755 sp_repr_css_set_property(css, "alignment-baseline", NULL); // not implemented yet
3756 sp_repr_css_set_property(css, "baseline-shift", NULL); // not implemented yet
3758 return css;
3759 }
3761 bool
3762 is_url(char const *p)
3763 {
3764 if (p == NULL)
3765 return false;
3766 /** \todo
3767 * FIXME: I'm not sure if this applies to SVG as well, but CSS2 says any URIs
3768 * in property values must start with 'url('.
3769 */
3770 return (g_ascii_strncasecmp(p, "url(", 4) == 0);
3771 }
3773 /**
3774 * Unset any properties that contain URI values.
3775 *
3776 * Used for storing style that will be reused across documents when carrying
3777 * the referenced defs is impractical.
3778 */
3779 SPCSSAttr *
3780 sp_css_attr_unset_uris(SPCSSAttr *css)
3781 {
3782 // All properties that may hold <uri> or <paint> according to SVG 1.1
3783 if (is_url(sp_repr_css_property(css, "clip-path", NULL))) sp_repr_css_set_property(css, "clip-path", NULL);
3784 if (is_url(sp_repr_css_property(css, "color-profile", NULL))) sp_repr_css_set_property(css, "color-profile", NULL);
3785 if (is_url(sp_repr_css_property(css, "cursor", NULL))) sp_repr_css_set_property(css, "cursor", NULL);
3786 if (is_url(sp_repr_css_property(css, "filter", NULL))) sp_repr_css_set_property(css, "filter", NULL);
3787 if (is_url(sp_repr_css_property(css, "marker-start", NULL))) sp_repr_css_set_property(css, "marker-start", NULL);
3788 if (is_url(sp_repr_css_property(css, "marker-mid", NULL))) sp_repr_css_set_property(css, "marker-mid", NULL);
3789 if (is_url(sp_repr_css_property(css, "marker-end", NULL))) sp_repr_css_set_property(css, "marker-end", NULL);
3790 if (is_url(sp_repr_css_property(css, "mask", NULL))) sp_repr_css_set_property(css, "mask", NULL);
3791 if (is_url(sp_repr_css_property(css, "fill", NULL))) sp_repr_css_set_property(css, "fill", NULL);
3792 if (is_url(sp_repr_css_property(css, "stroke", NULL))) sp_repr_css_set_property(css, "stroke", NULL);
3794 return css;
3795 }
3797 /**
3798 * Scale a single-value property.
3799 */
3800 void
3801 sp_css_attr_scale_property_single(SPCSSAttr *css, gchar const *property,
3802 double ex, bool only_with_units = false)
3803 {
3804 gchar const *w = sp_repr_css_property(css, property, NULL);
3805 if (w) {
3806 gchar *units = NULL;
3807 double wd = g_ascii_strtod(w, &units) * ex;
3808 if (w == units) {// nothing converted, non-numeric value
3809 return;
3810 }
3811 if (only_with_units && (units == NULL || *units == '\0' || *units == '%')) {
3812 // only_with_units, but no units found, so do nothing.
3813 return;
3814 }
3815 Inkscape::CSSOStringStream os;
3816 os << wd << units; // reattach units
3817 sp_repr_css_set_property(css, property, os.str().c_str());
3818 }
3819 }
3821 /**
3822 * Scale a list-of-values property.
3823 */
3824 void
3825 sp_css_attr_scale_property_list(SPCSSAttr *css, gchar const *property, double ex)
3826 {
3827 gchar const *string = sp_repr_css_property(css, property, NULL);
3828 if (string) {
3829 Inkscape::CSSOStringStream os;
3830 gchar **a = g_strsplit(string, ",", 10000);
3831 bool first = true;
3832 for (gchar **i = a; i != NULL; i++) {
3833 gchar *w = *i;
3834 if (w == NULL)
3835 break;
3836 gchar *units = NULL;
3837 double wd = g_ascii_strtod(w, &units) * ex;
3838 if (w == units) {// nothing converted, non-numeric value ("none" or "inherit"); do nothing
3839 g_strfreev(a);
3840 return;
3841 }
3842 if (!first) {
3843 os << ",";
3844 }
3845 os << wd << units; // reattach units
3846 first = false;
3847 }
3848 sp_repr_css_set_property(css, property, os.str().c_str());
3849 g_strfreev(a);
3850 }
3851 }
3853 /**
3854 * Scale any properties that may hold <length> by ex.
3855 */
3856 SPCSSAttr *
3857 sp_css_attr_scale(SPCSSAttr *css, double ex)
3858 {
3859 sp_css_attr_scale_property_single(css, "baseline-shift", ex);
3860 sp_css_attr_scale_property_single(css, "stroke-width", ex);
3861 sp_css_attr_scale_property_list (css, "stroke-dasharray", ex);
3862 sp_css_attr_scale_property_single(css, "stroke-dashoffset", ex);
3863 sp_css_attr_scale_property_single(css, "font-size", ex);
3864 sp_css_attr_scale_property_single(css, "kerning", ex);
3865 sp_css_attr_scale_property_single(css, "letter-spacing", ex);
3866 sp_css_attr_scale_property_single(css, "word-spacing", ex);
3867 sp_css_attr_scale_property_single(css, "line-height", ex, true);
3869 return css;
3870 }
3873 /**
3874 * Remove quotes from SPIString object value.
3875 *
3876 * \todo FIXME: now used for font family, but perhaps this should apply to
3877 * ALL strings (check CSS spec), in which case this should be part of
3878 * read_istring.
3879 */
3880 static void
3881 css2_unescape_unquote(SPIString *val)
3882 {
3883 if (val->set && val->value && strlen(val->value) >= 2) {
3885 /// \todo unescape all \-escaped chars
3887 int l = strlen(val->value);
3888 if ( ( val->value[0] == '"' && val->value[l - 1] == '"' ) ||
3889 ( val->value[0] == '\'' && val->value[l - 1] == '\'' ) ) {
3890 memcpy(val->value, val->value + 1, l - 2);
3891 val->value[l - 2] = '\0';
3892 }
3893 }
3894 }
3897 /*
3898 Local Variables:
3899 mode:c++
3900 c-file-style:"stroustrup"
3901 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3902 indent-tabs-mode:nil
3903 fill-column:99
3904 End:
3905 */
3906 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :