summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/libnrtype/Layout-TNG-Compute.cpp27
-rw-r--r--src/text-chemistry.cpp36
-rw-r--r--src/ui/shape-editor-knotholders.cpp189
-rw-r--r--src/ui/tools/text-tool.cpp93
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 &para,
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)