Code

add breton win32 installer translation
[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::DOCUMENT_NODE:
274             return NULL;   // this had better never happen
275     }
276     return NULL;
279 /** returns the sum of the (recursive) lengths of all the SPStrings prior
280 to \a item at the same level. */
281 static unsigned sum_sibling_text_lengths_before(SPObject const *item)
283     unsigned char_index = 0;
284     for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = SP_OBJECT_NEXT(sibling))
285         char_index += sp_text_get_length(sibling);
286     return char_index;
289 /** splits the attributes for the first object at the given \a char_index
290 and moves the ones after that point into \a second_item. */
291 static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
293     TextTagAttributes *first_attrs = attributes_for_object(first_item);
294     TextTagAttributes *second_attrs = attributes_for_object(second_item);
295     if (first_attrs && second_attrs)
296         first_attrs->split(char_index, second_attrs);
299 /** recursively divides the XML node tree into two objects: the original will
300 contain all objects up to and including \a split_obj and the returned value
301 will be the new leaf which represents the copy of \a split_obj and extends
302 down the tree with new elements all the way to the common root which is the
303 parent of the first line break node encountered.
304 */
305 static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
307     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(split_obj));
308     if (is_line_break_object(split_obj)) {
309         Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
310         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
311         Inkscape::GC::release(new_node);
312         split_attributes(split_obj, SP_OBJECT_NEXT(split_obj), char_index);
313         return SP_OBJECT_NEXT(split_obj);
314     }
316     unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
317     SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
318     // copy the split node
319     Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
320     SP_OBJECT_REPR(duplicate_obj)->appendChild(new_node);
321     Inkscape::GC::release(new_node);
323     // sort out the copied attributes (x/y/dx/dy/rotate)
324     split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
326     // then move all the subsequent nodes
327     split_obj = SP_OBJECT_NEXT(split_obj);
328     while (split_obj) {
329         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(split_obj);
330         SPObject *next_obj = SP_OBJECT_NEXT(split_obj);  // this is about to become invalidated by removeChild()
331         Inkscape::GC::anchor(move_repr);
332         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->removeChild(move_repr);
333         SP_OBJECT_REPR(duplicate_obj)->appendChild(move_repr);
334         Inkscape::GC::release(move_repr);
336         split_obj = next_obj;
337     }
338     return duplicate_obj->firstChild();
341 /** inserts a new line break at the given position in a text or flowtext
342 object. If the position is in the middle of a span, the XML tree must be
343 chopped in two such that the line can be created at the root of the text
344 element. Returns an iterator pointing just after the inserted break. */
345 Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
347     // Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
348     // texpaths attached to the same path, with a vertical shift
349     if (SP_IS_TEXT_TEXTPATH (item) || SP_IS_TREF(item))
350         return position;
351         
352     SPDesktop *desktop = SP_ACTIVE_DESKTOP; 
354     Inkscape::Text::Layout const *layout = te_get_layout(item);
355     SPObject *split_obj = 0;
356     Glib::ustring::iterator split_text_iter;
357     if (position != layout->end()) {
358         void *rawptr = 0;
359         layout->getSourceOfCharacter(position, &rawptr, &split_text_iter);
360         split_obj = SP_OBJECT(rawptr);
361     }
363     if (split_obj == 0 || is_line_break_object(split_obj)) {
364         if (split_obj == 0) split_obj = item->lastChild();
365         
366         if (SP_IS_TREF(split_obj)) {
367                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
368             return position;
369         }
370         
371         if (split_obj) {
372             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(split_obj));
373             Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
374             SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
375             Inkscape::GC::release(new_node);
376         }
377     } else if (SP_IS_STRING(split_obj)) {
378         // If the parent is a tref, editing on this particular string is disallowed.
379         if (SP_IS_TREF(SP_OBJECT_PARENT(split_obj))) {
380             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
381             return position;
382         }
383         
384         Glib::ustring *string = &SP_STRING(split_obj)->string;
385         unsigned char_index = 0;
386         for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; it++)
387             char_index++;
388         // we need to split the entire text tree into two
389         SPString *new_string = SP_STRING(split_text_object_tree_at(split_obj, char_index));
390         SP_OBJECT_REPR(new_string)->setContent(&*split_text_iter.base());   // a little ugly
391         string->erase(split_text_iter, string->end());
392         SP_OBJECT_REPR(split_obj)->setContent(string->c_str());
393         // TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
394     } else {
395         // TODO
396         // I think the only case to put here is arbitrary gaps, which nobody uses yet
397     }
398     item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
399     unsigned char_index = layout->iteratorToCharIndex(position);
400     te_update_layout_now(item);
401     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
402     return layout->charIndexToIterator(char_index + 1);
405 /** finds the first SPString after the given position, including children, excluding parents */
406 static SPString* sp_te_seek_next_string_recursive(SPObject *start_obj)
408     while (start_obj) {
409         if (start_obj->hasChildren()) {
410             SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
411             if (found_string) return found_string;
412         }
413         if (SP_IS_STRING(start_obj)) return SP_STRING(start_obj);
414         start_obj = SP_OBJECT_NEXT(start_obj);
415         if (is_line_break_object(start_obj))
416             break;   // don't cross line breaks
417     }
418     return NULL;
421 /** inserts the given characters into the given string and inserts
422 corresponding new x/y/dx/dy/rotate attributes into all its parents. */
423 static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
425     unsigned char_index = 0;
426     unsigned char_count = g_utf8_strlen(utf8, -1);
427     Glib::ustring *string = &SP_STRING(string_item)->string;
429     for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; it++)
430         char_index++;
431     string->replace(iter_at, iter_at, utf8);
433     SPObject *parent_item = string_item;
434     for ( ; ; ) {
435         char_index += sum_sibling_text_lengths_before(parent_item);
436         parent_item = SP_OBJECT_PARENT(parent_item);
437         TextTagAttributes *attributes = attributes_for_object(parent_item);
438         if (!attributes) break;
439         attributes->insert(char_index, char_count);
440     }
443 /** Inserts the given text into a text or flowroot object. Line breaks
444 cannot be inserted using this function, see sp_te_insert_line(). Returns
445 an iterator pointing just after the inserted text. */
446 Inkscape::Text::Layout::iterator
447 sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
449     if (!g_utf8_validate(utf8,-1,NULL)) {
450         g_warning("Trying to insert invalid utf8");
451         return position;
452     }
453     
454     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
456     Inkscape::Text::Layout const *layout = te_get_layout(item);
457     SPObject *source_obj = 0;
458     void *rawptr = 0;
459     Glib::ustring::iterator iter_text;
460     // we want to insert after the previous char, not before the current char.
461     // it makes a difference at span boundaries
462     Inkscape::Text::Layout::iterator it_prev_char = position;
463     bool cursor_at_start = !it_prev_char.prevCharacter();
464     bool cursor_at_end = position == layout->end();
465     layout->getSourceOfCharacter(it_prev_char, &rawptr, &iter_text);
466     source_obj = SP_OBJECT(rawptr);
467     if (SP_IS_STRING(source_obj)) {
468         // If the parent is a tref, editing on this particular string is disallowed.
469         if (SP_IS_TREF(SP_OBJECT_PARENT(source_obj))) {
470             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
471             return position;
472         }
473         
474         // Now the simple case can begin...
475         if (!cursor_at_start) iter_text++;
476         SPString *string_item = SP_STRING(source_obj);
477         insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
478     } else {
479         // the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
480         Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
481         if (cursor_at_start) {
482             source_obj = item;
483             if (source_obj->hasChildren()) {
484                 source_obj = source_obj->firstChild();
485                 if (SP_IS_FLOWTEXT(item)) {
486                     while (SP_IS_FLOWREGION(source_obj) || SP_IS_FLOWREGIONEXCLUDE(source_obj))
487                         source_obj = SP_OBJECT_NEXT(source_obj);
488                     if (source_obj == NULL)
489                         source_obj = item;
490                 }
491             }
492             if (source_obj == item && SP_IS_FLOWTEXT(item)) {
493                 Inkscape::XML::Node *para = xml_doc->createElement("svg:flowPara");
494                 SP_OBJECT_REPR(item)->appendChild(para);
495                 source_obj = item->lastChild();
496             }
497         } else
498             source_obj = SP_OBJECT_NEXT(source_obj);
500         if (source_obj) {  // never fails
501             SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
502             if (string_item == NULL) {
503                 // need to add an SPString in this (pathological) case
504                 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
505                 SP_OBJECT_REPR(source_obj)->addChild(rstring, NULL);
506                 Inkscape::GC::release(rstring);
507                 g_assert(SP_IS_STRING(source_obj->firstChild()));
508                 string_item = SP_STRING(source_obj->firstChild());
509             }
510             // If the parent is a tref, editing on this particular string is disallowed.
511             if (SP_IS_TREF(SP_OBJECT_PARENT(string_item))) {
512                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
513                 return position;
514             }
515             
516             insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
517         }
518     }
520     item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
521     unsigned char_index = layout->iteratorToCharIndex(position);
522     te_update_layout_now(item);
523     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
524     return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
528 /* ***************************************************************************************************/
529 //                            D E L E T I N G   T E X T
531 /** moves all the children of \a from_repr to \a to_repr, either before
532 the existing children or after them. Order is maintained. The empty
533 \a from_repr is not deleted. */
534 static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
536     while (from_repr->childCount()) {
537         Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
538         Inkscape::GC::anchor(child);
539         from_repr->removeChild(child);
540         if (prepend) to_repr->addChild(child, NULL);
541         else to_repr->appendChild(child);
542         Inkscape::GC::release(child);
543     }
546 /** returns the object in the tree which is the closest ancestor of both
547 \a one and \a two. It will never return anything higher than \a text. */
548 static SPObject* get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
550     if (one == NULL || two == NULL)
551         return text;
552     SPObject *common_ancestor = one;
553     if (SP_IS_STRING(common_ancestor))
554         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
555     while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
556         g_assert(common_ancestor != text);
557         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
558     }
559     return common_ancestor;
562 /** positions \a para_obj and \a text_iter to be pointing at the end
563 of the last string in the last leaf object of \a para_obj. If the last
564 leaf is not an SPString then \a text_iter will be unchanged. */
565 static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
567     while ((*para_obj)->hasChildren())
568         *para_obj = (*para_obj)->lastChild();
569     if (SP_IS_STRING(*para_obj))
570         *text_iter = SP_STRING(*para_obj)->string.end();
573 /** delete the line break pointed to by \a item by merging its children into
574 the next suitable object and deleting \a item. Returns the object after the
575 ones that have just been moved and sets \a next_is_sibling accordingly. */
576 static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
578     Inkscape::XML::Node *this_repr = SP_OBJECT_REPR(item);
579     SPObject *next_item = NULL;
580     unsigned moved_char_count = sp_text_get_length(item) - 1;   // the -1 is because it's going to count the line break
582     /* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
583       <div></div><p>*text</p>
584       <p><div></div>*text</p>
585       <p><div></div></p><p>*text</p>
586     */
587     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
588     Inkscape::XML::Node *new_span_repr = xml_doc->createElement(span_name_for_text_object(root));
590     if (gchar const *a = this_repr->attribute("dx"))
591         new_span_repr->setAttribute("dx", a);
592     if (gchar const *a = this_repr->attribute("dy"))
593         new_span_repr->setAttribute("dy", a);
594     if (gchar const *a = this_repr->attribute("rotate"))
595         new_span_repr->setAttribute("rotate", a);
597     SPObject *following_item = item;
598     while (SP_OBJECT_NEXT(following_item) == NULL) {
599         following_item = SP_OBJECT_PARENT(following_item);
600         g_assert(following_item != root);
601     }
602     following_item = SP_OBJECT_NEXT(following_item);
604     SPObject *new_parent_item;
605     if (SP_IS_STRING(following_item)) {
606         new_parent_item = SP_OBJECT_PARENT(following_item);
607         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, SP_OBJECT_PREV(following_item) ? SP_OBJECT_REPR(SP_OBJECT_PREV(following_item)) : NULL);
608         next_item = following_item;
609         *next_is_sibling = true;
610     } else {
611         new_parent_item = following_item;
612         next_item = new_parent_item->firstChild();
613         *next_is_sibling = true;
614         if (next_item == NULL) {
615             next_item = new_parent_item;
616             *next_is_sibling = false;
617         }
618         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, NULL);
619     }
621     // work around a bug in sp_style_write_difference() which causes the difference
622     // not to be written if the second param has a style set which the first does not
623     // by causing the first param to have everything set
624     SPCSSAttr *dest_node_attrs = sp_repr_css_attr(SP_OBJECT_REPR(new_parent_item), "style");
625     SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
626     SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
627     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
628     for ( ; attrs ; attrs++) {
629         gchar const *key = g_quark_to_string(attrs->key);
630         gchar const *this_attr = this_node_attrs_inherited->attribute(key);
631         if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
632             this_node_attrs->setAttribute(key, this_attr);
633     }
634     sp_repr_css_attr_unref(this_node_attrs_inherited);
635     sp_repr_css_attr_unref(this_node_attrs);
636     sp_repr_css_attr_unref(dest_node_attrs);
637     sp_repr_css_change(new_span_repr, this_node_attrs, "style");
639     TextTagAttributes *attributes = attributes_for_object(new_parent_item);
640     if (attributes)
641         attributes->insert(0, moved_char_count);
642     move_child_nodes(this_repr, new_span_repr);
643     this_repr->parent()->removeChild(this_repr);
644     return next_item;
647 /** erases the given characters from the given string and deletes the
648 corresponding x/y/dx/dy/rotate attributes from all its parents. */
649 static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
651     unsigned char_index = 0;
652     unsigned char_count = 0;
653     Glib::ustring *string = &SP_STRING(string_item)->string;
655     for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; it++)
656         char_index++;
657     for (Glib::ustring::iterator it = iter_from ; it != iter_to ; it++)
658         char_count++;
659     string->erase(iter_from, iter_to);
660     SP_OBJECT_REPR(string_item)->setContent(string->c_str());
662     SPObject *parent_item = string_item;
663     for ( ; ; ) {
664         char_index += sum_sibling_text_lengths_before(parent_item);
665         parent_item = SP_OBJECT_PARENT(parent_item);
666         TextTagAttributes *attributes = attributes_for_object(parent_item);
667         if (attributes == NULL) break;
669         attributes->erase(char_index, char_count);
670         attributes->writeTo(SP_OBJECT_REPR(parent_item));
671     }
674 /* Deletes the given characters from a text or flowroot object. This is
675 quite a complicated operation, partly due to the cleanup that is done if all
676 the text in a subobject has been deleted, and partly due to the difficulty
677 of figuring out what is a line break and how to delete one. Returns the
678 real start and ending iterators based on the situation. */
679 bool
680 sp_te_delete (SPItem *item, Inkscape::Text::Layout::iterator const &start,
681               Inkscape::Text::Layout::iterator const &end, iterator_pair &iter_pair)
683     bool success = false;
685     iter_pair.first = start;
686     iter_pair.second = end;
687     
688     if (start == end) return success;
689     
690     if (start > end) {
691         iter_pair.first = end;
692         iter_pair.second = start;
693     }
694     
695     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
696     
697     Inkscape::Text::Layout const *layout = te_get_layout(item);
698     SPObject *start_item = 0, *end_item = 0;
699     void *rawptr = 0;
700     Glib::ustring::iterator start_text_iter, end_text_iter;
701     layout->getSourceOfCharacter(iter_pair.first, &rawptr, &start_text_iter);
702     start_item = SP_OBJECT(rawptr);
703     layout->getSourceOfCharacter(iter_pair.second, &rawptr, &end_text_iter);
704     end_item = SP_OBJECT(rawptr);
705     if (start_item == 0)
706         return success;   // start is at end of text
707     if (is_line_break_object(start_item))
708         move_to_end_of_paragraph(&start_item, &start_text_iter);
709     if (end_item == 0) {
710         end_item = item->lastChild();
711         move_to_end_of_paragraph(&end_item, &end_text_iter);
712     }
713     else if (is_line_break_object(end_item))
714         move_to_end_of_paragraph(&end_item, &end_text_iter);
716     SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
718     if (start_item == end_item) {
719         // the quick case where we're deleting stuff all from the same string
720         if (SP_IS_STRING(start_item)) {     // always true (if it_start != it_end anyway)
721             // If the parent is a tref, editing on this particular string is disallowed.
722             if (SP_IS_TREF(SP_OBJECT_PARENT(start_item))) {
723                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
724             } else {
725                 erase_from_spstring(SP_STRING(start_item), start_text_iter, end_text_iter);
726                 success = true;
727             }
728         }
729     } else {
730         SPObject *sub_item = start_item;
731         // walk the tree from start_item to end_item, deleting as we go
732         while (sub_item != item) {
733             if (sub_item == end_item) {
734                 if (SP_IS_STRING(sub_item)) {
735                     // If the parent is a tref, editing on this particular string is disallowed.
736                     if (SP_IS_TREF(SP_OBJECT_PARENT(sub_item))) {
737                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
738                         break;
739                     }
740             
741                     Glib::ustring *string = &SP_STRING(sub_item)->string;
742                     erase_from_spstring(SP_STRING(sub_item), string->begin(), end_text_iter);
743                     success = true;
744                 }
745                 break;
746             }
747             if (SP_IS_STRING(sub_item)) {
748                 SPString *string = SP_STRING(sub_item);
749                 if (sub_item == start_item)
750                     erase_from_spstring(string, start_text_iter, string->string.end());
751                 else
752                     erase_from_spstring(string, string->string.begin(), string->string.end());
753                 success = true;
754             }
755             // walk to the next item in the tree
756             if (sub_item->hasChildren())
757                 sub_item = sub_item->firstChild();
758             else {
759                 SPObject *next_item;
760                 do {
761                     bool is_sibling = true;
762                     next_item = SP_OBJECT_NEXT(sub_item);
763                     if (next_item == NULL) {
764                         next_item = SP_OBJECT_PARENT(sub_item);
765                         is_sibling = false;
766                     }
768                     if (is_line_break_object(sub_item))
769                         next_item = delete_line_break(item, sub_item, &is_sibling);
771                     sub_item = next_item;
772                     if (is_sibling) break;
773                     // no more siblings, go up a parent
774                 } while (sub_item != item && sub_item != end_item);
775             }
776         }
777     }
779     while (tidy_xml_tree_recursively(common_ancestor));
780     te_update_layout_now(item);
781     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
782     layout->validateIterator(&iter_pair.first);
783     layout->validateIterator(&iter_pair.second);
784     return success;
788 /* ***************************************************************************************************/
789 //                            P L A I N   T E X T   F U N C T I O N S
791 /** Gets a text-only representation of the given text or flowroot object,
792 replacing line break elements with '\n'. */
793 static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
795     if (*pending_line_break)
796         *string += '\n';
797     for (SPObject const *child = root->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
798         if (SP_IS_STRING(child))
799             *string += SP_STRING(child)->string;
800         else
801             sp_te_get_ustring_multiline(child, string, pending_line_break);
802     }
803     if (!SP_IS_TEXT(root) && !SP_IS_TEXTPATH(root) && is_line_break_object(root))
804         *pending_line_break = true;
807 /** Gets a text-only representation of the given text or flowroot object,
808 replacing line break elements with '\n'. The return value must be free()d. */
809 gchar *
810 sp_te_get_string_multiline (SPItem const *text)
812     Glib::ustring string;
813     bool pending_line_break = false;
815     if (!SP_IS_TEXT(text) && !SP_IS_FLOWTEXT(text)) return NULL;
816     sp_te_get_ustring_multiline(text, &string, &pending_line_break);
817     if (string.empty()) return NULL;
818     return strdup(string.data());
821 /** Gets a text-only representation of the characters in a text or flowroot
822 object from \a start to \a end only. Line break elements are replaced with
823 '\n'. */
824 Glib::ustring
825 sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
827     if (start == end) return "";
828     Inkscape::Text::Layout::iterator first, last;
829     if (start < end) {
830         first = start;
831         last = end;
832     } else {
833         first = end;
834         last = start;
835     }
836     Inkscape::Text::Layout const *layout = te_get_layout(text);
837     Glib::ustring result;
838     // not a particularly fast piece of code. I'll optimise it if people start to notice.
839     for ( ; first < last ; first.nextCharacter()) {
840         SPObject *char_item = 0;
841         void *rawptr = 0;
842         Glib::ustring::iterator text_iter;
843         layout->getSourceOfCharacter(first, &rawptr, &text_iter);
844         char_item = SP_OBJECT(rawptr);
845         if (SP_IS_STRING(char_item))
846             result += *text_iter;
847         else
848             result += '\n';
849     }
850     return result;
853 void
854 sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
856     g_return_if_fail (text != NULL);
857     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
859     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(text)->document();
860     Inkscape::XML::Node *repr;
861     SPObject *object;
862     bool is_textpath = false;
863     if (SP_IS_TEXT_TEXTPATH (text)) {
864         repr = SP_OBJECT_REPR (sp_object_first_child(SP_OBJECT (text)));
865         object = sp_object_first_child(SP_OBJECT (text));
866         is_textpath = true;
867     } else {
868         repr = SP_OBJECT_REPR (text);
869         object = SP_OBJECT (text);
870     }
872     if (!str) str = "";
873     gchar *content = g_strdup (str);
875     repr->setContent("");
876     SPObject *child = object->firstChild();
877     while (child) {
878         SPObject *next = SP_OBJECT_NEXT(child);
879         if (!SP_IS_FLOWREGION(child) && !SP_IS_FLOWREGIONEXCLUDE(child))
880             repr->removeChild(SP_OBJECT_REPR(child));
881         child = next;
882     }
884     gchar *p = content;
885     while (p) {
886         gchar *e = strchr (p, '\n');
887         if (is_textpath) {
888             if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
889         } else {
890             if (e) *e = '\0';
891             Inkscape::XML::Node *rtspan;
892             if (SP_IS_TEXT(text)) { // create a tspan for each line
893                 rtspan = xml_doc->createElement("svg:tspan");
894                 rtspan->setAttribute("sodipodi:role", "line");
895             } else { // create a flowPara for each line
896                 rtspan = xml_doc->createElement("svg:flowPara");
897             }
898             Inkscape::XML::Node *rstr = xml_doc->createTextNode(p);
899             rtspan->addChild(rstr, NULL);
900             Inkscape::GC::release(rstr);
901             repr->appendChild(rtspan);
902             Inkscape::GC::release(rtspan);
903         }
904         p = (e) ? e + 1 : NULL;
905     }
906     if (is_textpath) {
907         Inkscape::XML::Node *rstr = xml_doc->createTextNode(content);
908         repr->addChild(rstr, NULL);
909         Inkscape::GC::release(rstr);
910     }
912     g_free (content);
915 /* ***************************************************************************************************/
916 //                           K E R N I N G   A N D   S P A C I N G
918 /** Returns the attributes block and the character index within that block
919 which represents the iterator \a position. */
920 static TextTagAttributes*
921 text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
923     if (item == NULL || char_index == NULL || !SP_IS_TEXT(item))
924         return NULL;   // flowtext doesn't support kerning yet
925     SPText *text = SP_TEXT(item);
927     SPObject *source_item = 0;
928     void *rawptr = 0;
929     Glib::ustring::iterator source_text_iter;
930     text->layout.getSourceOfCharacter(position, &rawptr, &source_text_iter);
931     source_item = SP_OBJECT(rawptr);
933     if (!SP_IS_STRING(source_item)) return NULL;
934     Glib::ustring *string = &SP_STRING(source_item)->string;
935     *char_index = sum_sibling_text_lengths_before(source_item);
936     for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; it++)
937         ++*char_index;
939     return attributes_for_object(SP_OBJECT_PARENT(source_item));
942 void
943 sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, NR::Point by)
945     // divide increment by zoom
946     // divide increment by matrix expansion
947     gdouble factor = 1 / desktop->current_zoom();
948     NR::Matrix t = sp_item_i2doc_affine(item);
949     factor = factor / NR::expansion(t);
950     by = factor * by;
952     unsigned char_index;
953     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
954     if (attributes) attributes->addToDxDy(char_index, by);
955     if (start != end) {
956         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
957         if (attributes) attributes->addToDxDy(char_index, -by);
958     }
960     item->updateRepr();
961     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
964 void
965 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
967     // divide increment by zoom
968     // divide increment by matrix expansion
969     gdouble factor = 1 / desktop->current_zoom();
970     NR::Matrix t = sp_item_i2doc_affine(text);
971     factor = factor / NR::expansion(t);
972     Inkscape::Text::Layout const *layout = te_get_layout(text);
973     if (layout == NULL) return;
974     SPObject *source_item = 0;
975     void *rawptr = 0;
976     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
977     source_item = SP_OBJECT(rawptr);
978     if (source_item == 0) return;
979     gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
981     sp_te_adjust_rotation(text, start, end, desktop, degrees);
984 void
985 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
987     unsigned char_index;
988     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
989     if (attributes == NULL) return;
991     if (start != end) {
992         for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
993             attributes = text_tag_attributes_at_position(text, it, &char_index);
994             if (attributes) attributes->addToRotate(char_index, degrees);
995         }
996     } else
997         attributes->addToRotate(char_index, degrees);
999     text->updateRepr();
1000     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1003 void
1004 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
1006     g_return_if_fail (text != NULL);
1007     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1009     Inkscape::Text::Layout const *layout = te_get_layout(text);
1011     gdouble val;
1012     SPObject *source_obj = 0;
1013     void *rawptr = 0;
1014     unsigned nb_let;
1015     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1016     source_obj = SP_OBJECT(rawptr);
1018     if (source_obj == 0) {   // end of text
1019         source_obj = text->lastChild();
1020     }
1021     if (SP_IS_STRING(source_obj)) {
1022         source_obj = source_obj->parent;
1023     }
1025     SPStyle *style = SP_OBJECT_STYLE (source_obj);
1027     // calculate real value
1028     /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
1029        get rid of the `else val = 0.0'.  Similarly below and in sp-string.cpp. */
1030     if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1031         if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1032             val = style->font_size.computed * style->letter_spacing.value;
1033         } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1034             val = style->font_size.computed * style->letter_spacing.value * 0.5;
1035         } else { // unknown unit - should not happen
1036             val = 0.0;
1037         }
1038     } else { // there's a real value in .computed, or it's zero
1039         val = style->letter_spacing.computed;
1040     }
1042     if (start == end) {
1043         while (!is_line_break_object(source_obj))     // move up the tree so we apply to the closest paragraph
1044             source_obj = SP_OBJECT_PARENT(source_obj);
1045         nb_let = sp_text_get_length(source_obj);
1046     } else {
1047         nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
1048     }
1050     // divide increment by zoom and by the number of characters in the line,
1051     // so that the entire line is expanded by by pixels, no matter what its length
1052     gdouble const zoom = desktop->current_zoom();
1053     gdouble const zby = (by
1054                          / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
1055                          / NR::expansion(sp_item_i2doc_affine(SP_ITEM(source_obj))));
1056     val += zby;
1058     if (start == end) {
1059         // set back value to entire paragraph
1060         style->letter_spacing.normal = FALSE;
1061         if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1062             if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1063                 style->letter_spacing.value = val / style->font_size.computed;
1064             } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1065                 style->letter_spacing.value = val / style->font_size.computed * 2;
1066             }
1067         } else {
1068             style->letter_spacing.computed = val;
1069         }
1071         style->letter_spacing.set = TRUE;
1072     } else {
1073         // apply to selection only
1074         SPCSSAttr *css = sp_repr_css_attr_new();
1075         char string_val[40];
1076         g_snprintf(string_val, sizeof(string_val), "%f", val);
1077         sp_repr_css_set_property(css, "letter-spacing", string_val);
1078         sp_te_apply_style(text, start, end, css);
1079         sp_repr_css_attr_unref(css);
1080     }
1082     text->updateRepr();
1083     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1086 void
1087 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &/*start*/, Inkscape::Text::Layout::iterator const &/*end*/, SPDesktop *desktop, gdouble by)
1089     // TODO: use start and end iterators to delineate the area to be affected
1090     g_return_if_fail (text != NULL);
1091     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1093     Inkscape::Text::Layout const *layout = te_get_layout(text);
1094     SPStyle *style = SP_OBJECT_STYLE (text);
1096     if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
1097         style->line_height.set = TRUE;
1098         style->line_height.inherit = FALSE;
1099         style->line_height.normal = FALSE;
1100         style->line_height.unit = SP_CSS_UNIT_PERCENT;
1101         style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1102     }
1104     unsigned line_count = layout->lineIndex(layout->end());
1105     double all_lines_height = layout->characterAnchorPoint(layout->end())[NR::Y] - layout->characterAnchorPoint(layout->begin())[NR::Y];
1106     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1107     if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1109     // divide increment by zoom and by the number of lines,
1110     // so that the entire object is expanded by by pixels
1111     gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1113     // divide increment by matrix expansion
1114     NR::Matrix t = sp_item_i2doc_affine (SP_ITEM(text));
1115     zby = zby / NR::expansion(t);
1117     switch (style->line_height.unit) {
1118         case SP_CSS_UNIT_NONE:
1119         default:
1120             // multiplier-type units, stored in computed
1121             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
1122             else style->line_height.computed *= (average_line_height + zby) / average_line_height;
1123             style->line_height.value = style->line_height.computed;
1124             break;
1125         case SP_CSS_UNIT_EM:
1126         case SP_CSS_UNIT_EX:
1127         case SP_CSS_UNIT_PERCENT:
1128             // multiplier-type units, stored in value
1129             if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
1130             else style->line_height.value *= (average_line_height + zby) / average_line_height;
1131             break;
1132             // absolute-type units
1133         case SP_CSS_UNIT_PX:
1134             style->line_height.computed += zby;
1135             style->line_height.value = style->line_height.computed;
1136             break;
1137         case SP_CSS_UNIT_PT:
1138             style->line_height.computed += zby * PT_PER_PX;
1139             style->line_height.value = style->line_height.computed;
1140             break;
1141         case SP_CSS_UNIT_PC:
1142             style->line_height.computed += zby * (PT_PER_PX / 12);
1143             style->line_height.value = style->line_height.computed;
1144             break;
1145         case SP_CSS_UNIT_MM:
1146             style->line_height.computed += zby * MM_PER_PX;
1147             style->line_height.value = style->line_height.computed;
1148             break;
1149         case SP_CSS_UNIT_CM:
1150             style->line_height.computed += zby * CM_PER_PX;
1151             style->line_height.value = style->line_height.computed;
1152             break;
1153         case SP_CSS_UNIT_IN:
1154             style->line_height.computed += zby * IN_PER_PX;
1155             style->line_height.value = style->line_height.computed;
1156             break;
1157     }
1158     text->updateRepr();
1159     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1163 /* ***************************************************************************************************/
1164 //                           S T Y L E   A P P L I C A T I O N
1167 /** converts an iterator to a character index, mainly because ustring::substr()
1168 doesn't have a version that takes iterators as parameters. */
1169 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1171     unsigned n = 0;
1172     for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1173         n++;
1174     return n;
1177 /** applies the given style string on top of the existing styles for \a item,
1178 as opposed to sp_style_merge_from_style_string which merges its parameter
1179 underneath the existing styles (ie ignoring already set properties). */
1180 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1182     SPStyle *new_style = sp_style_new(SP_OBJECT_DOCUMENT(item));
1183     sp_style_merge_from_style_string(new_style, style_string);
1184     gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1185     if (item_style_string && *item_style_string)
1186         sp_style_merge_from_style_string(new_style, item_style_string);
1187     gchar *new_style_string = sp_style_write_string(new_style);
1188     sp_style_unref(new_style);
1189     SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1190     g_free(new_style_string);
1193 /** Returns true if the style of \a parent and the style of \a child are
1194 equivalent (and hence the children of both will appear the same). It is a
1195 limitation of the current implementation that \a parent must be a (not
1196 necessarily immediate) ancestor of \a child. */
1197 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1199     // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1200     // will not work. Firstly because of an inheritance bug in style.cpp that has
1201     // implications too large for me to feel safe fixing, but mainly because the css spec
1202     // requires that the computed value is inherited, not the specified value.
1203     g_assert(parent->isAncestorOf(child));
1204     gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1205     // we have to write parent_style then read it again, because some properties format their values
1206     // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1207     SPStyle *parent_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1208     sp_style_merge_from_style_string(parent_spstyle, parent_style);
1209     g_free(parent_style);
1210     parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1211     sp_style_unref(parent_spstyle);
1213     Glib::ustring child_style_construction(parent_style);
1214     while (child != parent) {
1215         // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1216         char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1217         if (style_text && *style_text) {
1218             child_style_construction += ';';
1219             child_style_construction += style_text;
1220         }
1221         child = SP_OBJECT_PARENT(child);
1222     }
1223     SPStyle *child_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1224     sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1225     gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1226     sp_style_unref(child_spstyle);
1227     bool equal = !strcmp(child_style, parent_style);
1228     g_free(child_style);
1229     g_free(parent_style);
1230     return equal;
1233 /** returns true if \a first and \a second contain all the same attributes
1234 with the same values as each other. Note that we have to compare both
1235 forwards and backwards to make sure we don't miss any attributes that are
1236 in one but not the other. */
1237 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1239     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1240     for ( ; attrs ; attrs++) {
1241         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1242         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1243             return false;
1244     }
1245     attrs = second->attributeList();
1246     for ( ; attrs ; attrs++) {
1247         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1248         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1249             return false;
1250     }
1251     return true;
1254 /** sets the given css attribute on this object and all its descendants.
1255 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1256 transform stuff. */
1257 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1259     sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1261     for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1262         if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1263             // Unset properties which are accumulating and thus should not be set recursively.
1264             // 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.
1265             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1266             sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1267             sp_repr_css_set_property(css_recurse, "opacity", NULL);
1268             apply_css_recursive(child, css_recurse);
1269             sp_repr_css_attr_unref(css_recurse);
1270         } else {
1271             apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1272         }
1273     }
1276 /** applies the given style to all the objects at the given level and below
1277 which are between \a start_item and \a end_item, creating spans as necessary.
1278 If \a start_item or \a end_item are NULL then the style is applied to all
1279 objects to the beginning or end respectively. \a span_object_name is the
1280 name of the xml for a text span (ie tspan or flowspan). */
1281 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)
1283     bool passed_start = start_item == NULL ? true : false;
1284     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(common_ancestor));
1285     
1286     for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1287         if (start_item == child)
1288             passed_start = true;
1290         if (passed_start) {
1291             if (end_item && child->isAncestorOf(end_item)) {
1292                 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1293                 break;
1294             }
1295             // apply style
1297             // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1298             // often this means that new spans are created before child and child is modified only
1299             if (SP_IS_STRING(child)) {
1300                 SPString *string_item = SP_STRING(child);
1301                 bool surround_entire_string = true;
1303                 Inkscape::XML::Node *child_span = xml_doc->createElement(span_object_name);
1304                 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style");   // better hope that prototype wasn't nonconst for a good reason
1305                 SPObject *prev_item = SP_OBJECT_PREV(child);
1306                 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1308                 if (child == start_item || child == end_item) {
1309                     surround_entire_string = false;
1310                     if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1311                         // eg "abcDEFghi"  -> "abc"<span>"DEF"</span>"ghi"
1312                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1313                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1315                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1316                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1317                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1318                         Inkscape::GC::release(text_before);
1319                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
1320                         child_span->appendChild(text_in_span);
1321                         Inkscape::GC::release(text_in_span);
1322                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1324                     } else if (child == end_item) {
1325                         // eg "ABCdef" -> <span>"ABC"</span>"def"
1326                         //  (includes case where start_text_iter == begin())
1327                         // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1328                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1330                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1331                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(0, end_char_index).c_str());
1332                         child_span->appendChild(text_in_span);
1333                         Inkscape::GC::release(text_in_span);
1334                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1336                     } else if (start_text_iter != string_item->string.begin()) {
1337                         // eg "abcDEF" -> "abc"<span>"DEF"</span>
1338                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1340                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1341                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1342                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1343                         Inkscape::GC::release(text_before);
1344                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index).c_str());
1345                         child_span->appendChild(text_in_span);
1346                         Inkscape::GC::release(text_in_span);
1347                         child->deleteObject();
1348                         child = sp_object_get_child_by_repr(common_ancestor, child_span);
1350                     } else
1351                         surround_entire_string = true;
1352                 }
1353                 if (surround_entire_string) {
1354                     Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1355                     SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1356                     Inkscape::GC::anchor(child_repr);
1357                     SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1358                     child_span->appendChild(child_repr);
1359                     Inkscape::GC::release(child_repr);
1360                     child = sp_object_get_child_by_repr(common_ancestor, child_span);
1361                 }
1362                 Inkscape::GC::release(child_span);
1364             } else if (child != end_item) {   // not a string and we're applying to the entire object. This is easy
1365                 apply_css_recursive(child, css);
1366             }
1368         } else {  // !passed_start
1369             if (child->isAncestorOf(start_item)) {
1370                 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1371                 if (end_item && child->isAncestorOf(end_item))
1372                     break;   // only happens when start_item == end_item (I think)
1373                 passed_start = true;
1374             }
1375         }
1377         if (end_item == child)
1378             break;
1379     }
1382 /* if item is at the beginning of a tree it doesn't matter which element
1383 it points to so for neatness we would like it to point to the highest
1384 possible child of \a common_ancestor. There is no iterator return because
1385 a string can never be an ancestor.
1387 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1388 like * to point to the inner span because we can apply style to that whole
1389 span. */
1390 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1392     if (item == common_ancestor)
1393         return item;
1394     if (SP_IS_STRING(item))
1395         if (text_iter != SP_STRING(item)->string.begin())
1396             return item;
1397     for ( ; ; ) {
1398         SPObject *parent = SP_OBJECT_PARENT(item);
1399         if (parent == common_ancestor)
1400             break;
1401         if (item != parent->firstChild())
1402             break;
1403         item = parent;
1404     }
1405     return item;
1409 /**     empty spans: abc<span></span>def
1410                       -> abcdef                  */
1411 static bool tidy_operator_empty_spans(SPObject **item)
1413     if ((*item)->hasChildren()) return false;
1414     if (is_line_break_object(*item)) return false;
1415     if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1416     SPObject *next = SP_OBJECT_NEXT(*item);
1417     (*item)->deleteObject();
1418     *item = next;
1419     return true;
1422 /**    inexplicable spans: abc<span style="">def</span>ghi
1423                             -> "abc""def""ghi"
1424 the repeated strings will be merged by another operator. */
1425 static bool tidy_operator_inexplicable_spans(SPObject **item)
1427     if (SP_IS_STRING(*item)) return false;
1428     if (is_line_break_object(*item)) return false;
1429     TextTagAttributes *attrs = attributes_for_object(*item);
1430     if (attrs && attrs->anyAttributesSet()) return false;
1431     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1432     SPObject *next = *item;
1433     while ((*item)->hasChildren()) {
1434         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1435         Inkscape::GC::anchor(repr);
1436         SP_OBJECT_REPR(*item)->removeChild(repr);
1437         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1438         Inkscape::GC::release(repr);
1439         next = SP_OBJECT_NEXT(next);
1440     }
1441     (*item)->deleteObject();
1442     *item = next;
1443     return true;
1446 /**    repeated spans: <font a>abc</font><font a>def</font>
1447                         -> <font a>abcdef</font>            */
1448 static bool tidy_operator_repeated_spans(SPObject **item)
1450     SPObject *first = *item;
1451     SPObject *second = SP_OBJECT_NEXT(first);
1452     if (second == NULL) return false;
1454     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1455     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1457     if (first_repr->type() != second_repr->type()) return false;
1459     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1460         // also amalgamate consecutive SPStrings into one
1461         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1462         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1463         second_repr->parent()->removeChild(second_repr);
1464         return true;
1465     }
1467     // merge consecutive spans with identical styles into one
1468     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1469     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1470     if (is_line_break_object(second)) return false;
1471     gchar const *first_style = first_repr->attribute("style");
1472     gchar const *second_style = second_repr->attribute("style");
1473     if (!((first_style == NULL && second_style == NULL)
1474           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1475         return false;
1477     // all our tests passed: do the merge
1478     TextTagAttributes *attributes_first = attributes_for_object(first);
1479     TextTagAttributes *attributes_second = attributes_for_object(second);
1480     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1481         TextTagAttributes attributes_first_copy = *attributes_first;
1482         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1483     }
1484     move_child_nodes(second_repr, first_repr);
1485     second_repr->parent()->removeChild(second_repr);
1486     return true;
1487     // *item is still the next object to process
1490 /**    redundant nesting: <font a><font b>abc</font></font>
1491                            -> <font b>abc</font>
1492        excessive nesting: <font a><size 1>abc</size></font>
1493                            -> <font a,size 1>abc</font>      */
1494 static bool tidy_operator_excessive_nesting(SPObject **item)
1496     if (!(*item)->hasChildren()) return false;
1497     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1498     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1499         return false;
1500     if (SP_IS_STRING((*item)->firstChild())) return false;
1501     if (is_line_break_object((*item)->firstChild())) return false;
1502     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1503     if (attrs && attrs->anyAttributesSet()) return false;
1504     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1505     if (child_style && *child_style)
1506         overwrite_style_with_string(*item, child_style);
1507     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1508     (*item)->firstChild()->deleteObject();
1509     return true;
1512 /** helper for tidy_operator_redundant_double_nesting() */
1513 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1515     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1516         return false;
1517     if (SP_IS_STRING(child)) return false;
1518     if (is_line_break_object(child)) return false;
1519     if (is_line_break_object(*item)) return false;
1520     TextTagAttributes *attrs = attributes_for_object(child);
1521     if (attrs && attrs->anyAttributesSet()) return false;
1522     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1524     Inkscape::XML::Node *insert_after_repr;
1525     if (prepend) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1526     else insert_after_repr = SP_OBJECT_REPR(*item);
1527     while (SP_OBJECT_REPR(child)->childCount()) {
1528         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1529         Inkscape::GC::anchor(move_repr);
1530         SP_OBJECT_REPR(child)->removeChild(move_repr);
1531         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1532         Inkscape::GC::release(move_repr);
1533         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1534     }
1535     child->deleteObject();
1536     return true;
1539 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1540                                 -> <font b>abc<font a>def</font>ghi</font>
1541 this function does its work when the parameter is the <font a> tag in the
1542 example. You may note that this only does its work when the doubly-nested
1543 child is the first or last. The other cases are called 'style inversion'
1544 below, and I'm not yet convinced that the result of that operation will be
1545 tidier in all cases. */
1546 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1548     if (!(*item)->hasChildren()) return false;
1549     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1550     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1551         return true;
1552     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1553         return true;
1554     return false;
1557 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1558 then compares the styles for item+child versus just child. If they're equal,
1559 tidying is possible. */
1560 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1562     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1563         return false;
1564     if (SP_IS_STRING(child)) return false;
1565     if (is_line_break_object(child)) return false;
1566     if (is_line_break_object(*item)) return false;
1567     TextTagAttributes *attrs = attributes_for_object(child);
1568     if (attrs && attrs->anyAttributesSet()) return false;
1569     attrs = attributes_for_object(*item);
1570     if (attrs && attrs->anyAttributesSet()) return false;
1572     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1573     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1574     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1575     if (child_style && *child_style) {
1576         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1577         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1578     }
1579     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1580     if (item_style && *item_style) {
1581         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1582     }
1583     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1584     sp_repr_css_attr_unref(css_child_and_item);
1585     sp_repr_css_attr_unref(css_child_only);
1586     if (!equal) return false;
1588     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(*item)->document();
1589     Inkscape::XML::Node *new_span = xml_doc->createElement(SP_OBJECT_REPR(*item)->name());
1590     if (prepend) {
1591         SPObject *prev = SP_OBJECT_PREV(*item);
1592         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1593     } else
1594         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1595     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1596     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1597     Inkscape::GC::release(new_span);
1598     child->deleteObject();
1599     return true;
1602 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1603                                 -> <font b>abc</font><font>def</font>
1604 test this by applying a colour to a region, then a different colour to
1605 a partially-overlapping region. */
1606 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1608     if (!(*item)->hasChildren()) return false;
1609     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1610     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1611         return true;
1612     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1613         return true;
1614     return false;
1617 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1618 in a paragraph which is not \a not_obj. */
1619 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1621     for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1622     {
1623         if (child == not_obj) continue;
1624         if (child->hasChildren()) {
1625             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1626             if (ret) return ret;
1627         } else if (SP_IS_STRING(child))
1628             return SP_STRING(child);
1629     }
1630     return NULL;
1633 /** whitespace-only spans: abc<font> </font>def
1634                             -> abc<font></font> def
1635                            abc<b><i>def</i> </b>ghi
1636                             -> abc<b><i>def</i></b> ghi   */
1637 static bool tidy_operator_styled_whitespace(SPObject **item)
1639     if (!SP_IS_STRING(*item)) return false;
1640     Glib::ustring const &str = SP_STRING(*item)->string;
1641     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1642         if (!g_unichar_isspace(*it)) return false;
1644     SPObject *test_item = *item;
1645     SPString *next_string;
1646     for ( ; ; ) {  // find the next string
1647         next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1648         if (next_string) {
1649             next_string->string.insert(0, str);
1650             break;
1651         }
1652         for ( ; ; ) {   // go up one item in the xml
1653             test_item = SP_OBJECT_PARENT(test_item);
1654             if (is_line_break_object(test_item)) break;
1655             SPObject *next = SP_OBJECT_NEXT(test_item);
1656             if (next) {
1657                 test_item = next;
1658                 break;
1659             }
1660         }
1661         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1662             next_string = find_last_string_child_not_equal_to(test_item, *item);
1663             if (next_string == NULL) return false;   // an empty paragraph
1664             next_string->string += str;
1665             break;
1666         }
1667     }
1668     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1669     SPObject *delete_obj = *item;
1670     *item = SP_OBJECT_NEXT(*item);
1671     delete_obj->deleteObject();
1672     return true;
1675 /* possible tidy operators that are not yet implemented, either because
1676 they are difficult, occur infrequently, or because I'm not sure that the
1677 output is tidier in all cases:
1678     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1679                                               -> <div italic><para>abc</para></div>
1680     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1681                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1682     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1683                           -> <size 1><font a>abc</font>def</size>
1684 */
1686 /** Recursively walks the xml tree calling a set of cleanup operations on
1687 every child. Returns true if any changes were made to the tree.
1689 All the tidy operators return true if they made changes, and alter their
1690 parameter to point to the next object that should be processed, or NULL.
1691 They must not significantly alter (ie delete) any ancestor elements of the
1692 one they are passed.
1694 It may be that some of the later tidy operators that I wrote are actually
1695 general cases of the earlier operators, and hence the special-case-only
1696 versions can be removed. I haven't analysed my work in detail to figure
1697 out if this is so. */
1698 static bool tidy_xml_tree_recursively(SPObject *root)
1700     static bool (* const tidy_operators[])(SPObject**) = {
1701         tidy_operator_empty_spans,
1702         tidy_operator_inexplicable_spans,
1703         tidy_operator_repeated_spans,
1704         tidy_operator_excessive_nesting,
1705         tidy_operator_redundant_double_nesting,
1706         tidy_operator_redundant_semi_nesting,
1707         tidy_operator_styled_whitespace
1708     };
1709     bool changes = false;
1711     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1712         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child) || SP_IS_TREF(child)) {
1713             child = SP_OBJECT_NEXT(child);
1714             continue;
1715         }
1716         if (child->hasChildren())
1717             changes |= tidy_xml_tree_recursively(child);
1719         unsigned i;
1720         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1721             if (tidy_operators[i](&child)) {
1722                 changes = true;
1723                 break;
1724             }
1725         }
1726         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1727             child = SP_OBJECT_NEXT(child);
1728     }
1729     return changes;
1732 /** Applies the given CSS fragment to the characters of the given text or
1733 flowtext object between \a start and \a end, creating or removing span
1734 elements as necessary and optimal. */
1735 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1737     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1738     if (start == end) return;
1739     Inkscape::Text::Layout::iterator first, last;
1740     if (start < end) {
1741         first = start;
1742         last = end;
1743     } else {
1744         first = end;
1745         last = start;
1746     }
1747     Inkscape::Text::Layout const *layout = te_get_layout(text);
1748     SPObject *start_item = 0, *end_item = 0;
1749     void *rawptr = 0;
1750     Glib::ustring::iterator start_text_iter, end_text_iter;
1751     layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
1752     start_item = SP_OBJECT(rawptr);
1753     layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
1754     end_item = SP_OBJECT(rawptr);
1755     if (start_item == 0)
1756         return;   // start is at end of text
1757     if (is_line_break_object(start_item))
1758         start_item = SP_OBJECT_NEXT(start_item);
1759     if (is_line_break_object(end_item))
1760         end_item = SP_OBJECT_NEXT(end_item);
1761     if (end_item == 0) end_item = text;
1762     
1763     
1764     /* Special case: With a tref, we only want to change its style when the whole
1765      * string is selected, in which case the style can be applied directly to the
1766      * tref node.  If only part of the tref's string child is selected, just return. */
1767      
1768     if (!sp_tref_fully_contained(start_item, start_text_iter, end_item, end_text_iter)) {
1769         
1770         return;
1771     } 
1773     /* stage 1: applying the style. Go up to the closest common ancestor of
1774     start and end and then semi-recursively apply the style to all the
1775     objects in between. The semi-recursion is because it's only necessary
1776     at the beginning and end; the style can just be applied to the root
1777     child in the middle.
1778     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1779     The recursion may involve creating new spans.
1780     */
1781     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1782     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1783     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1784     recursively_apply_style(common_ancestor, css, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1786     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1787     /* discussion: this stage requires a certain level of inventiveness because
1788     it's not clear what the best representation is in many cases. An ideal
1789     implementation would provide some sort of scoring function to rate the
1790     ugliness of a given xml tree and try to reduce said function, but providing
1791     the various possibilities to be rated is non-trivial. Instead, I have opted
1792     for a multi-pass technique which simply recognises known-ugly patterns and
1793     has matching routines for optimising the patterns it finds. It's reasonably
1794     easy to add new pattern matching processors. If everything gets disastrous
1795     and neither option can be made to work, a fallback could be to reduce
1796     everything to a single level of nesting and drop all pretence of
1797     roundtrippability. */
1798     while (tidy_xml_tree_recursively(common_ancestor));
1800     // if we only modified subobjects this won't have been automatically sent
1801     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1804 /*
1805   Local Variables:
1806   mode:c++
1807   c-file-style:"stroustrup"
1808   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1809   indent-tabs-mode:nil
1810   fill-column:99
1811   End:
1812 */
1813 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :