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 sp_te_adjust_dx(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop * /*desktop*/, double delta)
976 {
977 unsigned char_index = 0;
978 TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
979 if (attributes) {
980 attributes->addToDx(char_index, delta);
981 }
982 if (start != end) {
983 attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
984 if (attributes) {
985 attributes->addToDx(char_index, -delta);
986 }
987 }
989 item->updateRepr();
990 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
991 }
993 void sp_te_adjust_dy(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop * /*desktop*/, double delta)
994 {
995 unsigned char_index = 0;
996 TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
997 if (attributes) {
998 attributes->addToDy(char_index, delta);
999 }
1000 if (start != end) {
1001 attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
1002 if (attributes) {
1003 attributes->addToDy(char_index, -delta);
1004 }
1005 }
1007 item->updateRepr();
1008 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1009 }
1011 void
1012 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
1013 {
1014 // divide increment by zoom
1015 // divide increment by matrix expansion
1016 gdouble factor = 1 / desktop->current_zoom();
1017 Geom::Matrix t (sp_item_i2doc_affine(text));
1018 factor = factor / t.descrim();
1019 Inkscape::Text::Layout const *layout = te_get_layout(text);
1020 if (layout == NULL) return;
1021 SPObject *source_item = 0;
1022 void *rawptr = 0;
1023 layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1024 source_item = SP_OBJECT(rawptr);
1025 if (source_item == 0) return;
1026 gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
1028 sp_te_adjust_rotation(text, start, end, desktop, degrees);
1029 }
1031 void
1032 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
1033 {
1034 unsigned char_index;
1035 TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1036 if (attributes == NULL) return;
1038 if (start != end) {
1039 for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1040 attributes = text_tag_attributes_at_position(text, it, &char_index);
1041 if (attributes) attributes->addToRotate(char_index, degrees);
1042 }
1043 } else
1044 attributes->addToRotate(char_index, degrees);
1046 text->updateRepr();
1047 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1048 }
1050 void sp_te_set_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
1051 {
1052 unsigned char_index = 0;
1053 TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1054 if (attributes != NULL) {
1055 if (start != end) {
1056 for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1057 attributes = text_tag_attributes_at_position(text, it, &char_index);
1058 if (attributes) {
1059 attributes->setRotate(char_index, degrees);
1060 }
1061 }
1062 } else {
1063 attributes->setRotate(char_index, degrees);
1064 }
1066 text->updateRepr();
1067 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1068 }
1069 }
1071 void
1072 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
1073 {
1074 g_return_if_fail (text != NULL);
1075 g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1077 Inkscape::Text::Layout const *layout = te_get_layout(text);
1079 gdouble val;
1080 SPObject *source_obj = 0;
1081 void *rawptr = 0;
1082 unsigned nb_let;
1083 layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1084 source_obj = SP_OBJECT(rawptr);
1086 if (source_obj == 0) { // end of text
1087 source_obj = text->lastChild();
1088 }
1089 if (SP_IS_STRING(source_obj)) {
1090 source_obj = source_obj->parent;
1091 }
1093 SPStyle *style = SP_OBJECT_STYLE (source_obj);
1095 // calculate real value
1096 /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
1097 get rid of the `else val = 0.0'. Similarly below and in sp-string.cpp. */
1098 if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1099 if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1100 val = style->font_size.computed * style->letter_spacing.value;
1101 } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1102 val = style->font_size.computed * style->letter_spacing.value * 0.5;
1103 } else { // unknown unit - should not happen
1104 val = 0.0;
1105 }
1106 } else { // there's a real value in .computed, or it's zero
1107 val = style->letter_spacing.computed;
1108 }
1110 if (start == end) {
1111 while (!is_line_break_object(source_obj)) // move up the tree so we apply to the closest paragraph
1112 source_obj = SP_OBJECT_PARENT(source_obj);
1113 nb_let = sp_text_get_length(source_obj);
1114 } else {
1115 nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
1116 }
1118 // divide increment by zoom and by the number of characters in the line,
1119 // so that the entire line is expanded by by pixels, no matter what its length
1120 gdouble const zoom = desktop->current_zoom();
1121 gdouble const zby = (by
1122 / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
1123 / to_2geom(sp_item_i2doc_affine(SP_ITEM(source_obj))).descrim());
1124 val += zby;
1126 if (start == end) {
1127 // set back value to entire paragraph
1128 style->letter_spacing.normal = FALSE;
1129 if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1130 if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1131 style->letter_spacing.value = val / style->font_size.computed;
1132 } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1133 style->letter_spacing.value = val / style->font_size.computed * 2;
1134 }
1135 } else {
1136 style->letter_spacing.computed = val;
1137 }
1139 style->letter_spacing.set = TRUE;
1140 } else {
1141 // apply to selection only
1142 SPCSSAttr *css = sp_repr_css_attr_new();
1143 char string_val[40];
1144 g_snprintf(string_val, sizeof(string_val), "%f", val);
1145 sp_repr_css_set_property(css, "letter-spacing", string_val);
1146 sp_te_apply_style(text, start, end, css);
1147 sp_repr_css_attr_unref(css);
1148 }
1150 text->updateRepr();
1151 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1152 }
1154 double
1155 sp_te_get_average_linespacing (SPItem *text)
1156 {
1157 Inkscape::Text::Layout const *layout = te_get_layout(text);
1158 if (!layout)
1159 return 0;
1161 unsigned line_count = layout->lineIndex(layout->end());
1162 double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
1163 double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1164 return average_line_height;
1165 }
1167 void
1168 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &/*start*/, Inkscape::Text::Layout::iterator const &/*end*/, SPDesktop *desktop, gdouble by)
1169 {
1170 // TODO: use start and end iterators to delineate the area to be affected
1171 g_return_if_fail (text != NULL);
1172 g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1174 Inkscape::Text::Layout const *layout = te_get_layout(text);
1175 SPStyle *style = SP_OBJECT_STYLE (text);
1177 if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
1178 style->line_height.set = TRUE;
1179 style->line_height.inherit = FALSE;
1180 style->line_height.normal = FALSE;
1181 style->line_height.unit = SP_CSS_UNIT_PERCENT;
1182 style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1183 }
1185 unsigned line_count = layout->lineIndex(layout->end());
1186 double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
1187 double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1188 if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1190 // divide increment by zoom and by the number of lines,
1191 // so that the entire object is expanded by by pixels
1192 gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1194 // divide increment by matrix expansion
1195 Geom::Matrix t (sp_item_i2doc_affine (SP_ITEM(text)));
1196 zby = zby / t.descrim();
1198 switch (style->line_height.unit) {
1199 case SP_CSS_UNIT_NONE:
1200 default:
1201 // multiplier-type units, stored in computed
1202 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
1203 else style->line_height.computed *= (average_line_height + zby) / average_line_height;
1204 style->line_height.value = style->line_height.computed;
1205 break;
1206 case SP_CSS_UNIT_EM:
1207 case SP_CSS_UNIT_EX:
1208 case SP_CSS_UNIT_PERCENT:
1209 // multiplier-type units, stored in value
1210 if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
1211 else style->line_height.value *= (average_line_height + zby) / average_line_height;
1212 break;
1213 // absolute-type units
1214 case SP_CSS_UNIT_PX:
1215 style->line_height.computed += zby;
1216 style->line_height.value = style->line_height.computed;
1217 break;
1218 case SP_CSS_UNIT_PT:
1219 style->line_height.computed += zby * PT_PER_PX;
1220 style->line_height.value = style->line_height.computed;
1221 break;
1222 case SP_CSS_UNIT_PC:
1223 style->line_height.computed += zby * (PT_PER_PX / 12);
1224 style->line_height.value = style->line_height.computed;
1225 break;
1226 case SP_CSS_UNIT_MM:
1227 style->line_height.computed += zby * MM_PER_PX;
1228 style->line_height.value = style->line_height.computed;
1229 break;
1230 case SP_CSS_UNIT_CM:
1231 style->line_height.computed += zby * CM_PER_PX;
1232 style->line_height.value = style->line_height.computed;
1233 break;
1234 case SP_CSS_UNIT_IN:
1235 style->line_height.computed += zby * IN_PER_PX;
1236 style->line_height.value = style->line_height.computed;
1237 break;
1238 }
1239 text->updateRepr();
1240 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1241 }
1244 /* ***************************************************************************************************/
1245 // S T Y L E A P P L I C A T I O N
1248 /** converts an iterator to a character index, mainly because ustring::substr()
1249 doesn't have a version that takes iterators as parameters. */
1250 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1251 {
1252 unsigned n = 0;
1253 for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1254 n++;
1255 return n;
1256 }
1258 /** applies the given style string on top of the existing styles for \a item,
1259 as opposed to sp_style_merge_from_style_string which merges its parameter
1260 underneath the existing styles (ie ignoring already set properties). */
1261 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1262 {
1263 SPStyle *new_style = sp_style_new(SP_OBJECT_DOCUMENT(item));
1264 sp_style_merge_from_style_string(new_style, style_string);
1265 gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1266 if (item_style_string && *item_style_string)
1267 sp_style_merge_from_style_string(new_style, item_style_string);
1268 gchar *new_style_string = sp_style_write_string(new_style);
1269 sp_style_unref(new_style);
1270 SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1271 g_free(new_style_string);
1272 }
1274 /** Returns true if the style of \a parent and the style of \a child are
1275 equivalent (and hence the children of both will appear the same). It is a
1276 limitation of the current implementation that \a parent must be a (not
1277 necessarily immediate) ancestor of \a child. */
1278 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1279 {
1280 // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1281 // will not work. Firstly because of an inheritance bug in style.cpp that has
1282 // implications too large for me to feel safe fixing, but mainly because the css spec
1283 // requires that the computed value is inherited, not the specified value.
1284 g_assert(parent->isAncestorOf(child));
1285 gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1286 // we have to write parent_style then read it again, because some properties format their values
1287 // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1288 SPStyle *parent_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1289 sp_style_merge_from_style_string(parent_spstyle, parent_style);
1290 g_free(parent_style);
1291 parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1292 sp_style_unref(parent_spstyle);
1294 Glib::ustring child_style_construction(parent_style);
1295 while (child != parent) {
1296 // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1297 char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1298 if (style_text && *style_text) {
1299 child_style_construction += ';';
1300 child_style_construction += style_text;
1301 }
1302 child = SP_OBJECT_PARENT(child);
1303 }
1304 SPStyle *child_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1305 sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1306 gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1307 sp_style_unref(child_spstyle);
1308 bool equal = !strcmp(child_style, parent_style);
1309 g_free(child_style);
1310 g_free(parent_style);
1311 return equal;
1312 }
1314 /** returns true if \a first and \a second contain all the same attributes
1315 with the same values as each other. Note that we have to compare both
1316 forwards and backwards to make sure we don't miss any attributes that are
1317 in one but not the other. */
1318 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1319 {
1320 Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1321 for ( ; attrs ; attrs++) {
1322 gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1323 if (other_attr == NULL || strcmp(attrs->value, other_attr))
1324 return false;
1325 }
1326 attrs = second->attributeList();
1327 for ( ; attrs ; attrs++) {
1328 gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1329 if (other_attr == NULL || strcmp(attrs->value, other_attr))
1330 return false;
1331 }
1332 return true;
1333 }
1335 /** sets the given css attribute on this object and all its descendants.
1336 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1337 transform stuff. */
1338 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1339 {
1340 sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1342 for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1343 if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1344 // Unset properties which are accumulating and thus should not be set recursively.
1345 // 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.
1346 SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1347 sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1348 sp_repr_css_set_property(css_recurse, "opacity", NULL);
1349 apply_css_recursive(child, css_recurse);
1350 sp_repr_css_attr_unref(css_recurse);
1351 } else {
1352 apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1353 }
1354 }
1355 }
1357 /** applies the given style to all the objects at the given level and below
1358 which are between \a start_item and \a end_item, creating spans as necessary.
1359 If \a start_item or \a end_item are NULL then the style is applied to all
1360 objects to the beginning or end respectively. \a span_object_name is the
1361 name of the xml for a text span (ie tspan or flowspan). */
1362 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)
1363 {
1364 bool passed_start = start_item == NULL ? true : false;
1365 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(common_ancestor));
1367 for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1368 if (start_item == child)
1369 passed_start = true;
1371 if (passed_start) {
1372 if (end_item && child->isAncestorOf(end_item)) {
1373 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1374 break;
1375 }
1376 // apply style
1378 // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1379 // often this means that new spans are created before child and child is modified only
1380 if (SP_IS_STRING(child)) {
1381 SPString *string_item = SP_STRING(child);
1382 bool surround_entire_string = true;
1384 Inkscape::XML::Node *child_span = xml_doc->createElement(span_object_name);
1385 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style"); // better hope that prototype wasn't nonconst for a good reason
1386 SPObject *prev_item = SP_OBJECT_PREV(child);
1387 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1389 if (child == start_item || child == end_item) {
1390 surround_entire_string = false;
1391 if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1392 // eg "abcDEFghi" -> "abc"<span>"DEF"</span>"ghi"
1393 unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1394 unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1396 Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1397 SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1398 SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1399 Inkscape::GC::release(text_before);
1400 Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
1401 child_span->appendChild(text_in_span);
1402 Inkscape::GC::release(text_in_span);
1403 SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1405 } else if (child == end_item) {
1406 // eg "ABCdef" -> <span>"ABC"</span>"def"
1407 // (includes case where start_text_iter == begin())
1408 // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1409 unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1411 SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1412 Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(0, end_char_index).c_str());
1413 child_span->appendChild(text_in_span);
1414 Inkscape::GC::release(text_in_span);
1415 SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1417 } else if (start_text_iter != string_item->string.begin()) {
1418 // eg "abcDEF" -> "abc"<span>"DEF"</span>
1419 unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1421 Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1422 SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1423 SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1424 Inkscape::GC::release(text_before);
1425 Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index).c_str());
1426 child_span->appendChild(text_in_span);
1427 Inkscape::GC::release(text_in_span);
1428 child->deleteObject();
1429 child = sp_object_get_child_by_repr(common_ancestor, child_span);
1431 } else
1432 surround_entire_string = true;
1433 }
1434 if (surround_entire_string) {
1435 Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1436 SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1437 Inkscape::GC::anchor(child_repr);
1438 SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1439 child_span->appendChild(child_repr);
1440 Inkscape::GC::release(child_repr);
1441 child = sp_object_get_child_by_repr(common_ancestor, child_span);
1442 }
1443 Inkscape::GC::release(child_span);
1445 } else if (child != end_item) { // not a string and we're applying to the entire object. This is easy
1446 apply_css_recursive(child, css);
1447 }
1449 } else { // !passed_start
1450 if (child->isAncestorOf(start_item)) {
1451 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1452 if (end_item && child->isAncestorOf(end_item))
1453 break; // only happens when start_item == end_item (I think)
1454 passed_start = true;
1455 }
1456 }
1458 if (end_item == child)
1459 break;
1460 }
1461 }
1463 /* if item is at the beginning of a tree it doesn't matter which element
1464 it points to so for neatness we would like it to point to the highest
1465 possible child of \a common_ancestor. There is no iterator return because
1466 a string can never be an ancestor.
1468 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1469 like * to point to the inner span because we can apply style to that whole
1470 span. */
1471 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1472 {
1473 if (item == common_ancestor)
1474 return item;
1475 if (SP_IS_STRING(item))
1476 if (text_iter != SP_STRING(item)->string.begin())
1477 return item;
1478 for ( ; ; ) {
1479 SPObject *parent = SP_OBJECT_PARENT(item);
1480 if (parent == common_ancestor)
1481 break;
1482 if (item != parent->firstChild())
1483 break;
1484 item = parent;
1485 }
1486 return item;
1487 }
1490 /** empty spans: abc<span></span>def
1491 -> abcdef */
1492 static bool tidy_operator_empty_spans(SPObject **item)
1493 {
1494 if ((*item)->hasChildren()) return false;
1495 if (is_line_break_object(*item)) return false;
1496 if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1497 SPObject *next = SP_OBJECT_NEXT(*item);
1498 (*item)->deleteObject();
1499 *item = next;
1500 return true;
1501 }
1503 /** inexplicable spans: abc<span style="">def</span>ghi
1504 -> "abc""def""ghi"
1505 the repeated strings will be merged by another operator. */
1506 static bool tidy_operator_inexplicable_spans(SPObject **item)
1507 {
1508 if (*item && sp_repr_is_meta_element((*item)->repr)) return false;
1509 if (SP_IS_STRING(*item)) return false;
1510 if (is_line_break_object(*item)) return false;
1511 TextTagAttributes *attrs = attributes_for_object(*item);
1512 if (attrs && attrs->anyAttributesSet()) return false;
1513 if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1514 SPObject *next = *item;
1515 while ((*item)->hasChildren()) {
1516 Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1517 Inkscape::GC::anchor(repr);
1518 SP_OBJECT_REPR(*item)->removeChild(repr);
1519 SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1520 Inkscape::GC::release(repr);
1521 next = SP_OBJECT_NEXT(next);
1522 }
1523 (*item)->deleteObject();
1524 *item = next;
1525 return true;
1526 }
1528 /** repeated spans: <font a>abc</font><font a>def</font>
1529 -> <font a>abcdef</font> */
1530 static bool tidy_operator_repeated_spans(SPObject **item)
1531 {
1532 SPObject *first = *item;
1533 SPObject *second = SP_OBJECT_NEXT(first);
1534 if (second == NULL) return false;
1536 Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1537 Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1539 if (first_repr->type() != second_repr->type()) return false;
1541 if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1542 // also amalgamate consecutive SPStrings into one
1543 Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1544 SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1545 second_repr->parent()->removeChild(second_repr);
1546 return true;
1547 }
1549 // merge consecutive spans with identical styles into one
1550 if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1551 if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1552 if (is_line_break_object(second)) return false;
1553 gchar const *first_style = first_repr->attribute("style");
1554 gchar const *second_style = second_repr->attribute("style");
1555 if (!((first_style == NULL && second_style == NULL)
1556 || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1557 return false;
1559 // all our tests passed: do the merge
1560 TextTagAttributes *attributes_first = attributes_for_object(first);
1561 TextTagAttributes *attributes_second = attributes_for_object(second);
1562 if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1563 TextTagAttributes attributes_first_copy = *attributes_first;
1564 attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1565 }
1566 move_child_nodes(second_repr, first_repr);
1567 second_repr->parent()->removeChild(second_repr);
1568 return true;
1569 // *item is still the next object to process
1570 }
1572 /** redundant nesting: <font a><font b>abc</font></font>
1573 -> <font b>abc</font>
1574 excessive nesting: <font a><size 1>abc</size></font>
1575 -> <font a,size 1>abc</font> */
1576 static bool tidy_operator_excessive_nesting(SPObject **item)
1577 {
1578 if (!(*item)->hasChildren()) return false;
1579 if ((*item)->firstChild() != (*item)->lastChild()) return false;
1580 if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1581 return false;
1582 if (SP_IS_STRING((*item)->firstChild())) return false;
1583 if (is_line_break_object((*item)->firstChild())) return false;
1584 TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1585 if (attrs && attrs->anyAttributesSet()) return false;
1586 gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1587 if (child_style && *child_style)
1588 overwrite_style_with_string(*item, child_style);
1589 move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1590 (*item)->firstChild()->deleteObject();
1591 return true;
1592 }
1594 /** helper for tidy_operator_redundant_double_nesting() */
1595 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1596 {
1597 if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1598 return false;
1599 if (SP_IS_STRING(child)) return false;
1600 if (is_line_break_object(child)) return false;
1601 if (is_line_break_object(*item)) return false;
1602 TextTagAttributes *attrs = attributes_for_object(child);
1603 if (attrs && attrs->anyAttributesSet()) return false;
1604 if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1606 Inkscape::XML::Node *insert_after_repr;
1607 if (!prepend) insert_after_repr = SP_OBJECT_REPR(*item);
1608 else if (SP_OBJECT_PREV(*item)) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1609 else insert_after_repr = NULL;
1610 while (SP_OBJECT_REPR(child)->childCount()) {
1611 Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1612 Inkscape::GC::anchor(move_repr);
1613 SP_OBJECT_REPR(child)->removeChild(move_repr);
1614 SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1615 Inkscape::GC::release(move_repr);
1616 insert_after_repr = move_repr; // I think this will stay valid long enough. It's garbage collected these days.
1617 }
1618 child->deleteObject();
1619 return true;
1620 }
1622 /** redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1623 -> <font b>abc<font a>def</font>ghi</font>
1624 this function does its work when the parameter is the <font a> tag in the
1625 example. You may note that this only does its work when the doubly-nested
1626 child is the first or last. The other cases are called 'style inversion'
1627 below, and I'm not yet convinced that the result of that operation will be
1628 tidier in all cases. */
1629 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1630 {
1631 if (!(*item)->hasChildren()) return false;
1632 if ((*item)->firstChild() == (*item)->lastChild()) return false; // this is excessive nesting, done above
1633 if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1634 return true;
1635 if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1636 return true;
1637 return false;
1638 }
1640 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1641 then compares the styles for item+child versus just child. If they're equal,
1642 tidying is possible. */
1643 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1644 {
1645 if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1646 return false;
1647 if (SP_IS_STRING(child)) return false;
1648 if (is_line_break_object(child)) return false;
1649 if (is_line_break_object(*item)) return false;
1650 TextTagAttributes *attrs = attributes_for_object(child);
1651 if (attrs && attrs->anyAttributesSet()) return false;
1652 attrs = attributes_for_object(*item);
1653 if (attrs && attrs->anyAttributesSet()) return false;
1655 SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1656 SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1657 gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1658 if (child_style && *child_style) {
1659 sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1660 sp_repr_css_attr_add_from_string(css_child_only, child_style);
1661 }
1662 gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1663 if (item_style && *item_style) {
1664 sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1665 }
1666 bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1667 sp_repr_css_attr_unref(css_child_and_item);
1668 sp_repr_css_attr_unref(css_child_only);
1669 if (!equal) return false;
1671 Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(*item)->document();
1672 Inkscape::XML::Node *new_span = xml_doc->createElement(SP_OBJECT_REPR(*item)->name());
1673 if (prepend) {
1674 SPObject *prev = SP_OBJECT_PREV(*item);
1675 SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1676 } else
1677 SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1678 new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1679 move_child_nodes(SP_OBJECT_REPR(child), new_span);
1680 Inkscape::GC::release(new_span);
1681 child->deleteObject();
1682 return true;
1683 }
1685 /** redundant semi-nesting: <font a><font b>abc</font>def</font>
1686 -> <font b>abc</font><font>def</font>
1687 test this by applying a colour to a region, then a different colour to
1688 a partially-overlapping region. */
1689 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1690 {
1691 if (!(*item)->hasChildren()) return false;
1692 if ((*item)->firstChild() == (*item)->lastChild()) return false; // this is redundant nesting, done above
1693 if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1694 return true;
1695 if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1696 return true;
1697 return false;
1698 }
1700 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1701 in a paragraph which is not \a not_obj. */
1702 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1703 {
1704 for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1705 {
1706 if (child == not_obj) continue;
1707 if (child->hasChildren()) {
1708 SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1709 if (ret) return ret;
1710 } else if (SP_IS_STRING(child))
1711 return SP_STRING(child);
1712 }
1713 return NULL;
1714 }
1716 /** whitespace-only spans: abc<font> </font>def
1717 -> abc<font></font> def
1718 abc<b><i>def</i> </b>ghi
1719 -> abc<b><i>def</i></b> ghi */
1720 static bool tidy_operator_styled_whitespace(SPObject **item)
1721 {
1722 if (!SP_IS_STRING(*item)) return false;
1723 Glib::ustring const &str = SP_STRING(*item)->string;
1724 for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1725 if (!g_unichar_isspace(*it)) return false;
1727 SPObject *test_item = *item;
1728 SPString *next_string;
1729 for ( ; ; ) { // find the next string
1730 next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1731 if (next_string) {
1732 next_string->string.insert(0, str);
1733 break;
1734 }
1735 for ( ; ; ) { // go up one item in the xml
1736 test_item = SP_OBJECT_PARENT(test_item);
1737 if (is_line_break_object(test_item)) break;
1738 if (SP_IS_FLOWTEXT(test_item)) return false;
1739 SPObject *next = SP_OBJECT_NEXT(test_item);
1740 if (next) {
1741 test_item = next;
1742 break;
1743 }
1744 }
1745 if (is_line_break_object(test_item)) { // no next string, see if there's a prev string
1746 next_string = find_last_string_child_not_equal_to(test_item, *item);
1747 if (next_string == NULL) return false; // an empty paragraph
1748 next_string->string += str;
1749 break;
1750 }
1751 }
1752 SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1753 SPObject *delete_obj = *item;
1754 *item = SP_OBJECT_NEXT(*item);
1755 delete_obj->deleteObject();
1756 return true;
1757 }
1759 /* possible tidy operators that are not yet implemented, either because
1760 they are difficult, occur infrequently, or because I'm not sure that the
1761 output is tidier in all cases:
1762 duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1763 -> <div italic><para>abc</para></div>
1764 style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1765 -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1766 mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1767 -> <size 1><font a>abc</font>def</size>
1768 */
1770 /** Recursively walks the xml tree calling a set of cleanup operations on
1771 every child. Returns true if any changes were made to the tree.
1773 All the tidy operators return true if they made changes, and alter their
1774 parameter to point to the next object that should be processed, or NULL.
1775 They must not significantly alter (ie delete) any ancestor elements of the
1776 one they are passed.
1778 It may be that some of the later tidy operators that I wrote are actually
1779 general cases of the earlier operators, and hence the special-case-only
1780 versions can be removed. I haven't analysed my work in detail to figure
1781 out if this is so. */
1782 static bool tidy_xml_tree_recursively(SPObject *root)
1783 {
1784 static bool (* const tidy_operators[])(SPObject**) = {
1785 tidy_operator_empty_spans,
1786 tidy_operator_inexplicable_spans,
1787 tidy_operator_repeated_spans,
1788 tidy_operator_excessive_nesting,
1789 tidy_operator_redundant_double_nesting,
1790 tidy_operator_redundant_semi_nesting,
1791 tidy_operator_styled_whitespace
1792 };
1793 bool changes = false;
1795 for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1796 if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child) || SP_IS_TREF(child)) {
1797 child = SP_OBJECT_NEXT(child);
1798 continue;
1799 }
1800 if (child->hasChildren())
1801 changes |= tidy_xml_tree_recursively(child);
1803 unsigned i;
1804 for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1805 if (tidy_operators[i](&child)) {
1806 changes = true;
1807 break;
1808 }
1809 }
1810 if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1811 child = SP_OBJECT_NEXT(child);
1812 }
1813 return changes;
1814 }
1816 /** Applies the given CSS fragment to the characters of the given text or
1817 flowtext object between \a start and \a end, creating or removing span
1818 elements as necessary and optimal. */
1819 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1820 {
1821 // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1822 if (start == end) return;
1823 Inkscape::Text::Layout::iterator first, last;
1824 if (start < end) {
1825 first = start;
1826 last = end;
1827 } else {
1828 first = end;
1829 last = start;
1830 }
1831 Inkscape::Text::Layout const *layout = te_get_layout(text);
1832 SPObject *start_item = 0, *end_item = 0;
1833 void *rawptr = 0;
1834 Glib::ustring::iterator start_text_iter, end_text_iter;
1835 layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
1836 start_item = SP_OBJECT(rawptr);
1837 layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
1838 end_item = SP_OBJECT(rawptr);
1839 if (start_item == 0)
1840 return; // start is at end of text
1841 if (is_line_break_object(start_item))
1842 start_item = SP_OBJECT_NEXT(start_item);
1843 if (is_line_break_object(end_item))
1844 end_item = SP_OBJECT_NEXT(end_item);
1845 if (end_item == 0) end_item = text;
1848 /* Special case: With a tref, we only want to change its style when the whole
1849 * string is selected, in which case the style can be applied directly to the
1850 * tref node. If only part of the tref's string child is selected, just return. */
1852 if (!sp_tref_fully_contained(start_item, start_text_iter, end_item, end_text_iter)) {
1854 return;
1855 }
1857 /* stage 1: applying the style. Go up to the closest common ancestor of
1858 start and end and then semi-recursively apply the style to all the
1859 objects in between. The semi-recursion is because it's only necessary
1860 at the beginning and end; the style can just be applied to the root
1861 child in the middle.
1862 eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1863 The recursion may involve creating new spans.
1864 */
1865 SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1867 // bug #168370 (consider parent transform and viewBox)
1868 // snipplet copied from desktop-style.cpp sp_desktop_apply_css_recursive(...)
1869 SPCSSAttr *css_set = sp_repr_css_attr_new();
1870 sp_repr_css_merge(css_set, (SPCSSAttr*) css);
1871 {
1872 Geom::Matrix const local(sp_item_i2doc_affine(SP_ITEM(common_ancestor)));
1873 double const ex(local.descrim());
1874 if ( ( ex != 0. )
1875 && ( ex != 1. ) ) {
1876 sp_css_attr_scale(css_set, 1/ex);
1877 }
1878 }
1880 start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1881 end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1882 recursively_apply_style(common_ancestor, css_set, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1883 sp_repr_css_attr_unref(css_set);
1885 /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1886 /* discussion: this stage requires a certain level of inventiveness because
1887 it's not clear what the best representation is in many cases. An ideal
1888 implementation would provide some sort of scoring function to rate the
1889 ugliness of a given xml tree and try to reduce said function, but providing
1890 the various possibilities to be rated is non-trivial. Instead, I have opted
1891 for a multi-pass technique which simply recognises known-ugly patterns and
1892 has matching routines for optimising the patterns it finds. It's reasonably
1893 easy to add new pattern matching processors. If everything gets disastrous
1894 and neither option can be made to work, a fallback could be to reduce
1895 everything to a single level of nesting and drop all pretence of
1896 roundtrippability. */
1897 while (tidy_xml_tree_recursively(common_ancestor)){};
1899 // if we only modified subobjects this won't have been automatically sent
1900 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1901 }
1903 bool is_part_of_text_subtree (SPObject *obj)
1904 {
1905 return (SP_IS_TSPAN(obj)
1906 || SP_IS_TEXT(obj)
1907 || SP_IS_FLOWTEXT(obj)
1908 || SP_IS_FLOWTSPAN(obj)
1909 || SP_IS_FLOWDIV(obj)
1910 || SP_IS_FLOWPARA(obj)
1911 || SP_IS_FLOWLINE(obj)
1912 || SP_IS_FLOWREGIONBREAK(obj));
1913 }
1915 bool is_top_level_text_object (SPObject *obj)
1916 {
1917 return (SP_IS_TEXT(obj)
1918 || SP_IS_FLOWTEXT(obj));
1919 }
1921 bool has_visible_text (SPObject *obj)
1922 {
1923 if (SP_IS_STRING(obj) && !SP_STRING(obj)->string.empty())
1924 return true; // maybe we should also check that it's not all whitespace?
1926 for (SPObject const *child = obj->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
1927 if (has_visible_text((SPObject *) child))
1928 return true;
1929 }
1931 return false;
1932 }
1934 /*
1935 Local Variables:
1936 mode:c++
1937 c-file-style:"stroustrup"
1938 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1939 indent-tabs-mode:nil
1940 fill-column:99
1941 End:
1942 */
1943 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :