Code

Applying patch #1415498 by James Kilfiger / zeimusu - Allow color & transparency...
[inkscape.git] / src / text-chemistry.cpp
1 #define __SP_TEXT_CHEMISTRY_C__
3 /*
4  * Text commands
5  *
6  * Authors:
7  *   bulia byak
8  *
9  * Copyright (C) 2004 authors
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
17 #include "libnr/nr-matrix-fns.h"
18 #include "xml/repr.h"
19 #include <glibmm/i18n.h>
20 #include "sp-rect.h"
21 #include "sp-textpath.h"
22 #include "inkscape.h"
23 #include "document.h"
24 #include "message-stack.h"
25 #include "selection.h"
26 #include "desktop-handles.h"
27 #include "text-editing.h"
28 #include "sp-flowtext.h"
29 #include "sp-flowregion.h"
30 #include "sp-flowdiv.h"
33 SPItem *
34 text_in_selection(Inkscape::Selection *selection)
35 {
36     for (GSList *items = (GSList *) selection->itemList();
37          items != NULL;
38          items = items->next) {
39         if (SP_IS_TEXT(items->data))
40             return ((SPItem *) items->data);
41     }
42     return NULL;
43 }
45 SPItem *
46 flowtext_in_selection(Inkscape::Selection *selection)
47 {
48     for (GSList *items = (GSList *) selection->itemList();
49          items != NULL;
50          items = items->next) {
51         if (SP_IS_FLOWTEXT(items->data))
52             return ((SPItem *) items->data);
53     }
54     return NULL;
55 }
57 SPItem *
58 shape_in_selection(Inkscape::Selection *selection)
59 {
60     for (GSList *items = (GSList *) selection->itemList();
61          items != NULL;
62          items = items->next) {
63         if (SP_IS_SHAPE(items->data))
64             return ((SPItem *) items->data);
65     }
66     return NULL;
67 }
69 void
70 text_put_on_path()
71 {
72     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
73     if (!desktop)
74         return;
76     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
78     SPItem *text = text_in_selection(selection);
79     SPItem *shape = shape_in_selection(selection);
81     if (!text || !shape || g_slist_length((GSList *) selection->itemList()) != 2) {
82         SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text and a path</b> to put text on path."));
83         return;
84     }
86     if (SP_IS_TEXT_TEXTPATH(text)) {
87         SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("This text object is <b>already put to a path</b>. Remove it from the path first. Use <b>Shift+D</b> to look up its path."));
88         return;
89     }
91     if (SP_IS_RECT(shape)) {
92         // rect is the only SPShape which is not <path> yet, and thus SVG forbids us from putting text on it
93         SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot put text on a rectangle in this version. Convert rectangle to path first."));
94         return;
95     }
97     Inkscape::Text::Layout const *layout = te_get_layout(text);
98     Inkscape::Text::Layout::Alignment text_alignment = layout->paragraphAlignment(layout->begin());
100     // remove transform from text, but recursively scale text's fontsize by the expansion
101     SP_TEXT(text)->_adjustFontsizeRecursive (text, NR::expansion(SP_ITEM(text)->transform));
102     SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
104     // make a list of text children
105     GSList *text_reprs = NULL;
106     for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
107         text_reprs = g_slist_prepend(text_reprs, SP_OBJECT_REPR(o));
108     }
110     // create textPath and put it into the text
111     Inkscape::XML::Node *textpath = sp_repr_new("svg:textPath");
112     // reference the shape
113     textpath->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(shape)->attribute("id")));
114     if (text_alignment == Inkscape::Text::Layout::RIGHT)
115         textpath->setAttribute("startOffset", "100%");
116     else if (text_alignment == Inkscape::Text::Layout::CENTER)
117         textpath->setAttribute("startOffset", "50%");
118     SP_OBJECT_REPR(text)->addChild(textpath, NULL);
120     for ( GSList *i = text_reprs ; i ; i = i->next ) {
121         // make a copy of each text child
122         Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate();
123         // We cannot have multiline in textpath, so remove line attrs from tspans
124         if (!strcmp(copy->name(), "svg:tspan")) {
125             copy->setAttribute("sodipodi:role", NULL);
126             copy->setAttribute("x", NULL);
127             copy->setAttribute("y", NULL);
128         }
129         // remove the old repr from under text
130         SP_OBJECT_REPR(text)->removeChild((Inkscape::XML::Node *) i->data);
131         // put its copy into under textPath
132         textpath->addChild(copy, NULL); // fixme: copy id
133     }
135     // x/y are useless with textpath, and confuse Batik 1.5
136     SP_OBJECT_REPR(text)->setAttribute("x", NULL);
137     SP_OBJECT_REPR(text)->setAttribute("y", NULL);
139     sp_document_done(SP_DT_DOCUMENT(desktop));
140     g_slist_free(text_reprs);
143 void
144 text_remove_from_path()
146     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
148     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
150     if (selection->isEmpty()) {
151         SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text on path</b> to remove it from path."));
152         return;
153     }
155     bool did = false;
157     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
158          items != NULL;
159          items = items->next) {
161         if (!SP_IS_TEXT_TEXTPATH(SP_OBJECT(items->data))) {
162             continue;
163         }
165         SPObject *tp = sp_object_first_child(SP_OBJECT(items->data));
167         did = true;
169         sp_textpath_to_text(tp);
170     }
172     if (!did) {
173         SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No texts-on-paths</b> in the selection."));
174     } else {
175         selection->setList(g_slist_copy((GSList *) selection->itemList())); // reselect to update statusbar description
176         sp_document_done(SP_DT_DOCUMENT(desktop));
177     }
180 void
181 text_remove_all_kerns_recursively(SPObject *o)
183     SP_OBJECT_REPR(o)->setAttribute("dx", NULL);
184     SP_OBJECT_REPR(o)->setAttribute("dy", NULL);
185     SP_OBJECT_REPR(o)->setAttribute("rotate", NULL);
187     for (SPObject *i = sp_object_first_child(o); i != NULL; i = SP_OBJECT_NEXT(i)) {
188         text_remove_all_kerns_recursively(i);
189     }
192 //FIXME: must work with text selection
193 void
194 text_remove_all_kerns()
196     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
198     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
200     if (selection->isEmpty()) {
201         SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
202         return;
203     }
205     bool did = false;
207     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
208          items != NULL;
209          items = items->next) {
211         if (!SP_IS_TEXT(SP_OBJECT(items->data))) {
212             continue;
213         }
215         text_remove_all_kerns_recursively(SP_OBJECT(items->data));
216         SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
217         did = true;
218     }
220     if (!did) {
221         SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
222     } else {
223         sp_document_done(SP_DT_DOCUMENT(desktop));
224     }
227 void
228 text_flow_into_shape()
230     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
231     if (!desktop)
232         return;
234     SPDocument *doc = SP_DT_DOCUMENT (desktop);
236     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
238     SPItem *text = text_in_selection(selection);
239     SPItem *shape = shape_in_selection(selection);
241     if (!text || !shape || g_slist_length((GSList *) selection->itemList()) < 2) {
242         SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text</b> and one or more <b>paths or shapes</b> to flow text into frame."));
243         return;
244     }
246     // remove transform from text, but recursively scale text's fontsize by the expansion
247     SP_TEXT(text)->_adjustFontsizeRecursive(text, NR::expansion(SP_ITEM(text)->transform));
248     SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
250     Inkscape::XML::Node *root_repr = sp_repr_new("svg:flowRoot");
251     root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
252     root_repr->setAttribute("style", SP_OBJECT_REPR(text)->attribute("style")); // fixme: transfer style attrs too
253     SP_OBJECT_REPR(SP_OBJECT_PARENT(shape))->appendChild(root_repr);
254     SPObject *root_object = doc->getObjectByRepr(root_repr);
255     g_return_if_fail(SP_IS_FLOWTEXT(root_object));
257     Inkscape::XML::Node *region_repr = sp_repr_new("svg:flowRegion");
258     root_repr->appendChild(region_repr);
259     SPObject *object = doc->getObjectByRepr(region_repr);
260     g_return_if_fail(SP_IS_FLOWREGION(object));
262     /* Add clones */
263     for (GSList *items = (GSList *) selection->itemList();
264          items != NULL;
265          items = items->next) {
266         SPItem *item = SP_ITEM(items->data);
267         if (SP_IS_SHAPE(item)){
268             Inkscape::XML::Node *clone = sp_repr_new("svg:use");
269             clone->setAttribute("x", "0");
270             clone->setAttribute("y", "0");
271             clone->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(item)->attribute("id")));
273             // add the new clone to the region
274             region_repr->appendChild(clone);
275         }
276     }
278     Inkscape::XML::Node *para_repr = sp_repr_new("svg:flowPara");
279     root_repr->appendChild(para_repr);
280     object = doc->getObjectByRepr(para_repr);
281     g_return_if_fail(SP_IS_FLOWPARA(object));
283     Inkscape::Text::Layout const *layout = te_get_layout(text);
284     Glib::ustring text_ustring = sp_te_get_string_multiline(text, layout->begin(), layout->end());
286     Inkscape::XML::Node *text_repr = sp_repr_new_text(text_ustring.c_str()); // FIXME: transfer all formatting! and convert newlines into flowParas!
287     para_repr->appendChild(text_repr);
289     SP_OBJECT(text)->deleteObject (true);
291     sp_document_done(doc);
293     SP_DT_SELECTION(desktop)->set(SP_ITEM(root_object));
295     Inkscape::GC::release(root_repr);
296     Inkscape::GC::release(region_repr);
297     Inkscape::GC::release(para_repr);
298     Inkscape::GC::release(text_repr);
301 void
302 text_unflow ()
304     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
305     if (!desktop)
306         return;
308     SPDocument *doc = SP_DT_DOCUMENT (desktop);
310     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
313     if (!flowtext_in_selection(selection) || g_slist_length((GSList *) selection->itemList()) < 1) {
314         SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a flowed text</b> to unflow it."));
315         return;
316     }
318     GSList *new_objs = NULL;
319     GSList *old_objs = NULL;
321     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
322          items != NULL;
323          items = items->next) {
325         if (!SP_IS_FLOWTEXT(SP_OBJECT(items->data))) {
326             continue;
327         }
329         SPItem *flowtext = SP_ITEM(items->data);
331         /* Create <text> */
332         Inkscape::XML::Node *rtext = sp_repr_new("svg:text");
333         rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
335         /* Set style */
336         rtext->setAttribute("style", SP_OBJECT_REPR(flowtext)->attribute("style")); // fixme: transfer style attrs too; and from descendants
338         NRRect bbox;
339         sp_item_invoke_bbox(SP_ITEM(flowtext), &bbox, sp_item_i2doc_affine(SP_ITEM(flowtext)), TRUE);
340         NR::Point xy(bbox.x0, bbox.y0);
341         if (xy[NR::X] != 1e18 && xy[NR::Y] != 1e18) {
342             sp_repr_set_svg_double(rtext, "x", xy[NR::X]);
343             sp_repr_set_svg_double(rtext, "y", xy[NR::Y]);
344         }
346         /* Create <tspan> */
347         Inkscape::XML::Node *rtspan = sp_repr_new("svg:tspan");
348         rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
349         rtext->addChild(rtspan, NULL);
351         gchar *text_string = sp_te_get_string_multiline(flowtext);
352         Inkscape::XML::Node *text_repr = sp_repr_new_text(text_string); // FIXME: transfer all formatting!!!
353         free(text_string);
354         rtspan->appendChild(text_repr);
356         SP_OBJECT_REPR(SP_OBJECT_PARENT(flowtext))->appendChild(rtext);
357         SPObject *text_object = doc->getObjectByRepr(rtext);
359         new_objs = g_slist_prepend (new_objs, text_object);
360         old_objs = g_slist_prepend (old_objs, flowtext);
362         Inkscape::GC::release(rtext);
363         Inkscape::GC::release(rtspan);
364         Inkscape::GC::release(text_repr);
365     }
367     selection->clear();
368     selection->setList(new_objs);
369     for (GSList *i = old_objs; i; i = i->next) {
370         SP_OBJECT(i->data)->deleteObject (true);
371     }
373     g_slist_free (old_objs);
374     g_slist_free (new_objs);
376     sp_document_done(doc);
381 /*
382   Local Variables:
383   mode:c++
384   c-file-style:"stroustrup"
385   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
386   indent-tabs-mode:nil
387   fill-column:99
388   End:
389 */
390 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :