Code

Extensions. Fix for bug #647744 ([inx] min/max float values ignored with locale using...
[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, Geom::Point const &i_p)
82 {
83     Geom::Matrix im (sp_item_i2d_affine (item));
84     im = im.inverse();
86     Geom::Point p = i_p * im;
87     Inkscape::Text::Layout const *layout = te_get_layout(item);
88     return layout->getNearestCursorPositionTo(p);
89 }
91 std::vector<Geom::Point> sp_te_create_selection_quads(SPItem const *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, Geom::Matrix const &transform)
92 {
93     if (start == end)
94         return std::vector<Geom::Point>();
95     Inkscape::Text::Layout const *layout = te_get_layout(item);
96     if (layout == NULL)
97         return std::vector<Geom::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, Geom::Point &p0, Geom::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 = Geom::Point(p0[Geom::X] + height * sin(rotation), p0[Geom::Y] - height * cos(rotation));
111 SPStyle const * sp_te_style_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
113     SPObject const *pos_obj = sp_te_object_at_position(text, position);
114     if (pos_obj)
115         return SP_OBJECT_STYLE(pos_obj);
116     return NULL;
119 SPObject const * sp_te_object_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
121     Inkscape::Text::Layout const *layout = te_get_layout(text);
122     if (layout == NULL)
123         return NULL;
124     SPObject const *pos_obj = 0;
125     void *rawptr = 0;
126     layout->getSourceOfCharacter(position, &rawptr);
127     pos_obj = SP_OBJECT(rawptr);
128     if (pos_obj == 0) pos_obj = text;
129     while (SP_OBJECT_STYLE(pos_obj) == NULL)
130         pos_obj = SP_OBJECT_PARENT(pos_obj);   // not interested in SPStrings 
131     return pos_obj;
134 /*
135  * for debugging input
136  *
137 char * dump_hexy(const gchar * utf8)
139     static char buffer[1024];
141     buffer[0]='\0';
142     for (const char *ptr=utf8; *ptr; ptr++) {
143         sprintf(buffer+strlen(buffer),"x%02X",(unsigned char)*ptr);
144     }
145     return buffer;
147 */
149 Inkscape::Text::Layout::iterator sp_te_replace(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, gchar const *utf8)
151     iterator_pair pair;
152     sp_te_delete(item, start, end, pair);
153     return sp_te_insert(item, pair.first, utf8);
157 /* ***************************************************************************************************/
158 //                             I N S E R T I N G   T E X T
160 static bool is_line_break_object(SPObject const *object)
162     bool is_line_break = false;
163     
164     if (object) {
165         if (SP_IS_TEXT(object)
166                 || (SP_IS_TSPAN(object) && SP_TSPAN(object)->role != SP_TSPAN_ROLE_UNSPECIFIED)
167                 || SP_IS_TEXTPATH(object)
168                 || SP_IS_FLOWDIV(object)
169                 || SP_IS_FLOWPARA(object)
170                 || SP_IS_FLOWLINE(object)
171                 || SP_IS_FLOWREGIONBREAK(object)) {
172                     
173             is_line_break = true;
174         }
175     }
176     
177     return is_line_break;
180 /** returns the attributes for an object, or NULL if it isn't a text,
181 tspan, tref, or textpath. */
182 static TextTagAttributes* attributes_for_object(SPObject *object)
184     if (SP_IS_TSPAN(object))
185         return &SP_TSPAN(object)->attributes;
186     if (SP_IS_TEXT(object))
187         return &SP_TEXT(object)->attributes;
188     if (SP_IS_TREF(object))
189         return &SP_TREF(object)->attributes;
190     if (SP_IS_TEXTPATH(object))
191         return &SP_TEXTPATH(object)->attributes;
192     return NULL;
195 static const char * span_name_for_text_object(SPObject const *object)
197     if (SP_IS_TEXT(object)) return "svg:tspan";
198     else if (SP_IS_FLOWTEXT(object)) return "svg:flowSpan";
199     return NULL;
202 /** Recursively gets the length of all the SPStrings at or below the given
203 \a item. Also adds 1 for each line break encountered. */
204 unsigned sp_text_get_length(SPObject const *item)
206     unsigned length = 0;
208     if (SP_IS_STRING(item)) return SP_STRING(item)->string.length();
209     
210     if (is_line_break_object(item)) length++;
211     
212     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
213         if (SP_IS_STRING(child)) length += SP_STRING(child)->string.length();
214         else length += sp_text_get_length(child);
215     }
216     return length;
219 /** Recursively gets the length of all the SPStrings at or below the given
220 \a item, before and not including \a upto. Also adds 1 for each line break encountered. */
221 unsigned sp_text_get_length_upto(SPObject const *item, SPObject const *upto)
223     unsigned length = 0;
225     // The string is the lowest level and the length can be counted directly. 
226     if (SP_IS_STRING(item)) {
227         return SP_STRING(item)->string.length();
228     }
229     
230     // Take care of new lines...
231     if (is_line_break_object(item) && !SP_IS_TEXT(item)) {
232         if (item != SP_OBJECT_PARENT(item)->firstChild()) {
233             // add 1 for each newline
234             length++;
235         }
236     }
237     
238     // Count the length of the children
239     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
240         if (upto && child == upto) {
241             // hit upto, return immediately
242             return length;
243         }
244         if (SP_IS_STRING(child)) {
245             length += SP_STRING(child)->string.length();
246         }
247         else {
248             if (upto && child->isAncestorOf(upto)) {
249                 // upto is below us, recurse and break loop
250                 length += sp_text_get_length_upto(child, upto);
251                 return length;
252             } else {
253                 // recurse and go to the next sibling
254                 length += sp_text_get_length_upto(child, upto);
255             }
256         }
257     }
258     return length;
261 static Inkscape::XML::Node* duplicate_node_without_children(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node const *old_node)
263     switch (old_node->type()) {
264         case Inkscape::XML::ELEMENT_NODE: {
265             Inkscape::XML::Node *new_node = xml_doc->createElement(old_node->name());
266             Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attributes = old_node->attributeList();
267             GQuark const id_key = g_quark_from_string("id");
268             for ( ; attributes ; attributes++) {
269                 if (attributes->key == id_key) continue;
270                 new_node->setAttribute(g_quark_to_string(attributes->key), attributes->value);
271             }
272             return new_node;
273         }
275         case Inkscape::XML::TEXT_NODE:
276             return xml_doc->createTextNode(old_node->content());
278         case Inkscape::XML::COMMENT_NODE:
279             return xml_doc->createComment(old_node->content());
281         case Inkscape::XML::PI_NODE:
282             return xml_doc->createPI(old_node->name(), old_node->content());
284         case Inkscape::XML::DOCUMENT_NODE:
285             return NULL;   // this had better never happen
286     }
287     return NULL;
290 /** returns the sum of the (recursive) lengths of all the SPStrings prior
291 to \a item at the same level. */
292 static unsigned sum_sibling_text_lengths_before(SPObject const *item)
294     unsigned char_index = 0;
295     for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = SP_OBJECT_NEXT(sibling))
296         char_index += sp_text_get_length(sibling);
297     return char_index;
300 /** splits the attributes for the first object at the given \a char_index
301 and moves the ones after that point into \a second_item. */
302 static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
304     TextTagAttributes *first_attrs = attributes_for_object(first_item);
305     TextTagAttributes *second_attrs = attributes_for_object(second_item);
306     if (first_attrs && second_attrs)
307         first_attrs->split(char_index, second_attrs);
310 /** recursively divides the XML node tree into two objects: the original will
311 contain all objects up to and including \a split_obj and the returned value
312 will be the new leaf which represents the copy of \a split_obj and extends
313 down the tree with new elements all the way to the common root which is the
314 parent of the first line break node encountered.
315 */
316 static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
318     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(split_obj));
319     if (is_line_break_object(split_obj)) {
320         Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
321         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
322         Inkscape::GC::release(new_node);
323         split_attributes(split_obj, SP_OBJECT_NEXT(split_obj), char_index);
324         return SP_OBJECT_NEXT(split_obj);
325     }
327     unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
328     SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
329     // copy the split node
330     Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
331     SP_OBJECT_REPR(duplicate_obj)->appendChild(new_node);
332     Inkscape::GC::release(new_node);
334     // sort out the copied attributes (x/y/dx/dy/rotate)
335     split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
337     // then move all the subsequent nodes
338     split_obj = SP_OBJECT_NEXT(split_obj);
339     while (split_obj) {
340         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(split_obj);
341         SPObject *next_obj = SP_OBJECT_NEXT(split_obj);  // this is about to become invalidated by removeChild()
342         Inkscape::GC::anchor(move_repr);
343         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->removeChild(move_repr);
344         SP_OBJECT_REPR(duplicate_obj)->appendChild(move_repr);
345         Inkscape::GC::release(move_repr);
347         split_obj = next_obj;
348     }
349     return duplicate_obj->firstChild();
352 /** inserts a new line break at the given position in a text or flowtext
353 object. If the position is in the middle of a span, the XML tree must be
354 chopped in two such that the line can be created at the root of the text
355 element. Returns an iterator pointing just after the inserted break. */
356 Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
358     // Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
359     // texpaths attached to the same path, with a vertical shift
360     if (SP_IS_TEXT_TEXTPATH (item) || SP_IS_TREF(item))
361         return position;
362         
363     SPDesktop *desktop = SP_ACTIVE_DESKTOP; 
365     Inkscape::Text::Layout const *layout = te_get_layout(item);
366     SPObject *split_obj = 0;
367     Glib::ustring::iterator split_text_iter;
368     if (position != layout->end()) {
369         void *rawptr = 0;
370         layout->getSourceOfCharacter(position, &rawptr, &split_text_iter);
371         split_obj = SP_OBJECT(rawptr);
372     }
374     if (split_obj == 0 || is_line_break_object(split_obj)) {
375         if (split_obj == 0) split_obj = item->lastChild();
376         
377         if (SP_IS_TREF(split_obj)) {
378                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
379             return position;
380         }
381         
382         if (split_obj) {
383             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(split_obj));
384             Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
385             SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
386             Inkscape::GC::release(new_node);
387         }
388     } else if (SP_IS_STRING(split_obj)) {
389         // If the parent is a tref, editing on this particular string is disallowed.
390         if (SP_IS_TREF(SP_OBJECT_PARENT(split_obj))) {
391             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
392             return position;
393         }
394         
395         Glib::ustring *string = &SP_STRING(split_obj)->string;
396         unsigned char_index = 0;
397         for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; it++)
398             char_index++;
399         // we need to split the entire text tree into two
400         SPString *new_string = SP_STRING(split_text_object_tree_at(split_obj, char_index));
401         SP_OBJECT_REPR(new_string)->setContent(&*split_text_iter.base());   // a little ugly
402         string->erase(split_text_iter, string->end());
403         SP_OBJECT_REPR(split_obj)->setContent(string->c_str());
404         // TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
405     } else {
406         // TODO
407         // I think the only case to put here is arbitrary gaps, which nobody uses yet
408     }
409     item->updateRepr();
410     unsigned char_index = layout->iteratorToCharIndex(position);
411     te_update_layout_now(item);
412     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
413     return layout->charIndexToIterator(char_index + 1);
416 /** finds the first SPString after the given position, including children, excluding parents */
417 static SPString* sp_te_seek_next_string_recursive(SPObject *start_obj)
419     while (start_obj) {
420         if (start_obj->hasChildren()) {
421             SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
422             if (found_string) return found_string;
423         }
424         if (SP_IS_STRING(start_obj)) return SP_STRING(start_obj);
425         start_obj = SP_OBJECT_NEXT(start_obj);
426         if (is_line_break_object(start_obj))
427             break;   // don't cross line breaks
428     }
429     return NULL;
432 /** inserts the given characters into the given string and inserts
433 corresponding new x/y/dx/dy/rotate attributes into all its parents. */
434 static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
436     unsigned char_index = 0;
437     unsigned char_count = g_utf8_strlen(utf8, -1);
438     Glib::ustring *string = &SP_STRING(string_item)->string;
440     for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; it++)
441         char_index++;
442     string->replace(iter_at, iter_at, utf8);
444     SPObject *parent_item = string_item;
445     for ( ; ; ) {
446         char_index += sum_sibling_text_lengths_before(parent_item);
447         parent_item = SP_OBJECT_PARENT(parent_item);
448         TextTagAttributes *attributes = attributes_for_object(parent_item);
449         if (!attributes) break;
450         attributes->insert(char_index, char_count);
451     }
454 /** Inserts the given text into a text or flowroot object. Line breaks
455 cannot be inserted using this function, see sp_te_insert_line(). Returns
456 an iterator pointing just after the inserted text. */
457 Inkscape::Text::Layout::iterator
458 sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
460     if (!g_utf8_validate(utf8,-1,NULL)) {
461         g_warning("Trying to insert invalid utf8");
462         return position;
463     }
464     
465     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
467     Inkscape::Text::Layout const *layout = te_get_layout(item);
468     SPObject *source_obj = 0;
469     void *rawptr = 0;
470     Glib::ustring::iterator iter_text;
471     // we want to insert after the previous char, not before the current char.
472     // it makes a difference at span boundaries
473     Inkscape::Text::Layout::iterator it_prev_char = position;
474     bool cursor_at_start = !it_prev_char.prevCharacter();
475     bool cursor_at_end = position == layout->end();
476     layout->getSourceOfCharacter(it_prev_char, &rawptr, &iter_text);
477     source_obj = SP_OBJECT(rawptr);
478     if (SP_IS_STRING(source_obj)) {
479         // If the parent is a tref, editing on this particular string is disallowed.
480         if (SP_IS_TREF(SP_OBJECT_PARENT(source_obj))) {
481             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
482             return position;
483         }
484         
485         // Now the simple case can begin...
486         if (!cursor_at_start) iter_text++;
487         SPString *string_item = SP_STRING(source_obj);
488         insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
489     } else {
490         // the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
491         Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
492         if (cursor_at_start) {
493             source_obj = item;
494             if (source_obj->hasChildren()) {
495                 source_obj = source_obj->firstChild();
496                 if (SP_IS_FLOWTEXT(item)) {
497                     while (SP_IS_FLOWREGION(source_obj) || SP_IS_FLOWREGIONEXCLUDE(source_obj))
498                         source_obj = SP_OBJECT_NEXT(source_obj);
499                     if (source_obj == NULL)
500                         source_obj = item;
501                 }
502             }
503             if (source_obj == item && SP_IS_FLOWTEXT(item)) {
504                 Inkscape::XML::Node *para = xml_doc->createElement("svg:flowPara");
505                 SP_OBJECT_REPR(item)->appendChild(para);
506                 source_obj = item->lastChild();
507             }
508         } else
509             source_obj = SP_OBJECT_NEXT(source_obj);
511         if (source_obj) {  // never fails
512             SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
513             if (string_item == NULL) {
514                 // need to add an SPString in this (pathological) case
515                 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
516                 SP_OBJECT_REPR(source_obj)->addChild(rstring, NULL);
517                 Inkscape::GC::release(rstring);
518                 g_assert(SP_IS_STRING(source_obj->firstChild()));
519                 string_item = SP_STRING(source_obj->firstChild());
520             }
521             // If the parent is a tref, editing on this particular string is disallowed.
522             if (SP_IS_TREF(SP_OBJECT_PARENT(string_item))) {
523                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
524                 return position;
525             }
526             
527             insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
528         }
529     }
531     item->updateRepr();
532     unsigned char_index = layout->iteratorToCharIndex(position);
533     te_update_layout_now(item);
534     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
535     return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
539 /* ***************************************************************************************************/
540 //                            D E L E T I N G   T E X T
542 /** moves all the children of \a from_repr to \a to_repr, either before
543 the existing children or after them. Order is maintained. The empty
544 \a from_repr is not deleted. */
545 static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
547     while (from_repr->childCount()) {
548         Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
549         Inkscape::GC::anchor(child);
550         from_repr->removeChild(child);
551         if (prepend) to_repr->addChild(child, NULL);
552         else to_repr->appendChild(child);
553         Inkscape::GC::release(child);
554     }
557 /** returns the object in the tree which is the closest ancestor of both
558 \a one and \a two. It will never return anything higher than \a text. */
559 static SPObject* get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
561     if (one == NULL || two == NULL)
562         return text;
563     SPObject *common_ancestor = one;
564     if (SP_IS_STRING(common_ancestor))
565         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
566     while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
567         g_assert(common_ancestor != text);
568         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
569     }
570     return common_ancestor;
573 /** positions \a para_obj and \a text_iter to be pointing at the end
574 of the last string in the last leaf object of \a para_obj. If the last
575 leaf is not an SPString then \a text_iter will be unchanged. */
576 static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
578     while ((*para_obj)->hasChildren())
579         *para_obj = (*para_obj)->lastChild();
580     if (SP_IS_STRING(*para_obj))
581         *text_iter = SP_STRING(*para_obj)->string.end();
584 /** delete the line break pointed to by \a item by merging its children into
585 the next suitable object and deleting \a item. Returns the object after the
586 ones that have just been moved and sets \a next_is_sibling accordingly. */
587 static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
589     Inkscape::XML::Node *this_repr = SP_OBJECT_REPR(item);
590     SPObject *next_item = NULL;
591     unsigned moved_char_count = sp_text_get_length(item) - 1;   // the -1 is because it's going to count the line break
593     /* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
594       <div></div><p>*text</p>
595       <p><div></div>*text</p>
596       <p><div></div></p><p>*text</p>
597     */
598     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
599     Inkscape::XML::Node *new_span_repr = xml_doc->createElement(span_name_for_text_object(root));
601     if (gchar const *a = this_repr->attribute("dx"))
602         new_span_repr->setAttribute("dx", a);
603     if (gchar const *a = this_repr->attribute("dy"))
604         new_span_repr->setAttribute("dy", a);
605     if (gchar const *a = this_repr->attribute("rotate"))
606         new_span_repr->setAttribute("rotate", a);
608     SPObject *following_item = item;
609     while (SP_OBJECT_NEXT(following_item) == NULL) {
610         following_item = SP_OBJECT_PARENT(following_item);
611         g_assert(following_item != root);
612     }
613     following_item = SP_OBJECT_NEXT(following_item);
615     SPObject *new_parent_item;
616     if (SP_IS_STRING(following_item)) {
617         new_parent_item = SP_OBJECT_PARENT(following_item);
618         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, SP_OBJECT_PREV(following_item) ? SP_OBJECT_REPR(SP_OBJECT_PREV(following_item)) : NULL);
619         next_item = following_item;
620         *next_is_sibling = true;
621     } else {
622         new_parent_item = following_item;
623         next_item = new_parent_item->firstChild();
624         *next_is_sibling = true;
625         if (next_item == NULL) {
626             next_item = new_parent_item;
627             *next_is_sibling = false;
628         }
629         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, NULL);
630     }
632     // work around a bug in sp_style_write_difference() which causes the difference
633     // not to be written if the second param has a style set which the first does not
634     // by causing the first param to have everything set
635     SPCSSAttr *dest_node_attrs = sp_repr_css_attr(SP_OBJECT_REPR(new_parent_item), "style");
636     SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
637     SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
638     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
639     for ( ; attrs ; attrs++) {
640         gchar const *key = g_quark_to_string(attrs->key);
641         gchar const *this_attr = this_node_attrs_inherited->attribute(key);
642         if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
643             this_node_attrs->setAttribute(key, this_attr);
644     }
645     sp_repr_css_attr_unref(this_node_attrs_inherited);
646     sp_repr_css_attr_unref(this_node_attrs);
647     sp_repr_css_attr_unref(dest_node_attrs);
648     sp_repr_css_change(new_span_repr, this_node_attrs, "style");
650     TextTagAttributes *attributes = attributes_for_object(new_parent_item);
651     if (attributes)
652         attributes->insert(0, moved_char_count);
653     move_child_nodes(this_repr, new_span_repr);
654     this_repr->parent()->removeChild(this_repr);
655     return next_item;
658 /** erases the given characters from the given string and deletes the
659 corresponding x/y/dx/dy/rotate attributes from all its parents. */
660 static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
662     unsigned char_index = 0;
663     unsigned char_count = 0;
664     Glib::ustring *string = &SP_STRING(string_item)->string;
666     for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; it++)
667         char_index++;
668     for (Glib::ustring::iterator it = iter_from ; it != iter_to ; it++)
669         char_count++;
670     string->erase(iter_from, iter_to);
671     SP_OBJECT_REPR(string_item)->setContent(string->c_str());
673     SPObject *parent_item = string_item;
674     for ( ; ; ) {
675         char_index += sum_sibling_text_lengths_before(parent_item);
676         parent_item = SP_OBJECT_PARENT(parent_item);
677         TextTagAttributes *attributes = attributes_for_object(parent_item);
678         if (attributes == NULL) break;
680         attributes->erase(char_index, char_count);
681         attributes->writeTo(SP_OBJECT_REPR(parent_item));
682     }
685 /* Deletes the given characters from a text or flowroot object. This is
686 quite a complicated operation, partly due to the cleanup that is done if all
687 the text in a subobject has been deleted, and partly due to the difficulty
688 of figuring out what is a line break and how to delete one. Returns the
689 real start and ending iterators based on the situation. */
690 bool
691 sp_te_delete (SPItem *item, Inkscape::Text::Layout::iterator const &start,
692               Inkscape::Text::Layout::iterator const &end, iterator_pair &iter_pair)
694     bool success = false;
696     iter_pair.first = start;
697     iter_pair.second = end;
698     
699     if (start == end) return success;
700     
701     if (start > end) {
702         iter_pair.first = end;
703         iter_pair.second = start;
704     }
705     
706     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
707     
708     Inkscape::Text::Layout const *layout = te_get_layout(item);
709     SPObject *start_item = 0, *end_item = 0;
710     void *rawptr = 0;
711     Glib::ustring::iterator start_text_iter, end_text_iter;
712     layout->getSourceOfCharacter(iter_pair.first, &rawptr, &start_text_iter);
713     start_item = SP_OBJECT(rawptr);
714     layout->getSourceOfCharacter(iter_pair.second, &rawptr, &end_text_iter);
715     end_item = SP_OBJECT(rawptr);
716     if (start_item == 0)
717         return success;   // start is at end of text
718     if (is_line_break_object(start_item))
719         move_to_end_of_paragraph(&start_item, &start_text_iter);
720     if (end_item == 0) {
721         end_item = item->lastChild();
722         move_to_end_of_paragraph(&end_item, &end_text_iter);
723     }
724     else if (is_line_break_object(end_item))
725         move_to_end_of_paragraph(&end_item, &end_text_iter);
727     SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
729     if (start_item == end_item) {
730         // the quick case where we're deleting stuff all from the same string
731         if (SP_IS_STRING(start_item)) {     // always true (if it_start != it_end anyway)
732             // If the parent is a tref, editing on this particular string is disallowed.
733             if (SP_IS_TREF(SP_OBJECT_PARENT(start_item))) {
734                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
735             } else {
736                 erase_from_spstring(SP_STRING(start_item), start_text_iter, end_text_iter);
737                 success = true;
738             }
739         }
740     } else {
741         SPObject *sub_item = start_item;
742         // walk the tree from start_item to end_item, deleting as we go
743         while (sub_item != item) {
744             if (sub_item == end_item) {
745                 if (SP_IS_STRING(sub_item)) {
746                     // If the parent is a tref, editing on this particular string is disallowed.
747                     if (SP_IS_TREF(SP_OBJECT_PARENT(sub_item))) {
748                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
749                         break;
750                     }
751             
752                     Glib::ustring *string = &SP_STRING(sub_item)->string;
753                     erase_from_spstring(SP_STRING(sub_item), string->begin(), end_text_iter);
754                     success = true;
755                 }
756                 break;
757             }
758             if (SP_IS_STRING(sub_item)) {
759                 SPString *string = SP_STRING(sub_item);
760                 if (sub_item == start_item)
761                     erase_from_spstring(string, start_text_iter, string->string.end());
762                 else
763                     erase_from_spstring(string, string->string.begin(), string->string.end());
764                 success = true;
765             }
766             // walk to the next item in the tree
767             if (sub_item->hasChildren())
768                 sub_item = sub_item->firstChild();
769             else {
770                 SPObject *next_item;
771                 do {
772                     bool is_sibling = true;
773                     next_item = SP_OBJECT_NEXT(sub_item);
774                     if (next_item == NULL) {
775                         next_item = SP_OBJECT_PARENT(sub_item);
776                         is_sibling = false;
777                     }
779                     if (is_line_break_object(sub_item))
780                         next_item = delete_line_break(item, sub_item, &is_sibling);
782                     sub_item = next_item;
783                     if (is_sibling) break;
784                     // no more siblings, go up a parent
785                 } while (sub_item != item && sub_item != end_item);
786             }
787         }
788     }
790     while (tidy_xml_tree_recursively(common_ancestor)){};
791     te_update_layout_now(item);
792     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
793     layout->validateIterator(&iter_pair.first);
794     layout->validateIterator(&iter_pair.second);
795     return success;
799 /* ***************************************************************************************************/
800 //                            P L A I N   T E X T   F U N C T I O N S
802 /** Gets a text-only representation of the given text or flowroot object,
803 replacing line break elements with '\n'. */
804 static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
806     if (*pending_line_break)
807         *string += '\n';
808     for (SPObject const *child = root->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
809         if (SP_IS_STRING(child))
810             *string += SP_STRING(child)->string;
811         else
812             sp_te_get_ustring_multiline(child, string, pending_line_break);
813     }
814     if (!SP_IS_TEXT(root) && !SP_IS_TEXTPATH(root) && is_line_break_object(root))
815         *pending_line_break = true;
818 /** Gets a text-only representation of the given text or flowroot object,
819 replacing line break elements with '\n'. The return value must be free()d. */
820 gchar *
821 sp_te_get_string_multiline (SPItem const *text)
823     Glib::ustring string;
824     bool pending_line_break = false;
826     if (!SP_IS_TEXT(text) && !SP_IS_FLOWTEXT(text)) return NULL;
827     sp_te_get_ustring_multiline(text, &string, &pending_line_break);
828     if (string.empty()) return NULL;
829     return strdup(string.data());
832 /** Gets a text-only representation of the characters in a text or flowroot
833 object from \a start to \a end only. Line break elements are replaced with
834 '\n'. */
835 Glib::ustring
836 sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
838     if (start == end) return "";
839     Inkscape::Text::Layout::iterator first, last;
840     if (start < end) {
841         first = start;
842         last = end;
843     } else {
844         first = end;
845         last = start;
846     }
847     Inkscape::Text::Layout const *layout = te_get_layout(text);
848     Glib::ustring result;
849     // not a particularly fast piece of code. I'll optimise it if people start to notice.
850     for ( ; first < last ; first.nextCharacter()) {
851         SPObject *char_item = 0;
852         void *rawptr = 0;
853         Glib::ustring::iterator text_iter;
854         layout->getSourceOfCharacter(first, &rawptr, &text_iter);
855         char_item = SP_OBJECT(rawptr);
856         if (SP_IS_STRING(char_item))
857             result += *text_iter;
858         else
859             result += '\n';
860     }
861     return result;
864 void
865 sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
867     g_return_if_fail (text != NULL);
868     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
870     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(text)->document();
871     Inkscape::XML::Node *repr;
872     SPObject *object;
873     bool is_textpath = false;
874     if (SP_IS_TEXT_TEXTPATH (text)) {
875         repr = SP_OBJECT_REPR (sp_object_first_child(SP_OBJECT (text)));
876         object = sp_object_first_child(SP_OBJECT (text));
877         is_textpath = true;
878     } else {
879         repr = SP_OBJECT_REPR (text);
880         object = SP_OBJECT (text);
881     }
883     if (!str) str = "";
884     gchar *content = g_strdup (str);
886     repr->setContent("");
887     SPObject *child = object->firstChild();
888     while (child) {
889         SPObject *next = SP_OBJECT_NEXT(child);
890         if (!SP_IS_FLOWREGION(child) && !SP_IS_FLOWREGIONEXCLUDE(child))
891             repr->removeChild(SP_OBJECT_REPR(child));
892         child = next;
893     }
895     gchar *p = content;
896     while (p) {
897         gchar *e = strchr (p, '\n');
898         if (is_textpath) {
899             if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
900         } else {
901             if (e) *e = '\0';
902             Inkscape::XML::Node *rtspan;
903             if (SP_IS_TEXT(text)) { // create a tspan for each line
904                 rtspan = xml_doc->createElement("svg:tspan");
905                 rtspan->setAttribute("sodipodi:role", "line");
906             } else { // create a flowPara for each line
907                 rtspan = xml_doc->createElement("svg:flowPara");
908             }
909             Inkscape::XML::Node *rstr = xml_doc->createTextNode(p);
910             rtspan->addChild(rstr, NULL);
911             Inkscape::GC::release(rstr);
912             repr->appendChild(rtspan);
913             Inkscape::GC::release(rtspan);
914         }
915         p = (e) ? e + 1 : NULL;
916     }
917     if (is_textpath) {
918         Inkscape::XML::Node *rstr = xml_doc->createTextNode(content);
919         repr->addChild(rstr, NULL);
920         Inkscape::GC::release(rstr);
921     }
923     g_free (content);
926 /* ***************************************************************************************************/
927 //                           K E R N I N G   A N D   S P A C I N G
929 /** Returns the attributes block and the character index within that block
930 which represents the iterator \a position. */
931 TextTagAttributes*
932 text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
934     if (item == NULL || char_index == NULL || !SP_IS_TEXT(item))
935         return NULL;   // flowtext doesn't support kerning yet
936     SPText *text = SP_TEXT(item);
938     SPObject *source_item = 0;
939     void *rawptr = 0;
940     Glib::ustring::iterator source_text_iter;
941     text->layout.getSourceOfCharacter(position, &rawptr, &source_text_iter);
942     source_item = SP_OBJECT(rawptr);
944     if (!SP_IS_STRING(source_item)) return NULL;
945     Glib::ustring *string = &SP_STRING(source_item)->string;
946     *char_index = sum_sibling_text_lengths_before(source_item);
947     for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; it++)
948         ++*char_index;
950     return attributes_for_object(SP_OBJECT_PARENT(source_item));
953 void
954 sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, Geom::Point by)
956     // divide increment by zoom
957     // divide increment by matrix expansion
958     gdouble factor = 1 / desktop->current_zoom();
959     Geom::Matrix t (sp_item_i2doc_affine(item));
960     factor = factor / t.descrim();
961     by = factor * by;
963     unsigned char_index;
964     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
965     if (attributes) attributes->addToDxDy(char_index, by);
966     if (start != end) {
967         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
968         if (attributes) attributes->addToDxDy(char_index, -by);
969     }
971     item->updateRepr();
972     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
975 void
976 sp_te_adjust_dx (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, double delta)
978     unsigned char_index;
979     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
980     if (attributes) attributes->addToDx(char_index, delta);
981     if (start != end) {
982         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
983         if (attributes) attributes->addToDx(char_index, -delta);
984     }
986     item->updateRepr();
987     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
990 void
991 sp_te_adjust_dy (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, double delta)
993     unsigned char_index;
994     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
995     if (attributes) attributes->addToDy(char_index, delta);
996     if (start != end) {
997         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
998         if (attributes) attributes->addToDy(char_index, -delta);
999     }
1001     item->updateRepr();
1002     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1005 void
1006 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
1008     // divide increment by zoom
1009     // divide increment by matrix expansion
1010     gdouble factor = 1 / desktop->current_zoom();
1011     Geom::Matrix t (sp_item_i2doc_affine(text));
1012     factor = factor / t.descrim();
1013     Inkscape::Text::Layout const *layout = te_get_layout(text);
1014     if (layout == NULL) return;
1015     SPObject *source_item = 0;
1016     void *rawptr = 0;
1017     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1018     source_item = SP_OBJECT(rawptr);
1019     if (source_item == 0) return;
1020     gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
1022     sp_te_adjust_rotation(text, start, end, desktop, degrees);
1025 void
1026 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
1028     unsigned char_index;
1029     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1030     if (attributes == NULL) return;
1032     if (start != end) {
1033         for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1034             attributes = text_tag_attributes_at_position(text, it, &char_index);
1035             if (attributes) attributes->addToRotate(char_index, degrees);
1036         }
1037     } else
1038         attributes->addToRotate(char_index, degrees);
1040     text->updateRepr();
1041     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1044 void
1045 sp_te_set_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
1047     unsigned char_index;
1048     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1049     if (attributes == NULL) return;
1051     if (start != end) {
1052         for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1053             attributes = text_tag_attributes_at_position(text, it, &char_index);
1054             if (attributes) attributes->setRotate(char_index, degrees);
1055         }
1056     } else
1057         attributes->setRotate(char_index, degrees);
1059     text->updateRepr();
1060     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1063 void
1064 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
1066     g_return_if_fail (text != NULL);
1067     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1069     Inkscape::Text::Layout const *layout = te_get_layout(text);
1071     gdouble val;
1072     SPObject *source_obj = 0;
1073     void *rawptr = 0;
1074     unsigned nb_let;
1075     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1076     source_obj = SP_OBJECT(rawptr);
1078     if (source_obj == 0) {   // end of text
1079         source_obj = text->lastChild();
1080     }
1081     if (SP_IS_STRING(source_obj)) {
1082         source_obj = source_obj->parent;
1083     }
1085     SPStyle *style = SP_OBJECT_STYLE (source_obj);
1087     // calculate real value
1088     /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
1089        get rid of the `else val = 0.0'.  Similarly below and in sp-string.cpp. */
1090     if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1091         if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1092             val = style->font_size.computed * style->letter_spacing.value;
1093         } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1094             val = style->font_size.computed * style->letter_spacing.value * 0.5;
1095         } else { // unknown unit - should not happen
1096             val = 0.0;
1097         }
1098     } else { // there's a real value in .computed, or it's zero
1099         val = style->letter_spacing.computed;
1100     }
1102     if (start == end) {
1103         while (!is_line_break_object(source_obj))     // move up the tree so we apply to the closest paragraph
1104             source_obj = SP_OBJECT_PARENT(source_obj);
1105         nb_let = sp_text_get_length(source_obj);
1106     } else {
1107         nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
1108     }
1110     // divide increment by zoom and by the number of characters in the line,
1111     // so that the entire line is expanded by by pixels, no matter what its length
1112     gdouble const zoom = desktop->current_zoom();
1113     gdouble const zby = (by
1114                          / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
1115                          / to_2geom(sp_item_i2doc_affine(SP_ITEM(source_obj))).descrim());
1116     val += zby;
1118     if (start == end) {
1119         // set back value to entire paragraph
1120         style->letter_spacing.normal = FALSE;
1121         if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1122             if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1123                 style->letter_spacing.value = val / style->font_size.computed;
1124             } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1125                 style->letter_spacing.value = val / style->font_size.computed * 2;
1126             }
1127         } else {
1128             style->letter_spacing.computed = val;
1129         }
1131         style->letter_spacing.set = TRUE;
1132     } else {
1133         // apply to selection only
1134         SPCSSAttr *css = sp_repr_css_attr_new();
1135         char string_val[40];
1136         g_snprintf(string_val, sizeof(string_val), "%f", val);
1137         sp_repr_css_set_property(css, "letter-spacing", string_val);
1138         sp_te_apply_style(text, start, end, css);
1139         sp_repr_css_attr_unref(css);
1140     }
1142     text->updateRepr();
1143     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1146 double
1147 sp_te_get_average_linespacing (SPItem *text)
1149     Inkscape::Text::Layout const *layout = te_get_layout(text);
1150     if (!layout)
1151         return 0;
1153     unsigned line_count = layout->lineIndex(layout->end());
1154     double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
1155     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1156     return average_line_height;
1159 void
1160 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &/*start*/, Inkscape::Text::Layout::iterator const &/*end*/, SPDesktop *desktop, gdouble by)
1162     // TODO: use start and end iterators to delineate the area to be affected
1163     g_return_if_fail (text != NULL);
1164     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1166     Inkscape::Text::Layout const *layout = te_get_layout(text);
1167     SPStyle *style = SP_OBJECT_STYLE (text);
1169     if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
1170         style->line_height.set = TRUE;
1171         style->line_height.inherit = FALSE;
1172         style->line_height.normal = FALSE;
1173         style->line_height.unit = SP_CSS_UNIT_PERCENT;
1174         style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1175     }
1177     unsigned line_count = layout->lineIndex(layout->end());
1178     double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
1179     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1180     if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1182     // divide increment by zoom and by the number of lines,
1183     // so that the entire object is expanded by by pixels
1184     gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1186     // divide increment by matrix expansion
1187     Geom::Matrix t (sp_item_i2doc_affine (SP_ITEM(text)));
1188     zby = zby / t.descrim();
1190     switch (style->line_height.unit) {
1191         case SP_CSS_UNIT_NONE:
1192         default:
1193             // multiplier-type units, stored in computed
1194             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
1195             else style->line_height.computed *= (average_line_height + zby) / average_line_height;
1196             style->line_height.value = style->line_height.computed;
1197             break;
1198         case SP_CSS_UNIT_EM:
1199         case SP_CSS_UNIT_EX:
1200         case SP_CSS_UNIT_PERCENT:
1201             // multiplier-type units, stored in value
1202             if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
1203             else style->line_height.value *= (average_line_height + zby) / average_line_height;
1204             break;
1205             // absolute-type units
1206         case SP_CSS_UNIT_PX:
1207             style->line_height.computed += zby;
1208             style->line_height.value = style->line_height.computed;
1209             break;
1210         case SP_CSS_UNIT_PT:
1211             style->line_height.computed += zby * PT_PER_PX;
1212             style->line_height.value = style->line_height.computed;
1213             break;
1214         case SP_CSS_UNIT_PC:
1215             style->line_height.computed += zby * (PT_PER_PX / 12);
1216             style->line_height.value = style->line_height.computed;
1217             break;
1218         case SP_CSS_UNIT_MM:
1219             style->line_height.computed += zby * MM_PER_PX;
1220             style->line_height.value = style->line_height.computed;
1221             break;
1222         case SP_CSS_UNIT_CM:
1223             style->line_height.computed += zby * CM_PER_PX;
1224             style->line_height.value = style->line_height.computed;
1225             break;
1226         case SP_CSS_UNIT_IN:
1227             style->line_height.computed += zby * IN_PER_PX;
1228             style->line_height.value = style->line_height.computed;
1229             break;
1230     }
1231     text->updateRepr();
1232     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1236 /* ***************************************************************************************************/
1237 //                           S T Y L E   A P P L I C A T I O N
1240 /** converts an iterator to a character index, mainly because ustring::substr()
1241 doesn't have a version that takes iterators as parameters. */
1242 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1244     unsigned n = 0;
1245     for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1246         n++;
1247     return n;
1250 /** applies the given style string on top of the existing styles for \a item,
1251 as opposed to sp_style_merge_from_style_string which merges its parameter
1252 underneath the existing styles (ie ignoring already set properties). */
1253 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1255     SPStyle *new_style = sp_style_new(SP_OBJECT_DOCUMENT(item));
1256     sp_style_merge_from_style_string(new_style, style_string);
1257     gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1258     if (item_style_string && *item_style_string)
1259         sp_style_merge_from_style_string(new_style, item_style_string);
1260     gchar *new_style_string = sp_style_write_string(new_style);
1261     sp_style_unref(new_style);
1262     SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1263     g_free(new_style_string);
1266 /** Returns true if the style of \a parent and the style of \a child are
1267 equivalent (and hence the children of both will appear the same). It is a
1268 limitation of the current implementation that \a parent must be a (not
1269 necessarily immediate) ancestor of \a child. */
1270 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1272     // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1273     // will not work. Firstly because of an inheritance bug in style.cpp that has
1274     // implications too large for me to feel safe fixing, but mainly because the css spec
1275     // requires that the computed value is inherited, not the specified value.
1276     g_assert(parent->isAncestorOf(child));
1277     gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1278     // we have to write parent_style then read it again, because some properties format their values
1279     // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1280     SPStyle *parent_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1281     sp_style_merge_from_style_string(parent_spstyle, parent_style);
1282     g_free(parent_style);
1283     parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1284     sp_style_unref(parent_spstyle);
1286     Glib::ustring child_style_construction(parent_style);
1287     while (child != parent) {
1288         // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1289         char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1290         if (style_text && *style_text) {
1291             child_style_construction += ';';
1292             child_style_construction += style_text;
1293         }
1294         child = SP_OBJECT_PARENT(child);
1295     }
1296     SPStyle *child_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1297     sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1298     gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1299     sp_style_unref(child_spstyle);
1300     bool equal = !strcmp(child_style, parent_style);
1301     g_free(child_style);
1302     g_free(parent_style);
1303     return equal;
1306 /** returns true if \a first and \a second contain all the same attributes
1307 with the same values as each other. Note that we have to compare both
1308 forwards and backwards to make sure we don't miss any attributes that are
1309 in one but not the other. */
1310 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1312     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1313     for ( ; attrs ; attrs++) {
1314         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1315         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1316             return false;
1317     }
1318     attrs = second->attributeList();
1319     for ( ; attrs ; attrs++) {
1320         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1321         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1322             return false;
1323     }
1324     return true;
1327 /** sets the given css attribute on this object and all its descendants.
1328 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1329 transform stuff. */
1330 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1332     sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1334     for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1335         if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1336             // Unset properties which are accumulating and thus should not be set recursively.
1337             // 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.
1338             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1339             sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1340             sp_repr_css_set_property(css_recurse, "opacity", NULL);
1341             apply_css_recursive(child, css_recurse);
1342             sp_repr_css_attr_unref(css_recurse);
1343         } else {
1344             apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1345         }
1346     }
1349 /** applies the given style to all the objects at the given level and below
1350 which are between \a start_item and \a end_item, creating spans as necessary.
1351 If \a start_item or \a end_item are NULL then the style is applied to all
1352 objects to the beginning or end respectively. \a span_object_name is the
1353 name of the xml for a text span (ie tspan or flowspan). */
1354 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)
1356     bool passed_start = start_item == NULL ? true : false;
1357     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(common_ancestor));
1358     
1359     for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1360         if (start_item == child)
1361             passed_start = true;
1363         if (passed_start) {
1364             if (end_item && child->isAncestorOf(end_item)) {
1365                 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1366                 break;
1367             }
1368             // apply style
1370             // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1371             // often this means that new spans are created before child and child is modified only
1372             if (SP_IS_STRING(child)) {
1373                 SPString *string_item = SP_STRING(child);
1374                 bool surround_entire_string = true;
1376                 Inkscape::XML::Node *child_span = xml_doc->createElement(span_object_name);
1377                 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style");   // better hope that prototype wasn't nonconst for a good reason
1378                 SPObject *prev_item = SP_OBJECT_PREV(child);
1379                 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1381                 if (child == start_item || child == end_item) {
1382                     surround_entire_string = false;
1383                     if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1384                         // eg "abcDEFghi"  -> "abc"<span>"DEF"</span>"ghi"
1385                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1386                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1388                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1389                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1390                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1391                         Inkscape::GC::release(text_before);
1392                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
1393                         child_span->appendChild(text_in_span);
1394                         Inkscape::GC::release(text_in_span);
1395                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1397                     } else if (child == end_item) {
1398                         // eg "ABCdef" -> <span>"ABC"</span>"def"
1399                         //  (includes case where start_text_iter == begin())
1400                         // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1401                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1403                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1404                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(0, end_char_index).c_str());
1405                         child_span->appendChild(text_in_span);
1406                         Inkscape::GC::release(text_in_span);
1407                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1409                     } else if (start_text_iter != string_item->string.begin()) {
1410                         // eg "abcDEF" -> "abc"<span>"DEF"</span>
1411                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1413                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1414                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1415                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1416                         Inkscape::GC::release(text_before);
1417                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index).c_str());
1418                         child_span->appendChild(text_in_span);
1419                         Inkscape::GC::release(text_in_span);
1420                         child->deleteObject();
1421                         child = sp_object_get_child_by_repr(common_ancestor, child_span);
1423                     } else
1424                         surround_entire_string = true;
1425                 }
1426                 if (surround_entire_string) {
1427                     Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1428                     SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1429                     Inkscape::GC::anchor(child_repr);
1430                     SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1431                     child_span->appendChild(child_repr);
1432                     Inkscape::GC::release(child_repr);
1433                     child = sp_object_get_child_by_repr(common_ancestor, child_span);
1434                 }
1435                 Inkscape::GC::release(child_span);
1437             } else if (child != end_item) {   // not a string and we're applying to the entire object. This is easy
1438                 apply_css_recursive(child, css);
1439             }
1441         } else {  // !passed_start
1442             if (child->isAncestorOf(start_item)) {
1443                 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1444                 if (end_item && child->isAncestorOf(end_item))
1445                     break;   // only happens when start_item == end_item (I think)
1446                 passed_start = true;
1447             }
1448         }
1450         if (end_item == child)
1451             break;
1452     }
1455 /* if item is at the beginning of a tree it doesn't matter which element
1456 it points to so for neatness we would like it to point to the highest
1457 possible child of \a common_ancestor. There is no iterator return because
1458 a string can never be an ancestor.
1460 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1461 like * to point to the inner span because we can apply style to that whole
1462 span. */
1463 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1465     if (item == common_ancestor)
1466         return item;
1467     if (SP_IS_STRING(item))
1468         if (text_iter != SP_STRING(item)->string.begin())
1469             return item;
1470     for ( ; ; ) {
1471         SPObject *parent = SP_OBJECT_PARENT(item);
1472         if (parent == common_ancestor)
1473             break;
1474         if (item != parent->firstChild())
1475             break;
1476         item = parent;
1477     }
1478     return item;
1482 /**     empty spans: abc<span></span>def
1483                       -> abcdef                  */
1484 static bool tidy_operator_empty_spans(SPObject **item)
1486     if ((*item)->hasChildren()) return false;
1487     if (is_line_break_object(*item)) return false;
1488     if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1489     SPObject *next = SP_OBJECT_NEXT(*item);
1490     (*item)->deleteObject();
1491     *item = next;
1492     return true;
1495 /**    inexplicable spans: abc<span style="">def</span>ghi
1496                             -> "abc""def""ghi"
1497 the repeated strings will be merged by another operator. */
1498 static bool tidy_operator_inexplicable_spans(SPObject **item)
1500     if (*item && sp_repr_is_meta_element((*item)->repr)) return false;
1501     if (SP_IS_STRING(*item)) return false;
1502     if (is_line_break_object(*item)) return false;
1503     TextTagAttributes *attrs = attributes_for_object(*item);
1504     if (attrs && attrs->anyAttributesSet()) return false;
1505     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1506     SPObject *next = *item;
1507     while ((*item)->hasChildren()) {
1508         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1509         Inkscape::GC::anchor(repr);
1510         SP_OBJECT_REPR(*item)->removeChild(repr);
1511         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1512         Inkscape::GC::release(repr);
1513         next = SP_OBJECT_NEXT(next);
1514     }
1515     (*item)->deleteObject();
1516     *item = next;
1517     return true;
1520 /**    repeated spans: <font a>abc</font><font a>def</font>
1521                         -> <font a>abcdef</font>            */
1522 static bool tidy_operator_repeated_spans(SPObject **item)
1524     SPObject *first = *item;
1525     SPObject *second = SP_OBJECT_NEXT(first);
1526     if (second == NULL) return false;
1528     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1529     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1531     if (first_repr->type() != second_repr->type()) return false;
1533     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1534         // also amalgamate consecutive SPStrings into one
1535         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1536         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1537         second_repr->parent()->removeChild(second_repr);
1538         return true;
1539     }
1541     // merge consecutive spans with identical styles into one
1542     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1543     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1544     if (is_line_break_object(second)) return false;
1545     gchar const *first_style = first_repr->attribute("style");
1546     gchar const *second_style = second_repr->attribute("style");
1547     if (!((first_style == NULL && second_style == NULL)
1548           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1549         return false;
1551     // all our tests passed: do the merge
1552     TextTagAttributes *attributes_first = attributes_for_object(first);
1553     TextTagAttributes *attributes_second = attributes_for_object(second);
1554     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1555         TextTagAttributes attributes_first_copy = *attributes_first;
1556         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1557     }
1558     move_child_nodes(second_repr, first_repr);
1559     second_repr->parent()->removeChild(second_repr);
1560     return true;
1561     // *item is still the next object to process
1564 /**    redundant nesting: <font a><font b>abc</font></font>
1565                            -> <font b>abc</font>
1566        excessive nesting: <font a><size 1>abc</size></font>
1567                            -> <font a,size 1>abc</font>      */
1568 static bool tidy_operator_excessive_nesting(SPObject **item)
1570     if (!(*item)->hasChildren()) return false;
1571     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1572     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1573         return false;
1574     if (SP_IS_STRING((*item)->firstChild())) return false;
1575     if (is_line_break_object((*item)->firstChild())) return false;
1576     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1577     if (attrs && attrs->anyAttributesSet()) return false;
1578     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1579     if (child_style && *child_style)
1580         overwrite_style_with_string(*item, child_style);
1581     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1582     (*item)->firstChild()->deleteObject();
1583     return true;
1586 /** helper for tidy_operator_redundant_double_nesting() */
1587 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1589     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1590         return false;
1591     if (SP_IS_STRING(child)) return false;
1592     if (is_line_break_object(child)) return false;
1593     if (is_line_break_object(*item)) return false;
1594     TextTagAttributes *attrs = attributes_for_object(child);
1595     if (attrs && attrs->anyAttributesSet()) return false;
1596     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1598     Inkscape::XML::Node *insert_after_repr;
1599     if (!prepend) insert_after_repr = SP_OBJECT_REPR(*item);
1600     else if (SP_OBJECT_PREV(*item)) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1601     else insert_after_repr = NULL;
1602     while (SP_OBJECT_REPR(child)->childCount()) {
1603         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1604         Inkscape::GC::anchor(move_repr);
1605         SP_OBJECT_REPR(child)->removeChild(move_repr);
1606         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1607         Inkscape::GC::release(move_repr);
1608         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1609     }
1610     child->deleteObject();
1611     return true;
1614 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1615                                 -> <font b>abc<font a>def</font>ghi</font>
1616 this function does its work when the parameter is the <font a> tag in the
1617 example. You may note that this only does its work when the doubly-nested
1618 child is the first or last. The other cases are called 'style inversion'
1619 below, and I'm not yet convinced that the result of that operation will be
1620 tidier in all cases. */
1621 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1623     if (!(*item)->hasChildren()) return false;
1624     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1625     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1626         return true;
1627     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1628         return true;
1629     return false;
1632 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1633 then compares the styles for item+child versus just child. If they're equal,
1634 tidying is possible. */
1635 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1637     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1638         return false;
1639     if (SP_IS_STRING(child)) return false;
1640     if (is_line_break_object(child)) return false;
1641     if (is_line_break_object(*item)) return false;
1642     TextTagAttributes *attrs = attributes_for_object(child);
1643     if (attrs && attrs->anyAttributesSet()) return false;
1644     attrs = attributes_for_object(*item);
1645     if (attrs && attrs->anyAttributesSet()) return false;
1647     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1648     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1649     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1650     if (child_style && *child_style) {
1651         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1652         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1653     }
1654     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1655     if (item_style && *item_style) {
1656         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1657     }
1658     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1659     sp_repr_css_attr_unref(css_child_and_item);
1660     sp_repr_css_attr_unref(css_child_only);
1661     if (!equal) return false;
1663     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(*item)->document();
1664     Inkscape::XML::Node *new_span = xml_doc->createElement(SP_OBJECT_REPR(*item)->name());
1665     if (prepend) {
1666         SPObject *prev = SP_OBJECT_PREV(*item);
1667         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1668     } else
1669         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1670     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1671     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1672     Inkscape::GC::release(new_span);
1673     child->deleteObject();
1674     return true;
1677 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1678                                 -> <font b>abc</font><font>def</font>
1679 test this by applying a colour to a region, then a different colour to
1680 a partially-overlapping region. */
1681 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1683     if (!(*item)->hasChildren()) return false;
1684     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1685     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1686         return true;
1687     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1688         return true;
1689     return false;
1692 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1693 in a paragraph which is not \a not_obj. */
1694 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1696     for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1697     {
1698         if (child == not_obj) continue;
1699         if (child->hasChildren()) {
1700             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1701             if (ret) return ret;
1702         } else if (SP_IS_STRING(child))
1703             return SP_STRING(child);
1704     }
1705     return NULL;
1708 /** whitespace-only spans: abc<font> </font>def
1709                             -> abc<font></font> def
1710                            abc<b><i>def</i> </b>ghi
1711                             -> abc<b><i>def</i></b> ghi   */
1712 static bool tidy_operator_styled_whitespace(SPObject **item)
1714     if (!SP_IS_STRING(*item)) return false;
1715     Glib::ustring const &str = SP_STRING(*item)->string;
1716     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1717         if (!g_unichar_isspace(*it)) return false;
1719     SPObject *test_item = *item;
1720     SPString *next_string;
1721     for ( ; ; ) {  // find the next string
1722         next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1723         if (next_string) {
1724             next_string->string.insert(0, str);
1725             break;
1726         }
1727         for ( ; ; ) {   // go up one item in the xml
1728             test_item = SP_OBJECT_PARENT(test_item);
1729             if (is_line_break_object(test_item)) break;
1730             if (SP_IS_FLOWTEXT(test_item)) return false;
1731             SPObject *next = SP_OBJECT_NEXT(test_item);
1732             if (next) {
1733                 test_item = next;
1734                 break;
1735             }
1736         }
1737         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1738             next_string = find_last_string_child_not_equal_to(test_item, *item);
1739             if (next_string == NULL) return false;   // an empty paragraph
1740             next_string->string += str;
1741             break;
1742         }
1743     }
1744     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1745     SPObject *delete_obj = *item;
1746     *item = SP_OBJECT_NEXT(*item);
1747     delete_obj->deleteObject();
1748     return true;
1751 /* possible tidy operators that are not yet implemented, either because
1752 they are difficult, occur infrequently, or because I'm not sure that the
1753 output is tidier in all cases:
1754     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1755                                               -> <div italic><para>abc</para></div>
1756     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1757                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1758     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1759                           -> <size 1><font a>abc</font>def</size>
1760 */
1762 /** Recursively walks the xml tree calling a set of cleanup operations on
1763 every child. Returns true if any changes were made to the tree.
1765 All the tidy operators return true if they made changes, and alter their
1766 parameter to point to the next object that should be processed, or NULL.
1767 They must not significantly alter (ie delete) any ancestor elements of the
1768 one they are passed.
1770 It may be that some of the later tidy operators that I wrote are actually
1771 general cases of the earlier operators, and hence the special-case-only
1772 versions can be removed. I haven't analysed my work in detail to figure
1773 out if this is so. */
1774 static bool tidy_xml_tree_recursively(SPObject *root)
1776     static bool (* const tidy_operators[])(SPObject**) = {
1777         tidy_operator_empty_spans,
1778         tidy_operator_inexplicable_spans,
1779         tidy_operator_repeated_spans,
1780         tidy_operator_excessive_nesting,
1781         tidy_operator_redundant_double_nesting,
1782         tidy_operator_redundant_semi_nesting,
1783         tidy_operator_styled_whitespace
1784     };
1785     bool changes = false;
1787     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1788         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child) || SP_IS_TREF(child)) {
1789             child = SP_OBJECT_NEXT(child);
1790             continue;
1791         }
1792         if (child->hasChildren())
1793             changes |= tidy_xml_tree_recursively(child);
1795         unsigned i;
1796         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1797             if (tidy_operators[i](&child)) {
1798                 changes = true;
1799                 break;
1800             }
1801         }
1802         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1803             child = SP_OBJECT_NEXT(child);
1804     }
1805     return changes;
1808 /** Applies the given CSS fragment to the characters of the given text or
1809 flowtext object between \a start and \a end, creating or removing span
1810 elements as necessary and optimal. */
1811 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1813     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1814     if (start == end) return;
1815     Inkscape::Text::Layout::iterator first, last;
1816     if (start < end) {
1817         first = start;
1818         last = end;
1819     } else {
1820         first = end;
1821         last = start;
1822     }
1823     Inkscape::Text::Layout const *layout = te_get_layout(text);
1824     SPObject *start_item = 0, *end_item = 0;
1825     void *rawptr = 0;
1826     Glib::ustring::iterator start_text_iter, end_text_iter;
1827     layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
1828     start_item = SP_OBJECT(rawptr);
1829     layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
1830     end_item = SP_OBJECT(rawptr);
1831     if (start_item == 0)
1832         return;   // start is at end of text
1833     if (is_line_break_object(start_item))
1834         start_item = SP_OBJECT_NEXT(start_item);
1835     if (is_line_break_object(end_item))
1836         end_item = SP_OBJECT_NEXT(end_item);
1837     if (end_item == 0) end_item = text;
1838     
1839     
1840     /* Special case: With a tref, we only want to change its style when the whole
1841      * string is selected, in which case the style can be applied directly to the
1842      * tref node.  If only part of the tref's string child is selected, just return. */
1843      
1844     if (!sp_tref_fully_contained(start_item, start_text_iter, end_item, end_text_iter)) {
1845         
1846         return;
1847     } 
1849     /* stage 1: applying the style. Go up to the closest common ancestor of
1850     start and end and then semi-recursively apply the style to all the
1851     objects in between. The semi-recursion is because it's only necessary
1852     at the beginning and end; the style can just be applied to the root
1853     child in the middle.
1854     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1855     The recursion may involve creating new spans.
1856     */
1857     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1859     // bug #168370 (consider parent transform and viewBox)
1860     // snipplet copied from desktop-style.cpp sp_desktop_apply_css_recursive(...)
1861     SPCSSAttr *css_set = sp_repr_css_attr_new();
1862     sp_repr_css_merge(css_set, (SPCSSAttr*) css);
1863     {
1864         Geom::Matrix const local(sp_item_i2doc_affine(SP_ITEM(common_ancestor)));
1865         double const ex(local.descrim());
1866         if ( ( ex != 0. )
1867              && ( ex != 1. ) ) {
1868             sp_css_attr_scale(css_set, 1/ex);
1869         }
1870     }
1872     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1873     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1874     recursively_apply_style(common_ancestor, css_set, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1875     sp_repr_css_attr_unref(css_set);
1877     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1878     /* discussion: this stage requires a certain level of inventiveness because
1879     it's not clear what the best representation is in many cases. An ideal
1880     implementation would provide some sort of scoring function to rate the
1881     ugliness of a given xml tree and try to reduce said function, but providing
1882     the various possibilities to be rated is non-trivial. Instead, I have opted
1883     for a multi-pass technique which simply recognises known-ugly patterns and
1884     has matching routines for optimising the patterns it finds. It's reasonably
1885     easy to add new pattern matching processors. If everything gets disastrous
1886     and neither option can be made to work, a fallback could be to reduce
1887     everything to a single level of nesting and drop all pretence of
1888     roundtrippability. */
1889     while (tidy_xml_tree_recursively(common_ancestor)){};
1891     // if we only modified subobjects this won't have been automatically sent
1892     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1895 bool is_part_of_text_subtree (SPObject *obj)
1897     return (SP_IS_TSPAN(obj) 
1898             || SP_IS_TEXT(obj) 
1899             || SP_IS_FLOWTEXT(obj)
1900             || SP_IS_FLOWTSPAN(obj)
1901             || SP_IS_FLOWDIV(obj)
1902             || SP_IS_FLOWPARA(obj)
1903             || SP_IS_FLOWLINE(obj)
1904             || SP_IS_FLOWREGIONBREAK(obj));
1907 bool is_top_level_text_object (SPObject *obj)
1909     return (SP_IS_TEXT(obj) 
1910             || SP_IS_FLOWTEXT(obj));
1913 bool has_visible_text (SPObject *obj)
1915     if (SP_IS_STRING(obj) && !SP_STRING(obj)->string.empty()) 
1916         return true; // maybe we should also check that it's not all whitespace?
1918     for (SPObject const *child = obj->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
1919         if (has_visible_text((SPObject *) child))
1920             return true;
1921     }
1923     return false;
1926 /*
1927   Local Variables:
1928   mode:c++
1929   c-file-style:"stroustrup"
1930   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1931   indent-tabs-mode:nil
1932   fill-column:99
1933   End:
1934 */
1935 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :