1 #define __SP_TSPAN_C__
3 /*
4 * SVG <text> and <tspan> implementation
5 *
6 * Author:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
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 <livarot/Path.h>
31 #include "svg/stringstream.h"
32 #include "attributes.h"
33 #include "sp-use-reference.h"
34 #include "sp-tspan.h"
35 #include "sp-textpath.h"
36 #include "text-editing.h"
37 #include "style.h"
38 #include "libnr/nr-matrix-fns.h"
39 #include "xml/repr.h"
40 #include "document.h"
43 /*#####################################################
44 # SPTSPAN
45 #####################################################*/
47 static void sp_tspan_class_init(SPTSpanClass *classname);
48 static void sp_tspan_init(SPTSpan *tspan);
50 static void sp_tspan_build(SPObject * object, SPDocument * document, Inkscape::XML::Node * repr);
51 static void sp_tspan_release(SPObject *object);
52 static void sp_tspan_set(SPObject *object, unsigned key, gchar const *value);
53 static void sp_tspan_update(SPObject *object, SPCtx *ctx, guint flags);
54 static void sp_tspan_modified(SPObject *object, unsigned flags);
55 static void sp_tspan_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags);
56 static Inkscape::XML::Node *sp_tspan_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
58 static SPItemClass *tspan_parent_class;
60 /**
61 *
62 */
63 GType
64 sp_tspan_get_type()
65 {
66 static GType type = 0;
67 if (!type) {
68 GTypeInfo info = {
69 sizeof(SPTSpanClass),
70 NULL, /* base_init */
71 NULL, /* base_finalize */
72 (GClassInitFunc) sp_tspan_class_init,
73 NULL, /* class_finalize */
74 NULL, /* class_data */
75 sizeof(SPTSpan),
76 16, /* n_preallocs */
77 (GInstanceInitFunc) sp_tspan_init,
78 NULL, /* value_table */
79 };
80 type = g_type_register_static(SP_TYPE_ITEM, "SPTSpan", &info, (GTypeFlags)0);
81 }
82 return type;
83 }
85 static void
86 sp_tspan_class_init(SPTSpanClass *classname)
87 {
88 SPObjectClass * sp_object_class;
89 SPItemClass * item_class;
91 sp_object_class = (SPObjectClass *) classname;
92 item_class = (SPItemClass *) classname;
94 tspan_parent_class = (SPItemClass*)g_type_class_ref(SP_TYPE_ITEM);
96 sp_object_class->build = sp_tspan_build;
97 sp_object_class->release = sp_tspan_release;
98 sp_object_class->set = sp_tspan_set;
99 sp_object_class->update = sp_tspan_update;
100 sp_object_class->modified = sp_tspan_modified;
101 sp_object_class->write = sp_tspan_write;
103 item_class->bbox = sp_tspan_bbox;
104 }
106 static void
107 sp_tspan_init(SPTSpan *tspan)
108 {
109 tspan->role = SP_TSPAN_ROLE_UNSPECIFIED;
110 new (&tspan->attributes) TextTagAttributes;
111 }
113 static void
114 sp_tspan_release(SPObject *object)
115 {
116 SPTSpan *tspan = SP_TSPAN(object);
118 tspan->attributes.~TextTagAttributes();
120 if (((SPObjectClass *) tspan_parent_class)->release)
121 ((SPObjectClass *) tspan_parent_class)->release(object);
122 }
124 static void
125 sp_tspan_build(SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr)
126 {
127 //SPTSpan *tspan = SP_TSPAN(object);
129 sp_object_read_attr(object, "x");
130 sp_object_read_attr(object, "y");
131 sp_object_read_attr(object, "dx");
132 sp_object_read_attr(object, "dy");
133 sp_object_read_attr(object, "rotate");
134 sp_object_read_attr(object, "sodipodi:role");
136 if (((SPObjectClass *) tspan_parent_class)->build)
137 ((SPObjectClass *) tspan_parent_class)->build(object, doc, repr);
138 }
140 static void
141 sp_tspan_set(SPObject *object, unsigned key, gchar const *value)
142 {
143 SPTSpan *tspan = SP_TSPAN(object);
145 if (tspan->attributes.readSingleAttribute(key, value)) {
146 if (tspan->role != SP_TSPAN_ROLE_LINE)
147 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
148 } else {
149 switch (key) {
150 case SP_ATTR_SODIPODI_ROLE:
151 if (value && (!strcmp(value, "line") || !strcmp(value, "paragraph"))) {
152 tspan->role = SP_TSPAN_ROLE_LINE;
153 } else {
154 tspan->role = SP_TSPAN_ROLE_UNSPECIFIED;
155 }
156 break;
157 default:
158 if (((SPObjectClass *) tspan_parent_class)->set)
159 (((SPObjectClass *) tspan_parent_class)->set)(object, key, value);
160 break;
161 }
162 }
163 }
165 static void
166 sp_tspan_update(SPObject *object, SPCtx *ctx, guint flags)
167 {
168 if (((SPObjectClass *) tspan_parent_class)->update)
169 ((SPObjectClass *) tspan_parent_class)->update(object, ctx, flags);
171 if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
172 flags &= SP_OBJECT_MODIFIED_CASCADE;
174 SPObject *ochild;
175 for ( ochild = sp_object_first_child(object) ; ochild ; ochild = SP_OBJECT_NEXT(ochild) ) {
176 if ( flags || ( ochild->uflags & SP_OBJECT_MODIFIED_FLAG )) {
177 ochild->updateDisplay(ctx, flags);
178 }
179 }
180 }
182 static void
183 sp_tspan_modified(SPObject *object, unsigned flags)
184 {
185 if (((SPObjectClass *) tspan_parent_class)->modified)
186 ((SPObjectClass *) tspan_parent_class)->modified(object, flags);
188 if (flags & SP_OBJECT_MODIFIED_FLAG)
189 flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
190 flags &= SP_OBJECT_MODIFIED_CASCADE;
192 SPObject *ochild;
193 for ( ochild = sp_object_first_child(object) ; ochild ; ochild = SP_OBJECT_NEXT(ochild) ) {
194 if (flags || (ochild->mflags & SP_OBJECT_MODIFIED_FLAG)) {
195 ochild->emitModified(flags);
196 }
197 }
198 }
200 static void sp_tspan_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags)
201 {
202 // find out the ancestor text which holds our layout
203 SPObject *parent_text = SP_OBJECT(item);
204 for (; parent_text != NULL && !SP_IS_TEXT(parent_text); parent_text = SP_OBJECT_PARENT (parent_text));
205 if (parent_text == NULL) return;
207 // get the bbox of our portion of the layout
208 SP_TEXT(parent_text)->layout.getBoundingBox(bbox, transform, sp_text_get_length_upto(parent_text, item), sp_text_get_length_upto(item, NULL) - 1);
210 // Add stroke width
211 SPStyle* style=SP_OBJECT_STYLE (item);
212 if (style->stroke.type != SP_PAINT_TYPE_NONE) {
213 double const scale = expansion(transform);
214 if ( fabs(style->stroke_width.computed * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord
215 double const width = MAX(0.125, style->stroke_width.computed * scale);
216 if ( fabs(bbox->x1 - bbox->x0) > -0.00001 && fabs(bbox->y1 - bbox->y0) > -0.00001 ) {
217 bbox->x0-=0.5*width;
218 bbox->x1+=0.5*width;
219 bbox->y0-=0.5*width;
220 bbox->y1+=0.5*width;
221 }
222 }
223 }
224 }
226 static Inkscape::XML::Node *
227 sp_tspan_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
228 {
229 SPTSpan *tspan = SP_TSPAN(object);
230 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
232 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
233 repr = xml_doc->createElement("svg:tspan");
234 }
236 tspan->attributes.writeTo(repr);
238 if ( flags&SP_OBJECT_WRITE_BUILD ) {
239 GSList *l = NULL;
240 for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
241 Inkscape::XML::Node* c_repr=NULL;
242 if ( SP_IS_TSPAN(child) ) {
243 c_repr = child->updateRepr(NULL, flags);
244 } else if ( SP_IS_TEXTPATH(child) ) {
245 //c_repr = child->updateRepr(NULL, flags); // shouldn't happen
246 } else if ( SP_IS_STRING(child) ) {
247 c_repr = xml_doc->createTextNode(SP_STRING(child)->string.c_str());
248 }
249 if ( c_repr ) l = g_slist_prepend(l, c_repr);
250 }
251 while ( l ) {
252 repr->addChild((Inkscape::XML::Node *) l->data, NULL);
253 Inkscape::GC::release((Inkscape::XML::Node *) l->data);
254 l = g_slist_remove(l, l->data);
255 }
256 } else {
257 for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
258 if ( SP_IS_TSPAN(child) ) {
259 child->updateRepr(flags);
260 } else if ( SP_IS_TEXTPATH(child) ) {
261 //c_repr = child->updateRepr(NULL, flags); // shouldn't happen
262 } else if ( SP_IS_STRING(child) ) {
263 SP_OBJECT_REPR(child)->setContent(SP_STRING(child)->string.c_str());
264 }
265 }
266 }
268 if (((SPObjectClass *) tspan_parent_class)->write)
269 ((SPObjectClass *) tspan_parent_class)->write(object, repr, flags);
271 return repr;
272 }
274 /*#####################################################
275 # SPTEXTPATH
276 #####################################################*/
278 static void sp_textpath_class_init(SPTextPathClass *classname);
279 static void sp_textpath_init(SPTextPath *textpath);
280 static void sp_textpath_finalize(GObject *obj);
282 static void sp_textpath_build(SPObject * object, SPDocument * document, Inkscape::XML::Node * repr);
283 static void sp_textpath_release(SPObject *object);
284 static void sp_textpath_set(SPObject *object, unsigned key, gchar const *value);
285 static void sp_textpath_update(SPObject *object, SPCtx *ctx, guint flags);
286 static void sp_textpath_modified(SPObject *object, unsigned flags);
287 static Inkscape::XML::Node *sp_textpath_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
289 static SPItemClass *textpath_parent_class;
291 void refresh_textpath_source(SPTextPath* offset);
294 /**
295 *
296 */
297 GType
298 sp_textpath_get_type()
299 {
300 static GType type = 0;
301 if (!type) {
302 GTypeInfo info = {
303 sizeof(SPTextPathClass),
304 NULL, /* base_init */
305 NULL, /* base_finalize */
306 (GClassInitFunc) sp_textpath_class_init,
307 NULL, /* class_finalize */
308 NULL, /* class_data */
309 sizeof(SPTextPath),
310 16, /* n_preallocs */
311 (GInstanceInitFunc) sp_textpath_init,
312 NULL, /* value_table */
313 };
314 type = g_type_register_static(SP_TYPE_ITEM, "SPTextPath", &info, (GTypeFlags)0);
315 }
316 return type;
317 }
319 static void
320 sp_textpath_class_init(SPTextPathClass *classname)
321 {
322 GObjectClass *gobject_class = (GObjectClass *) classname;
323 SPObjectClass * sp_object_class;
324 SPItemClass * item_class;
326 sp_object_class = (SPObjectClass *) classname;
327 item_class = (SPItemClass *) classname;
329 textpath_parent_class = (SPItemClass*)g_type_class_ref(SP_TYPE_ITEM);
331 gobject_class->finalize = sp_textpath_finalize;
333 sp_object_class->build = sp_textpath_build;
334 sp_object_class->release = sp_textpath_release;
335 sp_object_class->set = sp_textpath_set;
336 sp_object_class->update = sp_textpath_update;
337 sp_object_class->modified = sp_textpath_modified;
338 sp_object_class->write = sp_textpath_write;
339 }
341 static void
342 sp_textpath_init(SPTextPath *textpath)
343 {
344 new (&textpath->attributes) TextTagAttributes;
346 textpath->startOffset._set = false;
347 textpath->originalPath = NULL;
348 textpath->isUpdating=false;
349 // set up the uri reference
350 textpath->sourcePath = new SPUsePath(SP_OBJECT(textpath));
351 textpath->sourcePath->user_unlink = sp_textpath_to_text;
352 }
354 static void
355 sp_textpath_finalize(GObject *obj)
356 {
357 SPTextPath *textpath = (SPTextPath *) obj;
359 delete textpath->sourcePath;
360 }
362 static void
363 sp_textpath_release(SPObject *object)
364 {
365 SPTextPath *textpath = SP_TEXTPATH(object);
367 textpath->attributes.~TextTagAttributes();
369 if (textpath->originalPath) delete textpath->originalPath;
370 textpath->originalPath = NULL;
372 if (((SPObjectClass *) textpath_parent_class)->release)
373 ((SPObjectClass *) textpath_parent_class)->release(object);
374 }
376 static void
377 sp_textpath_build(SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr)
378 {
379 //SPTextPath *textpath = SP_TEXTPATH(object);
381 sp_object_read_attr(object, "x");
382 sp_object_read_attr(object, "y");
383 sp_object_read_attr(object, "dx");
384 sp_object_read_attr(object, "dy");
385 sp_object_read_attr(object, "rotate");
386 sp_object_read_attr(object, "startOffset");
387 sp_object_read_attr(object, "xlink:href");
389 bool no_content=true;
390 for (Inkscape::XML::Node* rch = repr->firstChild() ; rch != NULL; rch = rch->next()) {
391 if ( rch->type() == Inkscape::XML::TEXT_NODE ) {no_content=false;break;}
392 }
394 if ( no_content ) {
395 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
396 Inkscape::XML::Node* rch = xml_doc->createTextNode("");
397 repr->addChild(rch, NULL);
398 }
400 if (((SPObjectClass *) textpath_parent_class)->build)
401 ((SPObjectClass *) textpath_parent_class)->build(object, doc, repr);
402 }
404 static void
405 sp_textpath_set(SPObject *object, unsigned key, gchar const *value)
406 {
407 SPTextPath *textpath = SP_TEXTPATH(object);
409 if (textpath->attributes.readSingleAttribute(key, value)) {
410 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
411 } else {
412 switch (key) {
413 case SP_ATTR_XLINK_HREF:
414 textpath->sourcePath->link((char*)value);
415 break;
416 case SP_ATTR_STARTOFFSET:
417 textpath->startOffset.readOrUnset(value);
418 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
419 break;
420 default:
421 if (((SPObjectClass *) textpath_parent_class)->set)
422 (((SPObjectClass *) textpath_parent_class)->set)(object, key, value);
423 break;
424 }
425 }
426 }
428 static void
429 sp_textpath_update(SPObject *object, SPCtx *ctx, guint flags)
430 {
431 SPTextPath *textpath = SP_TEXTPATH(object);
433 textpath->isUpdating=true;
434 if ( textpath->sourcePath->sourceDirty ) refresh_textpath_source(textpath);
435 textpath->isUpdating=false;
437 if (((SPObjectClass *) textpath_parent_class)->update)
438 ((SPObjectClass *) textpath_parent_class)->update(object, ctx, flags);
440 if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
441 flags &= SP_OBJECT_MODIFIED_CASCADE;
443 SPObject *ochild;
444 for ( ochild = sp_object_first_child(object) ; ochild ; ochild = SP_OBJECT_NEXT(ochild) ) {
445 if ( flags || ( ochild->uflags & SP_OBJECT_MODIFIED_FLAG )) {
446 ochild->updateDisplay(ctx, flags);
447 }
448 }
449 }
452 void refresh_textpath_source(SPTextPath* tp)
453 {
454 if ( tp == NULL ) return;
455 tp->sourcePath->refresh_source();
456 tp->sourcePath->sourceDirty=false;
458 // finalisons
459 if ( tp->sourcePath->originalPath ) {
460 if (tp->originalPath) {
461 delete tp->originalPath;
462 }
463 tp->originalPath = NULL;
465 tp->originalPath = new Path;
466 tp->originalPath->Copy(tp->sourcePath->originalPath);
467 tp->originalPath->ConvertWithBackData(0.01);
469 }
470 }
472 static void
473 sp_textpath_modified(SPObject *object, unsigned flags)
474 {
475 if (((SPObjectClass *) textpath_parent_class)->modified)
476 ((SPObjectClass *) textpath_parent_class)->modified(object, flags);
478 if (flags & SP_OBJECT_MODIFIED_FLAG)
479 flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
480 flags &= SP_OBJECT_MODIFIED_CASCADE;
482 SPObject *ochild;
483 for ( ochild = sp_object_first_child(object) ; ochild ; ochild = SP_OBJECT_NEXT(ochild) ) {
484 if (flags || (ochild->mflags & SP_OBJECT_MODIFIED_FLAG)) {
485 ochild->emitModified(flags);
486 }
487 }
488 }
489 static Inkscape::XML::Node *
490 sp_textpath_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
491 {
492 SPTextPath *textpath = SP_TEXTPATH(object);
493 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
495 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
496 repr = xml_doc->createElement("svg:textPath");
497 }
499 textpath->attributes.writeTo(repr);
500 if (textpath->startOffset._set) {
501 if (textpath->startOffset.unit == SVGLength::PERCENT) {
502 Inkscape::SVGOStringStream os;
503 os << (textpath->startOffset.computed * 100.0) << "%";
504 SP_OBJECT_REPR(textpath)->setAttribute("startOffset", os.str().c_str());
505 } else {
506 /* FIXME: This logic looks rather undesirable if e.g. startOffset is to be
507 in ems. */
508 sp_repr_set_svg_double(repr, "startOffset", textpath->startOffset.computed);
509 }
510 }
512 if ( textpath->sourcePath->sourceHref ) repr->setAttribute("xlink:href", textpath->sourcePath->sourceHref);
514 if ( flags&SP_OBJECT_WRITE_BUILD ) {
515 GSList *l = NULL;
516 for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
517 Inkscape::XML::Node* c_repr=NULL;
518 if ( SP_IS_TSPAN(child) ) {
519 c_repr = child->updateRepr(NULL, flags);
520 } else if ( SP_IS_TEXTPATH(child) ) {
521 //c_repr = child->updateRepr(NULL, flags); // shouldn't happen
522 } else if ( SP_IS_STRING(child) ) {
523 c_repr = xml_doc->createTextNode(SP_STRING(child)->string.c_str());
524 }
525 if ( c_repr ) l = g_slist_prepend(l, c_repr);
526 }
527 while ( l ) {
528 repr->addChild((Inkscape::XML::Node *) l->data, NULL);
529 Inkscape::GC::release((Inkscape::XML::Node *) l->data);
530 l = g_slist_remove(l, l->data);
531 }
532 } else {
533 for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
534 if ( SP_IS_TSPAN(child) ) {
535 child->updateRepr(flags);
536 } else if ( SP_IS_TEXTPATH(child) ) {
537 //c_repr = child->updateRepr(NULL, flags); // shouldn't happen
538 } else if ( SP_IS_STRING(child) ) {
539 SP_OBJECT_REPR(child)->setContent(SP_STRING(child)->string.c_str());
540 }
541 }
542 }
544 if (((SPObjectClass *) textpath_parent_class)->write)
545 ((SPObjectClass *) textpath_parent_class)->write(object, repr, flags);
547 return repr;
548 }
551 SPItem *
552 sp_textpath_get_path_item(SPTextPath *tp)
553 {
554 if (tp && tp->sourcePath) {
555 SPItem *refobj = tp->sourcePath->getObject();
556 if (SP_IS_ITEM(refobj))
557 return (SPItem *) refobj;
558 }
559 return NULL;
560 }
562 void
563 sp_textpath_to_text(SPObject *tp)
564 {
565 SPObject *text = SP_OBJECT_PARENT(tp);
567 NRRect bbox;
568 sp_item_invoke_bbox(SP_ITEM(text), &bbox, sp_item_i2doc_affine(SP_ITEM(text)), TRUE);
569 NR::Point xy(bbox.x0, bbox.y0);
571 // make a list of textpath children
572 GSList *tp_reprs = NULL;
573 for (SPObject *o = SP_OBJECT(tp)->firstChild() ; o != NULL; o = o->next) {
574 tp_reprs = g_slist_prepend(tp_reprs, SP_OBJECT_REPR(o));
575 }
577 for ( GSList *i = tp_reprs ; i ; i = i->next ) {
578 // make a copy of each textpath child
579 Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate(SP_OBJECT_REPR(text)->document());
580 // remove the old repr from under textpath
581 SP_OBJECT_REPR(tp)->removeChild((Inkscape::XML::Node *) i->data);
582 // put its copy into under textPath
583 SP_OBJECT_REPR(text)->addChild(copy, NULL); // fixme: copy id
584 }
586 //remove textpath
587 tp->deleteObject();
588 g_slist_free(tp_reprs);
590 // set x/y on text
591 /* fixme: Yuck, is this really the right test? */
592 if (xy[NR::X] != 1e18 && xy[NR::Y] != 1e18) {
593 sp_repr_set_svg_double(SP_OBJECT_REPR(text), "x", xy[NR::X]);
594 sp_repr_set_svg_double(SP_OBJECT_REPR(text), "y", xy[NR::Y]);
595 }
596 }
599 /*
600 Local Variables:
601 mode:c++
602 c-file-style:"stroustrup"
603 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
604 indent-tabs-mode:nil
605 fill-column:99
606 End:
607 */
608 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :