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