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