1 /*
2 * SVG <text> and <tspan> implementation
3 *
4 * Author:
5 * Lauris Kaplinski <lauris@kaplinski.com>
6 * bulia byak <buliabyak@users.sf.net>
7 * Jon A. Cruz <jon@joncruz.org>
8 * Abhishek Sharma
9 *
10 * Copyright (C) 1999-2002 Lauris Kaplinski
11 * Copyright (C) 2000-2001 Ximian, Inc.
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 /*
17 * fixme:
18 *
19 * These subcomponents should not be items, or alternately
20 * we have to invent set of flags to mark, whether standard
21 * attributes are applicable to given item (I even like this
22 * idea somewhat - Lauris)
23 *
24 */
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
30 #include <2geom/matrix.h>
31 #include <libnr/nr-matrix-fns.h>
32 #include <libnrtype/FontFactory.h>
33 #include <libnrtype/font-instance.h>
34 #include <libnrtype/font-style-to-pos.h>
36 #include <glibmm/i18n.h>
37 #include "svg/svg.h"
38 #include "svg/stringstream.h"
39 #include "display/nr-arena-glyphs.h"
40 #include "attributes.h"
41 #include "document.h"
42 #include "desktop-handles.h"
43 #include "sp-namedview.h"
44 #include "style.h"
45 #include "inkscape.h"
46 #include "sp-metrics.h"
47 #include "xml/quote.h"
48 #include "xml/repr.h"
49 #include "mod360.h"
50 #include "sp-title.h"
51 #include "sp-desc.h"
53 #include "sp-textpath.h"
54 #include "sp-tref.h"
55 #include "sp-tspan.h"
57 #include "text-editing.h"
59 /*#####################################################
60 # SPTEXT
61 #####################################################*/
63 static void sp_text_class_init (SPTextClass *classname);
64 static void sp_text_init (SPText *text);
65 static void sp_text_release (SPObject *object);
67 static void sp_text_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
68 static void sp_text_set (SPObject *object, unsigned key, gchar const *value);
69 static void sp_text_child_added (SPObject *object, Inkscape::XML::Node *rch, Inkscape::XML::Node *ref);
70 static void sp_text_remove_child (SPObject *object, Inkscape::XML::Node *rch);
71 static void sp_text_update (SPObject *object, SPCtx *ctx, guint flags);
72 static void sp_text_modified (SPObject *object, guint flags);
73 static Inkscape::XML::Node *sp_text_write (SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
75 static void sp_text_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const flags);
76 static NRArenaItem *sp_text_show (SPItem *item, NRArena *arena, unsigned key, unsigned flags);
77 static void sp_text_hide (SPItem *item, unsigned key);
78 static char *sp_text_description (SPItem *item);
79 static void sp_text_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs);
80 static Geom::Matrix sp_text_set_transform(SPItem *item, Geom::Matrix const &xform);
81 static void sp_text_print (SPItem *item, SPPrintContext *gpc);
83 static SPItemClass *text_parent_class;
85 GType
86 sp_text_get_type ()
87 {
88 static GType type = 0;
89 if (!type) {
90 GTypeInfo info = {
91 sizeof (SPTextClass),
92 NULL, /* base_init */
93 NULL, /* base_finalize */
94 (GClassInitFunc) sp_text_class_init,
95 NULL, /* class_finalize */
96 NULL, /* class_data */
97 sizeof (SPText),
98 16, /* n_preallocs */
99 (GInstanceInitFunc) sp_text_init,
100 NULL, /* value_table */
101 };
102 type = g_type_register_static (SP_TYPE_ITEM, "SPText", &info, (GTypeFlags)0);
103 }
104 return type;
105 }
107 static void
108 sp_text_class_init (SPTextClass *classname)
109 {
110 SPObjectClass *sp_object_class = (SPObjectClass *) classname;
111 SPItemClass *item_class = (SPItemClass *) classname;
113 text_parent_class = (SPItemClass*)g_type_class_ref (SP_TYPE_ITEM);
115 sp_object_class->release = sp_text_release;
116 sp_object_class->build = sp_text_build;
117 sp_object_class->set = sp_text_set;
118 sp_object_class->child_added = sp_text_child_added;
119 sp_object_class->remove_child = sp_text_remove_child;
120 sp_object_class->update = sp_text_update;
121 sp_object_class->modified = sp_text_modified;
122 sp_object_class->write = sp_text_write;
124 item_class->bbox = sp_text_bbox;
125 item_class->show = sp_text_show;
126 item_class->hide = sp_text_hide;
127 item_class->description = sp_text_description;
128 item_class->snappoints = sp_text_snappoints;
129 item_class->set_transform = sp_text_set_transform;
130 item_class->print = sp_text_print;
131 }
133 static void
134 sp_text_init (SPText *text)
135 {
136 new (&text->layout) Inkscape::Text::Layout;
137 new (&text->attributes) TextTagAttributes;
138 }
140 static void
141 sp_text_release (SPObject *object)
142 {
143 SPText *text = SP_TEXT(object);
144 text->attributes.~TextTagAttributes();
145 text->layout.~Layout();
147 if (((SPObjectClass *) text_parent_class)->release)
148 ((SPObjectClass *) text_parent_class)->release(object);
149 }
151 static void
152 sp_text_build (SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr)
153 {
154 object->readAttr( "x" );
155 object->readAttr( "y" );
156 object->readAttr( "dx" );
157 object->readAttr( "dy" );
158 object->readAttr( "rotate" );
160 if (((SPObjectClass *) text_parent_class)->build)
161 ((SPObjectClass *) text_parent_class)->build(object, doc, repr);
163 object->readAttr( "sodipodi:linespacing" ); // has to happen after the styles are read
164 }
166 static void
167 sp_text_set(SPObject *object, unsigned key, gchar const *value)
168 {
169 SPText *text = SP_TEXT (object);
171 if (text->attributes.readSingleAttribute(key, value)) {
172 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
173 } else {
174 switch (key) {
175 case SP_ATTR_SODIPODI_LINESPACING:
176 // convert deprecated tag to css
177 if (value) {
178 text->style->line_height.set = TRUE;
179 text->style->line_height.inherit = FALSE;
180 text->style->line_height.normal = FALSE;
181 text->style->line_height.unit = SP_CSS_UNIT_PERCENT;
182 text->style->line_height.value = text->style->line_height.computed = sp_svg_read_percentage (value, 1.0);
183 }
184 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
185 break;
186 default:
187 if (((SPObjectClass *) text_parent_class)->set)
188 ((SPObjectClass *) text_parent_class)->set (object, key, value);
189 break;
190 }
191 }
192 }
194 static void
195 sp_text_child_added (SPObject *object, Inkscape::XML::Node *rch, Inkscape::XML::Node *ref)
196 {
197 SPText *text = SP_TEXT (object);
199 if (((SPObjectClass *) text_parent_class)->child_added)
200 ((SPObjectClass *) text_parent_class)->child_added (object, rch, ref);
202 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_CONTENT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
203 }
205 static void
206 sp_text_remove_child (SPObject *object, Inkscape::XML::Node *rch)
207 {
208 SPText *text = SP_TEXT (object);
210 if (((SPObjectClass *) text_parent_class)->remove_child)
211 ((SPObjectClass *) text_parent_class)->remove_child (object, rch);
213 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_CONTENT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
214 }
216 static void sp_text_update(SPObject *object, SPCtx *ctx, guint flags)
217 {
218 SPText *text = SP_TEXT (object);
220 if (((SPObjectClass *) text_parent_class)->update)
221 ((SPObjectClass *) text_parent_class)->update (object, ctx, flags);
223 guint cflags = (flags & SP_OBJECT_MODIFIED_CASCADE);
224 if (flags & SP_OBJECT_MODIFIED_FLAG) cflags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
227 // Create temporary list of children
228 GSList *l = NULL;
229 for (SPObject *child = object->firstChild() ; child ; child = child->getNext() ) {
230 sp_object_ref (SP_OBJECT (child), object);
231 l = g_slist_prepend (l, child);
232 }
233 l = g_slist_reverse (l);
234 while (l) {
235 SPObject *child = SP_OBJECT (l->data);
236 l = g_slist_remove (l, child);
237 if (cflags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
238 /* fixme: Do we need transform? */
239 child->updateDisplay(ctx, cflags);
240 }
241 sp_object_unref (SP_OBJECT (child), object);
242 }
243 if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG |
244 SP_OBJECT_CHILD_MODIFIED_FLAG |
245 SP_TEXT_LAYOUT_MODIFIED_FLAG ) )
246 {
247 /* fixme: It is not nice to have it here, but otherwise children content changes does not work */
248 /* fixme: Even now it may not work, as we are delayed */
249 /* fixme: So check modification flag everywhere immediate state is used */
250 text->rebuildLayout();
252 NRRect paintbox;
253 text->invoke_bbox( &paintbox, Geom::identity(), TRUE);
254 for (SPItemView* v = text->display; v != NULL; v = v->next) {
255 text->_clearFlow(NR_ARENA_GROUP(v->arenaitem));
256 nr_arena_group_set_style(NR_ARENA_GROUP(v->arenaitem), SP_OBJECT_STYLE(object));
257 // pass the bbox of the text object as paintbox (used for paintserver fills)
258 text->layout.show(NR_ARENA_GROUP(v->arenaitem), &paintbox);
259 }
260 }
261 }
263 static void sp_text_modified(SPObject *object, guint flags)
264 {
265 if (((SPObjectClass *) text_parent_class)->modified) {
266 ((SPObjectClass *) text_parent_class)->modified (object, flags);
267 }
269 guint cflags = (flags & SP_OBJECT_MODIFIED_CASCADE);
270 if (flags & SP_OBJECT_MODIFIED_FLAG) {
271 cflags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
272 }
274 // FIXME: all that we need to do here is nr_arena_glyphs_[group_]set_style, to set the changed
275 // style, but there's no easy way to access the arena glyphs or glyph groups corresponding to a
276 // text object. Therefore we do here the same as in _update, that is, destroy all arena items
277 // and create new ones. This is probably quite wasteful.
278 if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG )) {
279 SPText *text = SP_TEXT (object);
280 NRRect paintbox;
281 text->invoke_bbox( &paintbox, Geom::identity(), TRUE);
282 for (SPItemView* v = text->display; v != NULL; v = v->next) {
283 text->_clearFlow(NR_ARENA_GROUP(v->arenaitem));
284 nr_arena_group_set_style(NR_ARENA_GROUP(v->arenaitem), SP_OBJECT_STYLE(object));
285 text->layout.show(NR_ARENA_GROUP(v->arenaitem), &paintbox);
286 }
287 }
289 // Create temporary list of children
290 GSList *l = NULL;
291 for (SPObject *child = object->firstChild() ; child ; child = child->getNext() ) {
292 sp_object_ref (SP_OBJECT (child), object);
293 l = g_slist_prepend (l, child);
294 }
295 l = g_slist_reverse (l);
296 while (l) {
297 SPObject *child = SP_OBJECT (l->data);
298 l = g_slist_remove (l, child);
299 if (cflags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
300 child->emitModified(cflags);
301 }
302 sp_object_unref (SP_OBJECT (child), object);
303 }
304 }
306 static Inkscape::XML::Node *sp_text_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
307 {
308 SPText *text = SP_TEXT (object);
310 if (flags & SP_OBJECT_WRITE_BUILD) {
311 if (!repr) {
312 repr = xml_doc->createElement("svg:text");
313 }
314 GSList *l = NULL;
315 for (SPObject *child = object->firstChild() ; child ; child = child->getNext() ) {
316 if (SP_IS_TITLE(child) || SP_IS_DESC(child)) {
317 continue;
318 }
319 Inkscape::XML::Node *crepr = NULL;
320 if (SP_IS_STRING(child)) {
321 crepr = xml_doc->createTextNode(SP_STRING(child)->string.c_str());
322 } else {
323 crepr = child->updateRepr(xml_doc, NULL, flags);
324 }
325 if (crepr) {
326 l = g_slist_prepend (l, crepr);
327 }
328 }
329 while (l) {
330 repr->addChild((Inkscape::XML::Node *) l->data, NULL);
331 Inkscape::GC::release((Inkscape::XML::Node *) l->data);
332 l = g_slist_remove (l, l->data);
333 }
334 } else {
335 for (SPObject *child = object->firstChild() ; child ; child = child->getNext() ) {
336 if (SP_IS_TITLE(child) || SP_IS_DESC(child)) {
337 continue;
338 }
339 if (SP_IS_STRING(child)) {
340 SP_OBJECT_REPR(child)->setContent(SP_STRING(child)->string.c_str());
341 } else {
342 child->updateRepr(flags);
343 }
344 }
345 }
347 text->attributes.writeTo(repr);
349 // deprecated attribute, but keep it around for backwards compatibility
350 if (text->style->line_height.set && !text->style->line_height.inherit && !text->style->line_height.normal && text->style->line_height.unit == SP_CSS_UNIT_PERCENT) {
351 Inkscape::SVGOStringStream os;
352 os << (text->style->line_height.value * 100.0) << "%";
353 SP_OBJECT_REPR(text)->setAttribute("sodipodi:linespacing", os.str().c_str());
354 } else {
355 SP_OBJECT_REPR(text)->setAttribute("sodipodi:linespacing", NULL);
356 }
358 if (((SPObjectClass *) (text_parent_class))->write) {
359 ((SPObjectClass *) (text_parent_class))->write (object, xml_doc, repr, flags);
360 }
362 return repr;
363 }
365 static void
366 sp_text_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const /*flags*/)
367 {
368 SP_TEXT(item)->layout.getBoundingBox(bbox, transform);
370 // Add stroke width
371 SPStyle* style=SP_OBJECT_STYLE (item);
372 if (!style->stroke.isNone()) {
373 double const scale = transform.descrim();
374 if ( fabs(style->stroke_width.computed * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord
375 double const width = MAX(0.125, style->stroke_width.computed * scale);
376 if ( fabs(bbox->x1 - bbox->x0) > -0.00001 && fabs(bbox->y1 - bbox->y0) > -0.00001 ) {
377 bbox->x0-=0.5*width;
378 bbox->x1+=0.5*width;
379 bbox->y0-=0.5*width;
380 bbox->y1+=0.5*width;
381 }
382 }
383 }
384 }
387 static NRArenaItem *
388 sp_text_show(SPItem *item, NRArena *arena, unsigned /* key*/, unsigned /*flags*/)
389 {
390 SPText *group = (SPText *) item;
392 NRArenaGroup *flowed = NRArenaGroup::create(arena);
393 nr_arena_group_set_transparent (flowed, FALSE);
395 nr_arena_group_set_style(flowed, group->style);
397 // pass the bbox of the text object as paintbox (used for paintserver fills)
398 NRRect paintbox;
399 item->invoke_bbox( &paintbox, Geom::identity(), TRUE);
400 group->layout.show(flowed, &paintbox);
402 return flowed;
403 }
405 static void
406 sp_text_hide(SPItem *item, unsigned key)
407 {
408 if (((SPItemClass *) text_parent_class)->hide)
409 ((SPItemClass *) text_parent_class)->hide (item, key);
410 }
412 static char *
413 sp_text_description(SPItem *item)
414 {
415 SPText *text = (SPText *) item;
416 SPStyle *style = SP_OBJECT_STYLE(text);
418 font_instance *tf = font_factory::Default()->FaceFromStyle(style);
420 char name_buf[256];
421 char *n;
422 if (tf) {
423 tf->Name(name_buf, sizeof(name_buf));
424 n = xml_quote_strdup(name_buf);
425 tf->Unref();
426 } else {
427 /* TRANSLATORS: For description of font with no name. */
428 n = g_strdup(_("<no name found>"));
429 }
431 GString *xs = SP_PX_TO_METRIC_STRING(style->font_size.computed, sp_desktop_namedview(SP_ACTIVE_DESKTOP)->getDefaultMetric());
433 char const *trunc = "";
434 Inkscape::Text::Layout const *layout = te_get_layout((SPItem *) item);
435 if (layout && layout->inputTruncated()) {
436 trunc = _(" [truncated]");
437 }
439 char *ret = ( SP_IS_TEXT_TEXTPATH(item)
440 ? g_strdup_printf(_("<b>Text on path</b>%s (%s, %s)"), trunc, n, xs->str)
441 : g_strdup_printf(_("<b>Text</b>%s (%s, %s)"), trunc, n, xs->str) );
442 g_free(n);
443 return ret;
444 }
446 static void sp_text_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/)
447 {
448 // Choose a point on the baseline for snapping from or to, with the horizontal position
449 // of this point depending on the text alignment (left vs. right)
450 Inkscape::Text::Layout const *layout = te_get_layout((SPItem *) item);
451 if (layout != NULL && layout->outputExists()) {
452 boost::optional<Geom::Point> pt = layout->baselineAnchorPoint();
453 if (pt) {
454 p.push_back(Inkscape::SnapCandidatePoint((*pt) * item->i2d_affine(), Inkscape::SNAPSOURCE_TEXT_BASELINE, Inkscape::SNAPTARGET_TEXT_BASELINE));
455 }
456 }
457 }
459 static Geom::Matrix
460 sp_text_set_transform (SPItem *item, Geom::Matrix const &xform)
461 {
462 SPText *text = SP_TEXT(item);
464 // we cannot optimize textpath because changing its fontsize will break its match to the path
465 if (SP_IS_TEXT_TEXTPATH (text))
466 return xform;
468 /* This function takes care of scaling & translation only, we return whatever parts we can't
469 handle. */
471 // TODO: pjrm tried to use fontsize_expansion(xform) here and it works for text in that font size
472 // is scaled more intuitively when scaling non-uniformly; however this necessitated using
473 // fontsize_expansion instead of expansion in other places too, where it was not appropriate
474 // (e.g. it broke stroke width on copy/pasting of style from horizontally stretched to vertically
475 // stretched shape). Using fontsize_expansion only here broke setting the style via font
476 // dialog. This needs to be investigated further.
477 double const ex = xform.descrim();
478 if (ex == 0) {
479 return xform;
480 }
482 Geom::Matrix ret(Geom::Matrix(xform).without_translation());
483 ret[0] /= ex;
484 ret[1] /= ex;
485 ret[2] /= ex;
486 ret[3] /= ex;
488 // Adjust x/y, dx/dy
489 text->_adjustCoordsRecursive (item, xform * ret.inverse(), ex);
491 // Adjust font size
492 text->_adjustFontsizeRecursive (item, ex);
494 // Adjust stroke width
495 item->adjust_stroke_width_recursive (ex);
497 // Adjust pattern fill
498 item->adjust_pattern(xform * ret.inverse());
500 // Adjust gradient fill
501 item->adjust_gradient(xform * ret.inverse());
503 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
505 return ret;
506 }
508 static void
509 sp_text_print (SPItem *item, SPPrintContext *ctx)
510 {
511 NRRect pbox, dbox, bbox;
512 SPText *group = SP_TEXT (item);
514 item->invoke_bbox( &pbox, Geom::identity(), TRUE);
515 item->getBboxDesktop (&bbox);
516 dbox.x0 = 0.0;
517 dbox.y0 = 0.0;
518 dbox.x1 = SP_OBJECT_DOCUMENT (item)->getWidth ();
519 dbox.y1 = SP_OBJECT_DOCUMENT (item)->getHeight ();
520 Geom::Matrix const ctm (item->i2d_affine());
522 group->layout.print(ctx,&pbox,&dbox,&bbox,ctm);
523 }
525 /*
526 * Member functions
527 */
529 unsigned SPText::_buildLayoutInput(SPObject *root, Inkscape::Text::Layout::OptionalTextTagAttrs const &parent_optional_attrs, unsigned parent_attrs_offset, bool in_textpath)
530 {
531 unsigned length = 0;
532 int child_attrs_offset = 0;
533 Inkscape::Text::Layout::OptionalTextTagAttrs optional_attrs;
535 if (SP_IS_TEXT(root)) {
536 SP_TEXT(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true);
537 }
538 else if (SP_IS_TSPAN(root)) {
539 SPTSpan *tspan = SP_TSPAN(root);
540 // x, y attributes are stripped from some tspans marked with role="line" as we do our own line layout.
541 // This should be checked carefully, as it can undo line layout in imported SVG files.
542 bool use_xy = !in_textpath && (tspan->role == SP_TSPAN_ROLE_UNSPECIFIED || !tspan->attributes.singleXYCoordinates());
543 tspan->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, use_xy, true);
544 }
545 else if (SP_IS_TREF(root)) {
546 SP_TREF(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true);
547 }
548 else if (SP_IS_TEXTPATH(root)) {
549 in_textpath = true;
550 SP_TEXTPATH(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, false, true);
551 optional_attrs.x.clear();
552 optional_attrs.y.clear();
553 }
554 else {
555 optional_attrs = parent_optional_attrs;
556 child_attrs_offset = parent_attrs_offset;
557 }
559 if (SP_IS_TSPAN(root))
560 if (SP_TSPAN(root)->role != SP_TSPAN_ROLE_UNSPECIFIED) {
561 // we need to allow the first line not to have role=line, but still set the source_cookie to the right value
562 SPObject *prev_object = root->getPrev();
563 if (prev_object && SP_IS_TSPAN(prev_object)) {
564 if (!layout.inputExists()) {
565 layout.appendText("", prev_object->style, prev_object, &optional_attrs);
566 }
567 layout.appendControlCode(Inkscape::Text::Layout::PARAGRAPH_BREAK, prev_object);
568 }
569 if (!root->hasChildren()) {
570 layout.appendText("", root->style, root, &optional_attrs);
571 }
572 length++; // interpreting line breaks as a character for the purposes of x/y/etc attributes
573 // is a liberal interpretation of the svg spec, but a strict reading would mean
574 // that if the first line is empty the second line would take its place at the
575 // start position. Very confusing.
576 child_attrs_offset--;
577 }
579 for (SPObject *child = root->firstChild() ; child ; child = child->getNext() ) {
580 if (SP_IS_STRING(child)) {
581 Glib::ustring const &string = SP_STRING(child)->string;
582 layout.appendText(string, root->style, child, &optional_attrs, child_attrs_offset + length);
583 length += string.length();
584 } /*XML Tree being directly used here while it shouldn't be.*/ else if (!sp_repr_is_meta_element(child->getRepr())) {
585 length += _buildLayoutInput(child, optional_attrs, child_attrs_offset + length, in_textpath);
586 }
587 }
589 return length;
590 }
592 void SPText::rebuildLayout()
593 {
594 layout.clear();
595 Inkscape::Text::Layout::OptionalTextTagAttrs optional_attrs;
596 _buildLayoutInput(this, optional_attrs, 0, false);
597 layout.calculateFlow();
598 for (SPObject *child = firstChild() ; child ; child = child->getNext() ) {
599 if (SP_IS_TEXTPATH(child)) {
600 SPTextPath const *textpath = SP_TEXTPATH(child);
601 if (textpath->originalPath != NULL) {
602 //g_print(layout.dumpAsText().c_str());
603 layout.fitToPathAlign(textpath->startOffset, *textpath->originalPath);
604 }
605 }
606 }
607 //g_print(layout.dumpAsText().c_str());
609 // set the x,y attributes on role:line spans
610 for (SPObject *child = firstChild() ; child ; child = child->getNext() ) {
611 if (SP_IS_TSPAN(child)) {
612 SPTSpan *tspan = SP_TSPAN(child);
613 if ( (tspan->role != SP_TSPAN_ROLE_UNSPECIFIED)
614 && tspan->attributes.singleXYCoordinates() ) {
615 Inkscape::Text::Layout::iterator iter = layout.sourceToIterator(tspan);
616 Geom::Point anchor_point = layout.chunkAnchorPoint(iter);
617 tspan->attributes.setFirstXY(anchor_point);
618 }
619 }
620 }
621 }
624 void SPText::_adjustFontsizeRecursive(SPItem *item, double ex, bool is_root)
625 {
626 SPStyle *style = SP_OBJECT_STYLE (item);
628 if (style && !NR_DF_TEST_CLOSE (ex, 1.0, NR_EPSILON)) {
629 if (!style->font_size.set && is_root) {
630 style->font_size.set = 1;
631 }
632 style->font_size.type = SP_FONT_SIZE_LENGTH;
633 style->font_size.computed *= ex;
634 style->letter_spacing.computed *= ex;
635 style->word_spacing.computed *= ex;
636 item->updateRepr();
637 }
639 for (SPObject *o = item->children; o != NULL; o = o->next) {
640 if (SP_IS_ITEM(o))
641 _adjustFontsizeRecursive(SP_ITEM(o), ex, false);
642 }
643 }
645 void SPText::_adjustCoordsRecursive(SPItem *item, Geom::Matrix const &m, double ex, bool is_root)
646 {
647 if (SP_IS_TSPAN(item))
648 SP_TSPAN(item)->attributes.transform(m, ex, ex, is_root);
649 // it doesn't matter if we change the x,y for role=line spans because we'll just overwrite them anyway
650 else if (SP_IS_TEXT(item))
651 SP_TEXT(item)->attributes.transform(m, ex, ex, is_root);
652 else if (SP_IS_TEXTPATH(item))
653 SP_TEXTPATH(item)->attributes.transform(m, ex, ex, is_root);
654 else if (SP_IS_TREF(item)) {
655 SP_TREF(item)->attributes.transform(m, ex, ex, is_root);
656 }
658 for (SPObject *o = item->children; o != NULL; o = o->next) {
659 if (SP_IS_ITEM(o))
660 _adjustCoordsRecursive(SP_ITEM(o), m, ex, false);
661 }
662 }
665 void SPText::_clearFlow(NRArenaGroup *in_arena)
666 {
667 nr_arena_item_request_render (in_arena);
668 for (NRArenaItem *child = in_arena->children; child != NULL; ) {
669 NRArenaItem *nchild = child->next;
671 nr_arena_glyphs_group_clear(NR_ARENA_GLYPHS_GROUP(child));
672 nr_arena_item_remove_child (in_arena, child);
674 child=nchild;
675 }
676 }
679 /*
680 * TextTagAttributes implementation
681 */
683 void TextTagAttributes::readFrom(Inkscape::XML::Node const *node)
684 {
685 readSingleAttribute(SP_ATTR_X, node->attribute("x"));
686 readSingleAttribute(SP_ATTR_Y, node->attribute("y"));
687 readSingleAttribute(SP_ATTR_DX, node->attribute("dx"));
688 readSingleAttribute(SP_ATTR_DY, node->attribute("dy"));
689 readSingleAttribute(SP_ATTR_ROTATE, node->attribute("rotate"));
690 }
692 bool TextTagAttributes::readSingleAttribute(unsigned key, gchar const *value)
693 {
694 std::vector<SVGLength> *attr_vector;
695 switch (key) {
696 case SP_ATTR_X: attr_vector = &attributes.x; break;
697 case SP_ATTR_Y: attr_vector = &attributes.y; break;
698 case SP_ATTR_DX: attr_vector = &attributes.dx; break;
699 case SP_ATTR_DY: attr_vector = &attributes.dy; break;
700 case SP_ATTR_ROTATE: attr_vector = &attributes.rotate; break;
701 default: return false;
702 }
704 // FIXME: sp_svg_length_list_read() amalgamates repeated separators. This prevents unset values.
705 *attr_vector = sp_svg_length_list_read(value);
706 return true;
707 }
709 void TextTagAttributes::writeTo(Inkscape::XML::Node *node) const
710 {
711 writeSingleAttribute(node, "x", attributes.x);
712 writeSingleAttribute(node, "y", attributes.y);
713 writeSingleAttribute(node, "dx", attributes.dx);
714 writeSingleAttribute(node, "dy", attributes.dy);
715 writeSingleAttribute(node, "rotate", attributes.rotate);
716 }
718 void TextTagAttributes::writeSingleAttribute(Inkscape::XML::Node *node, gchar const *key, std::vector<SVGLength> const &attr_vector)
719 {
720 if (attr_vector.empty())
721 node->setAttribute(key, NULL);
722 else {
723 Glib::ustring string;
724 gchar single_value_string[32];
726 // FIXME: this has no concept of unset values because sp_svg_length_list_read() can't read them back in
727 for (std::vector<SVGLength>::const_iterator it = attr_vector.begin() ; it != attr_vector.end() ; it++) {
728 g_ascii_formatd(single_value_string, sizeof (single_value_string), "%.8g", it->computed);
729 if (!string.empty()) string += ' ';
730 string += single_value_string;
731 }
732 node->setAttribute(key, string.c_str());
733 }
734 }
736 bool TextTagAttributes::singleXYCoordinates() const
737 {
738 return attributes.x.size() <= 1 && attributes.y.size() <= 1;
739 }
741 bool TextTagAttributes::anyAttributesSet() const
742 {
743 return !attributes.x.empty() || !attributes.y.empty() || !attributes.dx.empty() || !attributes.dy.empty() || !attributes.rotate.empty();
744 }
746 Geom::Point TextTagAttributes::firstXY() const
747 {
748 Geom::Point point;
749 if (attributes.x.empty()) point[Geom::X] = 0.0;
750 else point[Geom::X] = attributes.x[0].computed;
751 if (attributes.y.empty()) point[Geom::Y] = 0.0;
752 else point[Geom::Y] = attributes.y[0].computed;
753 return point;
754 }
756 void TextTagAttributes::setFirstXY(Geom::Point &point)
757 {
758 SVGLength zero_length;
759 zero_length = 0.0;
761 if (attributes.x.empty())
762 attributes.x.resize(1, zero_length);
763 if (attributes.y.empty())
764 attributes.y.resize(1, zero_length);
765 attributes.x[0].computed = point[Geom::X];
766 attributes.y[0].computed = point[Geom::Y];
767 }
769 void TextTagAttributes::mergeInto(Inkscape::Text::Layout::OptionalTextTagAttrs *output, Inkscape::Text::Layout::OptionalTextTagAttrs const &parent_attrs, unsigned parent_attrs_offset, bool copy_xy, bool copy_dxdyrotate) const
770 {
771 mergeSingleAttribute(&output->x, parent_attrs.x, parent_attrs_offset, copy_xy ? &attributes.x : NULL);
772 mergeSingleAttribute(&output->y, parent_attrs.y, parent_attrs_offset, copy_xy ? &attributes.y : NULL);
773 mergeSingleAttribute(&output->dx, parent_attrs.dx, parent_attrs_offset, copy_dxdyrotate ? &attributes.dx : NULL);
774 mergeSingleAttribute(&output->dy, parent_attrs.dy, parent_attrs_offset, copy_dxdyrotate ? &attributes.dy : NULL);
775 mergeSingleAttribute(&output->rotate, parent_attrs.rotate, parent_attrs_offset, copy_dxdyrotate ? &attributes.rotate : NULL);
776 }
778 void TextTagAttributes::mergeSingleAttribute(std::vector<SVGLength> *output_list, std::vector<SVGLength> const &parent_list, unsigned parent_offset, std::vector<SVGLength> const *overlay_list)
779 {
780 output_list->clear();
781 if (overlay_list == NULL) {
782 if (parent_list.size() > parent_offset)
783 {
784 output_list->reserve(parent_list.size() - parent_offset);
785 std::copy(parent_list.begin() + parent_offset, parent_list.end(), std::back_inserter(*output_list));
786 }
787 } else {
788 output_list->reserve(std::max((int)parent_list.size() - (int)parent_offset, (int)overlay_list->size()));
789 unsigned overlay_offset = 0;
790 while (parent_offset < parent_list.size() || overlay_offset < overlay_list->size()) {
791 SVGLength const *this_item;
792 if (overlay_offset < overlay_list->size()) {
793 this_item = &(*overlay_list)[overlay_offset];
794 overlay_offset++;
795 parent_offset++;
796 } else {
797 this_item = &parent_list[parent_offset];
798 parent_offset++;
799 }
800 output_list->push_back(*this_item);
801 }
802 }
803 }
805 void TextTagAttributes::erase(unsigned start_index, unsigned n)
806 {
807 if (n == 0) return;
808 if (!singleXYCoordinates()) {
809 eraseSingleAttribute(&attributes.x, start_index, n);
810 eraseSingleAttribute(&attributes.y, start_index, n);
811 }
812 eraseSingleAttribute(&attributes.dx, start_index, n);
813 eraseSingleAttribute(&attributes.dy, start_index, n);
814 eraseSingleAttribute(&attributes.rotate, start_index, n);
815 }
817 void TextTagAttributes::eraseSingleAttribute(std::vector<SVGLength> *attr_vector, unsigned start_index, unsigned n)
818 {
819 if (attr_vector->size() <= start_index) return;
820 if (attr_vector->size() <= start_index + n)
821 attr_vector->erase(attr_vector->begin() + start_index, attr_vector->end());
822 else
823 attr_vector->erase(attr_vector->begin() + start_index, attr_vector->begin() + start_index + n);
824 }
826 void TextTagAttributes::insert(unsigned start_index, unsigned n)
827 {
828 if (n == 0) return;
829 if (!singleXYCoordinates()) {
830 insertSingleAttribute(&attributes.x, start_index, n, true);
831 insertSingleAttribute(&attributes.y, start_index, n, true);
832 }
833 insertSingleAttribute(&attributes.dx, start_index, n, false);
834 insertSingleAttribute(&attributes.dy, start_index, n, false);
835 insertSingleAttribute(&attributes.rotate, start_index, n, false);
836 }
838 void TextTagAttributes::insertSingleAttribute(std::vector<SVGLength> *attr_vector, unsigned start_index, unsigned n, bool is_xy)
839 {
840 if (attr_vector->size() <= start_index) return;
841 SVGLength zero_length;
842 zero_length = 0.0;
843 attr_vector->insert(attr_vector->begin() + start_index, n, zero_length);
844 if (is_xy) {
845 double begin = start_index == 0 ? (*attr_vector)[start_index + n].computed : (*attr_vector)[start_index - 1].computed;
846 double diff = ((*attr_vector)[start_index + n].computed - begin) / n; // n tested for nonzero in insert()
847 for (unsigned i = 0 ; i < n ; i++)
848 (*attr_vector)[start_index + i] = begin + diff * i;
849 }
850 }
852 void TextTagAttributes::split(unsigned index, TextTagAttributes *second)
853 {
854 if (!singleXYCoordinates()) {
855 splitSingleAttribute(&attributes.x, index, &second->attributes.x, false);
856 splitSingleAttribute(&attributes.y, index, &second->attributes.y, false);
857 }
858 splitSingleAttribute(&attributes.dx, index, &second->attributes.dx, true);
859 splitSingleAttribute(&attributes.dy, index, &second->attributes.dy, true);
860 splitSingleAttribute(&attributes.rotate, index, &second->attributes.rotate, true);
861 }
863 void TextTagAttributes::splitSingleAttribute(std::vector<SVGLength> *first_vector, unsigned index, std::vector<SVGLength> *second_vector, bool trimZeros)
864 {
865 second_vector->clear();
866 if (first_vector->size() <= index) return;
867 second_vector->resize(first_vector->size() - index);
868 std::copy(first_vector->begin() + index, first_vector->end(), second_vector->begin());
869 first_vector->resize(index);
870 if (trimZeros)
871 while (!first_vector->empty() && (!first_vector->back()._set || first_vector->back().value == 0.0))
872 first_vector->resize(first_vector->size() - 1);
873 }
875 void TextTagAttributes::join(TextTagAttributes const &first, TextTagAttributes const &second, unsigned second_index)
876 {
877 if (second.singleXYCoordinates()) {
878 attributes.x = first.attributes.x;
879 attributes.y = first.attributes.y;
880 } else {
881 joinSingleAttribute(&attributes.x, first.attributes.x, second.attributes.x, second_index);
882 joinSingleAttribute(&attributes.y, first.attributes.y, second.attributes.y, second_index);
883 }
884 joinSingleAttribute(&attributes.dx, first.attributes.dx, second.attributes.dx, second_index);
885 joinSingleAttribute(&attributes.dy, first.attributes.dy, second.attributes.dy, second_index);
886 joinSingleAttribute(&attributes.rotate, first.attributes.rotate, second.attributes.rotate, second_index);
887 }
889 void TextTagAttributes::joinSingleAttribute(std::vector<SVGLength> *dest_vector, std::vector<SVGLength> const &first_vector, std::vector<SVGLength> const &second_vector, unsigned second_index)
890 {
891 if (second_vector.empty())
892 *dest_vector = first_vector;
893 else {
894 dest_vector->resize(second_index + second_vector.size());
895 if (first_vector.size() < second_index) {
896 std::copy(first_vector.begin(), first_vector.end(), dest_vector->begin());
897 SVGLength zero_length;
898 zero_length = 0.0;
899 std::fill(dest_vector->begin() + first_vector.size(), dest_vector->begin() + second_index, zero_length);
900 } else
901 std::copy(first_vector.begin(), first_vector.begin() + second_index, dest_vector->begin());
902 std::copy(second_vector.begin(), second_vector.end(), dest_vector->begin() + second_index);
903 }
904 }
906 void TextTagAttributes::transform(Geom::Matrix const &matrix, double scale_x, double scale_y, bool extend_zero_length)
907 {
908 SVGLength zero_length;
909 zero_length = 0.0;
911 /* edge testcases for this code:
912 1) moving text elements whose position is done entirely with transform="...", no x,y attributes
913 2) unflowing multi-line flowtext then moving it (it has x but not y)
914 */
915 unsigned points_count = std::max(attributes.x.size(), attributes.y.size());
916 if (extend_zero_length && points_count < 1)
917 points_count = 1;
918 for (unsigned i = 0 ; i < points_count ; i++) {
919 Geom::Point point;
920 if (i < attributes.x.size()) point[Geom::X] = attributes.x[i].computed;
921 else point[Geom::X] = 0.0;
922 if (i < attributes.y.size()) point[Geom::Y] = attributes.y[i].computed;
923 else point[Geom::Y] = 0.0;
924 point *= matrix;
925 if (i < attributes.x.size())
926 attributes.x[i] = point[Geom::X];
927 else if (point[Geom::X] != 0.0 && extend_zero_length) {
928 attributes.x.resize(i + 1, zero_length);
929 attributes.x[i] = point[Geom::X];
930 }
931 if (i < attributes.y.size())
932 attributes.y[i] = point[Geom::Y];
933 else if (point[Geom::Y] != 0.0 && extend_zero_length) {
934 attributes.y.resize(i + 1, zero_length);
935 attributes.y[i] = point[Geom::Y];
936 }
937 }
938 for (std::vector<SVGLength>::iterator it = attributes.dx.begin() ; it != attributes.dx.end() ; it++)
939 *it = it->computed * scale_x;
940 for (std::vector<SVGLength>::iterator it = attributes.dy.begin() ; it != attributes.dy.end() ; it++)
941 *it = it->computed * scale_y;
942 }
944 double TextTagAttributes::getDx(unsigned index)
945 {
946 if( attributes.dx.size() == 0 ) {
947 return 0.0;
948 }
949 if( index < attributes.dx.size() ) {
950 return attributes.dx[index].computed;
951 } else {
952 return 0.0; // attributes.dx.back().computed;
953 }
954 }
957 double TextTagAttributes::getDy(unsigned index)
958 {
959 if( attributes.dy.size() == 0 ) {
960 return 0.0;
961 }
962 if( index < attributes.dy.size() ) {
963 return attributes.dy[index].computed;
964 } else {
965 return 0.0; // attributes.dy.back().computed;
966 }
967 }
970 void TextTagAttributes::addToDx(unsigned index, double delta)
971 {
972 SVGLength zero_length;
973 zero_length = 0.0;
975 if (attributes.dx.size() < index + 1) attributes.dx.resize(index + 1, zero_length);
976 attributes.dx[index] = attributes.dx[index].computed + delta;
977 }
979 void TextTagAttributes::addToDy(unsigned index, double delta)
980 {
981 SVGLength zero_length;
982 zero_length = 0.0;
984 if (attributes.dy.size() < index + 1) attributes.dy.resize(index + 1, zero_length);
985 attributes.dy[index] = attributes.dy[index].computed + delta;
986 }
988 void TextTagAttributes::addToDxDy(unsigned index, Geom::Point const &adjust)
989 {
990 SVGLength zero_length;
991 zero_length = 0.0;
993 if (adjust[Geom::X] != 0.0) {
994 if (attributes.dx.size() < index + 1) attributes.dx.resize(index + 1, zero_length);
995 attributes.dx[index] = attributes.dx[index].computed + adjust[Geom::X];
996 }
997 if (adjust[Geom::Y] != 0.0) {
998 if (attributes.dy.size() < index + 1) attributes.dy.resize(index + 1, zero_length);
999 attributes.dy[index] = attributes.dy[index].computed + adjust[Geom::Y];
1000 }
1001 }
1003 double TextTagAttributes::getRotate(unsigned index)
1004 {
1005 if( attributes.rotate.size() == 0 ) {
1006 return 0.0;
1007 }
1008 if( index < attributes.rotate.size() ) {
1009 return attributes.rotate[index].computed;
1010 } else {
1011 return attributes.rotate.back().computed;
1012 }
1013 }
1016 void TextTagAttributes::addToRotate(unsigned index, double delta)
1017 {
1018 SVGLength zero_length;
1019 zero_length = 0.0;
1021 if (attributes.rotate.size() < index + 2) {
1022 if (attributes.rotate.empty())
1023 attributes.rotate.resize(index + 2, zero_length);
1024 else
1025 attributes.rotate.resize(index + 2, attributes.rotate.back());
1026 }
1027 attributes.rotate[index] = mod360(attributes.rotate[index].computed + delta);
1028 }
1031 void TextTagAttributes::setRotate(unsigned index, double angle)
1032 {
1033 SVGLength zero_length;
1034 zero_length = 0.0;
1036 if (attributes.rotate.size() < index + 2) {
1037 if (attributes.rotate.empty())
1038 attributes.rotate.resize(index + 2, zero_length);
1039 else
1040 attributes.rotate.resize(index + 2, attributes.rotate.back());
1041 }
1042 attributes.rotate[index] = mod360(angle);
1043 }
1046 /*
1047 Local Variables:
1048 mode:c++
1049 c-file-style:"stroustrup"
1050 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1051 indent-tabs-mode:nil
1052 fill-column:99
1053 End:
1054 */
1055 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :