diff options
| -rw-r--r-- | src/libnrtype/Layout-TNG-Compute.cpp | 27 | ||||
| -rw-r--r-- | src/text-chemistry.cpp | 36 | ||||
| -rw-r--r-- | src/ui/shape-editor-knotholders.cpp | 189 | ||||
| -rw-r--r-- | src/ui/tools/text-tool.cpp | 93 |
4 files changed, 333 insertions, 12 deletions
diff --git a/src/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp index 54780b5fc..212aa5f97 100644 --- a/src/libnrtype/Layout-TNG-Compute.cpp +++ b/src/libnrtype/Layout-TNG-Compute.cpp @@ -947,10 +947,10 @@ void Layout::Calculator::_outputLine(ParagraphInfo const ¶, void Layout::Calculator::_createFirstScanlineMaker() { _current_shape_index = 0; + InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front()); if (_flow._input_wrap_shapes.empty()) { // create the special no-wrapping infinite scanline maker double initial_x = 0, initial_y = 0; - InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front()); if (!text_source->x.empty()) initial_x = text_source->x.front().computed; if (!text_source->y.empty()) @@ -961,6 +961,31 @@ void Layout::Calculator::_createFirstScanlineMaker() else { _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression); TRACE((" begin wrap shape 0\n")); + + // 'inside-shape' uses an infinitely high (wide) shape. We must set initial y. (We only need to do it here as there is only one shape.) + if (text_source->style->inline_size.set) { + _block_progression = _flow._blockProgression(); + if( _block_progression == RIGHT_TO_LEFT || + _block_progression == LEFT_TO_RIGHT ) { + // Vertical text, CJK + if (!text_source->x.empty()) { + double initial_x = text_source->x.front().computed; + _scanline_maker->setNewYCoordinate(initial_x); + } else { + std::cerr << "Layout::Calculator::_createFirstScanlineMaker: no x value with 'inline-size'!" << std::endl; + _scanline_maker->setNewYCoordinate(0); + } + } else { + // Horizontal text + if (!text_source->y.empty()) { + double initial_y = text_source->y.front().computed; + _scanline_maker->setNewYCoordinate(initial_y); + } else { + std::cerr << "Layout::Calculator::_createFirstScanlineMaker: no y value with 'inline-size'!" << std::endl; + _scanline_maker->setNewYCoordinate(0); + } + } + } } } diff --git a/src/text-chemistry.cpp b/src/text-chemistry.cpp index 268902424..61e2eff6c 100644 --- a/src/text-chemistry.cpp +++ b/src/text-chemistry.cpp @@ -299,6 +299,40 @@ text_flow_into_shape() return; } + if (false) { + // SVG 2 Text + + if (SP_IS_TEXT(text)) { + + // Make list of all shapes. + Glib::ustring shape_inside; + auto items = selection->items(); + for (auto item : items) { + if (SP_IS_SHAPE(item)) { + shape_inside += "url(#"; + shape_inside += item->getId(); + shape_inside += ") "; + } + } + + // Remove extra space at end. + if (shape_inside.length() > 1) { + shape_inside.erase (shape_inside.length() - 1); + } + + // Set 'shape-inside' property. + SPCSSAttr* css = sp_repr_css_attr (text->getRepr(), "style"); + sp_repr_css_set_property (css, "shape-inside", shape_inside.c_str()); + sp_repr_css_set (text->getRepr(), css, "style"); + } + + DocumentUndo::done(doc, SP_VERB_CONTEXT_TEXT, + _("Flow text into shape")); + + + } else { + // SVG 1.2 Flowed Text + if (SP_IS_TEXT(text)) { // remove transform from text, but recursively scale text's fontsize by the expansion SP_TEXT(text)->_adjustFontsizeRecursive(text, text->transform.descrim()); @@ -370,6 +404,8 @@ text_flow_into_shape() Inkscape::GC::release(root_repr); Inkscape::GC::release(region_repr); + + } } void diff --git a/src/ui/shape-editor-knotholders.cpp b/src/ui/shape-editor-knotholders.cpp index 2f865504a..3e90f796c 100644 --- a/src/ui/shape-editor-knotholders.cpp +++ b/src/ui/shape-editor-knotholders.cpp @@ -31,6 +31,7 @@ #include "object/sp-rect.h" #include "object/sp-spiral.h" #include "object/sp-star.h" +#include "object/sp-text.h" #include "style.h" #include "include/macros.h" @@ -71,6 +72,12 @@ public: ~OffsetKnotHolder() override = default;; }; +class TextKnotHolder : public KnotHolder { +public: + TextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + ~TextKnotHolder() override = default;; +}; + class FlowtextKnotHolder : public KnotHolder { public: FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); @@ -116,6 +123,8 @@ KnotHolder *createKnotHolder(SPItem *item, SPDesktop *desktop) knotholder = new SpiralKnotHolder(desktop, item, nullptr); } else if (dynamic_cast<SPOffset *>(item)) { knotholder = new OffsetKnotHolder(desktop, item, nullptr); + } else if (dynamic_cast<SPText *>(item)) { + knotholder = new TextKnotHolder(desktop, item, nullptr); } else { SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(item); if (flowtext && flowtext->has_internal_frame()) { @@ -1628,6 +1637,186 @@ OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolde add_pattern_knotholder(); } + +/* SPText */ +class TextKnotHolderEntityInlineSize : public KnotHolderEntity { +public: + Geom::Point knot_get() const override; + void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override; + void knot_click(unsigned int state) override; +}; + +Geom::Point +TextKnotHolderEntityInlineSize::knot_get() const +{ + SPText *text = dynamic_cast<SPText *>(item); + g_assert(text != nullptr); + + Geom::Point p; + + if (text->style->shape_inside.set) { + // SVG 2 'shape-inside'. We only get here if there is a rectangle shape. + + Geom::OptRect frame = text->get_frame(); + if (frame) { + p = (*frame).corner(2); + } else { + std::cerr << "TextKnotHolderEntityInlineSize::knot_get(): no frame!" << std::endl; + } + + } else { + // 'shape-inside' or normal text. + + SPStyle* style = text->style; + + double inline_size = style->inline_size.computed; + unsigned mode = style->writing_mode.computed; + unsigned anchor = style->text_anchor.computed; + unsigned direction = style->direction.computed; + + p = text->attributes.firstXY(); + + if (text->style->inline_size.set) { + // SVG 2 'inline-size' + + // Keep handle at end of text line. + if (mode == SP_CSS_WRITING_MODE_LR_TB || + mode == SP_CSS_WRITING_MODE_RL_TB) { + // horizontal + if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_START ) || + (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_END) ) { + p *= Geom::Translate (inline_size, 0); + } else if ( direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + p *= Geom::Translate (inline_size/2.0, 0 ); + } else if ( direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + p *= 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) ) { + p *= Geom::Translate (-inline_size, 0); + } + } else { + // vertical + if (anchor == SP_CSS_TEXT_ANCHOR_START) { + p *= Geom::Translate (0, inline_size); + } else if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + p *= Geom::Translate (0, inline_size/2.0); + } else if (anchor == SP_CSS_TEXT_ANCHOR_END) { + p *= Geom::Translate (0, -inline_size); + } + } + } else { + // Normal single line text. + Geom::OptRect bbox = text->geometricBounds(); // Check if this is best. + if (bbox) { + if (mode == SP_CSS_WRITING_MODE_LR_TB || + mode == SP_CSS_WRITING_MODE_RL_TB) { + // horizontal + if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_START ) || + (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_END) ) { + p *= Geom::Translate ((*bbox).width(), 0); + } else if ( direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + p *= Geom::Translate ((*bbox).width()/2, 0); + } else if ( direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + p *= Geom::Translate (-(*bbox).width()/2, 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) ) { + p *= Geom::Translate (-(*bbox).width(), 0); + } + } else { + // vertical + if (anchor == SP_CSS_TEXT_ANCHOR_START) { + p *= Geom::Translate (0, (*bbox).height()); + } else if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + p *= Geom::Translate (0, (*bbox).height()/2); + } else if (anchor == SP_CSS_TEXT_ANCHOR_END) { + p *= Geom::Translate (0, -(*bbox).height()); + } + p += Geom::Point((*bbox).width(), 0); // Keep on right side + } + } + } + } + + return p; +} + +void +TextKnotHolderEntityInlineSize::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) +{ + SPText *text = dynamic_cast<SPText *>(item); + g_assert(text != nullptr); + + Geom::Point const s = snap_knot_position(p, state); + + if (text->style->shape_inside.set) { + // Text in a shape: rectangle + + Inkscape::XML::Node* rectangle = text->get_first_rectangle(); + double x = 0.0; + double y = 0.0; + sp_repr_get_double (rectangle, "x", &x); + sp_repr_get_double (rectangle, "y", &y); + double width = s[Geom::X] - x; + double height = s[Geom::Y] - y; + sp_repr_set_svg_double (rectangle, "width", width); + sp_repr_set_svg_double (rectangle, "height", height); + + } else { + // Normal or 'inline-size' text. + + SPStyle* style = text->style; + + unsigned mode = style->writing_mode.computed; + unsigned anchor = style->text_anchor.computed; + unsigned direction = style->direction.computed; + + Geom::Point delta = s - text->attributes.firstXY(); + double size = 0.0; + if (mode == SP_CSS_WRITING_MODE_LR_TB || + mode == SP_CSS_WRITING_MODE_RL_TB) { + size = abs(delta[Geom::X]); + } else { + size = delta[Geom::Y]; + } + if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + size *= 2.0; + } + + text->style->inline_size.setDouble(abs(size)); + text->style->inline_size.set = true; + } + + text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + text->updateRepr(); +} + +void +TextKnotHolderEntityInlineSize::knot_click(unsigned int state) +{ + SPText *text = dynamic_cast<SPText *>(item); + g_assert(text != nullptr); + + if (state & GDK_CONTROL_MASK) { + text->style->inline_size.clear(); + } + + text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + text->updateRepr(); +} + +TextKnotHolder::TextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) : + KnotHolder(desktop, item, relhandler) +{ + TextKnotHolderEntityInlineSize *entity_inlinesize = new TextKnotHolderEntityInlineSize(); + + entity_inlinesize->create(desktop, item, this, Inkscape::CTRL_TYPE_SIZER, + _("Adjust the <b>inline size</b> (line length) of the text."), + SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR); + + entity.push_back(entity_inlinesize); +} + + // TODO: this is derived from RectKnotHolderEntityWH because it used the same static function // set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes // sense logically. diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp index 3ec0e6916..4bdf5e5ba 100644 --- a/src/ui/tools/text-tool.cpp +++ b/src/ui/tools/text-tool.cpp @@ -17,6 +17,7 @@ #include <gdk/gdkkeysyms.h> #include <gtkmm/clipboard.h> #include <glibmm/i18n.h> +#include <glibmm/regex.h> #include <display/sp-ctrlline.h> #include <display/sodipodi-ctrlrect.h> @@ -28,6 +29,7 @@ #include "document-undo.h" #include "document.h" #include "include/macros.h" +#include "inkscape.h" #include "message-context.h" #include "message-stack.h" #include "rubberband.h" @@ -39,6 +41,10 @@ #include "object/sp-flowtext.h" #include "object/sp-namedview.h" #include "object/sp-text.h" +#include "object/sp-rect.h" +#include "object/sp-shape.h" +#include "object/sp-ellipse.h" + #include "style.h" #include "ui/pixmaps/cursor-text-insert.xpm" @@ -125,12 +131,14 @@ void TextTool::setup() { this->cursor->setRgba32(0x000000ff); sp_canvas_item_hide(this->cursor); + // The rectangle box tightly wrapping text object when selected or under cursor. this->indicator = sp_canvas_item_new(desktop->getControls(), SP_TYPE_CTRLRECT, nullptr); SP_CTRLRECT(this->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100))); SP_CTRLRECT(this->indicator)->setColor(0x0000ff7f, false, 0); SP_CTRLRECT(this->indicator)->setShadow(1, 0xffffff7f); sp_canvas_item_hide(this->indicator); + // The rectangle box outlining wrapping the shape for text in a shape. this->frame = sp_canvas_item_new(desktop->getControls(), SP_TYPE_CTRLRECT, nullptr); SP_CTRLRECT(this->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100))); SP_CTRLRECT(this->frame)->setColor(0x0000ff7f, false, 0); @@ -166,7 +174,10 @@ 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()) { + if (item && ( + (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) || + (SP_IS_TEXT(item) && !SP_TEXT(item)->has_shape_inside()) ) + ) { this->shape_editor->set_item(item); } @@ -640,12 +651,41 @@ bool TextTool::root_handler(GdkEvent* event) { double cursor_height = sp_desktop_get_font_size_tool(desktop); if (fabs(p1[Geom::Y] - this->p0[Geom::Y]) > cursor_height) { // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance) - SPItem *ft = create_flowtext_with_internal_frame (desktop, this->p0, p1); - /* Set style */ - sp_desktop_apply_style_tool(desktop, ft->getRepr(), "/tools/text", true); - desktop->getSelection()->set(ft); + + if (false) { + // SVG 2 text + + SPItem *text = create_text_with_rectangle (desktop, this->p0, p1); + + /* Get "shape-inside" */ + gchar* shape_inside = g_strdup(text->style->shape_inside.value); + + /* Set style */ + sp_desktop_apply_style_tool(desktop, text->getRepr(), "/tools/text", true); + + /* Restore "shape-inside" */ + text->style->shape_inside.read( shape_inside ); + g_free( shape_inside ); + text->updateRepr(); + + desktop->getSelection()->set(text); + + } else { + // SVG 1.2 text + + SPItem *ft = create_flowtext_with_internal_frame (desktop, this->p0, p1); + + /* Set style */ + sp_desktop_apply_style_tool(desktop, ft->getRepr(), "/tools/text", true); + + ft->updateRepr(); + + desktop->getSelection()->set(ft); + } + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created.")); DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT, _("Create flowed text")); + } else { desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created.")); } @@ -1449,7 +1489,11 @@ void TextTool::_selectionChanged(Inkscape::Selection *selection) ec->shape_editor->unset_item(); SPItem *item = selection->singleItem(); - if (item && SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) { + 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())) + )) { ec->shape_editor->set_item(item); } @@ -1599,23 +1643,44 @@ static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see) truncated = true; trunc = _(" [truncated]"); } + + if (truncated) { + SP_CTRLRECT(tc->frame)->setColor(0xff0000ff, false, 0); + } else { + SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0); + } + if (SP_IS_FLOWTEXT(tc->text)) { SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (nullptr); // first frame only if (frame) { - if (truncated) { - SP_CTRLRECT(tc->frame)->setColor(0xff0000ff, false, 0); - } else { - SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0); - } sp_canvas_item_show(tc->frame); Geom::OptRect frame_bbox = frame->desktopVisualBounds(); if (frame_bbox) { SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox); + sp_canvas_item_show(tc->frame); } } SP_EVENT_CONTEXT(tc)->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit flowed text (%d character%s); <b>Enter</b> to start new paragraph.", "Type or edit flowed text (%d characters%s); <b>Enter</b> to start new paragraph.", nChars), nChars, trunc); + + } else if (SP_IS_TEXT(tc->text)) { + + Geom::OptRect opt_frame = SP_TEXT(tc->text)->get_frame(); + + if (opt_frame) { + // User units to screen pixels + Geom::Rect frame = *opt_frame; + frame *= SP_TEXT(tc->text)->i2doc_affine(); + frame *= SP_ACTIVE_DESKTOP->dt2doc().inverse(); + + SP_CTRLRECT(tc->frame)->setRectangle(frame); + sp_canvas_item_show(tc->frame); + } else { + sp_canvas_item_hide(tc->frame); + } + } else { + SP_EVENT_CONTEXT(tc)->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit text (%d character%s); <b>Enter</b> to start new line.", "Type or edit text (%d characters%s); <b>Enter</b> to start new line.", nChars), nChars, trunc); } @@ -1656,6 +1721,12 @@ static void sp_text_context_update_text_selection(TextTool *tc) sp_canvas_item_show(quad_canvasitem); tc->text_selection_quads.push_back(quad_canvasitem); } + + if (tc->shape_editor != nullptr) { + if (tc->shape_editor->knotholder) { + tc->shape_editor->knotholder->update_knots(); + } + } } static gint sp_text_context_timeout(TextTool *tc) |
