Code

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