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