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