diff options
| -rw-r--r-- | src/extension/internal/svg.cpp | 106 | ||||
| -rw-r--r-- | src/object/sp-text.cpp | 333 | ||||
| -rw-r--r-- | src/object/sp-text.h | 12 | ||||
| -rw-r--r-- | src/text-tag-attributes.h | 6 |
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; } |
