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