summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTavmjong Bah <tavmjong@free.fr>2018-12-14 20:24:10 +0000
committerTavmjong Bah <tavmjong@free.fr>2018-12-14 20:24:10 +0000
commita2ed0d2066f87c1062db97b36c25791bb5c48605 (patch)
tree43cc195c410a7027c974ed0f3a24d50128006386 /src
parentDocumentUndo::ScopedInsensitive (diff)
downloadinkscape-a2ed0d2066f87c1062db97b36c25791bb5c48605.tar.gz
inkscape-a2ed0d2066f87c1062db97b36c25791bb5c48605.zip
Round-trip SVG 2 flowed text with SVG 1.1 backup.
Diffstat (limited to 'src')
-rw-r--r--src/extension/internal/svg.cpp106
-rw-r--r--src/object/sp-text.cpp333
-rw-r--r--src/object/sp-text.h12
-rw-r--r--src/text-tag-attributes.h6
4 files changed, 364 insertions, 93 deletions
diff --git a/src/extension/internal/svg.cpp b/src/extension/internal/svg.cpp
index 8fe87ad4d..29e5b0e3e 100644
--- a/src/extension/internal/svg.cpp
+++ b/src/extension/internal/svg.cpp
@@ -289,6 +289,7 @@ static void remove_marker_context_paint (Inkscape::XML::Node *repr,
}
sp_repr_css_set(child, css, "style");
+ sp_repr_css_attr_unref(css);
}
defs->addChild(marker_fixed, repr);
@@ -298,6 +299,7 @@ static void remove_marker_context_paint (Inkscape::XML::Node *repr,
Glib::ustring marker_value = "url(#" + marker_fixed_id + ")";
sp_repr_css_set_property (css, property.c_str(), marker_value.c_str());
sp_repr_css_set (it, css, "style");
+ sp_repr_css_attr_unref(css);
}
}
@@ -329,6 +331,7 @@ static void remove_marker_context_paint (Inkscape::XML::Node *repr,
need_to_fix = true;
break;
}
+ sp_repr_css_attr_unref(css);
}
if (need_to_fix) {
@@ -344,7 +347,7 @@ static void remove_marker_context_paint (Inkscape::XML::Node *repr,
}
/*
- * Recursively insert SVG 1.1 fallback for SVG 2 text (ignored by SVG 2 renderers).
+ * Recursively insert SVG 1.1 fallback for SVG 2 text (ignored by SVG 2 renderers including ours).
* Notes:
* Text must have been layed out. Access via old document.
*/
@@ -357,17 +360,6 @@ static void insert_text_fallback( Inkscape::XML::Node *repr, SPDocument *doc, In
auto id = repr->attribute("id");
// std::cout << "insert_text_fallback: found text! id: " << (id?id:"null") << std::endl;
- // See if we need to do anything (i.e. do we have SVG 2 text?).
- SPCSSAttr* css = sp_repr_css_attr_inherited (repr, "style");
- Glib::ustring inline_size = sp_repr_css_property (css, "inline-size", "");
- Glib::ustring shape_inside = sp_repr_css_property (css, "shape-inside", "");
-
- // No SVG 2 text, nothing to do.
- if (inline_size.length() == 0 &&
- shape_inside.length() == 0) {
- return;
- }
-
// We need to get SPText object to access layout.
SPText* text = static_cast<SPText *>(doc->getObjectById( id ));
if (text == nullptr) {
@@ -375,6 +367,12 @@ static void insert_text_fallback( Inkscape::XML::Node *repr, SPDocument *doc, In
return;
}
+ if (!text->has_inline_size() &&
+ !text->has_shape_inside()) {
+ // No SVG 2 text, nothing to do.
+ return;
+ }
+
// We will keep this text node but replace all children.
// Make a list of children to delete at end:
@@ -386,10 +384,14 @@ static void insert_text_fallback( Inkscape::XML::Node *repr, SPDocument *doc, In
// For round-tripping, xml:space (or 'white-space:pre') must be set.
repr->setAttribute("xml:space", "preserve");
- // Set 'x' and 'y' on <text>
- Geom::Point anchor_point = text->layout.characterAnchorPoint(text->layout.begin());
- sp_repr_set_svg_double(repr, "x", anchor_point[Geom::X]);
- sp_repr_set_svg_double(repr, "y", anchor_point[Geom::Y]);
+ Geom::Point text_anchor_point = text->layout.characterAnchorPoint(text->layout.begin());
+ // std::cout << " text_anchor_point: " << text_anchor_point << std::endl;
+
+ double text_x = 0.0;
+ double text_y = 0.0;
+ sp_repr_get_double(repr, "x", &text_x);
+ sp_repr_get_double(repr, "y", &text_y);
+ // std::cout << "text_x: " << text_x << " text_y: " << text_y << std::endl;
// Loop over all lines in layout.
for (auto it = text->layout.begin() ; it != text->layout.end() ; ) {
@@ -400,59 +402,71 @@ static void insert_text_fallback( Inkscape::XML::Node *repr, SPDocument *doc, In
// This could be useful if one wants to edit in an old version of Inkscape but we need to check if it breaks anything:
// line_tspan->setAttribute("sodipodi:role", "line");
- // Inside line <tspan>, create <tspan>s for each change of style or shift.
+ Geom::Point line_anchor_point = text->layout.characterAnchorPoint(it);
+ double line_x = line_anchor_point[Geom::X];
+ double line_y = line_anchor_point[Geom::Y];
+ if (!text->is_horizontal()) {
+ std::swap(line_x, line_y); // Anchor points rotated & y inverted in vertical layout.
+ }
+
+ // std::cout << " line_anchor_point: " << line_anchor_point << std::endl;
+ if (line_tspan->childCount() == 0) {
+ if (text->is_horizontal()) {
+ // std::cout << " horizontal: " << text_x << " " << line_anchor_point[Geom::Y] << std::endl;
+ if (text->has_inline_size()) {
+ // We use text_x as this is the reference for 'text-anchor'
+ // (line_x is the start of the line which gives wrong position when 'text-anchor' not start).
+ sp_repr_set_svg_double(line_tspan, "x", text_x);
+ } else {
+ // shape-inside (we don't have to worry about 'text-anchor').
+ sp_repr_set_svg_double(line_tspan, "x", line_x);
+ }
+ sp_repr_set_svg_double(line_tspan, "y", line_y); // FIXME: this will pick up the wrong end of counter-directional runs
+ } else {
+ // std::cout << " vertical: " << line_anchor_point[Geom::X] << " " << text_y << std::endl;
+ sp_repr_set_svg_double(line_tspan, "x", line_x); // FIXME: this will pick up the wrong end of counter-directional runs
+ if (text->has_inline_size()) {
+ sp_repr_set_svg_double(line_tspan, "y", text_y);
+ } else {
+ sp_repr_set_svg_double(line_tspan, "y", line_y);
+ }
+ }
+ }
+
+ // Inside line <tspan>, create <tspan>s for each change of style or shift. (No shifts in SVG 2 flowed text.)
// For simple lines, this creates an unneeded <tspan> but so be it.
Inkscape::Text::Layout::iterator it_line_end = it;
it_line_end.nextStartOfLine();
+ // Loop over chunks in line
while (it != it_line_end) {
Inkscape::XML::Node *span_tspan = repr->document()->createElement("svg:tspan");
- Geom::Point anchor_point = text->layout.characterAnchorPoint(it);
+
// use kerning to simulate justification and whatnot
Inkscape::Text::Layout::iterator it_span_end = it;
it_span_end.nextStartOfSpan();
Inkscape::Text::Layout::OptionalTextTagAttrs attrs;
text->layout.simulateLayoutUsingKerning(it, it_span_end, &attrs);
- // set x,y attributes only when we need to
- bool set_x = false;
- bool set_y = false;
- if (!text->transform.isIdentity()) {
- set_x = set_y = true;
- } else {
- Inkscape::Text::Layout::iterator it_chunk_start = it;
- it_chunk_start.thisStartOfChunk();
- if (it == it_chunk_start) {
- set_x = true;
- // don't set y so linespacing adjustments and things will still work
- }
- Inkscape::Text::Layout::iterator it_shape_start = it;
- it_shape_start.thisStartOfShape();
- if (it == it_shape_start)
- set_y = true;
+
+ // 'dx' and 'dy' attributes are used to simulated justified text.
+ if (!text->is_horizontal()) {
+ std::swap(attrs.dx, attrs.dy);
}
- if (set_x && !attrs.dx.empty())
- attrs.dx[0] = 0.0;
TextTagAttributes(attrs).writeTo(span_tspan);
- if (set_x)
- sp_repr_set_svg_double(span_tspan, "x", anchor_point[Geom::X]); // FIXME: this will pick up the wrong end of counter-directional runs
- if (set_y)
- sp_repr_set_svg_double(span_tspan, "y", anchor_point[Geom::Y]);
- if (line_tspan->childCount() == 0) {
- sp_repr_set_svg_double(line_tspan, "x", anchor_point[Geom::X]); // FIXME: this will pick up the wrong end of counter-directional runs
- sp_repr_set_svg_double(line_tspan, "y", anchor_point[Geom::Y]);
- }
void *rawptr = nullptr;
Glib::ustring::iterator span_text_start_iter;
text->layout.getSourceOfCharacter(it, &rawptr, &span_text_start_iter);
SPObject *source_obj = reinterpret_cast<SPObject *>(rawptr);
+ // Set tspan style
Glib::ustring style_text = (dynamic_cast<SPString *>(source_obj) ? source_obj->parent : source_obj)->style->write( SP_STYLE_FLAG_IFDIFF, SP_STYLE_SRC_UNSET, text->style);
if (!style_text.empty()) {
span_tspan->setAttribute("style", style_text.c_str());
}
+ // Add text node
SPString *str = dynamic_cast<SPString *>(source_obj);
if (str) {
Glib::ustring *string = &(str->string); // TODO fixme: dangerous, unsafe premature-optimization
@@ -481,10 +495,12 @@ static void insert_text_fallback( Inkscape::XML::Node *repr, SPDocument *doc, In
}
it = it_span_end;
+ // Add tspan to document
line_tspan->appendChild(span_tspan);
Inkscape::GC::release(span_tspan);
}
+ // Add line tspan to document
repr->appendChild(line_tspan);
Inkscape::GC::release(line_tspan);
}
@@ -590,6 +606,8 @@ static void transform_2_to_1( Inkscape::XML::Node *repr, Inkscape::XML::Node *de
for ( Node *child = repr->firstChild(); child; child = child->next() ) {
transform_2_to_1 (child, defs);
}
+
+ sp_repr_css_attr_unref(css);
}
}
diff --git a/src/object/sp-text.cpp b/src/object/sp-text.cpp
index 8098e2196..c12d007ed 100644
--- a/src/object/sp-text.cpp
+++ b/src/object/sp-text.cpp
@@ -30,6 +30,7 @@
#include <glibmm/i18n.h>
#include <glibmm/regex.h>
+
#include "svg/svg.h"
#include "display/drawing-text.h"
#include "attributes.h"
@@ -168,8 +169,7 @@ void SPText::update(SPCtx *ctx, guint flags) {
// Set inline_size computed value if necessary (i.e. if unit is %).
if (style->inline_size.set) {
if (style->inline_size.unit == SP_CSS_UNIT_PERCENT) {
- if (style->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB ||
- style->writing_mode.computed == SP_CSS_WRITING_MODE_RL_TB) {
+ if (is_horizontal()) {
style->inline_size.computed = style->inline_size.value * ictx->viewport.width();
} else {
style->inline_size.computed = style->inline_size.value * ictx->viewport.height();
@@ -319,7 +319,13 @@ void SPText::hide(unsigned int key) {
}
const char* SPText::displayName() const {
- return _("Text");
+ if (has_inline_size()) {
+ return _("Auto-wrapped text");
+ } else if (has_shape_inside()) {
+ return _("Text in-a-shape");
+ } else {
+ return _("Text");
+ }
}
gchar* SPText::description() const {
@@ -546,32 +552,8 @@ void SPText::_buildLayoutInit()
// and the other dimension set to infinity. Text is laid out starting at the 'x' and 'y'
// attribute values. This is handled elsewhere.
- double inline_size = style->inline_size.computed;
- unsigned mode = style->writing_mode.computed;
- unsigned anchor = style->text_anchor.computed;
- unsigned direction = style->direction.computed;
-
- Geom::Rect frame;
- if (mode == SP_CSS_WRITING_MODE_LR_TB ||
- mode == SP_CSS_WRITING_MODE_RL_TB) {
- // horizontal
- frame = Geom::Rect::from_xywh(attributes.firstXY()[Geom::X], -100000, inline_size, 200000);
- if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
- frame *= Geom::Translate (-inline_size/2.0, 0 );
- } else if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_END ) ||
- (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_START) ) {
- frame *= Geom::Translate (-inline_size, 0);
- }
- } else {
- // vertical
- frame = Geom::Rect::from_xywh(-100000, attributes.firstXY()[Geom::Y], 200000, inline_size);
- if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
- frame *= Geom::Translate (0, -inline_size/2.0);
- } else if (anchor == SP_CSS_TEXT_ANCHOR_END) {
- frame *= Geom::Translate (0, -inline_size);
- }
- }
- // std::cout << " inline_size frame: " << frame << std::endl;
+ Geom::OptRect opt_frame = get_frame();
+ Geom::Rect frame = *opt_frame;
Shape *shape = new Shape;
shape->Reset();
@@ -606,16 +588,21 @@ unsigned SPText::_buildLayoutInput(SPObject *object, Inkscape::Text::Layout::Opt
}
if (SP_IS_TEXT(object)) {
- SP_TEXT(object)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true);
- // SVG 2 Text wrapping
- if (layout.wrap_mode == Inkscape::Text::Layout::WRAP_SHAPE_INSIDE) {
+ bool use_xy = true;
+ bool use_dxdyrotate = true;
+
+ // SVG 2 Text wrapping.
+ if (layout.wrap_mode == Inkscape::Text::Layout::WRAP_SHAPE_INSIDE ||
+ layout.wrap_mode == Inkscape::Text::Layout::WRAP_INLINE_SIZE) {
+ use_xy = false;
+ use_dxdyrotate = false;
+ }
- // 'x' and 'y' attributes are always ignored.
- optional_attrs.x.clear();
- optional_attrs.y.clear();
+ SP_TEXT(object)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, use_xy, use_dxdyrotate);
- } else if (layout.wrap_mode == Inkscape::Text::Layout::WRAP_INLINE_SIZE) {
+ // SVG 2 Text wrapping
+ if (layout.wrap_mode == Inkscape::Text::Layout::WRAP_INLINE_SIZE) {
// For horizontal text:
// 'x' is used to calculate the left/right edges of the rectangle but is not
@@ -629,20 +616,24 @@ unsigned SPText::_buildLayoutInput(SPObject *object, Inkscape::Text::Layout::Opt
// if not defined in the <text> element, use the 'x' and 'y' from the first child.
// We only look at the <text> element. (Doing otherwise means tracking if
// we've found 'x' and 'y' and then creating the Shape at the end.)
- if (style->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB ||
- style->writing_mode.computed == SP_CSS_WRITING_MODE_RL_TB) {
+ if (is_horizontal()) {
// Horizontal text
- optional_attrs.x.clear();
- if (optional_attrs.y.size() > 0) {
- optional_attrs.y.resize(1); // Keep only first
+ SVGLength* y = attributes.getFirstYLength();
+ if (y) {
+ optional_attrs.y.push_back(*y);
+ } else {
+ std::cerr << "SPText::_buildLayoutInput: No 'y' attribute value with horizontal 'inline-size'!" << std::endl;
}
} else {
// Vertical text
- if (optional_attrs.x.size() > 0) {
- optional_attrs.x.resize(1); // Keep only first
+ SVGLength* x = attributes.getFirstXLength();
+ if (x) {
+ optional_attrs.x.push_back(*x);
+ } else {
+ std::cerr << "SPText::_buildLayoutInput: No 'x' attribute value with vertical 'inline-size'!" << std::endl;
}
- optional_attrs.y.clear();
}
+
}
// set textLength on the entire layout, see note in TNG-Layout.h
@@ -657,19 +648,20 @@ unsigned SPText::_buildLayoutInput(SPObject *object, Inkscape::Text::Layout::Opt
else if (SP_IS_TSPAN(object)) {
SPTSpan *tspan = SP_TSPAN(object);
+
// x, y attributes are stripped from some tspans marked with role="line" as we do our own line layout.
// This should be checked carefully, as it can undo line layout in imported SVG files.
bool use_xy = !in_textpath && (tspan->role == SP_TSPAN_ROLE_UNSPECIFIED || !tspan->attributes.singleXYCoordinates());
- tspan->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, use_xy, true);
+ bool use_dxdyrotate = true;
// SVG 2 Text wrapping: see comment above.
if (layout.wrap_mode == Inkscape::Text::Layout::WRAP_SHAPE_INSIDE ||
layout.wrap_mode == Inkscape::Text::Layout::WRAP_INLINE_SIZE) {
-
- // 'x' and 'y' attributes are always ignored.
- optional_attrs.x.clear();
- optional_attrs.y.clear();
+ use_xy = false;
+ use_dxdyrotate = false;
}
+
+ tspan->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, use_xy, use_dxdyrotate);
}
else if (SP_IS_TREF(object)) {
@@ -876,6 +868,225 @@ void SPText::_clearFlow(Inkscape::DrawingGroup *in_arena)
in_arena->clearChildren();
}
+bool SPText::is_horizontal() const
+{
+ unsigned mode = style->writing_mode.computed;
+ return (mode == SP_CSS_WRITING_MODE_LR_TB || mode == SP_CSS_WRITING_MODE_RL_TB);
+}
+
+bool SPText::has_inline_size() const
+{
+ return (style->inline_size.set);
+}
+
+bool SPText::has_shape_inside() const
+{
+ return (style->shape_inside.set);
+}
+
+// Gets rectangle defined by <text> x, y and inline-size ("infinite" in one direction).
+Geom::OptRect SPText::get_frame()
+{
+ Geom::OptRect opt_frame;
+ Geom::Rect frame;
+
+ if (style->inline_size.set) {
+ double inline_size = style->inline_size.computed;
+ unsigned mode = style->writing_mode.computed;
+ unsigned anchor = style->text_anchor.computed;
+ unsigned direction = style->direction.computed;
+
+ if (is_horizontal()) {
+ // horizontal
+ frame = Geom::Rect::from_xywh(attributes.firstXY()[Geom::X], -100000, inline_size, 200000);
+ if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
+ frame *= Geom::Translate (-inline_size/2.0, 0 );
+ } else if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_END ) ||
+ (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_START) ) {
+ frame *= Geom::Translate (-inline_size, 0);
+ }
+ } else {
+ // vertical
+ frame = Geom::Rect::from_xywh(-100000, attributes.firstXY()[Geom::Y], 200000, inline_size);
+ if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
+ frame *= Geom::Translate (0, -inline_size/2.0);
+ } else if (anchor == SP_CSS_TEXT_ANCHOR_END) {
+ frame *= Geom::Translate (0, -inline_size);
+ }
+ }
+
+ opt_frame = frame;
+
+ } else {
+ // See if 'shape-inside' has rectangle
+ Inkscape::XML::Node* rectangle = get_first_rectangle();
+
+ if (rectangle) {
+ double x = 0.0;
+ double y = 0.0;
+ double width = 0.0;
+ double height = 0.0;
+ sp_repr_get_double (rectangle, "x", &x);
+ sp_repr_get_double (rectangle, "y", &y);
+ sp_repr_get_double (rectangle, "width", &width);
+ sp_repr_get_double (rectangle, "height", &height);
+ frame = Geom::Rect::from_xywh( x, y, width, height);
+ opt_frame = frame;
+ }
+ }
+
+ return opt_frame;
+}
+
+// Find the node of the first rectangle (if it exists) in 'shape-inside'.
+Inkscape::XML::Node* SPText::get_first_rectangle()
+{
+ Inkscape::XML::Node* first_rectangle = nullptr;
+
+ Inkscape::XML::Node *our_ref = getRepr();
+
+ if (style->shape_inside.set && style->shape_inside.value) {
+
+ std::vector<Glib::ustring> shapes = get_shapes();
+
+ for (auto shape: shapes) {
+
+ Inkscape::XML::Node *item =
+ sp_repr_lookup_descendant (our_ref->root(), "id", shape.c_str());
+
+ if (item && strncmp("svg:rect", item->name(), 8) == 0) {
+ return item;
+ break;
+ }
+ }
+ }
+
+ return first_rectangle;
+}
+
+// Get a list of shape in 'shape-inside' as a vector of strings.
+std::vector<Glib::ustring> SPText::get_shapes() const
+{
+ std::vector<Glib::ustring> shapes;
+ if (style->shape_inside.set && style->shape_inside.value) {
+
+ static Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("url\\(#([A-z0-9#]*)\\)");
+ Glib::MatchInfo matchInfo;
+ regex->match(style->shape_inside.value, matchInfo);
+
+ while (matchInfo.matches()) {
+ shapes.push_back(matchInfo.fetch(1));
+ matchInfo.next();
+ }
+ }
+
+ return shapes;
+}
+
+
+SPItem *create_text_with_inline_size (SPDesktop *desktop, Geom::Point p0, Geom::Point p1)
+{
+ SPDocument *doc = desktop->getDocument();
+
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *text_repr = xml_doc->createElement("svg:text");
+ text_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
+
+ SPText *text_object = dynamic_cast<SPText *>(desktop->currentLayer()->appendChildRepr(text_repr));
+ g_assert(text_object != nullptr);
+
+ // Invert coordinate system?
+ p0 *= desktop->dt2doc();
+ p1 *= desktop->dt2doc();
+
+ // Pixels to user units
+ p0 *= SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+ p1 *= SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+
+ sp_repr_set_svg_double( text_repr, "x", p0[Geom::X]);
+ sp_repr_set_svg_double( text_repr, "y", p0[Geom::Y]);
+
+ double inline_size = p1[Geom::X] - p0[Geom::X];
+
+ text_object->style->inline_size.setDouble( inline_size );
+ text_object->style->inline_size.set = true;
+
+ Inkscape::XML::Node *text_node = xml_doc->createTextNode("");
+ text_repr->appendChild(text_node);
+
+ SPItem *item = dynamic_cast<SPItem *>(desktop->currentLayer());
+ g_assert(item != nullptr);
+
+ // text_object->transform = item->i2doc_affine().inverse();
+
+ text_object->updateRepr();
+
+ SPCSSAttr* css = sp_repr_css_attr (text_repr, "style");
+
+ Inkscape::GC::release(text_repr);
+ Inkscape::GC::release(text_node);
+
+ return text_object;
+}
+
+SPItem *create_text_with_rectangle (SPDesktop *desktop, Geom::Point p0, Geom::Point p1)
+{
+ SPDocument *doc = desktop->getDocument();
+
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *text_repr = xml_doc->createElement("svg:text");
+ text_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
+
+ SPText *text_object = dynamic_cast<SPText *>(desktop->currentLayer()->appendChildRepr(text_repr));
+ g_assert(text_object != nullptr);
+
+ // Invert coordinate system?
+ p0 *= desktop->dt2doc();
+ p1 *= desktop->dt2doc();
+
+ // Pixels to user units
+ p0 *= SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+ p1 *= SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+
+ // Create rectangle
+ Inkscape::XML::Node *rect_repr = xml_doc->createElement("svg:rect");
+ sp_repr_set_svg_double( rect_repr, "x", p0[Geom::X]);
+ sp_repr_set_svg_double( rect_repr, "y", p0[Geom::Y]);
+ sp_repr_set_svg_double( rect_repr, "width", abs(p1[Geom::X]-p0[Geom::X]));
+ sp_repr_set_svg_double( rect_repr, "height", abs(p1[Geom::Y]-p0[Geom::Y]));
+
+ // Find defs, if does not exist, create.
+ Inkscape::XML::Node *defs_repr = sp_repr_lookup_name (xml_doc->root(), "svg:defs");
+ if (defs_repr == nullptr) {
+ defs_repr = xml_doc->createElement("svg:defs");
+ xml_doc->root()->addChild(defs_repr, nullptr);
+ }
+
+ // Add rectangle to defs.
+ defs_repr->addChild(rect_repr, nullptr);
+
+ // Link rectangle to text
+ std::string value("url(#");
+ value += rect_repr->attribute("id");
+ value += ")";
+ SPCSSAttr* css = sp_repr_css_attr (text_repr, "style");
+ sp_repr_css_set_property (css, "shape-inside", value.c_str());
+ sp_repr_css_set(text_repr, css, "style");
+ sp_repr_css_attr_unref(css);
+
+ Inkscape::XML::Node *text_node = xml_doc->createTextNode("");
+ text_repr->appendChild(text_node);
+
+ SPItem *item = dynamic_cast<SPItem *>(desktop->currentLayer());
+ g_assert(item != nullptr);
+
+ Inkscape::GC::release(text_repr);
+ Inkscape::GC::release(text_node);
+ Inkscape::GC::release(defs_repr);
+ Inkscape::GC::release(rect_repr);
+
+ return text_object;
+}
/*
* TextTagAttributes implementation
@@ -1029,6 +1240,30 @@ void TextTagAttributes::setFirstXY(Geom::Point &point)
attributes.y[0] = point[Geom::Y];
}
+SVGLength* TextTagAttributes::getFirstXLength()
+{
+ if (!attributes.x.empty()) {
+ return &attributes.x[0];
+ } else {
+ return nullptr;
+ }
+}
+
+SVGLength* TextTagAttributes::getFirstYLength()
+{
+ if (!attributes.y.empty()) {
+ return &attributes.y[0];
+ } else {
+ return nullptr;
+ }
+}
+
+// Instance of TextTagAttributes contains attributes as defined by text/tspan element.
+// output: What will be sent to the rendering engine.
+// parent_attrs: Attributes collected from all ancestors.
+// parent_attrs_offset: Where this element fits into the parent_attrs.
+// copy_xy: Should this elements x, y attributes contribute to output (can preserve set values but not use them... kind of strange).
+// copy_dxdxrotate: Should this elements dx, dy, rotate attributes contribute to output.
void TextTagAttributes::mergeInto(Inkscape::Text::Layout::OptionalTextTagAttrs *output, Inkscape::Text::Layout::OptionalTextTagAttrs const &parent_attrs, unsigned parent_attrs_offset, bool copy_xy, bool copy_dxdyrotate) const
{
mergeSingleAttribute(&output->x, parent_attrs.x, parent_attrs_offset, copy_xy ? &attributes.x : nullptr);
diff --git a/src/object/sp-text.h b/src/object/sp-text.h
index 8111fce56..a4c8b9d84 100644
--- a/src/object/sp-text.h
+++ b/src/object/sp-text.h
@@ -22,6 +22,8 @@
#include "sp-string.h" // Provides many other headers with SP_IS_STRING
#include "text-tag-attributes.h"
+#include "desktop.h"
+
#define SP_TEXT(obj) (dynamic_cast<SPText*>((SPObject*)obj))
#define SP_IS_TEXT(obj) (dynamic_cast<const SPText*>((SPObject*)obj) != NULL)
@@ -97,8 +99,18 @@ public:
void hide(unsigned int key) override;
void snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const override;
Geom::Affine set_transform(Geom::Affine const &transform) override;
+
+ bool is_horizontal() const;
+ bool has_inline_size() const;
+ bool has_shape_inside() const;
+ Geom::OptRect get_frame(); // Gets inline-size or shape-inside frame.
+ Inkscape::XML::Node* get_first_rectangle(); // Gets first shape-inside rectangle (if it exists).
+ std::vector<Glib::ustring> get_shapes() const; // Gets list of shapes in shape-inside.
};
+SPItem *create_text_with_inline_size (SPDesktop *desktop, Geom::Point p0, Geom::Point p1);
+SPItem *create_text_with_rectangle (SPDesktop *desktop, Geom::Point p0, Geom::Point p1);
+
#endif
/*
diff --git a/src/text-tag-attributes.h b/src/text-tag-attributes.h
index f0b4a8693..7a57d361a 100644
--- a/src/text-tag-attributes.h
+++ b/src/text-tag-attributes.h
@@ -137,6 +137,12 @@ public:
/** Sets the first coordinates in the x and y vectors. */
void setFirstXY(Geom::Point &point);
+ /** Gets first value in the x vector as an SVGLength. Not guaranteed to remain valid. */
+ SVGLength* getFirstXLength();
+
+ /** Gets first value in the y vector as an SVGLength. Not guaranteed to remain valid. */
+ SVGLength* getFirstYLength();
+
SVGLength *getTextLength() { return &(attributes.textLength); }
int getLengthAdjust() { return attributes.lengthAdjust; }