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