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