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 <cstring>
31 #include <string>
32 #include <glibmm/i18n.h>
34 #include <livarot/Path.h>
35 #include "svg/stringstream.h"
36 #include "attributes.h"
37 #include "sp-use-reference.h"
38 #include "sp-tspan.h"
39 #include "sp-tref.h"
40 #include "sp-textpath.h"
41 #include "text-editing.h"
42 #include "style.h"
43 #include "libnr/nr-matrix-fns.h"
44 #include "xml/repr.h"
45 #include "document.h"
48 /*#####################################################
49 # SPTSPAN
50 #####################################################*/
52 static void sp_tspan_class_init(SPTSpanClass *classname);
53 static void sp_tspan_init(SPTSpan *tspan);
55 static void sp_tspan_build(SPObject * object, SPDocument * document, Inkscape::XML::Node * repr);
56 static void sp_tspan_release(SPObject *object);
57 static void sp_tspan_set(SPObject *object, unsigned key, gchar const *value);
58 static void sp_tspan_update(SPObject *object, SPCtx *ctx, guint flags);
59 static void sp_tspan_modified(SPObject *object, unsigned flags);
60 static void sp_tspan_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const flags);
61 static Inkscape::XML::Node *sp_tspan_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
62 static char *sp_tspan_description (SPItem *item);
64 static SPItemClass *tspan_parent_class;
66 /**
67 *
68 */
69 GType
70 sp_tspan_get_type()
71 {
72 static GType type = 0;
73 if (!type) {
74 GTypeInfo info = {
75 sizeof(SPTSpanClass),
76 NULL, /* base_init */
77 NULL, /* base_finalize */
78 (GClassInitFunc) sp_tspan_class_init,
79 NULL, /* class_finalize */
80 NULL, /* class_data */
81 sizeof(SPTSpan),
82 16, /* n_preallocs */
83 (GInstanceInitFunc) sp_tspan_init,
84 NULL, /* value_table */
85 };
86 type = g_type_register_static(SP_TYPE_ITEM, "SPTSpan", &info, (GTypeFlags)0);
87 }
88 return type;
89 }
91 static void
92 sp_tspan_class_init(SPTSpanClass *classname)
93 {
94 SPObjectClass * sp_object_class;
95 SPItemClass * item_class;
97 sp_object_class = (SPObjectClass *) classname;
98 item_class = (SPItemClass *) classname;
100 tspan_parent_class = (SPItemClass*)g_type_class_ref(SP_TYPE_ITEM);
102 sp_object_class->build = sp_tspan_build;
103 sp_object_class->release = sp_tspan_release;
104 sp_object_class->set = sp_tspan_set;
105 sp_object_class->update = sp_tspan_update;
106 sp_object_class->modified = sp_tspan_modified;
107 sp_object_class->write = sp_tspan_write;
109 item_class->bbox = sp_tspan_bbox;
110 item_class->description = sp_tspan_description;
111 }
113 static void
114 sp_tspan_init(SPTSpan *tspan)
115 {
116 tspan->role = SP_TSPAN_ROLE_UNSPECIFIED;
117 new (&tspan->attributes) TextTagAttributes;
118 }
120 static void
121 sp_tspan_release(SPObject *object)
122 {
123 SPTSpan *tspan = SP_TSPAN(object);
125 tspan->attributes.~TextTagAttributes();
127 if (((SPObjectClass *) tspan_parent_class)->release)
128 ((SPObjectClass *) tspan_parent_class)->release(object);
129 }
131 static void
132 sp_tspan_build(SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr)
133 {
134 //SPTSpan *tspan = SP_TSPAN(object);
136 object->readAttr( "x" );
137 object->readAttr( "y" );
138 object->readAttr( "dx" );
139 object->readAttr( "dy" );
140 object->readAttr( "rotate" );
141 object->readAttr( "sodipodi:role" );
143 if (((SPObjectClass *) tspan_parent_class)->build)
144 ((SPObjectClass *) tspan_parent_class)->build(object, doc, repr);
145 }
147 static void
148 sp_tspan_set(SPObject *object, unsigned key, gchar const *value)
149 {
150 SPTSpan *tspan = SP_TSPAN(object);
152 if (tspan->attributes.readSingleAttribute(key, value)) {
153 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
154 } else {
155 switch (key) {
156 case SP_ATTR_SODIPODI_ROLE:
157 if (value && (!strcmp(value, "line") || !strcmp(value, "paragraph"))) {
158 tspan->role = SP_TSPAN_ROLE_LINE;
159 } else {
160 tspan->role = SP_TSPAN_ROLE_UNSPECIFIED;
161 }
162 break;
163 default:
164 if (((SPObjectClass *) tspan_parent_class)->set)
165 (((SPObjectClass *) tspan_parent_class)->set)(object, key, value);
166 break;
167 }
168 }
169 }
171 static void sp_tspan_update(SPObject *object, SPCtx *ctx, guint flags)
172 {
173 if (((SPObjectClass *) tspan_parent_class)->update) {
174 ((SPObjectClass *) tspan_parent_class)->update(object, ctx, flags);
175 }
177 if (flags & SP_OBJECT_MODIFIED_FLAG) {
178 flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
179 }
180 flags &= SP_OBJECT_MODIFIED_CASCADE;
182 for ( SPObject *ochild = object->firstChild() ; ochild ; ochild = ochild->getNext() ) {
183 if ( flags || ( ochild->uflags & SP_OBJECT_MODIFIED_FLAG )) {
184 ochild->updateDisplay(ctx, flags);
185 }
186 }
187 }
189 static void sp_tspan_modified(SPObject *object, unsigned flags)
190 {
191 if (((SPObjectClass *) tspan_parent_class)->modified) {
192 ((SPObjectClass *) tspan_parent_class)->modified(object, flags);
193 }
195 if (flags & SP_OBJECT_MODIFIED_FLAG) {
196 flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
197 }
198 flags &= SP_OBJECT_MODIFIED_CASCADE;
200 for ( SPObject *ochild = object->firstChild() ; ochild ; ochild = ochild->getNext() ) {
201 if (flags || (ochild->mflags & SP_OBJECT_MODIFIED_FLAG)) {
202 ochild->emitModified(flags);
203 }
204 }
205 }
207 static void sp_tspan_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const /*flags*/)
208 {
209 // find out the ancestor text which holds our layout
210 SPObject *parent_text = SP_OBJECT(item);
211 for (; parent_text != NULL && !SP_IS_TEXT(parent_text); parent_text = SP_OBJECT_PARENT (parent_text)){};
212 if (parent_text == NULL) return;
214 // get the bbox of our portion of the layout
215 SP_TEXT(parent_text)->layout.getBoundingBox(bbox, transform, sp_text_get_length_upto(parent_text, item), sp_text_get_length_upto(item, NULL) - 1);
217 // Add stroke width
218 SPStyle* style=SP_OBJECT_STYLE (item);
219 if (!style->stroke.isNone()) {
220 double const scale = transform.descrim();
221 if ( fabs(style->stroke_width.computed * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord
222 double const width = MAX(0.125, style->stroke_width.computed * scale);
223 if ( fabs(bbox->x1 - bbox->x0) > -0.00001 && fabs(bbox->y1 - bbox->y0) > -0.00001 ) {
224 bbox->x0-=0.5*width;
225 bbox->x1+=0.5*width;
226 bbox->y0-=0.5*width;
227 bbox->y1+=0.5*width;
228 }
229 }
230 }
231 }
233 static Inkscape::XML::Node *
234 sp_tspan_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
235 {
236 SPTSpan *tspan = SP_TSPAN(object);
238 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
239 repr = xml_doc->createElement("svg:tspan");
240 }
242 tspan->attributes.writeTo(repr);
244 if ( flags&SP_OBJECT_WRITE_BUILD ) {
245 GSList *l = NULL;
246 for (SPObject* child = object->firstChild() ; child ; child = child->getNext() ) {
247 Inkscape::XML::Node* c_repr=NULL;
248 if ( SP_IS_TSPAN(child) || SP_IS_TREF(child) ) {
249 c_repr = child->updateRepr(xml_doc, NULL, flags);
250 } else if ( SP_IS_TEXTPATH(child) ) {
251 //c_repr = child->updateRepr(xml_doc, NULL, flags); // shouldn't happen
252 } else if ( SP_IS_STRING(child) ) {
253 c_repr = xml_doc->createTextNode(SP_STRING(child)->string.c_str());
254 }
255 if ( c_repr ) {
256 l = g_slist_prepend(l, c_repr);
257 }
258 }
259 while ( l ) {
260 repr->addChild((Inkscape::XML::Node *) l->data, NULL);
261 Inkscape::GC::release((Inkscape::XML::Node *) l->data);
262 l = g_slist_remove(l, l->data);
263 }
264 } else {
265 for (SPObject* child = object->firstChild() ; child ; child = child->getNext() ) {
266 if ( SP_IS_TSPAN(child) || SP_IS_TREF(child) ) {
267 child->updateRepr(flags);
268 } else if ( SP_IS_TEXTPATH(child) ) {
269 //c_repr = child->updateRepr(xml_doc, NULL, flags); // shouldn't happen
270 } else if ( SP_IS_STRING(child) ) {
271 SP_OBJECT_REPR(child)->setContent(SP_STRING(child)->string.c_str());
272 }
273 }
274 }
276 if (((SPObjectClass *) tspan_parent_class)->write) {
277 ((SPObjectClass *) tspan_parent_class)->write(object, xml_doc, repr, flags);
278 }
280 return repr;
281 }
283 static char *
284 sp_tspan_description(SPItem *item)
285 {
286 g_return_val_if_fail(SP_IS_TSPAN(item), NULL);
288 return g_strdup(_("<b>Text span</b>"));
289 }
292 /*#####################################################
293 # SPTEXTPATH
294 #####################################################*/
296 static void sp_textpath_class_init(SPTextPathClass *classname);
297 static void sp_textpath_init(SPTextPath *textpath);
298 static void sp_textpath_finalize(GObject *obj);
300 static void sp_textpath_build(SPObject * object, SPDocument * document, Inkscape::XML::Node * repr);
301 static void sp_textpath_release(SPObject *object);
302 static void sp_textpath_set(SPObject *object, unsigned key, gchar const *value);
303 static void sp_textpath_update(SPObject *object, SPCtx *ctx, guint flags);
304 static void sp_textpath_modified(SPObject *object, unsigned flags);
305 static Inkscape::XML::Node *sp_textpath_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
307 static SPItemClass *textpath_parent_class;
309 void refresh_textpath_source(SPTextPath* offset);
312 /**
313 *
314 */
315 GType
316 sp_textpath_get_type()
317 {
318 static GType type = 0;
319 if (!type) {
320 GTypeInfo info = {
321 sizeof(SPTextPathClass),
322 NULL, /* base_init */
323 NULL, /* base_finalize */
324 (GClassInitFunc) sp_textpath_class_init,
325 NULL, /* class_finalize */
326 NULL, /* class_data */
327 sizeof(SPTextPath),
328 16, /* n_preallocs */
329 (GInstanceInitFunc) sp_textpath_init,
330 NULL, /* value_table */
331 };
332 type = g_type_register_static(SP_TYPE_ITEM, "SPTextPath", &info, (GTypeFlags)0);
333 }
334 return type;
335 }
337 static void
338 sp_textpath_class_init(SPTextPathClass *classname)
339 {
340 GObjectClass *gobject_class = (GObjectClass *) classname;
341 SPObjectClass * sp_object_class;
342 SPItemClass * item_class;
344 sp_object_class = (SPObjectClass *) classname;
345 item_class = (SPItemClass *) classname;
347 textpath_parent_class = (SPItemClass*)g_type_class_ref(SP_TYPE_ITEM);
349 gobject_class->finalize = sp_textpath_finalize;
351 sp_object_class->build = sp_textpath_build;
352 sp_object_class->release = sp_textpath_release;
353 sp_object_class->set = sp_textpath_set;
354 sp_object_class->update = sp_textpath_update;
355 sp_object_class->modified = sp_textpath_modified;
356 sp_object_class->write = sp_textpath_write;
357 }
359 static void
360 sp_textpath_init(SPTextPath *textpath)
361 {
362 new (&textpath->attributes) TextTagAttributes;
364 textpath->startOffset._set = false;
365 textpath->originalPath = NULL;
366 textpath->isUpdating=false;
367 // set up the uri reference
368 textpath->sourcePath = new SPUsePath(SP_OBJECT(textpath));
369 textpath->sourcePath->user_unlink = sp_textpath_to_text;
370 }
372 static void
373 sp_textpath_finalize(GObject *obj)
374 {
375 SPTextPath *textpath = (SPTextPath *) obj;
377 delete textpath->sourcePath;
378 }
380 static void
381 sp_textpath_release(SPObject *object)
382 {
383 SPTextPath *textpath = SP_TEXTPATH(object);
385 textpath->attributes.~TextTagAttributes();
387 if (textpath->originalPath) delete textpath->originalPath;
388 textpath->originalPath = NULL;
390 if (((SPObjectClass *) textpath_parent_class)->release)
391 ((SPObjectClass *) textpath_parent_class)->release(object);
392 }
394 static void sp_textpath_build(SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr)
395 {
396 object->readAttr( "x" );
397 object->readAttr( "y" );
398 object->readAttr( "dx" );
399 object->readAttr( "dy" );
400 object->readAttr( "rotate" );
401 object->readAttr( "startOffset" );
402 object->readAttr( "xlink:href" );
404 bool no_content = true;
405 for (Inkscape::XML::Node* rch = repr->firstChild() ; rch != NULL; rch = rch->next()) {
406 if ( rch->type() == Inkscape::XML::TEXT_NODE )
407 {
408 no_content = false;
409 break;
410 }
411 }
413 if ( no_content ) {
414 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
415 Inkscape::XML::Node* rch = xml_doc->createTextNode("");
416 repr->addChild(rch, NULL);
417 }
419 if (((SPObjectClass *) textpath_parent_class)->build) {
420 ((SPObjectClass *) textpath_parent_class)->build(object, doc, repr);
421 }
422 }
424 static void
425 sp_textpath_set(SPObject *object, unsigned key, gchar const *value)
426 {
427 SPTextPath *textpath = SP_TEXTPATH(object);
429 if (textpath->attributes.readSingleAttribute(key, value)) {
430 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
431 } else {
432 switch (key) {
433 case SP_ATTR_XLINK_HREF:
434 textpath->sourcePath->link((char*)value);
435 break;
436 case SP_ATTR_STARTOFFSET:
437 textpath->startOffset.readOrUnset(value);
438 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
439 break;
440 default:
441 if (((SPObjectClass *) textpath_parent_class)->set)
442 (((SPObjectClass *) textpath_parent_class)->set)(object, key, value);
443 break;
444 }
445 }
446 }
448 static void sp_textpath_update(SPObject *object, SPCtx *ctx, guint flags)
449 {
450 SPTextPath *textpath = SP_TEXTPATH(object);
452 textpath->isUpdating = true;
453 if ( textpath->sourcePath->sourceDirty ) {
454 refresh_textpath_source(textpath);
455 }
456 textpath->isUpdating = false;
458 if (((SPObjectClass *) textpath_parent_class)->update) {
459 ((SPObjectClass *) textpath_parent_class)->update(object, ctx, flags);
460 }
462 if (flags & SP_OBJECT_MODIFIED_FLAG) {
463 flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
464 }
465 flags &= SP_OBJECT_MODIFIED_CASCADE;
467 for ( SPObject *ochild = object->firstChild() ; ochild ; ochild = ochild->getNext() ) {
468 if ( flags || ( ochild->uflags & SP_OBJECT_MODIFIED_FLAG )) {
469 ochild->updateDisplay(ctx, flags);
470 }
471 }
472 }
475 void refresh_textpath_source(SPTextPath* tp)
476 {
477 if ( tp == NULL ) return;
478 tp->sourcePath->refresh_source();
479 tp->sourcePath->sourceDirty=false;
481 // finalisons
482 if ( tp->sourcePath->originalPath ) {
483 if (tp->originalPath) {
484 delete tp->originalPath;
485 }
486 tp->originalPath = NULL;
488 tp->originalPath = new Path;
489 tp->originalPath->Copy(tp->sourcePath->originalPath);
490 tp->originalPath->ConvertWithBackData(0.01);
492 }
493 }
495 static void sp_textpath_modified(SPObject *object, unsigned flags)
496 {
497 if (((SPObjectClass *) textpath_parent_class)->modified) {
498 ((SPObjectClass *) textpath_parent_class)->modified(object, flags);
499 }
501 if (flags & SP_OBJECT_MODIFIED_FLAG) {
502 flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
503 }
504 flags &= SP_OBJECT_MODIFIED_CASCADE;
506 for ( SPObject *ochild = object->firstChild() ; ochild ; ochild = ochild->getNext() ) {
507 if (flags || (ochild->mflags & SP_OBJECT_MODIFIED_FLAG)) {
508 ochild->emitModified(flags);
509 }
510 }
511 }
512 static Inkscape::XML::Node *
513 sp_textpath_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
514 {
515 SPTextPath *textpath = SP_TEXTPATH(object);
517 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
518 repr = xml_doc->createElement("svg:textPath");
519 }
521 textpath->attributes.writeTo(repr);
522 if (textpath->startOffset._set) {
523 if (textpath->startOffset.unit == SVGLength::PERCENT) {
524 Inkscape::SVGOStringStream os;
525 os << (textpath->startOffset.computed * 100.0) << "%";
526 SP_OBJECT_REPR(textpath)->setAttribute("startOffset", os.str().c_str());
527 } else {
528 /* FIXME: This logic looks rather undesirable if e.g. startOffset is to be
529 in ems. */
530 sp_repr_set_svg_double(repr, "startOffset", textpath->startOffset.computed);
531 }
532 }
534 if ( textpath->sourcePath->sourceHref ) repr->setAttribute("xlink:href", textpath->sourcePath->sourceHref);
536 if ( flags&SP_OBJECT_WRITE_BUILD ) {
537 GSList *l = NULL;
538 for (SPObject* child = object->firstChild() ; child ; child = child->getNext() ) {
539 Inkscape::XML::Node* c_repr=NULL;
540 if ( SP_IS_TSPAN(child) || SP_IS_TREF(child) ) {
541 c_repr = child->updateRepr(xml_doc, NULL, flags);
542 } else if ( SP_IS_TEXTPATH(child) ) {
543 //c_repr = child->updateRepr(xml_doc, NULL, flags); // shouldn't happen
544 } else if ( SP_IS_STRING(child) ) {
545 c_repr = xml_doc->createTextNode(SP_STRING(child)->string.c_str());
546 }
547 if ( c_repr ) {
548 l = g_slist_prepend(l, c_repr);
549 }
550 }
551 while ( l ) {
552 repr->addChild((Inkscape::XML::Node *) l->data, NULL);
553 Inkscape::GC::release((Inkscape::XML::Node *) l->data);
554 l = g_slist_remove(l, l->data);
555 }
556 } else {
557 for (SPObject* child = object->firstChild() ; child ; child = child->getNext() ) {
558 if ( SP_IS_TSPAN(child) || SP_IS_TREF(child) ) {
559 child->updateRepr(flags);
560 } else if ( SP_IS_TEXTPATH(child) ) {
561 //c_repr = child->updateRepr(xml_doc, NULL, flags); // shouldn't happen
562 } else if ( SP_IS_STRING(child) ) {
563 SP_OBJECT_REPR(child)->setContent(SP_STRING(child)->string.c_str());
564 }
565 }
566 }
568 if (((SPObjectClass *) textpath_parent_class)->write) {
569 ((SPObjectClass *) textpath_parent_class)->write(object, xml_doc, repr, flags);
570 }
572 return repr;
573 }
576 SPItem *
577 sp_textpath_get_path_item(SPTextPath *tp)
578 {
579 if (tp && tp->sourcePath) {
580 SPItem *refobj = tp->sourcePath->getObject();
581 if (SP_IS_ITEM(refobj))
582 return (SPItem *) refobj;
583 }
584 return NULL;
585 }
587 void
588 sp_textpath_to_text(SPObject *tp)
589 {
590 SPObject *text = SP_OBJECT_PARENT(tp);
592 NRRect bbox;
593 SP_ITEM(text)->invoke_bbox( &bbox, SP_ITEM(text)->i2doc_affine(), TRUE);
594 Geom::Point xy(bbox.x0, bbox.y0);
596 // make a list of textpath children
597 GSList *tp_reprs = NULL;
598 for (SPObject *o = SP_OBJECT(tp)->firstChild() ; o != NULL; o = o->next) {
599 tp_reprs = g_slist_prepend(tp_reprs, SP_OBJECT_REPR(o));
600 }
602 for ( GSList *i = tp_reprs ; i ; i = i->next ) {
603 // make a copy of each textpath child
604 Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate(SP_OBJECT_REPR(text)->document());
605 // remove the old repr from under textpath
606 SP_OBJECT_REPR(tp)->removeChild((Inkscape::XML::Node *) i->data);
607 // put its copy under text
608 SP_OBJECT_REPR(text)->addChild(copy, NULL); // fixme: copy id
609 }
611 //remove textpath
612 tp->deleteObject();
613 g_slist_free(tp_reprs);
615 // set x/y on text
616 /* fixme: Yuck, is this really the right test? */
617 if (xy[Geom::X] != 1e18 && xy[Geom::Y] != 1e18) {
618 sp_repr_set_svg_double(SP_OBJECT_REPR(text), "x", xy[Geom::X]);
619 sp_repr_set_svg_double(SP_OBJECT_REPR(text), "y", xy[Geom::Y]);
620 }
621 }
624 /*
625 Local Variables:
626 mode:c++
627 c-file-style:"stroustrup"
628 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
629 indent-tabs-mode:nil
630 fill-column:99
631 End:
632 */
633 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :