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