Code

undo annotations
[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 text_or_flowtext_in_selection(Inkscape::Selection *selection)
59 {
60     for (GSList *items = (GSList *) selection->itemList();
61          items != NULL;
62          items = items->next) {
63         if (SP_IS_TEXT(items->data) || SP_IS_FLOWTEXT(items->data))
64             return ((SPItem *) items->data);
65     }
66     return NULL;
67 }
69 SPItem *
70 shape_in_selection(Inkscape::Selection *selection)
71 {
72     for (GSList *items = (GSList *) selection->itemList();
73          items != NULL;
74          items = items->next) {
75         if (SP_IS_SHAPE(items->data))
76             return ((SPItem *) items->data);
77     }
78     return NULL;
79 }
81 void
82 text_put_on_path()
83 {
84     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
85     if (!desktop)
86         return;
88     Inkscape::Selection *selection = sp_desktop_selection(desktop);
90     SPItem *text = text_or_flowtext_in_selection(selection);
91     SPItem *shape = shape_in_selection(selection);
93     if (!text || !shape || g_slist_length((GSList *) selection->itemList()) != 2) {
94         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text and a path</b> to put text on path."));
95         return;
96     }
98     if (SP_IS_TEXT_TEXTPATH(text)) {
99         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("This text object is <b>already put on a path</b>. Remove it from the path first. Use <b>Shift+D</b> to look up its path."));
100         return;
101     }
103     if (SP_IS_FLOWTEXT(text)) {
104         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot put flowtext on a path. Convert flowtext to text first."));
105         return;
106     }
108     if (SP_IS_RECT(shape)) {
109         // rect is the only SPShape which is not <path> yet, and thus SVG forbids us from putting text on it
110         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot put text on a rectangle in this version. Convert rectangle to path first."));
111         return;
112     }
114     Inkscape::Text::Layout const *layout = te_get_layout(text);
115     Inkscape::Text::Layout::Alignment text_alignment = layout->paragraphAlignment(layout->begin());
117     // remove transform from text, but recursively scale text's fontsize by the expansion
118     SP_TEXT(text)->_adjustFontsizeRecursive (text, NR::expansion(SP_ITEM(text)->transform));
119     SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
121     // make a list of text children
122     GSList *text_reprs = NULL;
123     for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
124         text_reprs = g_slist_prepend(text_reprs, SP_OBJECT_REPR(o));
125     }
127     // create textPath and put it into the text
128     Inkscape::XML::Node *textpath = sp_repr_new("svg:textPath");
129     // reference the shape
130     textpath->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(shape)->attribute("id")));
131     if (text_alignment == Inkscape::Text::Layout::RIGHT)
132         textpath->setAttribute("startOffset", "100%");
133     else if (text_alignment == Inkscape::Text::Layout::CENTER)
134         textpath->setAttribute("startOffset", "50%");
135     SP_OBJECT_REPR(text)->addChild(textpath, NULL);
137     for ( GSList *i = text_reprs ; i ; i = i->next ) {
138         // make a copy of each text child
139         Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate();
140         // We cannot have multiline in textpath, so remove line attrs from tspans
141         if (!strcmp(copy->name(), "svg:tspan")) {
142             copy->setAttribute("sodipodi:role", NULL);
143             copy->setAttribute("x", NULL);
144             copy->setAttribute("y", NULL);
145         }
146         // remove the old repr from under text
147         SP_OBJECT_REPR(text)->removeChild((Inkscape::XML::Node *) i->data);
148         // put its copy into under textPath
149         textpath->addChild(copy, NULL); // fixme: copy id
150     }
152     // x/y are useless with textpath, and confuse Batik 1.5
153     SP_OBJECT_REPR(text)->setAttribute("x", NULL);
154     SP_OBJECT_REPR(text)->setAttribute("y", NULL);
156     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
157                      _("Put text on path"));
158     g_slist_free(text_reprs);
161 void
162 text_remove_from_path()
164     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
166     Inkscape::Selection *selection = sp_desktop_selection(desktop);
168     if (selection->isEmpty()) {
169         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text on path</b> to remove it from path."));
170         return;
171     }
173     bool did = false;
175     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
176          items != NULL;
177          items = items->next) {
179         if (!SP_IS_TEXT_TEXTPATH(SP_OBJECT(items->data))) {
180             continue;
181         }
183         SPObject *tp = sp_object_first_child(SP_OBJECT(items->data));
185         did = true;
187         sp_textpath_to_text(tp);
188     }
190     if (!did) {
191         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No texts-on-paths</b> in the selection."));
192     } else {
193         selection->setList(g_slist_copy((GSList *) selection->itemList())); // reselect to update statusbar description
194         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
195                          _("Remove text from path"));
196     }
199 void
200 text_remove_all_kerns_recursively(SPObject *o)
202     SP_OBJECT_REPR(o)->setAttribute("dx", NULL);
203     SP_OBJECT_REPR(o)->setAttribute("dy", NULL);
204     SP_OBJECT_REPR(o)->setAttribute("rotate", NULL);
206     for (SPObject *i = sp_object_first_child(o); i != NULL; i = SP_OBJECT_NEXT(i)) {
207         text_remove_all_kerns_recursively(i);
208     }
211 //FIXME: must work with text selection
212 void
213 text_remove_all_kerns()
215     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
217     Inkscape::Selection *selection = sp_desktop_selection(desktop);
219     if (selection->isEmpty()) {
220         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
221         return;
222     }
224     bool did = false;
226     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
227          items != NULL;
228          items = items->next) {
230         if (!SP_IS_TEXT(SP_OBJECT(items->data))) {
231             continue;
232         }
234         text_remove_all_kerns_recursively(SP_OBJECT(items->data));
235         SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
236         did = true;
237     }
239     if (!did) {
240         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
241     } else {
242         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
243                          _("Remove manual kerns"));
244     }
247 void
248 text_flow_into_shape()
250     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
251     if (!desktop)
252         return;
254     SPDocument *doc = sp_desktop_document (desktop);
256     Inkscape::Selection *selection = sp_desktop_selection(desktop);
258     SPItem *text = text_in_selection(selection);
259     SPItem *shape = shape_in_selection(selection);
261     if (!text || !shape || g_slist_length((GSList *) selection->itemList()) < 2) {
262         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text</b> and one or more <b>paths or shapes</b> to flow text into frame."));
263         return;
264     }
266     // remove transform from text, but recursively scale text's fontsize by the expansion
267     SP_TEXT(text)->_adjustFontsizeRecursive(text, NR::expansion(SP_ITEM(text)->transform));
268     SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
270     Inkscape::XML::Node *root_repr = sp_repr_new("svg:flowRoot");
271     root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
272     root_repr->setAttribute("style", SP_OBJECT_REPR(text)->attribute("style")); // fixme: transfer style attrs too
273     SP_OBJECT_REPR(SP_OBJECT_PARENT(shape))->appendChild(root_repr);
274     SPObject *root_object = doc->getObjectByRepr(root_repr);
275     g_return_if_fail(SP_IS_FLOWTEXT(root_object));
277     Inkscape::XML::Node *region_repr = sp_repr_new("svg:flowRegion");
278     root_repr->appendChild(region_repr);
279     SPObject *object = doc->getObjectByRepr(region_repr);
280     g_return_if_fail(SP_IS_FLOWREGION(object));
282     /* Add clones */
283     for (GSList *items = (GSList *) selection->itemList();
284          items != NULL;
285          items = items->next) {
286         SPItem *item = SP_ITEM(items->data);
287         if (SP_IS_SHAPE(item)){
288             Inkscape::XML::Node *clone = sp_repr_new("svg:use");
289             clone->setAttribute("x", "0");
290             clone->setAttribute("y", "0");
291             clone->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(item)->attribute("id")));
293             // add the new clone to the region
294             region_repr->appendChild(clone);
295         }
296     }
298     Inkscape::XML::Node *para_repr = sp_repr_new("svg:flowPara");
299     root_repr->appendChild(para_repr);
300     object = doc->getObjectByRepr(para_repr);
301     g_return_if_fail(SP_IS_FLOWPARA(object));
303     Inkscape::Text::Layout const *layout = te_get_layout(text);
304     Glib::ustring text_ustring = sp_te_get_string_multiline(text, layout->begin(), layout->end());
306     Inkscape::XML::Node *text_repr = sp_repr_new_text(text_ustring.c_str()); // FIXME: transfer all formatting! and convert newlines into flowParas!
307     para_repr->appendChild(text_repr);
309     SP_OBJECT(text)->deleteObject (true);
311     sp_document_done(doc, SP_VERB_CONTEXT_TEXT,
312                      _("Flow text into shape"));
314     sp_desktop_selection(desktop)->set(SP_ITEM(root_object));
316     Inkscape::GC::release(root_repr);
317     Inkscape::GC::release(region_repr);
318     Inkscape::GC::release(para_repr);
319     Inkscape::GC::release(text_repr);
322 void
323 text_unflow ()
325     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
326     if (!desktop)
327         return;
329     SPDocument *doc = sp_desktop_document (desktop);
331     Inkscape::Selection *selection = sp_desktop_selection(desktop);
334     if (!flowtext_in_selection(selection) || g_slist_length((GSList *) selection->itemList()) < 1) {
335         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a flowed text</b> to unflow it."));
336         return;
337     }
339     GSList *new_objs = NULL;
340     GSList *old_objs = NULL;
342     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
343          items != NULL;
344          items = items->next) {
346         if (!SP_IS_FLOWTEXT(SP_OBJECT(items->data))) {
347             continue;
348         }
350         SPItem *flowtext = SP_ITEM(items->data);
352         /* Create <text> */
353         Inkscape::XML::Node *rtext = sp_repr_new("svg:text");
354         rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
356         /* Set style */
357         rtext->setAttribute("style", SP_OBJECT_REPR(flowtext)->attribute("style")); // fixme: transfer style attrs too; and from descendants
359         NRRect bbox;
360         sp_item_invoke_bbox(SP_ITEM(flowtext), &bbox, sp_item_i2doc_affine(SP_ITEM(flowtext)), TRUE);
361         NR::Point xy(bbox.x0, bbox.y0);
362         if (xy[NR::X] != 1e18 && xy[NR::Y] != 1e18) {
363             sp_repr_set_svg_double(rtext, "x", xy[NR::X]);
364             sp_repr_set_svg_double(rtext, "y", xy[NR::Y]);
365         }
367         /* Create <tspan> */
368         Inkscape::XML::Node *rtspan = sp_repr_new("svg:tspan");
369         rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
370         rtext->addChild(rtspan, NULL);
372         gchar *text_string = sp_te_get_string_multiline(flowtext);
373         Inkscape::XML::Node *text_repr = sp_repr_new_text(text_string); // FIXME: transfer all formatting!!!
374         free(text_string);
375         rtspan->appendChild(text_repr);
377         SP_OBJECT_REPR(SP_OBJECT_PARENT(flowtext))->appendChild(rtext);
378         SPObject *text_object = doc->getObjectByRepr(rtext);
380         new_objs = g_slist_prepend (new_objs, text_object);
381         old_objs = g_slist_prepend (old_objs, flowtext);
383         Inkscape::GC::release(rtext);
384         Inkscape::GC::release(rtspan);
385         Inkscape::GC::release(text_repr);
386     }
388     selection->clear();
389     selection->setList(new_objs);
390     for (GSList *i = old_objs; i; i = i->next) {
391         SP_OBJECT(i->data)->deleteObject (true);
392     }
394     g_slist_free (old_objs);
395     g_slist_free (new_objs);
397     sp_document_done(doc, SP_VERB_CONTEXT_TEXT, 
398                      _("Unflow flowed text"));
403 /*
404   Local Variables:
405   mode:c++
406   c-file-style:"stroustrup"
407   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
408   indent-tabs-mode:nil
409   fill-column:99
410   End:
411 */
412 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :