diff --git a/share/ui/menus.xml b/share/ui/menus.xml
index a1b777e599bb730fd51bbace7f2d4a9bdd769db2..65654a2bf5c3a66966c43d4200bc357ed2a7c388 100644
--- a/share/ui/menus.xml
+++ b/share/ui/menus.xml
@@ -281,6 +281,7 @@
+
diff --git a/src/object/sp-object.cpp b/src/object/sp-object.cpp
index e44bd435f42c63074339b86ecedfa63617784230..d64905412fc9d535ca2284186578f955e209a18a 100644
--- a/src/object/sp-object.cpp
+++ b/src/object/sp-object.cpp
@@ -224,6 +224,16 @@ gchar const* SPObject::getId() const {
return id;
}
+/**
+ * Returns the id as a url param, in the form 'url(#{id})'
+ */
+std::string SPObject::getUrl() const {
+ if (id) {
+ return std::string("url(#") + id + ")";
+ }
+ return "";
+}
+
Inkscape::XML::Node * SPObject::getRepr() {
return repr;
}
diff --git a/src/object/sp-object.h b/src/object/sp-object.h
index 6388f3ba4b61cc96e395043d7a288751df0e5849..dff1283d588ee5b8f74341861652799c09d89ba3 100644
--- a/src/object/sp-object.h
+++ b/src/object/sp-object.h
@@ -215,6 +215,11 @@ public:
*/
char const* getId() const;
+ /**
+ * Get the id in a URL format.
+ */
+ std::string getUrl() const;
+
/**
* Returns the XML representation of tree
*/
diff --git a/src/object/sp-text.cpp b/src/object/sp-text.cpp
index dad90d9f870ac1cbab9dd894ee37b20b4c7fa610..5ad11a6f17ab50779a49311b1ad67cd69677d3d8 100644
--- a/src/object/sp-text.cpp
+++ b/src/object/sp-text.cpp
@@ -512,7 +512,7 @@ void SPText::_buildLayoutInit()
// Find union of all exclusion shapes
Shape *exclusion_shape = nullptr;
if(style->shape_subtract.set) {
- exclusion_shape = _buildExclusionShape();
+ exclusion_shape = getExclusionShape();
}
// Find inside shape curves
@@ -764,7 +764,7 @@ unsigned SPText::_buildLayoutInput(SPObject *object, Inkscape::Text::Layout::Opt
return length;
}
-Shape* SPText::_buildExclusionShape() const
+Shape* SPText::getExclusionShape() const
{
std::unique_ptr result(new Shape()); // Union of all exclusion shapes
std::unique_ptr shape_temp(new Shape());
diff --git a/src/object/sp-text.h b/src/object/sp-text.h
index 353844b4b64f064c6c233d0d88eb3dadd3e605be..2fe5fdbdd6f7a675ec93ceffc9aa6cc927202441 100644
--- a/src/object/sp-text.h
+++ b/src/object/sp-text.h
@@ -69,6 +69,9 @@ public:
bool _optimizeTextpathText = false;
+ /** Union all exclusion shapes. */
+ Shape* getExclusionShape() const;
+
private:
/** Initializes layout from (i.e. this node). */
@@ -81,9 +84,6 @@ private:
that we don't get a spurious extra one at the end of the flow. */
unsigned _buildLayoutInput(SPObject *object, Inkscape::Text::Layout::OptionalTextTagAttrs const &parent_optional_attrs, unsigned parent_attrs_offset, bool in_textpath);
- /** Union all exclusion shapes. */
- Shape* _buildExclusionShape() const;
-
/** Find first x/y values which may be in a descendent element. */
SVGLength* _getFirstXLength();
SVGLength* _getFirstYLength();
diff --git a/src/text-chemistry.cpp b/src/text-chemistry.cpp
index ff81efce8cbfc8d5e6140316b8c7571a476cde33..0770e22118465153ca0abf6d8b28e0457e630a99 100644
--- a/src/text-chemistry.cpp
+++ b/src/text-chemistry.cpp
@@ -274,6 +274,39 @@ text_remove_all_kerns()
}
}
+void
+text_flow_shape_subtract()
+{
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (!desktop)
+ return;
+
+ SPDocument *doc = desktop->getDocument();
+ Inkscape::Selection *selection = desktop->getSelection();
+ SPItem *text = text_or_flowtext_in_selection(selection);
+
+ if (SP_IS_TEXT(text)) {
+ // Make list of all shapes.
+ Glib::ustring shapes;
+ auto items = selection->items();
+ for (auto item : items) {
+ if (SP_IS_SHAPE(item)) {
+ if (!shapes.empty()) shapes += " ";
+ shapes += item->getUrl();
+ }
+ }
+
+ // Set 'shape-subtract' property.
+ text->style->shape_subtract.read(shapes.c_str());
+ text->updateRepr();
+
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_TEXT, _("Flow text subtract shape"));
+ } else {
+ // SVG 1.2 Flowed Text
+ desktop->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Subtraction not available for SVG 1.2 Flowed text."));
+ }
+}
+
void
text_flow_into_shape()
{
@@ -290,7 +323,7 @@ text_flow_into_shape()
SPItem *shape = shape_in_selection(selection);
if (!text || !shape || boost::distance(selection->items()) < 2) {
- desktop->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select a text and one or more paths or shapes to flow text into frame."));
+ desktop->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select a text and one or more paths or shapes to flow text."));
return;
}
@@ -299,48 +332,34 @@ text_flow_into_shape()
// SVG 2 Text
if (SP_IS_TEXT(text)) {
- unsigned shape_count = 0;
-
// Make list of all shapes.
- Glib::ustring shape_inside;
+ Glib::ustring shapes;
auto items = selection->items();
for (auto item : items) {
if (SP_IS_SHAPE(item)) {
- shape_inside += "url(#";
- shape_inside += item->getId();
- shape_inside += ") ";
-
- // can only take one shape into account for transform compensation
- if (++shape_count > 1)
- continue;
-
- // compensate transform
- auto const new_transform = i2i_affine(item->parent, text->parent);
- auto const ex = text->transform.descrim() / new_transform.descrim();
- static_cast(text)->_adjustFontsizeRecursive(text, ex);
- text->transform = new_transform;
- }
- }
+ if (!shapes.empty()) {
+ shapes += " ";
+ } else {
+ // can only take one shape into account for transform compensation
+ // compensate transform
+ auto const new_transform = i2i_affine(item->parent, text->parent);
+ auto const ex = text->transform.descrim() / new_transform.descrim();
+ static_cast(text)->_adjustFontsizeRecursive(text, ex);
+ text->transform = new_transform;
+ }
- // Remove extra space at end.
- if (shape_inside.length() > 1) {
- shape_inside.erase (shape_inside.length() - 1);
+ shapes += item->getUrl();
+ }
}
// Set 'shape-inside' property.
- text->style->shape_inside.read(shape_inside.c_str());
+ text->style->shape_inside.read(shapes.c_str());
text->style->white_space.read("pre"); // Respect new lines.
-
text->updateRepr();
- }
-
- DocumentUndo::done(doc, SP_VERB_CONTEXT_TEXT,
- _("Flow text into shape"));
-
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_TEXT, _("Flow text into shape"));
+ }
} else {
- // SVG 1.2 Flowed Text
-
if (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text)) {
// remove transform from text, but recursively scale text's fontsize by the expansion
auto ex = i2i_affine(text, shape->parent).descrim();
@@ -402,18 +421,16 @@ text_flow_into_shape()
Inkscape::GC::release(para_repr);
}
}
- }
-
- text->deleteObject(true);
+ }
- DocumentUndo::done(doc, SP_VERB_CONTEXT_TEXT,
- _("Flow text into shape"));
+ text->deleteObject(true);
- desktop->getSelection()->set(SP_ITEM(root_object));
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_TEXT, _("Flow text into shape"));
- Inkscape::GC::release(root_repr);
- Inkscape::GC::release(region_repr);
+ desktop->getSelection()->set(SP_ITEM(root_object));
+ Inkscape::GC::release(root_repr);
+ Inkscape::GC::release(region_repr);
}
}
diff --git a/src/text-chemistry.h b/src/text-chemistry.h
index 5d2df62c8be7b9614adc41a5b90290e0b8bfd241..1c38a9a3ddcde1da5e1c19569b8b2bcd62831b78 100644
--- a/src/text-chemistry.h
+++ b/src/text-chemistry.h
@@ -25,6 +25,7 @@ void text_put_on_path();
void text_remove_from_path();
void text_remove_all_kerns();
void text_flow_into_shape();
+void text_flow_shape_subtract();
void text_unflow();
void flowtext_to_text();
enum text_ref_t { TEXT_REF_DEF = 0x1, TEXT_REF_EXTERNAL = 0x2, TEXT_REF_INTERNAL = 0x4, };
diff --git a/src/ui/shape-editor-knotholders.cpp b/src/ui/shape-editor-knotholders.cpp
index 74805f89bbd44f386bc5cb4a4600f268af5bf8b8..34c26d3a5a47f0be7d7ff264f4907380e3b4bf67 100644
--- a/src/ui/shape-editor-knotholders.cpp
+++ b/src/ui/shape-editor-knotholders.cpp
@@ -1844,6 +1844,113 @@ TextKnotHolderEntityInlineSize::knot_click(unsigned int state)
}
}
+/**
+ * Shape padding editor knot positioned top right corner of first object
+ */
+class TextKnotHolderEntityShapePadding : public KnotHolderEntity {
+public:
+ Geom::Point knot_get() const override;
+ void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
+ void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
+};
+
+Geom::Point
+TextKnotHolderEntityShapePadding::knot_get() const
+{
+ SPText *text = dynamic_cast(item);
+ g_assert(text != nullptr);
+ Geom::Point corner;
+ if (text->has_shape_inside()) {
+ auto shape = text->get_first_shape_dependency();
+ Geom::OptRect bounds = shape->geometricBounds();
+ if (bounds) {
+ corner = (*bounds).corner(1);
+ if (text->style->shape_padding.set) {
+ auto padding = text->style->shape_padding.computed;
+ corner *= Geom::Affine(Geom::Translate(-padding, padding));
+ }
+ corner *= shape->transform;
+ }
+ }
+ return corner;
+}
+
+void
+TextKnotHolderEntityShapePadding::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
+{
+ // Text in a shape: rectangle
+ SPText *text = dynamic_cast(item);
+
+ if (text->has_shape_inside()) {
+ auto shape = text->get_first_shape_dependency();
+ Geom::OptRect bounds = shape->geometricBounds();
+ if (bounds) {
+ Geom::Point const point_a = snap_knot_position(p, state);
+ Geom::Point point_b = point_a * shape->transform.inverse();
+ auto padding = (*bounds).corner(1)[Geom::X] - point_b[Geom::X];
+ gchar *pad = g_strdup_printf("%f", padding);
+ text->style->shape_padding.read(pad);
+ g_free(pad);
+
+ text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ text->updateRepr();
+ }
+ }
+}
+
+
+/**
+ * Shape margin editor knot positioned top right corner of each object
+ */
+class TextKnotHolderEntityShapeMargin : public KnotHolderEntity {
+public:
+ Geom::Point knot_get() const override;
+ void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
+ void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
+ void set_shape(SPShape *shape) { linked_shape = shape; }
+ SPShape *linked_shape;
+};
+
+Geom::Point
+TextKnotHolderEntityShapeMargin::knot_get() const
+{
+ Geom::Point corner;
+ if (linked_shape == nullptr) return corner;
+
+ Geom::OptRect bounds = linked_shape->geometricBounds();
+ if (bounds) {
+ corner = (*bounds).corner(1);
+ if (linked_shape->style->shape_margin.set) {
+ auto margin = linked_shape->style->shape_margin.computed;
+ corner *= Geom::Affine(Geom::Translate(margin, -margin));
+ }
+ corner *= linked_shape->transform;
+ }
+ return corner;
+}
+
+void
+TextKnotHolderEntityShapeMargin::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
+{
+ g_assert(linked_shape != nullptr);
+
+ Geom::OptRect bounds = linked_shape->geometricBounds();
+ if (bounds) {
+ Geom::Point const point_a = snap_knot_position(p, state);
+ Geom::Point point_b = point_a * linked_shape->transform.inverse();
+ auto margin = (*bounds).corner(1)[Geom::X] - point_b[Geom::X];
+ gchar *pad = g_strdup_printf("%f", -margin);
+ linked_shape->style->shape_margin.read(pad);
+ g_free(pad);
+
+ linked_shape->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ linked_shape->updateRepr();
+ }
+}
+
+
+
+
class TextKnotHolderEntityShapeInside : public KnotHolderEntity {
public:
Geom::Point knot_get() const override;
@@ -1860,7 +1967,7 @@ TextKnotHolderEntityShapeInside::knot_get() const
// we have a crash on undo cration so remove assert
// g_assert(text->style->shape_inside.set);
Geom::Point p;
- if (text->style->shape_inside.set) {
+ if (text->has_shape_inside()) {
Geom::OptRect frame = text->get_frame();
if (frame) {
p = (*frame).corner(2);
@@ -1902,11 +2009,33 @@ TextKnotHolder::TextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderRel
if (text->style->shape_inside.set) {
// 'shape-inside'
- TextKnotHolderEntityShapeInside *entity_shapeinside = new TextKnotHolderEntityShapeInside();
- entity_shapeinside->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Text:shapeinside",
- _("Adjust the rectangular region of the text."));
- entity.push_back(entity_shapeinside);
+ if (text->get_first_rectangle()) {
+ auto entity_shapeinside = new TextKnotHolderEntityShapeInside();
+ entity_shapeinside->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Text:shapeinside",
+ _("Adjust the rectangular region of the text."));
+ entity.push_back(entity_shapeinside);
+ }
+
+ auto entity_shapepadding = new TextKnotHolderEntityShapePadding();
+ entity_shapepadding->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Text:shapepadding",
+ _("Adjust the text shape padding."));
+ entity.push_back(entity_shapepadding);
+
+ // Add knots for shape subtraction margins
+ if (text->style->shape_subtract.set) {
+ for (auto *href : text->style->shape_subtract.hrefs) {
+ auto shape = href->getObject();
+ if (dynamic_cast(shape)) {
+ auto entity_shapemargin = new TextKnotHolderEntityShapeMargin();
+ entity_shapemargin->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Text:shapemargin",
+ _("Adjust the shape's text margin."));
+ entity_shapemargin->set_shape(shape);
+ entity_shapemargin->update_knot();
+ entity.push_back(entity_shapemargin);
+ }
+ }
+ }
} else {
// 'inline-size' or normal text
diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp
index 4d0d7f58853195e79901a4c058edab1fdf3be2f4..099cd464da2231d832d188bd1fcbe1f5a641198d 100644
--- a/src/ui/tools/text-tool.cpp
+++ b/src/ui/tools/text-tool.cpp
@@ -42,6 +42,8 @@
#include "display/control/canvas-item-rect.h"
#include "display/control/canvas-item-bpath.h"
#include "display/curve.h"
+#include "livarot/Path.h"
+#include "livarot/Shape.h"
#include "object/sp-flowtext.h"
#include "object/sp-namedview.h"
@@ -118,12 +120,18 @@ void TextTool::setup() {
indicator->set_shadow(0xffffff7f, 1);
indicator->hide();
- // The rectangle box outlining wrapping the shape for text in a shape.
+ // The shape that the text is flowing into
frame = new Inkscape::CanvasItemBpath(desktop->getCanvasControls());
frame->set_fill(0x00 /* zero alpha */, SP_WIND_RULE_NONZERO);
frame->set_stroke(0x0000ff7f);
frame->hide();
+ // A second frame for showing the padding of the above frame
+ padding_frame = new Inkscape::CanvasItemBpath(desktop->getCanvasControls());
+ padding_frame->set_fill(0x00 /* zero alpha */, SP_WIND_RULE_NONZERO);
+ padding_frame->set_stroke(0xccccccdf);
+ padding_frame->hide();
+
this->timeout = g_timeout_add(timeout, (GSourceFunc) sp_text_context_timeout, this);
this->imc = gtk_im_multicontext_new();
@@ -154,10 +162,7 @@ void TextTool::setup() {
this->shape_editor = new ShapeEditor(this->desktop);
SPItem *item = this->desktop->getSelection()->singleItem();
- if (item && (
- (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) ||
- (SP_IS_TEXT(item) && !SP_TEXT(item)->has_shape_inside()) )
- ) {
+ if (item && (SP_IS_FLOWTEXT(item) || SP_IS_TEXT(item))) {
this->shape_editor->set_item(item);
}
@@ -224,6 +229,11 @@ void TextTool::finish() {
this->frame = nullptr;
}
+ if (this->padding_frame) {
+ delete padding_frame;
+ this->padding_frame = nullptr;
+ }
+
for (auto & text_selection_quad : text_selection_quads) {
text_selection_quad->hide();
delete text_selection_quad;
@@ -1480,23 +1490,17 @@ bool sp_text_delete_selection(ToolBase *ec)
void TextTool::_selectionChanged(Inkscape::Selection *selection)
{
g_assert(selection != nullptr);
-
- shape_editor->unset_item();
SPItem *item = selection->singleItem();
- if (item && (
- (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) ||
- (SP_IS_TEXT(item) &&
- !(SP_TEXT(item)->has_shape_inside() && !SP_TEXT(item)->get_first_rectangle()))
- )) {
- shape_editor->set_item(item);
- }
if (this->text && (item != this->text)) {
sp_text_context_forget_text(this);
}
this->text = nullptr;
+ shape_editor->unset_item();
if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
+ shape_editor->set_item(item);
+
this->text = item;
Inkscape::Text::Layout const *layout = te_get_layout(this->text);
if (layout)
@@ -1679,6 +1683,8 @@ static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see)
}
std::vector shapes;
+ Shape *exclusion_shape = nullptr;
+ double padding;
// Frame around text
if (SP_IS_FLOWTEXT(tc->text)) {
@@ -1687,11 +1693,19 @@ static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see)
tc->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit flowed text (%d character%s); Enter to start new paragraph.", "Type or edit flowed text (%d characters%s); Enter to start new paragraph.", nChars), nChars, trunc);
- } else if (SP_IS_TEXT(tc->text)) {
- if (tc->text->style->shape_inside.set) {
- for (auto const *href : tc->text->style->shape_inside.hrefs) {
+ } else if (auto text = dynamic_cast(tc->text)) {
+ if (text->style->shape_inside.set) {
+ for (auto const *href : text->style->shape_inside.hrefs) {
shapes.push_back(href->getObject());
}
+ if (text->style->shape_padding.set) {
+ // Calculate it here so we never show padding on FlowText or non-flowed Text (even if set)
+ padding = text->style->shape_padding.computed;
+ }
+ if(text->style->shape_subtract.set) {
+ // Find union of all exclusion shapes for later use
+ exclusion_shape = text->getExclusionShape();
+ }
} else {
for (SPObject &child : tc->text->children) {
if (auto textpath = dynamic_cast(&child)) {
@@ -1717,11 +1731,50 @@ static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see)
}
if (!curve.is_empty()) {
+
+
+ if (padding) {
+ // See sp-text.cpp function _buildLayoutInit()
+ Path *temp = new Path;
+ Path *padded = new Path;
+
+ temp->LoadPathVector(curve.get_pathvector());
+ temp->OutsideOutline(padded, padding, join_round, butt_straight, 20.0);
+ padded->Convert(0.25); // Convert to polyline
+
+ Shape* sh = new Shape;
+ padded->Fill(sh, 0);
+ Shape *uncross = new Shape;
+ uncross->ConvertToShape(sh);
+
+ // Remove exclusions plus margins from padding frame
+ Shape *copy = new Shape;
+ if (exclusion_shape && exclusion_shape->hasEdges()) {
+ copy->Booleen(uncross, const_cast(exclusion_shape), bool_op_diff);
+ } else {
+ copy->Copy(uncross);
+ }
+ copy->ConvertToForme(padded);
+ padded->Transform(tc->text->i2dt_affine());
+ tc->padding_frame->set_bpath(padded->MakePathVector());
+ tc->padding_frame->show();
+
+ delete temp;
+ delete padded;
+ delete sh;
+ delete uncross;
+ delete copy;
+ } else {
+ tc->padding_frame->hide();
+ }
+
+ // Transform curve after doing padding.
curve.transform(tc->text->i2dt_affine());
tc->frame->set_bpath(&curve);
tc->frame->show();
} else {
tc->frame->hide();
+ tc->padding_frame->hide();
}
} else {
diff --git a/src/ui/tools/text-tool.h b/src/ui/tools/text-tool.h
index ce06d1e29dd30b43b70fa50133ec23446ed9d03f..7026b1fbab504852d30b7455d99c9bc3c00e8d09 100644
--- a/src/ui/tools/text-tool.h
+++ b/src/ui/tools/text-tool.h
@@ -66,6 +66,7 @@ public:
Inkscape::CanvasItemCurve *cursor = nullptr;
Inkscape::CanvasItemRect *indicator = nullptr;
Inkscape::CanvasItemBpath *frame = nullptr; // Highlighting flowtext shapes or textpath path
+ Inkscape::CanvasItemBpath *padding_frame = nullptr; // Highlighting flowtext padding
std::vector text_selection_quads;
gint timeout = 0;
diff --git a/src/verbs.cpp b/src/verbs.cpp
index 4f61898bf7f3ca1336f56f0412c0eaf713a6b423..d84b856f248f5c018a216e2650748b6999ae06cd 100644
--- a/src/verbs.cpp
+++ b/src/verbs.cpp
@@ -1611,6 +1611,9 @@ void ObjectVerb::perform( SPAction *action, void *data)
case SP_VERB_OBJECT_FLOW_TEXT:
text_flow_into_shape();
break;
+ case SP_VERB_OBJECT_FLOW_SUBTRACT:
+ text_flow_shape_subtract();
+ break;
case SP_VERB_OBJECT_UNFLOW_TEXT:
text_unflow();
break;
@@ -2656,6 +2659,9 @@ Verb *Verb::_base_verbs[] = {
new ObjectVerb(SP_VERB_OBJECT_FLOW_TEXT, "ObjectFlowText", N_("_Flow into Frame"),
N_("Put text into a frame (path or shape), creating a flowed text linked to the frame object"),
"text-flow-into-frame"),
+ new ObjectVerb(SP_VERB_OBJECT_FLOW_SUBTRACT, "ObjectFlowSubtract", N_("Set _Subtraction Frames"),
+ N_("Flow text around a frame (path or shape), only available for SVG 2.0 Flow text."),
+ "text-flow-subtract-frame"),
new ObjectVerb(SP_VERB_OBJECT_UNFLOW_TEXT, "ObjectUnFlowText", N_("_Unflow"),
N_("Remove text from frame (creates a single-line text object)"), INKSCAPE_ICON("text-unflow")),
new ObjectVerb(SP_VERB_OBJECT_FLOWTEXT_TO_TEXT, "ObjectFlowtextToText", N_("_Convert to Text"),
diff --git a/src/verbs.h b/src/verbs.h
index d1abce533a537015224124470a2531253bb4c8b6..ee38e8320af30cabd03614842bdb8e2a4444f8da 100644
--- a/src/verbs.h
+++ b/src/verbs.h
@@ -182,6 +182,7 @@ enum {
SP_VERB_OBJECT_FLATTEN,
SP_VERB_OBJECT_TO_CURVE,
SP_VERB_OBJECT_FLOW_TEXT,
+ SP_VERB_OBJECT_FLOW_SUBTRACT,
SP_VERB_OBJECT_UNFLOW_TEXT,
SP_VERB_OBJECT_FLOWTEXT_TO_TEXT,
SP_VERB_OBJECT_FLIP_HORIZONTAL,