1 /*
2 * Parent class for text and flowtext
3 *
4 * Authors:
5 * bulia byak
6 * Richard Hughes
7 *
8 * Copyright (C) 2004-5 authors
9 *
10 * Released under GNU GPL, read the file 'COPYING' for more information
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include "desktop.h"
18 #include "style.h"
19 #include "unit-constants.h"
21 #include "xml/repr.h"
22 #include "xml/attribute-record.h"
24 #include "sp-textpath.h"
25 #include "sp-flowtext.h"
26 #include "sp-flowdiv.h"
27 #include "sp-flowregion.h"
28 #include "sp-tspan.h"
30 #include "text-editing.h"
32 static bool tidy_xml_tree_recursively(SPObject *root);
34 Inkscape::Text::Layout const * te_get_layout (SPItem const *item)
35 {
36 if (SP_IS_TEXT(item)) {
37 return &(SP_TEXT(item)->layout);
38 } else if (SP_IS_FLOWTEXT (item)) {
39 return &(SP_FLOWTEXT(item)->layout);
40 }
41 return NULL;
42 }
44 static void te_update_layout_now (SPItem *item)
45 {
46 if (SP_IS_TEXT(item))
47 SP_TEXT(item)->rebuildLayout();
48 else if (SP_IS_FLOWTEXT (item))
49 SP_FLOWTEXT(item)->rebuildLayout();
50 }
52 /** Returns true if there are no visible characters on the canvas */
53 bool
54 sp_te_output_is_empty (SPItem const *item)
55 {
56 Inkscape::Text::Layout const *layout = te_get_layout(item);
57 return layout->begin() == layout->end();
58 }
60 /** Returns true if the user has typed nothing in the text box */
61 bool
62 sp_te_input_is_empty (SPObject const *item)
63 {
64 if (SP_IS_STRING(item)) return SP_STRING(item)->string.empty();
65 for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child))
66 if (!sp_te_input_is_empty(child)) return false;
67 return true;
68 }
70 Inkscape::Text::Layout::iterator
71 sp_te_get_position_by_coords (SPItem const *item, NR::Point &i_p)
72 {
73 NR::Matrix im=sp_item_i2d_affine (item);
74 im = im.inverse();
76 NR::Point p = i_p * im;
77 Inkscape::Text::Layout const *layout = te_get_layout(item);
78 return layout->getNearestCursorPositionTo(p);
79 }
81 std::vector<NR::Point> sp_te_create_selection_quads(SPItem const *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, NR::Matrix const &transform)
82 {
83 if (start == end)
84 return std::vector<NR::Point>();
85 Inkscape::Text::Layout const *layout = te_get_layout(item);
86 if (layout == NULL)
87 return std::vector<NR::Point>();
89 return layout->createSelectionShape(start, end, transform);
90 }
92 void
93 sp_te_get_cursor_coords (SPItem const *item, Inkscape::Text::Layout::iterator const &position, NR::Point &p0, NR::Point &p1)
94 {
95 Inkscape::Text::Layout const *layout = te_get_layout(item);
96 double height, rotation;
97 layout->queryCursorShape(position, &p0, &height, &rotation);
98 p1 = NR::Point(p0[NR::X] + height * sin(rotation), p0[NR::Y] - height * cos(rotation));
99 }
101 SPStyle const * sp_te_style_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
102 {
103 Inkscape::Text::Layout const *layout = te_get_layout(text);
104 if (layout == NULL)
105 return NULL;
106 SPObject const *pos_obj = 0;
107 void *rawptr = 0;
108 layout->getSourceOfCharacter(position, &rawptr);
109 pos_obj = SP_OBJECT(rawptr);
110 if (pos_obj == 0) pos_obj = text;
111 while (SP_OBJECT_STYLE(pos_obj) == NULL)
112 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
113 return SP_OBJECT_STYLE(pos_obj);
114 }
116 /*
117 * for debugging input
118 *
119 char * dump_hexy(const gchar * utf8)
120 {
121 static char buffer[1024];
123 buffer[0]='\0';
124 for (const char *ptr=utf8; *ptr; ptr++) {
125 sprintf(buffer+strlen(buffer),"x%02X",(unsigned char)*ptr);
126 }
127 return buffer;
128 }
129 */
131 Inkscape::Text::Layout::iterator sp_te_replace(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, gchar const *utf8)
132 {
133 Inkscape::Text::Layout::iterator new_start = sp_te_delete(item, start, end);
134 return sp_te_insert(item, new_start, utf8);
135 }
138 /* ***************************************************************************************************/
139 // I N S E R T I N G T E X T
141 static bool is_line_break_object(SPObject const *object)
142 {
143 return SP_IS_TEXT(object)
144 || (SP_IS_TSPAN(object) && SP_TSPAN(object)->role != SP_TSPAN_ROLE_UNSPECIFIED)
145 || SP_IS_TEXTPATH(object)
146 || SP_IS_FLOWDIV(object)
147 || SP_IS_FLOWPARA(object)
148 || SP_IS_FLOWLINE(object)
149 || SP_IS_FLOWREGIONBREAK(object);
150 }
152 /** returns the attributes for an object, or NULL if it isn't a text,
153 tspan or textpath. */
154 static TextTagAttributes* attributes_for_object(SPObject *object)
155 {
156 if (SP_IS_TSPAN(object))
157 return &SP_TSPAN(object)->attributes;
158 if (SP_IS_TEXT(object))
159 return &SP_TEXT(object)->attributes;
160 if (SP_IS_TEXTPATH(object))
161 return &SP_TEXTPATH(object)->attributes;
162 return NULL;
163 }
165 static const char * span_name_for_text_object(SPObject const *object)
166 {
167 if (SP_IS_TEXT(object)) return "svg:tspan";
168 else if (SP_IS_FLOWTEXT(object)) return "svg:flowSpan";
169 return NULL;
170 }
172 /** Recursively gets the length of all the SPStrings at or below the given
173 \a item. Also adds 1 for each line break encountered. */
174 unsigned sp_text_get_length(SPObject const *item)
175 {
176 unsigned length = 0;
178 if (SP_IS_STRING(item)) return SP_STRING(item)->string.length();
179 if (is_line_break_object(item)) length++;
180 for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
181 if (SP_IS_STRING(child)) length += SP_STRING(child)->string.length();
182 else length += sp_text_get_length(child);
183 }
184 return length;
185 }
187 /** Recursively gets the length of all the SPStrings at or below the given
188 \a item, before and not including \a upto. Also adds 1 for each line break encountered. */
189 unsigned sp_text_get_length_upto(SPObject const *item, SPObject const *upto)
190 {
191 unsigned length = 0;
193 if (SP_IS_STRING(item)) {
194 return SP_STRING(item)->string.length();
195 }
196 if (is_line_break_object(item) && !SP_IS_TEXT(item)) {
197 if (item != SP_OBJECT_PARENT(item)->firstChild()) {
198 // add 1 for each newline
199 length++;
200 }
201 }
202 for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
203 if (upto && child == upto) {
204 // hit upto, return immediately
205 return length;
206 }
207 if (SP_IS_STRING(child)) {
208 length += SP_STRING(child)->string.length();
209 }
210 else {
211 if (upto && child->isAncestorOf(upto)) {
212 // upto is below us, recurse and break loop
213 length += sp_text_get_length_upto(child, upto);
214 return length;
215 } else {
216 // recurse and go to the next sibling
217 length += sp_text_get_length_upto(child, upto);
218 }
219 }
220 }
221 return length;
222 }
224 static Inkscape::XML::Node* duplicate_node_without_children(Inkscape::XML::Node const *old_node)
225 {
226 switch (old_node->type()) {
227 case Inkscape::XML::ELEMENT_NODE: {
228 Inkscape::XML::Node *new_node = sp_repr_new(old_node->name());
229 Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attributes = old_node->attributeList();
230 GQuark const id_key = g_quark_from_string("id");
231 for ( ; attributes ; attributes++) {
232 if (attributes->key == id_key) continue;
233 new_node->setAttribute(g_quark_to_string(attributes->key), attributes->value);
234 }
235 return new_node;
236 }
238 case Inkscape::XML::TEXT_NODE:
239 return sp_repr_new_text(old_node->content());
241 case Inkscape::XML::COMMENT_NODE:
242 return sp_repr_new_comment(old_node->content());
244 case Inkscape::XML::DOCUMENT_NODE:
245 return NULL; // this had better never happen
246 }
247 return NULL;
248 }
250 /** returns the sum of the (recursive) lengths of all the SPStrings prior
251 to \a item at the same level. */
252 static unsigned sum_sibling_text_lengths_before(SPObject const *item)
253 {
254 unsigned char_index = 0;
255 for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = SP_OBJECT_NEXT(sibling))
256 char_index += sp_text_get_length(sibling);
257 return char_index;
258 }
260 /** splits the attributes for the first object at the given \a char_index
261 and moves the ones after that point into \a second_item. */
262 static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
263 {
264 TextTagAttributes *first_attrs = attributes_for_object(first_item);
265 TextTagAttributes *second_attrs = attributes_for_object(second_item);
266 if (first_attrs && second_attrs)
267 first_attrs->split(char_index, second_attrs);
268 }
270 /** recursively divides the XML node tree into two objects: the original will
271 contain all objects up to and including \a split_obj and the returned value
272 will be the new leaf which represents the copy of \a split_obj and extends
273 down the tree with new elements all the way to the common root which is the
274 parent of the first line break node encountered.
275 */
276 static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
277 {
278 if (is_line_break_object(split_obj)) {
279 Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
280 SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
281 Inkscape::GC::release(new_node);
282 split_attributes(split_obj, SP_OBJECT_NEXT(split_obj), char_index);
283 return SP_OBJECT_NEXT(split_obj);
284 }
286 unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
287 SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
288 // copy the split node
289 Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
290 SP_OBJECT_REPR(duplicate_obj)->appendChild(new_node);
291 Inkscape::GC::release(new_node);
293 // sort out the copied attributes (x/y/dx/dy/rotate)
294 split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
296 // then move all the subsequent nodes
297 split_obj = SP_OBJECT_NEXT(split_obj);
298 while (split_obj) {
299 Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(split_obj);
300 SPObject *next_obj = SP_OBJECT_NEXT(split_obj); // this is about to become invalidated by removeChild()
301 Inkscape::GC::anchor(move_repr);
302 SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->removeChild(move_repr);
303 SP_OBJECT_REPR(duplicate_obj)->appendChild(move_repr);
304 Inkscape::GC::release(move_repr);
306 split_obj = next_obj;
307 }
308 return duplicate_obj->firstChild();
309 }
311 /** inserts a new line break at the given position in a text or flowtext
312 object. If the position is in the middle of a span, the XML tree must be
313 chopped in two such that the line can be created at the root of the text
314 element. Returns an iterator pointing just after the inserted break. */
315 Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
316 {
317 // Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
318 // texpaths attached to the same path, with a vertical shift
319 if (SP_IS_TEXT_TEXTPATH (item))
320 return position;
322 Inkscape::Text::Layout const *layout = te_get_layout(item);
323 SPObject *split_obj = 0;
324 Glib::ustring::iterator split_text_iter;
325 if (position != layout->end()) {
326 void *rawptr = 0;
327 layout->getSourceOfCharacter(position, &rawptr, &split_text_iter);
328 split_obj = SP_OBJECT(rawptr);
329 }
331 if (split_obj == 0 || is_line_break_object(split_obj)) {
332 if (split_obj == 0) split_obj = item->lastChild();
333 if (split_obj) {
334 Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
335 SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
336 Inkscape::GC::release(new_node);
337 }
338 } else if (SP_IS_STRING(split_obj)) {
339 Glib::ustring *string = &SP_STRING(split_obj)->string;
340 unsigned char_index = 0;
341 for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; it++)
342 char_index++;
343 // we need to split the entire text tree into two
344 SPString *new_string = SP_STRING(split_text_object_tree_at(split_obj, char_index));
345 SP_OBJECT_REPR(new_string)->setContent(&*split_text_iter.base()); // a little ugly
346 string->erase(split_text_iter, string->end());
347 SP_OBJECT_REPR(split_obj)->setContent(string->c_str());
348 // TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
349 } else {
350 // TODO
351 // I think the only case to put here is arbitrary gaps, which nobody uses yet
352 }
353 item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
354 unsigned char_index = layout->iteratorToCharIndex(position);
355 te_update_layout_now(item);
356 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
357 return layout->charIndexToIterator(char_index + 1);
358 }
360 /** finds the first SPString after the given position, including children, excluding parents */
361 static SPString* sp_te_seek_next_string_recursive(SPObject *start_obj)
362 {
363 while (start_obj) {
364 if (start_obj->hasChildren()) {
365 SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
366 if (found_string) return found_string;
367 }
368 if (SP_IS_STRING(start_obj)) return SP_STRING(start_obj);
369 start_obj = SP_OBJECT_NEXT(start_obj);
370 if (is_line_break_object(start_obj))
371 break; // don't cross line breaks
372 }
373 return NULL;
374 }
376 /** inserts the given characters into the given string and inserts
377 corresponding new x/y/dx/dy/rotate attributes into all its parents. */
378 static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
379 {
380 unsigned char_index = 0;
381 unsigned char_count = g_utf8_strlen(utf8, -1);
382 Glib::ustring *string = &SP_STRING(string_item)->string;
384 for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; it++)
385 char_index++;
386 string->replace(iter_at, iter_at, utf8);
388 SPObject *parent_item = string_item;
389 for ( ; ; ) {
390 char_index += sum_sibling_text_lengths_before(parent_item);
391 parent_item = SP_OBJECT_PARENT(parent_item);
392 TextTagAttributes *attributes = attributes_for_object(parent_item);
393 if (!attributes) break;
394 attributes->insert(char_index, char_count);
395 }
396 }
398 /** Inserts the given text into a text or flowroot object. Line breaks
399 cannot be inserted using this function, see sp_te_insert_line(). Returns
400 an iterator pointing just after the inserted text. */
401 Inkscape::Text::Layout::iterator
402 sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
403 {
404 if (!g_utf8_validate(utf8,-1,NULL)) {
405 g_warning("Trying to insert invalid utf8");
406 return position;
407 }
409 Inkscape::Text::Layout const *layout = te_get_layout(item);
410 SPObject *source_obj = 0;
411 void *rawptr = 0;
412 Glib::ustring::iterator iter_text;
413 // we want to insert after the previous char, not before the current char.
414 // it makes a difference at span boundaries
415 Inkscape::Text::Layout::iterator it_prev_char = position;
416 bool cursor_at_start = !it_prev_char.prevCharacter();
417 bool cursor_at_end = position == layout->end();
418 layout->getSourceOfCharacter(it_prev_char, &rawptr, &iter_text);
419 source_obj = SP_OBJECT(rawptr);
420 if (SP_IS_STRING(source_obj)) {
421 // the simple case
422 if (!cursor_at_start) iter_text++;
423 SPString *string_item = SP_STRING(source_obj);
424 insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
425 } else {
426 // the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
427 if (cursor_at_start) {
428 source_obj = item;
429 if (source_obj->hasChildren()) {
430 source_obj = source_obj->firstChild();
431 if (SP_IS_FLOWTEXT(item)) {
432 while (SP_IS_FLOWREGION(source_obj) || SP_IS_FLOWREGIONEXCLUDE(source_obj))
433 source_obj = SP_OBJECT_NEXT(source_obj);
434 if (source_obj == NULL)
435 source_obj = item;
436 }
437 }
438 if (source_obj == item && SP_IS_FLOWTEXT(item)) {
439 Inkscape::XML::Node *para = sp_repr_new("svg:flowPara");
440 SP_OBJECT_REPR(item)->appendChild(para);
441 source_obj = item->lastChild();
442 }
443 } else
444 source_obj = SP_OBJECT_NEXT(source_obj);
446 if (source_obj) { // never fails
447 SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
448 if (string_item == NULL) {
449 // need to add an SPString in this (pathological) case
450 Inkscape::XML::Node *rstring = sp_repr_new_text("");
451 SP_OBJECT_REPR(source_obj)->addChild(rstring, NULL);
452 Inkscape::GC::release(rstring);
453 g_assert(SP_IS_STRING(source_obj->firstChild()));
454 string_item = SP_STRING(source_obj->firstChild());
455 }
456 insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
457 }
458 }
460 item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
461 unsigned char_index = layout->iteratorToCharIndex(position);
462 te_update_layout_now(item);
463 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
464 return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
465 }
468 /* ***************************************************************************************************/
469 // D E L E T I N G T E X T
471 /** moves all the children of \a from_repr to \a to_repr, either before
472 the existing children or after them. Order is maintained. The empty
473 \a from_repr is not deleted. */
474 static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
475 {
476 while (from_repr->childCount()) {
477 Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
478 Inkscape::GC::anchor(child);
479 from_repr->removeChild(child);
480 if (prepend) to_repr->addChild(child, NULL);
481 else to_repr->appendChild(child);
482 Inkscape::GC::release(child);
483 }
484 }
486 /** returns the object in the tree which is the closest ancestor of both
487 \a one and \a two. It will never return anything higher than \a text. */
488 static SPObject* get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
489 {
490 if (one == NULL || two == NULL)
491 return text;
492 SPObject *common_ancestor = one;
493 if (SP_IS_STRING(common_ancestor))
494 common_ancestor = SP_OBJECT_PARENT(common_ancestor);
495 while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
496 g_assert(common_ancestor != text);
497 common_ancestor = SP_OBJECT_PARENT(common_ancestor);
498 }
499 return common_ancestor;
500 }
502 /** positions \a para_obj and \a text_iter to be pointing at the end
503 of the last string in the last leaf object of \a para_obj. If the last
504 leaf is not an SPString then \a text_iter will be unchanged. */
505 static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
506 {
507 while ((*para_obj)->hasChildren())
508 *para_obj = (*para_obj)->lastChild();
509 if (SP_IS_STRING(*para_obj))
510 *text_iter = SP_STRING(*para_obj)->string.end();
511 }
513 /** delete the line break pointed to by \a item by merging its children into
514 the next suitable object and deleting \a item. Returns the object after the
515 ones that have just been moved and sets \a next_is_sibling accordingly. */
516 static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
517 {
518 Inkscape::XML::Node *this_repr = SP_OBJECT_REPR(item);
519 SPObject *next_item = NULL;
520 unsigned moved_char_count = sp_text_get_length(item) - 1; // the -1 is because it's going to count the line break
522 /* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
523 <div></div><p>*text</p>
524 <p><div></div>*text</p>
525 <p><div></div></p><p>*text</p>
526 */
527 Inkscape::XML::Node *new_span_repr = sp_repr_new(span_name_for_text_object(root));
529 if (gchar const *a = this_repr->attribute("dx"))
530 new_span_repr->setAttribute("dx", a);
531 if (gchar const *a = this_repr->attribute("dy"))
532 new_span_repr->setAttribute("dy", a);
533 if (gchar const *a = this_repr->attribute("rotate"))
534 new_span_repr->setAttribute("rotate", a);
536 SPObject *following_item = item;
537 while (SP_OBJECT_NEXT(following_item) == NULL) {
538 following_item = SP_OBJECT_PARENT(following_item);
539 g_assert(following_item != root);
540 }
541 following_item = SP_OBJECT_NEXT(following_item);
543 SPObject *new_parent_item;
544 if (SP_IS_STRING(following_item)) {
545 new_parent_item = SP_OBJECT_PARENT(following_item);
546 SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, SP_OBJECT_PREV(following_item) ? SP_OBJECT_REPR(SP_OBJECT_PREV(following_item)) : NULL);
547 next_item = following_item;
548 *next_is_sibling = true;
549 } else {
550 new_parent_item = following_item;
551 next_item = new_parent_item->firstChild();
552 *next_is_sibling = true;
553 if (next_item == NULL) {
554 next_item = new_parent_item;
555 *next_is_sibling = false;
556 }
557 SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, NULL);
558 }
560 // work around a bug in sp_style_write_difference() which causes the difference
561 // not to be written if the second param has a style set which the first does not
562 // by causing the first param to have everything set
563 SPCSSAttr *dest_node_attrs = sp_repr_css_attr(SP_OBJECT_REPR(new_parent_item), "style");
564 SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
565 SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
566 Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
567 for ( ; attrs ; attrs++) {
568 gchar const *key = g_quark_to_string(attrs->key);
569 gchar const *this_attr = this_node_attrs_inherited->attribute(key);
570 if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
571 this_node_attrs->setAttribute(key, this_attr);
572 }
573 sp_repr_css_attr_unref(this_node_attrs_inherited);
574 sp_repr_css_attr_unref(this_node_attrs);
575 sp_repr_css_attr_unref(dest_node_attrs);
576 sp_repr_css_change(new_span_repr, this_node_attrs, "style");
578 TextTagAttributes *attributes = attributes_for_object(new_parent_item);
579 if (attributes)
580 attributes->insert(0, moved_char_count);
581 move_child_nodes(this_repr, new_span_repr);
582 this_repr->parent()->removeChild(this_repr);
583 return next_item;
584 }
586 /** erases the given characters from the given string and deletes the
587 corresponding x/y/dx/dy/rotate attributes from all its parents. */
588 static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
589 {
590 unsigned char_index = 0;
591 unsigned char_count = 0;
592 Glib::ustring *string = &SP_STRING(string_item)->string;
594 for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; it++)
595 char_index++;
596 for (Glib::ustring::iterator it = iter_from ; it != iter_to ; it++)
597 char_count++;
598 string->erase(iter_from, iter_to);
599 SP_OBJECT_REPR(string_item)->setContent(string->c_str());
601 SPObject *parent_item = string_item;
602 for ( ; ; ) {
603 char_index += sum_sibling_text_lengths_before(parent_item);
604 parent_item = SP_OBJECT_PARENT(parent_item);
605 TextTagAttributes *attributes = attributes_for_object(parent_item);
606 if (attributes == NULL) break;
608 attributes->erase(char_index, char_count);
609 attributes->writeTo(SP_OBJECT_REPR(parent_item));
610 }
611 }
613 /* Deletes the given characters from a text or flowroot object. This is
614 quite a complicated operation, partly due to the cleanup that is done if all
615 the text in a subobject has been deleted, and partly due to the difficulty
616 of figuring out what is a line break and how to delete one. Returns the
617 lesser of \a start and \a end, because that is where the cursor should be
618 put after the deletion is done. */
619 Inkscape::Text::Layout::iterator
620 sp_te_delete (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
621 {
622 if (start == end) return start;
623 Inkscape::Text::Layout::iterator first, last;
624 if (start < end) {
625 first = start;
626 last = end;
627 } else {
628 first = end;
629 last = start;
630 }
631 Inkscape::Text::Layout const *layout = te_get_layout(item);
632 SPObject *start_item = 0, *end_item = 0;
633 void *rawptr = 0;
634 Glib::ustring::iterator start_text_iter, end_text_iter;
635 layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
636 start_item = SP_OBJECT(rawptr);
637 layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
638 end_item = SP_OBJECT(rawptr);
639 if (start_item == 0)
640 return first; // start is at end of text
641 if (is_line_break_object(start_item))
642 move_to_end_of_paragraph(&start_item, &start_text_iter);
643 if (end_item == 0) {
644 end_item = item->lastChild();
645 move_to_end_of_paragraph(&end_item, &end_text_iter);
646 }
647 else if (is_line_break_object(end_item))
648 move_to_end_of_paragraph(&end_item, &end_text_iter);
650 SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
652 if (start_item == end_item) {
653 // the quick case where we're deleting stuff all from the same string
654 if (SP_IS_STRING(start_item)) { // always true (if it_start != it_end anyway)
655 erase_from_spstring(SP_STRING(start_item), start_text_iter, end_text_iter);
656 }
657 } else {
658 SPObject *sub_item = start_item;
659 // walk the tree from start_item to end_item, deleting as we go
660 while (sub_item != item) {
661 if (sub_item == end_item) {
662 if (SP_IS_STRING(sub_item)) {
663 Glib::ustring *string = &SP_STRING(sub_item)->string;
664 erase_from_spstring(SP_STRING(sub_item), string->begin(), end_text_iter);
665 }
666 break;
667 }
668 if (SP_IS_STRING(sub_item)) {
669 SPString *string = SP_STRING(sub_item);
670 if (sub_item == start_item)
671 erase_from_spstring(string, start_text_iter, string->string.end());
672 else
673 erase_from_spstring(string, string->string.begin(), string->string.end());
674 }
675 // walk to the next item in the tree
676 if (sub_item->hasChildren())
677 sub_item = sub_item->firstChild();
678 else {
679 SPObject *next_item;
680 do {
681 bool is_sibling = true;
682 next_item = SP_OBJECT_NEXT(sub_item);
683 if (next_item == NULL) {
684 next_item = SP_OBJECT_PARENT(sub_item);
685 is_sibling = false;
686 }
688 if (is_line_break_object(sub_item))
689 next_item = delete_line_break(item, sub_item, &is_sibling);
691 sub_item = next_item;
692 if (is_sibling) break;
693 // no more siblings, go up a parent
694 } while (sub_item != item && sub_item != end_item);
695 }
696 }
697 }
699 while (tidy_xml_tree_recursively(common_ancestor));
700 te_update_layout_now(item);
701 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
702 layout->validateIterator(&first);
703 return first;
704 }
707 /* ***************************************************************************************************/
708 // P L A I N T E X T F U N C T I O N S
710 /** Gets a text-only representation of the given text or flowroot object,
711 replacing line break elements with '\n'. */
712 static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
713 {
714 if (*pending_line_break)
715 *string += '\n';
716 for (SPObject const *child = root->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
717 if (SP_IS_STRING(child))
718 *string += SP_STRING(child)->string;
719 else
720 sp_te_get_ustring_multiline(child, string, pending_line_break);
721 }
722 if (!SP_IS_TEXT(root) && !SP_IS_TEXTPATH(root) && is_line_break_object(root))
723 *pending_line_break = true;
724 }
726 /** Gets a text-only representation of the given text or flowroot object,
727 replacing line break elements with '\n'. The return value must be free()d. */
728 gchar *
729 sp_te_get_string_multiline (SPItem const *text)
730 {
731 Glib::ustring string;
732 bool pending_line_break = false;
734 if (!SP_IS_TEXT(text) && !SP_IS_FLOWTEXT(text)) return NULL;
735 sp_te_get_ustring_multiline(text, &string, &pending_line_break);
736 if (string.empty()) return NULL;
737 return strdup(string.data());
738 }
740 /** Gets a text-only representation of the characters in a text or flowroot
741 object from \a start to \a end only. Line break elements are replaced with
742 '\n'. */
743 Glib::ustring
744 sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
745 {
746 if (start == end) return "";
747 Inkscape::Text::Layout::iterator first, last;
748 if (start < end) {
749 first = start;
750 last = end;
751 } else {
752 first = end;
753 last = start;
754 }
755 Inkscape::Text::Layout const *layout = te_get_layout(text);
756 Glib::ustring result;
757 // not a particularly fast piece of code. I'll optimise it if people start to notice.
758 for ( ; first < last ; first.nextCharacter()) {
759 SPObject *char_item = 0;
760 void *rawptr = 0;
761 Glib::ustring::iterator text_iter;
762 layout->getSourceOfCharacter(first, &rawptr, &text_iter);
763 char_item = SP_OBJECT(rawptr);
764 if (SP_IS_STRING(char_item))
765 result += *text_iter;
766 else
767 result += '\n';
768 }
769 return result;
770 }
772 void
773 sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
774 {
775 g_return_if_fail (text != NULL);
776 g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
778 Inkscape::XML::Node *repr;
779 SPObject *object;
780 bool is_textpath = false;
781 if (SP_IS_TEXT_TEXTPATH (text)) {
782 repr = SP_OBJECT_REPR (sp_object_first_child(SP_OBJECT (text)));
783 object = sp_object_first_child(SP_OBJECT (text));
784 is_textpath = true;
785 } else {
786 repr = SP_OBJECT_REPR (text);
787 object = SP_OBJECT (text);
788 }
790 if (!str) str = "";
791 gchar *content = g_strdup (str);
793 repr->setContent("");
794 SPObject *child = object->firstChild();
795 while (child) {
796 SPObject *next = SP_OBJECT_NEXT(child);
797 if (!SP_IS_FLOWREGION(child) && !SP_IS_FLOWREGIONEXCLUDE(child))
798 repr->removeChild(SP_OBJECT_REPR(child));
799 child = next;
800 }
802 gchar *p = content;
803 while (p) {
804 gchar *e = strchr (p, '\n');
805 if (is_textpath) {
806 if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
807 } else {
808 if (e) *e = '\0';
809 Inkscape::XML::Node *rtspan;
810 if (SP_IS_TEXT(text)) { // create a tspan for each line
811 rtspan = sp_repr_new ("svg:tspan");
812 rtspan->setAttribute("sodipodi:role", "line");
813 } else { // create a flowPara for each line
814 rtspan = sp_repr_new ("svg:flowPara");
815 }
816 Inkscape::XML::Node *rstr = sp_repr_new_text(p);
817 rtspan->addChild(rstr, NULL);
818 Inkscape::GC::release(rstr);
819 repr->appendChild(rtspan);
820 Inkscape::GC::release(rtspan);
821 }
822 p = (e) ? e + 1 : NULL;
823 }
824 if (is_textpath) {
825 Inkscape::XML::Node *rstr = sp_repr_new_text(content);
826 repr->addChild(rstr, NULL);
827 Inkscape::GC::release(rstr);
828 }
830 g_free (content);
831 }
833 /* ***************************************************************************************************/
834 // K E R N I N G A N D S P A C I N G
836 /** Returns the attributes block and the character index within that block
837 which represents the iterator \a position. */
838 static TextTagAttributes*
839 text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
840 {
841 if (item == NULL || char_index == NULL || !SP_IS_TEXT(item))
842 return NULL; // flowtext doesn't support kerning yet
843 SPText *text = SP_TEXT(item);
845 SPObject *source_item = 0;
846 void *rawptr = 0;
847 Glib::ustring::iterator source_text_iter;
848 text->layout.getSourceOfCharacter(position, &rawptr, &source_text_iter);
849 source_item = SP_OBJECT(rawptr);
851 if (!SP_IS_STRING(source_item)) return NULL;
852 Glib::ustring *string = &SP_STRING(source_item)->string;
853 *char_index = sum_sibling_text_lengths_before(source_item);
854 for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; it++)
855 ++*char_index;
857 return attributes_for_object(SP_OBJECT_PARENT(source_item));
858 }
860 void
861 sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, NR::Point by)
862 {
863 // divide increment by zoom
864 // divide increment by matrix expansion
865 gdouble factor = 1 / desktop->current_zoom();
866 NR::Matrix t = sp_item_i2doc_affine(item);
867 factor = factor / NR::expansion(t);
868 by = factor * by;
870 unsigned char_index;
871 TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
872 if (attributes) attributes->addToDxDy(char_index, by);
873 if (start != end) {
874 attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
875 if (attributes) attributes->addToDxDy(char_index, -by);
876 }
878 item->updateRepr();
879 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
880 }
882 void
883 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
884 {
885 // divide increment by zoom
886 // divide increment by matrix expansion
887 gdouble factor = 1 / desktop->current_zoom();
888 NR::Matrix t = sp_item_i2doc_affine(text);
889 factor = factor / NR::expansion(t);
890 Inkscape::Text::Layout const *layout = te_get_layout(text);
891 if (layout == NULL) return;
892 SPObject *source_item = 0;
893 void *rawptr = 0;
894 layout->getSourceOfCharacter(std::min(start, end), &rawptr);
895 source_item = SP_OBJECT(rawptr);
896 if (source_item == 0) return;
897 gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
899 sp_te_adjust_rotation(text, start, end, desktop, degrees);
900 }
902 void
903 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble degrees)
904 {
905 unsigned char_index;
906 TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
907 if (attributes == NULL) return;
909 if (start != end) {
910 for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
911 attributes = text_tag_attributes_at_position(text, it, &char_index);
912 if (attributes) attributes->addToRotate(char_index, degrees);
913 }
914 } else
915 attributes->addToRotate(char_index, degrees);
917 text->updateRepr();
918 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
919 }
921 void
922 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
923 {
924 g_return_if_fail (text != NULL);
925 g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
927 Inkscape::Text::Layout const *layout = te_get_layout(text);
929 gdouble val;
930 SPObject *source_obj = 0;
931 void *rawptr = 0;
932 unsigned nb_let;
933 layout->getSourceOfCharacter(std::min(start, end), &rawptr);
934 source_obj = SP_OBJECT(rawptr);
936 if (source_obj == 0) { // end of text
937 source_obj = text->lastChild();
938 }
939 if (SP_IS_STRING(source_obj)) {
940 source_obj = source_obj->parent;
941 }
943 SPStyle *style = SP_OBJECT_STYLE (source_obj);
945 // calculate real value
946 /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
947 get rid of the `else val = 0.0'. Similarly below and in sp-string.cpp. */
948 if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
949 if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
950 val = style->font_size.computed * style->letter_spacing.value;
951 } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
952 val = style->font_size.computed * style->letter_spacing.value * 0.5;
953 } else { // unknown unit - should not happen
954 val = 0.0;
955 }
956 } else { // there's a real value in .computed, or it's zero
957 val = style->letter_spacing.computed;
958 }
960 if (start == end) {
961 while (!is_line_break_object(source_obj)) // move up the tree so we apply to the closest paragraph
962 source_obj = SP_OBJECT_PARENT(source_obj);
963 nb_let = sp_text_get_length(source_obj);
964 } else {
965 nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
966 }
968 // divide increment by zoom and by the number of characters in the line,
969 // so that the entire line is expanded by by pixels, no matter what its length
970 gdouble const zoom = desktop->current_zoom();
971 gdouble const zby = (by
972 / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
973 / NR::expansion(sp_item_i2doc_affine(SP_ITEM(source_obj))));
974 val += zby;
976 if (start == end) {
977 // set back value to entire paragraph
978 style->letter_spacing.normal = FALSE;
979 if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
980 if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
981 style->letter_spacing.value = val / style->font_size.computed;
982 } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
983 style->letter_spacing.value = val / style->font_size.computed * 2;
984 }
985 } else {
986 style->letter_spacing.computed = val;
987 }
989 style->letter_spacing.set = TRUE;
990 } else {
991 // apply to selection only
992 SPCSSAttr *css = sp_repr_css_attr_new();
993 char string_val[40];
994 g_snprintf(string_val, sizeof(string_val), "%f", val);
995 sp_repr_css_set_property(css, "letter-spacing", string_val);
996 sp_te_apply_style(text, start, end, css);
997 sp_repr_css_attr_unref(css);
998 }
1000 text->updateRepr();
1001 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1002 }
1004 void
1005 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
1006 {
1007 // TODO: use start and end iterators to delineate the area to be affected
1008 g_return_if_fail (text != NULL);
1009 g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1011 Inkscape::Text::Layout const *layout = te_get_layout(text);
1012 SPStyle *style = SP_OBJECT_STYLE (text);
1014 if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
1015 style->line_height.set = TRUE;
1016 style->line_height.inherit = FALSE;
1017 style->line_height.normal = FALSE;
1018 style->line_height.unit = SP_CSS_UNIT_PERCENT;
1019 style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1020 }
1022 unsigned line_count = layout->lineIndex(layout->end());
1023 double all_lines_height = layout->characterAnchorPoint(layout->end())[NR::Y] - layout->characterAnchorPoint(layout->begin())[NR::Y];
1024 double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1025 if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1027 // divide increment by zoom and by the number of lines,
1028 // so that the entire object is expanded by by pixels
1029 gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1031 // divide increment by matrix expansion
1032 NR::Matrix t = sp_item_i2doc_affine (SP_ITEM(text));
1033 zby = zby / NR::expansion(t);
1035 switch (style->line_height.unit) {
1036 case SP_CSS_UNIT_NONE:
1037 default:
1038 // multiplier-type units, stored in computed
1039 if (fabs(style->line_height.computed) < 0.001) style->line_height.computed = by < 0.0 ? -0.001 : 0.001; // the formula below could get stuck at zero
1040 else style->line_height.computed *= (average_line_height + zby) / average_line_height;
1041 style->line_height.value = style->line_height.computed;
1042 break;
1043 case SP_CSS_UNIT_EM:
1044 case SP_CSS_UNIT_EX:
1045 case SP_CSS_UNIT_PERCENT:
1046 // multiplier-type units, stored in value
1047 if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
1048 else style->line_height.value *= (average_line_height + zby) / average_line_height;
1049 break;
1050 // absolute-type units
1051 case SP_CSS_UNIT_PX:
1052 style->line_height.computed += zby;
1053 style->line_height.value = style->line_height.computed;
1054 break;
1055 case SP_CSS_UNIT_PT:
1056 style->line_height.computed += zby * PT_PER_PX;
1057 style->line_height.value = style->line_height.computed;
1058 break;
1059 case SP_CSS_UNIT_PC:
1060 style->line_height.computed += zby * (PT_PER_PX / 12);
1061 style->line_height.value = style->line_height.computed;
1062 break;
1063 case SP_CSS_UNIT_MM:
1064 style->line_height.computed += zby * MM_PER_PX;
1065 style->line_height.value = style->line_height.computed;
1066 break;
1067 case SP_CSS_UNIT_CM:
1068 style->line_height.computed += zby * CM_PER_PX;
1069 style->line_height.value = style->line_height.computed;
1070 break;
1071 case SP_CSS_UNIT_IN:
1072 style->line_height.computed += zby * IN_PER_PX;
1073 style->line_height.value = style->line_height.computed;
1074 break;
1075 }
1076 text->updateRepr();
1077 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1078 }
1081 /* ***************************************************************************************************/
1082 // S T Y L E A P P L I C A T I O N
1085 /** converts an iterator to a character index, mainly because ustring::substr()
1086 doesn't have a version that takes iterators as parameters. */
1087 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1088 {
1089 unsigned n = 0;
1090 for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1091 n++;
1092 return n;
1093 }
1095 /** applies the given style string on top of the existing styles for \a item,
1096 as opposed to sp_style_merge_from_style_string which merges its parameter
1097 underneath the existing styles (ie ignoring already set properties). */
1098 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1099 {
1100 SPStyle *new_style = sp_style_new();
1101 sp_style_merge_from_style_string(new_style, style_string);
1102 gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1103 if (item_style_string && *item_style_string)
1104 sp_style_merge_from_style_string(new_style, item_style_string);
1105 gchar *new_style_string = sp_style_write_string(new_style);
1106 sp_style_unref(new_style);
1107 SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1108 g_free(new_style_string);
1109 }
1111 /** Returns true if the style of \a parent and the style of \a child are
1112 equivalent (and hence the children of both will appear the same). It is a
1113 limitation of the current implementation that \a parent must be a (not
1114 necessarily immediate) ancestor of \a child. */
1115 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1116 {
1117 // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1118 // will not work. Firstly because of an inheritance bug in style.cpp that has
1119 // implications too large for me to feel safe fixing, but mainly because the css spec
1120 // requires that the computed value is inherited, not the specified value.
1121 g_assert(parent->isAncestorOf(child));
1122 gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1123 // we have to write parent_style then read it again, because some properties format their values
1124 // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1125 SPStyle *parent_spstyle = sp_style_new();
1126 sp_style_merge_from_style_string(parent_spstyle, parent_style);
1127 g_free(parent_style);
1128 parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1129 sp_style_unref(parent_spstyle);
1131 Glib::ustring child_style_construction(parent_style);
1132 while (child != parent) {
1133 // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1134 char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1135 if (style_text && *style_text) {
1136 child_style_construction += ';';
1137 child_style_construction += style_text;
1138 }
1139 child = SP_OBJECT_PARENT(child);
1140 }
1141 SPStyle *child_spstyle = sp_style_new();
1142 sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1143 gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1144 sp_style_unref(child_spstyle);
1145 bool equal = !strcmp(child_style, parent_style);
1146 g_free(child_style);
1147 g_free(parent_style);
1148 return equal;
1149 }
1151 /** returns true if \a first and \a second contain all the same attributes
1152 with the same values as each other. Note that we have to compare both
1153 forwards and backwards to make sure we don't miss any attributes that are
1154 in one but not the other. */
1155 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1156 {
1157 Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1158 for ( ; attrs ; attrs++) {
1159 gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1160 if (other_attr == NULL || strcmp(attrs->value, other_attr))
1161 return false;
1162 }
1163 attrs = second->attributeList();
1164 for ( ; attrs ; attrs++) {
1165 gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1166 if (other_attr == NULL || strcmp(attrs->value, other_attr))
1167 return false;
1168 }
1169 return true;
1170 }
1172 /** sets the given css attribute on this object and all its descendants.
1173 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1174 transform stuff. */
1175 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1176 {
1177 sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1179 for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1180 if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1181 // Unset properties which are accumulating and thus should not be set recursively.
1182 // For example, setting opacity 0.5 on a group recursively would result in the visible opacity of 0.25 for an item in the group.
1183 SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1184 sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1185 sp_repr_css_set_property(css_recurse, "opacity", NULL);
1186 apply_css_recursive(child, css_recurse);
1187 sp_repr_css_attr_unref(css_recurse);
1188 } else {
1189 apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1190 }
1191 }
1192 }
1194 /** applies the given style to all the objects at the given level and below
1195 which are between \a start_item and \a end_item, creating spans as necessary.
1196 If \a start_item or \a end_item are NULL then the style is applied to all
1197 objects to the beginning or end respectively. \a span_object_name is the
1198 name of the xml for a text span (ie tspan or flowspan). */
1199 static void recursively_apply_style(SPObject *common_ancestor, SPCSSAttr const *css, SPObject *start_item, Glib::ustring::iterator start_text_iter, SPObject *end_item, Glib::ustring::iterator end_text_iter, char const *span_object_name)
1200 {
1201 bool passed_start = start_item == NULL ? true : false;
1203 for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1204 if (start_item == child)
1205 passed_start = true;
1207 if (passed_start) {
1208 if (end_item && child->isAncestorOf(end_item)) {
1209 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1210 break;
1211 }
1212 // apply style
1214 // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1215 // often this means that new spans are created before child and child is modified only
1216 if (SP_IS_STRING(child)) {
1217 SPString *string_item = SP_STRING(child);
1218 bool surround_entire_string = true;
1220 Inkscape::XML::Node *child_span = sp_repr_new(span_object_name);
1221 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style"); // better hope that prototype wasn't nonconst for a good reason
1222 SPObject *prev_item = SP_OBJECT_PREV(child);
1223 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1225 if (child == start_item || child == end_item) {
1226 surround_entire_string = false;
1227 if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1228 // eg "abcDEFghi" -> "abc"<span>"DEF"</span>"ghi"
1229 unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1230 unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1232 Inkscape::XML::Node *text_before = sp_repr_new_text(string_item->string.substr(0, start_char_index).c_str());
1233 SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1234 SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1235 Inkscape::GC::release(text_before);
1236 Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
1237 child_span->appendChild(text_in_span);
1238 Inkscape::GC::release(text_in_span);
1239 SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1241 } else if (child == end_item) {
1242 // eg "ABCdef" -> <span>"ABC"</span>"def"
1243 // (includes case where start_text_iter == begin())
1244 // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1245 unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1247 SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1248 Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(0, end_char_index).c_str());
1249 child_span->appendChild(text_in_span);
1250 Inkscape::GC::release(text_in_span);
1251 SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1253 } else if (start_text_iter != string_item->string.begin()) {
1254 // eg "abcDEF" -> "abc"<span>"DEF"</span>
1255 unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1257 Inkscape::XML::Node *text_before = sp_repr_new_text(string_item->string.substr(0, start_char_index).c_str());
1258 SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1259 SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1260 Inkscape::GC::release(text_before);
1261 Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(start_char_index).c_str());
1262 child_span->appendChild(text_in_span);
1263 Inkscape::GC::release(text_in_span);
1264 child->deleteObject();
1265 child = sp_object_get_child_by_repr(common_ancestor, child_span);
1267 } else
1268 surround_entire_string = true;
1269 }
1270 if (surround_entire_string) {
1271 Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1272 SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1273 Inkscape::GC::anchor(child_repr);
1274 SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1275 child_span->appendChild(child_repr);
1276 Inkscape::GC::release(child_repr);
1277 child = sp_object_get_child_by_repr(common_ancestor, child_span);
1278 }
1279 Inkscape::GC::release(child_span);
1281 } else if (child != end_item) { // not a string and we're applying to the entire object. This is easy
1282 apply_css_recursive(child, css);
1283 }
1285 } else { // !passed_start
1286 if (child->isAncestorOf(start_item)) {
1287 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1288 if (end_item && child->isAncestorOf(end_item))
1289 break; // only happens when start_item == end_item (I think)
1290 passed_start = true;
1291 }
1292 }
1294 if (end_item == child)
1295 break;
1296 }
1297 }
1299 /* if item is at the beginning of a tree it doesn't matter which element
1300 it points to so for neatness we would like it to point to the highest
1301 possible child of \a common_ancestor. There is no iterator return because
1302 a string can never be an ancestor.
1304 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1305 like * to point to the inner span because we can apply style to that whole
1306 span. */
1307 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1308 {
1309 if (item == common_ancestor)
1310 return item;
1311 if (SP_IS_STRING(item))
1312 if (text_iter != SP_STRING(item)->string.begin())
1313 return item;
1314 for ( ; ; ) {
1315 SPObject *parent = SP_OBJECT_PARENT(item);
1316 if (parent == common_ancestor)
1317 break;
1318 if (item != parent->firstChild())
1319 break;
1320 item = parent;
1321 }
1322 return item;
1323 }
1326 /** empty spans: abc<span></span>def
1327 -> abcdef */
1328 static bool tidy_operator_empty_spans(SPObject **item)
1329 {
1330 if ((*item)->hasChildren()) return false;
1331 if (is_line_break_object(*item)) return false;
1332 if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1333 SPObject *next = SP_OBJECT_NEXT(*item);
1334 (*item)->deleteObject();
1335 *item = next;
1336 return true;
1337 }
1339 /** inexplicable spans: abc<span style="">def</span>ghi
1340 -> "abc""def""ghi"
1341 the repeated strings will be merged by another operator. */
1342 static bool tidy_operator_inexplicable_spans(SPObject **item)
1343 {
1344 if (SP_IS_STRING(*item)) return false;
1345 if (is_line_break_object(*item)) return false;
1346 TextTagAttributes *attrs = attributes_for_object(*item);
1347 if (attrs && attrs->anyAttributesSet()) return false;
1348 if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1349 SPObject *next = *item;
1350 while ((*item)->hasChildren()) {
1351 Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1352 Inkscape::GC::anchor(repr);
1353 SP_OBJECT_REPR(*item)->removeChild(repr);
1354 SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1355 Inkscape::GC::release(repr);
1356 next = SP_OBJECT_NEXT(next);
1357 }
1358 (*item)->deleteObject();
1359 *item = next;
1360 return true;
1361 }
1363 /** repeated spans: <font a>abc</font><font a>def</font>
1364 -> <font a>abcdef</font> */
1365 static bool tidy_operator_repeated_spans(SPObject **item)
1366 {
1367 SPObject *first = *item;
1368 SPObject *second = SP_OBJECT_NEXT(first);
1369 if (second == NULL) return false;
1371 Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1372 Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1374 if (first_repr->type() != second_repr->type()) return false;
1376 if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1377 // also amalgamate consecutive SPStrings into one
1378 Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1379 SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1380 second_repr->parent()->removeChild(second_repr);
1381 return true;
1382 }
1384 // merge consecutive spans with identical styles into one
1385 if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1386 if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1387 if (is_line_break_object(second)) return false;
1388 gchar const *first_style = first_repr->attribute("style");
1389 gchar const *second_style = second_repr->attribute("style");
1390 if (!((first_style == NULL && second_style == NULL)
1391 || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1392 return false;
1394 // all our tests passed: do the merge
1395 TextTagAttributes *attributes_first = attributes_for_object(first);
1396 TextTagAttributes *attributes_second = attributes_for_object(second);
1397 if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1398 TextTagAttributes attributes_first_copy = *attributes_first;
1399 attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1400 }
1401 move_child_nodes(second_repr, first_repr);
1402 second_repr->parent()->removeChild(second_repr);
1403 return true;
1404 // *item is still the next object to process
1405 }
1407 /** redundant nesting: <font a><font b>abc</font></font>
1408 -> <font b>abc</font>
1409 excessive nesting: <font a><size 1>abc</size></font>
1410 -> <font a,size 1>abc</font> */
1411 static bool tidy_operator_excessive_nesting(SPObject **item)
1412 {
1413 if (!(*item)->hasChildren()) return false;
1414 if ((*item)->firstChild() != (*item)->lastChild()) return false;
1415 if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1416 return false;
1417 if (SP_IS_STRING((*item)->firstChild())) return false;
1418 if (is_line_break_object((*item)->firstChild())) return false;
1419 TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1420 if (attrs && attrs->anyAttributesSet()) return false;
1421 gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1422 if (child_style && *child_style)
1423 overwrite_style_with_string(*item, child_style);
1424 move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1425 (*item)->firstChild()->deleteObject();
1426 return true;
1427 }
1429 /** helper for tidy_operator_redundant_double_nesting() */
1430 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1431 {
1432 if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1433 return false;
1434 if (SP_IS_STRING(child)) return false;
1435 if (is_line_break_object(child)) return false;
1436 if (is_line_break_object(*item)) return false;
1437 TextTagAttributes *attrs = attributes_for_object(child);
1438 if (attrs && attrs->anyAttributesSet()) return false;
1439 if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1441 Inkscape::XML::Node *insert_after_repr;
1442 if (prepend) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1443 else insert_after_repr = SP_OBJECT_REPR(*item);
1444 while (SP_OBJECT_REPR(child)->childCount()) {
1445 Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1446 Inkscape::GC::anchor(move_repr);
1447 SP_OBJECT_REPR(child)->removeChild(move_repr);
1448 SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1449 Inkscape::GC::release(move_repr);
1450 insert_after_repr = move_repr; // I think this will stay valid long enough. It's garbage collected these days.
1451 }
1452 child->deleteObject();
1453 return true;
1454 }
1456 /** redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1457 -> <font b>abc<font a>def</font>ghi</font>
1458 this function does its work when the parameter is the <font a> tag in the
1459 example. You may note that this only does its work when the doubly-nested
1460 child is the first or last. The other cases are called 'style inversion'
1461 below, and I'm not yet convinced that the result of that operation will be
1462 tidier in all cases. */
1463 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1464 {
1465 if (!(*item)->hasChildren()) return false;
1466 if ((*item)->firstChild() == (*item)->lastChild()) return false; // this is excessive nesting, done above
1467 if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1468 return true;
1469 if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1470 return true;
1471 return false;
1472 }
1474 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1475 then compares the styles for item+child versus just child. If they're equal,
1476 tidying is possible. */
1477 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1478 {
1479 if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1480 return false;
1481 if (SP_IS_STRING(child)) return false;
1482 if (is_line_break_object(child)) return false;
1483 if (is_line_break_object(*item)) return false;
1484 TextTagAttributes *attrs = attributes_for_object(child);
1485 if (attrs && attrs->anyAttributesSet()) return false;
1486 attrs = attributes_for_object(*item);
1487 if (attrs && attrs->anyAttributesSet()) return false;
1489 SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1490 SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1491 gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1492 if (child_style && *child_style) {
1493 sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1494 sp_repr_css_attr_add_from_string(css_child_only, child_style);
1495 }
1496 gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1497 if (item_style && *item_style) {
1498 sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1499 }
1500 bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1501 sp_repr_css_attr_unref(css_child_and_item);
1502 sp_repr_css_attr_unref(css_child_only);
1503 if (!equal) return false;
1505 Inkscape::XML::Node *new_span = sp_repr_new(SP_OBJECT_REPR(*item)->name());
1506 if (prepend) {
1507 SPObject *prev = SP_OBJECT_PREV(*item);
1508 SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1509 } else
1510 SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1511 new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1512 move_child_nodes(SP_OBJECT_REPR(child), new_span);
1513 Inkscape::GC::release(new_span);
1514 child->deleteObject();
1515 return true;
1516 }
1518 /** redundant semi-nesting: <font a><font b>abc</font>def</font>
1519 -> <font b>abc</font><font>def</font>
1520 test this by applying a colour to a region, then a different colour to
1521 a partially-overlapping region. */
1522 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1523 {
1524 if (!(*item)->hasChildren()) return false;
1525 if ((*item)->firstChild() == (*item)->lastChild()) return false; // this is redundant nesting, done above
1526 if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1527 return true;
1528 if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1529 return true;
1530 return false;
1531 }
1533 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1534 in a paragraph which is not \a not_obj. */
1535 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1536 {
1537 for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1538 {
1539 if (child == not_obj) continue;
1540 if (child->hasChildren()) {
1541 SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1542 if (ret) return ret;
1543 } else if (SP_IS_STRING(child))
1544 return SP_STRING(child);
1545 }
1546 return NULL;
1547 }
1549 /** whitespace-only spans: abc<font> </font>def
1550 -> abc<font></font> def
1551 abc<b><i>def</i> </b>ghi
1552 -> abc<b><i>def</i></b> ghi */
1553 static bool tidy_operator_styled_whitespace(SPObject **item)
1554 {
1555 if (!SP_IS_STRING(*item)) return false;
1556 Glib::ustring const &str = SP_STRING(*item)->string;
1557 for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1558 if (!g_unichar_isspace(*it)) return false;
1560 SPObject *test_item = *item;
1561 SPString *next_string;
1562 for ( ; ; ) { // find the next string
1563 next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1564 if (next_string) {
1565 next_string->string.insert(0, str);
1566 break;
1567 }
1568 for ( ; ; ) { // go up one item in the xml
1569 test_item = SP_OBJECT_PARENT(test_item);
1570 if (is_line_break_object(test_item)) break;
1571 SPObject *next = SP_OBJECT_NEXT(test_item);
1572 if (next) {
1573 test_item = next;
1574 break;
1575 }
1576 }
1577 if (is_line_break_object(test_item)) { // no next string, see if there's a prev string
1578 next_string = find_last_string_child_not_equal_to(test_item, *item);
1579 if (next_string == NULL) return false; // an empty paragraph
1580 next_string->string += str;
1581 break;
1582 }
1583 }
1584 SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1585 SPObject *delete_obj = *item;
1586 *item = SP_OBJECT_NEXT(*item);
1587 delete_obj->deleteObject();
1588 return true;
1589 }
1591 /* possible tidy operators that are not yet implemented, either because
1592 they are difficult, occur infrequently, or because I'm not sure that the
1593 output is tidier in all cases:
1594 duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1595 -> <div italic><para>abc</para></div>
1596 style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1597 -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1598 mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1599 -> <size 1><font a>abc</font>def</size>
1600 */
1602 /** Recursively walks the xml tree calling a set of cleanup operations on
1603 every child. Returns true if any changes were made to the tree.
1605 All the tidy operators return true if they made changes, and alter their
1606 parameter to point to the next object that should be processed, or NULL.
1607 They must not significantly alter (ie delete) any ancestor elements of the
1608 one they are passed.
1610 It may be that some of the later tidy operators that I wrote are actually
1611 general cases of the earlier operators, and hence the special-case-only
1612 versions can be removed. I haven't analysed my work in detail to figure
1613 out if this is so. */
1614 static bool tidy_xml_tree_recursively(SPObject *root)
1615 {
1616 static bool (* const tidy_operators[])(SPObject**) = {
1617 tidy_operator_empty_spans,
1618 tidy_operator_inexplicable_spans,
1619 tidy_operator_repeated_spans,
1620 tidy_operator_excessive_nesting,
1621 tidy_operator_redundant_double_nesting,
1622 tidy_operator_redundant_semi_nesting,
1623 tidy_operator_styled_whitespace
1624 };
1625 bool changes = false;
1627 for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1628 if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child)) {
1629 child = SP_OBJECT_NEXT(child);
1630 continue;
1631 }
1632 if (child->hasChildren())
1633 changes |= tidy_xml_tree_recursively(child);
1635 unsigned i;
1636 for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1637 if (tidy_operators[i](&child)) {
1638 changes = true;
1639 break;
1640 }
1641 }
1642 if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1643 child = SP_OBJECT_NEXT(child);
1644 }
1645 return changes;
1646 }
1648 /** Applies the given CSS fragment to the characters of the given text or
1649 flowtext object between \a start and \a end, creating or removing span
1650 elements as necessary and optimal. */
1651 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1652 {
1653 // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1654 if (start == end) return;
1655 Inkscape::Text::Layout::iterator first, last;
1656 if (start < end) {
1657 first = start;
1658 last = end;
1659 } else {
1660 first = end;
1661 last = start;
1662 }
1663 Inkscape::Text::Layout const *layout = te_get_layout(text);
1664 SPObject *start_item = 0, *end_item = 0;
1665 void *rawptr = 0;
1666 Glib::ustring::iterator start_text_iter, end_text_iter;
1667 layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
1668 start_item = SP_OBJECT(rawptr);
1669 layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
1670 end_item = SP_OBJECT(rawptr);
1671 if (start_item == 0)
1672 return; // start is at end of text
1673 if (is_line_break_object(start_item))
1674 start_item = SP_OBJECT_NEXT(start_item);
1675 if (is_line_break_object(end_item))
1676 end_item = SP_OBJECT_NEXT(end_item);
1677 if (end_item == 0) end_item = text;
1679 /* stage 1: applying the style. Go up to the closest common ancestor of
1680 start and end and then semi-recursively apply the style to all the
1681 objects in between. The semi-recursion is because it's only necessary
1682 at the beginning and end; the style can just be applied to the root
1683 child in the middle.
1684 eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1685 The recursion may involve creating new spans.
1686 */
1687 SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1688 start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1689 end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1690 recursively_apply_style(common_ancestor, css, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1692 /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1693 /* discussion: this stage requires a certain level of inventiveness because
1694 it's not clear what the best representation is in many cases. An ideal
1695 implementation would provide some sort of scoring function to rate the
1696 ugliness of a given xml tree and try to reduce said function, but providing
1697 the various possibilities to be rated is non-trivial. Instead, I have opted
1698 for a multi-pass technique which simply recognises known-ugly patterns and
1699 has matching routines for optimising the patterns it finds. It's reasonably
1700 easy to add new pattern matching processors. If everything gets disastrous
1701 and neither option can be made to work, a fallback could be to reduce
1702 everything to a single level of nesting and drop all pretence of
1703 roundtrippability. */
1704 while (tidy_xml_tree_recursively(common_ancestor));
1706 // if we only modified subobjects this won't have been automatically sent
1707 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1708 }
1710 /*
1711 Local Variables:
1712 mode:c++
1713 c-file-style:"stroustrup"
1714 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1715 indent-tabs-mode:nil
1716 fill-column:99
1717 End:
1718 */
1719 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :