diff --git a/src/libnrtype/Layout-TNG.h b/src/libnrtype/Layout-TNG.h index f8857c09e4a1e7120fed83fa020163eaa12e9f1c..6ab2df8087f30fb66c91a7a192289c4f142ce638 100644 --- a/src/libnrtype/Layout-TNG.h +++ b/src/libnrtype/Layout-TNG.h @@ -511,9 +511,8 @@ public: output, use iterator::nextStartOfWord() and friends instead. */ inline bool isWhitespace(iterator const &it) const; - /** Returns the unicode character code of the character pointed to by - \a it. If \a it == end() the result is undefined. */ - inline int characterAt(iterator const &it) const; + /** Returns character pointed to by \a it. If \a it == end() the result is undefined. */ + inline gchar characterAt(iterator const &it) const; /** Returns true if the text at \a it is hidden (i.e. overflowed). */ bool isHidden(iterator const &it) const; @@ -1122,12 +1121,9 @@ inline unsigned Layout::shapeIndex(iterator const &it) const inline bool Layout::isWhitespace(iterator const &it) const {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_white;} -inline int Layout::characterAt(iterator const &it) const +inline gchar Layout::characterAt(iterator const &it) const { - SPObject *unused; - Glib::ustring::iterator text_iter; - getSourceOfCharacter(it, &unused, &text_iter); - return *text_iter; + return _characters[it._char_index].the_char; } inline bool Layout::isCursorPosition(iterator const &it) const diff --git a/src/object/sp-text.cpp b/src/object/sp-text.cpp index aec6b4815c8db8ae5018aa16bee1a222af2705db..addcd520df8a198d1bb25df81204532de398fed8 100644 --- a/src/object/sp-text.cpp +++ b/src/object/sp-text.cpp @@ -1008,17 +1008,82 @@ void SPText::_clearFlow(Inkscape::DrawingGroup *in_arena) in_arena->clearChildren(); } + /** Remove 'x' and 'y' values on children (lines) or they will be interpreted as absolute positions * when 'inline-size' is removed. */ void SPText::remove_svg11_fallback() { for (auto& child: children) { - std::cout << "Found child: " << child << std::endl; child.removeAttribute("x"); child.removeAttribute("y"); } } +/** Convert new lines in 'inline-size' text to tspans with sodipodi:role="tspan". + * Note sodipodi:role="tspan" will be removed in the future! + */ +void SPText::newline_to_sodipodi() { + + // New lines can come anywhere, we must search character-by-character. + auto it = layout.begin(); + while (true) { + if (layout.characterAt(it) == '\n') { + + // Delete newline ('\n'). + iterator_pair pair; + auto it_end = it; + it_end.nextCharacter(); + sp_te_delete (this, it, it_end, pair); + it = pair.first; + + // Insert newline (sodipodi:role="line"). + it = sp_te_insert_line(this, it); + } + + it.nextCharacter(); + layout.validateIterator(&it); + + if (it == layout.end()) { + break; + } + } +} + +/** Convert tspans with sodipodi:role="tspans" to '\n'. + * Note sodipodi:role="tspan" will be removed in the future! + */ +void SPText::sodipodi_to_newline() { + + // tspans with sodipodi:role="line" are only direct children of a element. + for (auto child : childList(false)) { + + auto tspan = dynamic_cast(child); // Could have or . + if (tspan && tspan->role == SP_TSPAN_ROLE_LINE) { + + // Remove sodipodi:role attribute. + tspan->removeAttribute("sodipodi:role"); + tspan->updateRepr(); + + // Insert '/n' if not last line. + // This may screw up dx, dy, rotate but... SVG 2 text cannot have these values. + if (tspan != lastChild()) { + auto last_child = tspan->lastChild(); + auto last_string = dynamic_cast<SPString *>(last_child); + if (last_string) { + // Add '/n' to string. + last_string->string += "\n"; + last_string->updateRepr(); + } else { + // Insert new string with '\n'. + auto tspan_node = tspan->getRepr(); + auto xml_doc = tspan_node->document(); + tspan_node->appendChild(xml_doc->createTextNode("\n")); + } + } + } + } +} + bool SPText::is_horizontal() const { unsigned mode = style->writing_mode.computed; diff --git a/src/object/sp-text.h b/src/object/sp-text.h index f31b033f4fd2d9f3f4ea2d923431d680440436db..55e2399e093d419ee436d3f6997f27129925060d 100644 --- a/src/object/sp-text.h +++ b/src/object/sp-text.h @@ -120,6 +120,9 @@ private: // For 'inline-size', need to also remove any 'x' and 'y' added by SVG 1.1 fallback. void remove_svg11_fallback(); + void newline_to_sodipodi(); // 'inline-size' to Inkscape multi-line text. + void sodipodi_to_newline(); // Inkscape mult-line text to SVG 2 text. + bool is_horizontal() const; bool has_inline_size() const; bool has_shape_inside() const; diff --git a/src/object/sp-tspan.cpp b/src/object/sp-tspan.cpp index 338dfb01bf3b26e4cefd73380b12e4ecbd45d58c..bfa52c57b61b9ae33df8a5c56031990b3579e25a 100644 --- a/src/object/sp-tspan.cpp +++ b/src/object/sp-tspan.cpp @@ -63,10 +63,11 @@ void SPTSpan::build(SPDocument *doc, Inkscape::XML::Node *repr) { this->readAttr( "rotate" ); // Strip sodipodi:role from SVG 2 flowed text. - SPText* text = dynamic_cast<SPText*>(parent); - //if (text && !(text->has_shape_inside() /*|| text->has_inline_size()*/)) { + // this->role = SP_TSPAN_ROLE_UNSPECIFIED; + SPText* text = dynamic_cast<SPText *>(parent); + if (text && !(text->has_shape_inside()|| text->has_inline_size())) { this->readAttr( "sodipodi:role" ); - //} + } // We'll intercept "style" to strip "visibility" property (SVG 1.1 fallback for SVG 2 text) then pass it on. this->readAttr( "style" ); diff --git a/src/ui/shape-editor-knotholders.cpp b/src/ui/shape-editor-knotholders.cpp index f63f20c76c663fb010f79d849aa9f0ec4123d10c..ef395fa0473441e35b399c49c54aedc84e9a5e0c 100644 --- a/src/ui/shape-editor-knotholders.cpp +++ b/src/ui/shape-editor-knotholders.cpp @@ -34,6 +34,7 @@ #include "object/sp-star.h" #include "object/sp-text.h" #include "object/sp-textpath.h" +#include "object/sp-tspan.h" class RectKnotHolder : public KnotHolder { public: @@ -1770,6 +1771,7 @@ TextKnotHolderEntityInlineSize::knot_get() const return p; } +// Conversion from Inkscape SVG 1.1 to SVG 2 'inline-size'. void TextKnotHolderEntityInlineSize::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) { @@ -1819,13 +1821,22 @@ TextKnotHolderEntityInlineSize::knot_set(Geom::Point const &p, Geom::Point const size = 0.0; } + // Set 'inline-size'. text->style->inline_size.setDouble(size); text->style->inline_size.set = true; + // Ensure we respect new lines. + text->style->white_space.read("pre"); + text->style->white_space.set = true; + + // Convert sodipodi:role="line" to '\n'. + text->sodipodi_to_newline(); + text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); text->updateRepr(); } +// Conversion from SVG 2 'inline-size' to Inkscape's SVG 1.1. void TextKnotHolderEntityInlineSize::knot_click(unsigned int state) { @@ -1833,12 +1844,14 @@ TextKnotHolderEntityInlineSize::knot_click(unsigned int state) g_assert(text != nullptr); if (state & GDK_CONTROL_MASK) { + text->style->inline_size.clear(); - text->remove_svg11_fallback(); - } + text->remove_svg11_fallback(); // Else 'x' and 'y' will be interpreted as absolute positions. + text->newline_to_sodipodi(); // Convert '\n' to tspans with sodipodi:role="line". - text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - text->updateRepr(); + text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + text->updateRepr(); + } } class TextKnotHolderEntityShapeInside : public KnotHolderEntity { diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp index 1b243e4fb9a506764cf0b397f417d04059ed6742..7b75b2b80755989bb1cf3ac00a8ddbefec984f14 100644 --- a/src/ui/tools/text-tool.cpp +++ b/src/ui/tools/text-tool.cpp @@ -906,7 +906,7 @@ bool TextTool::root_handler(GdkEvent* event) { } SPText* text_element = dynamic_cast<SPText*>(text); - if (text_element && (text_element->has_shape_inside() /*|| text_element->has_inline_size()*/)) { + if (text_element && (text_element->has_shape_inside() || text_element->has_inline_size())) { // Handle new line like any other character. this->text_sel_start = this->text_sel_end = sp_te_insert(this->text, this->text_sel_start, "\n"); } else {