Code

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