Code

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