Code

Disable the page selector when there's only one page
[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 "desktop.h"
24 #include "document.h"
25 #include "message-stack.h"
26 #include "selection.h"
27 #include "style.h"
28 #include "desktop-handles.h"
29 #include "text-editing.h"
30 #include "text-chemistry.h"
31 #include "sp-flowtext.h"
32 #include "sp-flowregion.h"
33 #include "sp-flowdiv.h"
34 #include "sp-tspan.h"
37 SPItem *
38 text_in_selection(Inkscape::Selection *selection)
39 {
40     for (GSList *items = (GSList *) selection->itemList();
41          items != NULL;
42          items = items->next) {
43         if (SP_IS_TEXT(items->data))
44             return ((SPItem *) items->data);
45     }
46     return NULL;
47 }
49 SPItem *
50 flowtext_in_selection(Inkscape::Selection *selection)
51 {
52     for (GSList *items = (GSList *) selection->itemList();
53          items != NULL;
54          items = items->next) {
55         if (SP_IS_FLOWTEXT(items->data))
56             return ((SPItem *) items->data);
57     }
58     return NULL;
59 }
61 SPItem *
62 text_or_flowtext_in_selection(Inkscape::Selection *selection)
63 {
64     for (GSList *items = (GSList *) selection->itemList();
65          items != NULL;
66          items = items->next) {
67         if (SP_IS_TEXT(items->data) || SP_IS_FLOWTEXT(items->data))
68             return ((SPItem *) items->data);
69     }
70     return NULL;
71 }
73 SPItem *
74 shape_in_selection(Inkscape::Selection *selection)
75 {
76     for (GSList *items = (GSList *) selection->itemList();
77          items != NULL;
78          items = items->next) {
79         if (SP_IS_SHAPE(items->data))
80             return ((SPItem *) items->data);
81     }
82     return NULL;
83 }
85 void
86 text_put_on_path()
87 {
88     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
89     if (!desktop)
90         return;
92     Inkscape::Selection *selection = sp_desktop_selection(desktop);
94     SPItem *text = text_or_flowtext_in_selection(selection);
95     SPItem *shape = shape_in_selection(selection);
97     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
99     if (!text || !shape || g_slist_length((GSList *) selection->itemList()) != 2) {
100         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text and a path</b> to put text on path."));
101         return;
102     }
104     if (SP_IS_TEXT_TEXTPATH(text)) {
105         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."));
106         return;
107     }
109     if (SP_IS_RECT(shape)) {
110         // rect is the only SPShape which is not <path> yet, and thus SVG forbids us from putting text on it
111         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot put text on a rectangle in this version. Convert rectangle to path first."));
112         return;
113     }
115     // if a flowed text is selected, convert it to a regular text object
116     if (SP_IS_FLOWTEXT(text)) {
118         if (!SP_FLOWTEXT(text)->layout.outputExists()) {
119             sp_desktop_message_stack(desktop)->
120                 flash(Inkscape::WARNING_MESSAGE, 
121                       _("The flowed text(s) must be <b>visible</b> in order to be put on a path."));
122         }
124         Inkscape::XML::Node *repr = SP_FLOWTEXT(text)->getAsText();
126         if (!repr) return;
128         Inkscape::XML::Node *parent = SP_OBJECT_REPR(text)->parent();
129         parent->appendChild(repr);
131         SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
132         sp_item_write_transform(new_item, repr, text->transform);
133         SP_OBJECT(new_item)->updateRepr();
135         Inkscape::GC::release(repr);
136         text->deleteObject(); // delete the orignal flowtext
138         sp_document_ensure_up_to_date(sp_desktop_document(desktop));
140         selection->clear();
142         text = new_item; // point to the new text
143     }
145     Inkscape::Text::Layout const *layout = te_get_layout(text);
146     Inkscape::Text::Layout::Alignment text_alignment = layout->paragraphAlignment(layout->begin());
148     // remove transform from text, but recursively scale text's fontsize by the expansion
149     SP_TEXT(text)->_adjustFontsizeRecursive (text, NR::expansion(SP_ITEM(text)->transform));
150     SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
152     // make a list of text children
153     GSList *text_reprs = NULL;
154     for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
155         text_reprs = g_slist_prepend(text_reprs, SP_OBJECT_REPR(o));
156     }
158     // create textPath and put it into the text
159     Inkscape::XML::Node *textpath = xml_doc->createElement("svg:textPath");
160     // reference the shape
161     textpath->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(shape)->attribute("id")));
162     if (text_alignment == Inkscape::Text::Layout::RIGHT)
163         textpath->setAttribute("startOffset", "100%");
164     else if (text_alignment == Inkscape::Text::Layout::CENTER)
165         textpath->setAttribute("startOffset", "50%");
166     SP_OBJECT_REPR(text)->addChild(textpath, NULL);
168     for ( GSList *i = text_reprs ; i ; i = i->next ) {
169         // Make a copy of each text child
170         Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate(xml_doc);
171         // We cannot have multiline in textpath, so remove line attrs from tspans
172         if (!strcmp(copy->name(), "svg:tspan")) {
173             copy->setAttribute("sodipodi:role", NULL);
174             copy->setAttribute("x", NULL);
175             copy->setAttribute("y", NULL);
176         }
177         // remove the old repr from under text
178         SP_OBJECT_REPR(text)->removeChild((Inkscape::XML::Node *) i->data);
179         // put its copy into under textPath
180         textpath->addChild(copy, NULL); // fixme: copy id
181     }
183     // x/y are useless with textpath, and confuse Batik 1.5
184     SP_OBJECT_REPR(text)->setAttribute("x", NULL);
185     SP_OBJECT_REPR(text)->setAttribute("y", NULL);
187     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
188                      _("Put text on path"));
189     g_slist_free(text_reprs);
192 void
193 text_remove_from_path()
195     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
197     Inkscape::Selection *selection = sp_desktop_selection(desktop);
199     if (selection->isEmpty()) {
200         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text on path</b> to remove it from path."));
201         return;
202     }
204     bool did = false;
206     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
207          items != NULL;
208          items = items->next) {
210         if (!SP_IS_TEXT_TEXTPATH(SP_OBJECT(items->data))) {
211             continue;
212         }
214         SPObject *tp = sp_object_first_child(SP_OBJECT(items->data));
216         did = true;
218         sp_textpath_to_text(tp);
219     }
221     if (!did) {
222         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No texts-on-paths</b> in the selection."));
223     } else {
224         selection->setList(g_slist_copy((GSList *) selection->itemList())); // reselect to update statusbar description
225         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
226                          _("Remove text from path"));
227     }
230 void
231 text_remove_all_kerns_recursively(SPObject *o)
233     SP_OBJECT_REPR(o)->setAttribute("dx", NULL);
234     SP_OBJECT_REPR(o)->setAttribute("dy", NULL);
235     SP_OBJECT_REPR(o)->setAttribute("rotate", NULL);
237     // if x contains a list, leave only the first value
238     gchar *x = (gchar *) SP_OBJECT_REPR(o)->attribute("x");
239     if (x) {
240         gchar **xa_space = g_strsplit(x, " ", 0);
241         gchar **xa_comma = g_strsplit(x, ",", 0);
242         if (xa_space && *xa_space && *(xa_space + 1)) {
243             SP_OBJECT_REPR(o)->setAttribute("x", g_strdup(*xa_space));
244         } else if (xa_comma && *xa_comma && *(xa_comma + 1)) {
245             SP_OBJECT_REPR(o)->setAttribute("x", g_strdup(*xa_comma));
246         }
247         g_strfreev(xa_space);
248         g_strfreev(xa_comma);
249     }
251     for (SPObject *i = sp_object_first_child(o); i != NULL; i = SP_OBJECT_NEXT(i)) {
252         text_remove_all_kerns_recursively(i);
253     }
256 //FIXME: must work with text selection
257 void
258 text_remove_all_kerns()
260     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
262     Inkscape::Selection *selection = sp_desktop_selection(desktop);
264     if (selection->isEmpty()) {
265         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
266         return;
267     }
269     bool did = false;
271     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
272          items != NULL;
273          items = items->next) {
274         SPObject *obj = SP_OBJECT(items->data);
276         if (!SP_IS_TEXT(obj) && !SP_IS_TSPAN(obj) && !SP_IS_FLOWTEXT(obj)) {
277             continue;
278         }
280         text_remove_all_kerns_recursively(obj);
281         obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
282         did = true;
283     }
285     if (!did) {
286         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
287     } else {
288         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
289                          _("Remove manual kerns"));
290     }
293 void
294 text_flow_into_shape()
296     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
297     if (!desktop)
298         return;
300     SPDocument *doc = sp_desktop_document (desktop);
301     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
303     Inkscape::Selection *selection = sp_desktop_selection(desktop);
305     SPItem *text = text_or_flowtext_in_selection(selection);
306     SPItem *shape = shape_in_selection(selection);
308     if (!text || !shape || g_slist_length((GSList *) selection->itemList()) < 2) {
309         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."));
310         return;
311     }
313     if (SP_IS_TEXT(text)) {
314       // remove transform from text, but recursively scale text's fontsize by the expansion
315       SP_TEXT(text)->_adjustFontsizeRecursive(text, NR::expansion(SP_ITEM(text)->transform));
316       SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
317     }
319     Inkscape::XML::Node *root_repr = xml_doc->createElement("svg:flowRoot");
320     root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
321     root_repr->setAttribute("style", SP_OBJECT_REPR(text)->attribute("style")); // fixme: transfer style attrs too
322     SP_OBJECT_REPR(SP_OBJECT_PARENT(shape))->appendChild(root_repr);
323     SPObject *root_object = doc->getObjectByRepr(root_repr);
324     g_return_if_fail(SP_IS_FLOWTEXT(root_object));
326     Inkscape::XML::Node *region_repr = xml_doc->createElement("svg:flowRegion");
327     root_repr->appendChild(region_repr);
328     SPObject *object = doc->getObjectByRepr(region_repr);
329     g_return_if_fail(SP_IS_FLOWREGION(object));
331     /* Add clones */
332     for (GSList *items = (GSList *) selection->itemList();
333          items != NULL;
334          items = items->next) {
335         SPItem *item = SP_ITEM(items->data);
336         if (SP_IS_SHAPE(item)){
337             Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
338             clone->setAttribute("x", "0");
339             clone->setAttribute("y", "0");
340             clone->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(item)->attribute("id")));
342             // add the new clone to the region
343             region_repr->appendChild(clone);
344         }
345     }
347     if (SP_IS_TEXT(text)) { // flow from text, as string
348         Inkscape::XML::Node *para_repr = xml_doc->createElement("svg:flowPara");
349         root_repr->appendChild(para_repr);
350         object = doc->getObjectByRepr(para_repr);
351         g_return_if_fail(SP_IS_FLOWPARA(object));
353         Inkscape::Text::Layout const *layout = te_get_layout(text);
354         Glib::ustring text_ustring = sp_te_get_string_multiline(text, layout->begin(), layout->end());
356         Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_ustring.c_str()); // FIXME: transfer all formatting! and convert newlines into flowParas!
357         para_repr->appendChild(text_repr);
359         Inkscape::GC::release(para_repr);
360         Inkscape::GC::release(text_repr);
362     } else { // reflow an already flowed text, preserving paras
363         for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
364             if (SP_IS_FLOWPARA(o)) {
365                 Inkscape::XML::Node *para_repr = SP_OBJECT_REPR(o)->duplicate(xml_doc);
366                 root_repr->appendChild(para_repr);
367                 object = doc->getObjectByRepr(para_repr);
368                 g_return_if_fail(SP_IS_FLOWPARA(object));
369                 Inkscape::GC::release(para_repr);
370             }
371         }
372     }
374     SP_OBJECT(text)->deleteObject (true);
376     sp_document_done(doc, SP_VERB_CONTEXT_TEXT,
377                      _("Flow text into shape"));
379     sp_desktop_selection(desktop)->set(SP_ITEM(root_object));
381     Inkscape::GC::release(root_repr);
382     Inkscape::GC::release(region_repr);
385 void
386 text_unflow ()
388     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
389     if (!desktop)
390         return;
392     SPDocument *doc = sp_desktop_document (desktop);
393     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
395     Inkscape::Selection *selection = sp_desktop_selection(desktop);
398     if (!flowtext_in_selection(selection) || g_slist_length((GSList *) selection->itemList()) < 1) {
399         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a flowed text</b> to unflow it."));
400         return;
401     }
403     GSList *new_objs = NULL;
404     GSList *old_objs = NULL;
406     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
407          items != NULL;
408          items = items->next) {
410         if (!SP_IS_FLOWTEXT(SP_OBJECT(items->data))) {
411             continue;
412         }
414         SPItem *flowtext = SP_ITEM(items->data);
416         if (sp_te_get_string_multiline(flowtext) == NULL) { // flowtext is empty
417             continue;
418         }
420         /* Create <text> */
421         Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
422         rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
424         /* Set style */
425         rtext->setAttribute("style", SP_OBJECT_REPR(flowtext)->attribute("style")); // fixme: transfer style attrs too; and from descendants
427         NRRect bbox;
428         sp_item_invoke_bbox(SP_ITEM(flowtext), &bbox, sp_item_i2doc_affine(SP_ITEM(flowtext)), TRUE);
429         NR::Point xy(bbox.x0, bbox.y0);
430         if (xy[NR::X] != 1e18 && xy[NR::Y] != 1e18) {
431             sp_repr_set_svg_double(rtext, "x", xy[NR::X]);
432             sp_repr_set_svg_double(rtext, "y", xy[NR::Y]);
433         }
435         /* Create <tspan> */
436         Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
437         rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
438         rtext->addChild(rtspan, NULL);
440         gchar *text_string = sp_te_get_string_multiline(flowtext);
441         Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_string); // FIXME: transfer all formatting!!!
442         free(text_string);
443         rtspan->appendChild(text_repr);
445         SP_OBJECT_REPR(SP_OBJECT_PARENT(flowtext))->appendChild(rtext);
446         SPObject *text_object = doc->getObjectByRepr(rtext);
448         new_objs = g_slist_prepend (new_objs, text_object);
449         old_objs = g_slist_prepend (old_objs, flowtext);
451         Inkscape::GC::release(rtext);
452         Inkscape::GC::release(rtspan);
453         Inkscape::GC::release(text_repr);
454     }
456     selection->clear();
457     selection->setList(new_objs);
458     for (GSList *i = old_objs; i; i = i->next) {
459         SP_OBJECT(i->data)->deleteObject (true);
460     }
462     g_slist_free (old_objs);
463     g_slist_free (new_objs);
465     sp_document_done(doc, SP_VERB_CONTEXT_TEXT, 
466                      _("Unflow flowed text"));
469 void
470 flowtext_to_text()
472     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
474     Inkscape::Selection *selection = sp_desktop_selection(desktop);
476     if (selection->isEmpty()) {
477         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, 
478                                                  _("Select <b>flowed text(s)</b> to convert."));
479         return;
480     }
482     bool did = false;
484     GSList *reprs = NULL;
485     GSList *items = g_slist_copy((GSList *) selection->itemList());
486     for (; items != NULL; items = items->next) {
487         
488         SPItem *item = (SPItem *) items->data;
490         if (!SP_IS_FLOWTEXT(item))
491             continue;
493         if (!SP_FLOWTEXT(item)->layout.outputExists()) {
494             sp_desktop_message_stack(desktop)->
495                 flash(Inkscape::WARNING_MESSAGE, 
496                       _("The flowed text(s) must be <b>visible</b> in order to be converted."));
497             return;
498         }
500         Inkscape::XML::Node *repr = SP_FLOWTEXT(item)->getAsText();
502         if (!repr) break;
504         did = true;
506         Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
507         parent->appendChild(repr);
509         SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
510         sp_item_write_transform(new_item, repr, item->transform);
511         SP_OBJECT(new_item)->updateRepr();
512     
513         Inkscape::GC::release(repr);
514         item->deleteObject();
516         reprs = g_slist_prepend(reprs, repr);
517     }
519     g_slist_free(items);
521     if (did) {
522         sp_document_done(sp_desktop_document(desktop), 
523                          SP_VERB_OBJECT_FLOWTEXT_TO_TEXT,
524                          _("Convert flowed text to text"));
525         selection->setReprList(reprs);        
526     } else {
527         sp_desktop_message_stack(desktop)->
528             flash(Inkscape::ERROR_MESSAGE,
529                   _("<b>No flowed text(s)</b> to convert in the selection."));
530     }
532     g_slist_free(reprs);
536 /*
537   Local Variables:
538   mode:c++
539   c-file-style:"stroustrup"
540   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
541   indent-tabs-mode:nil
542   fill-column:99
543   End:
544 */
545 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :