From e99eb2dbcb5d3ba437dfd59640629d91e4b08c7a Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 11 Jun 2025 22:13:29 +0530 Subject: [PATCH 01/24] Created Class for rendering Squiggles on screen --- src/display/CMakeLists.txt | 2 + src/display/control/canvas-item-squiggle.cpp | 110 +++++++++++++++++++ src/display/control/canvas-item-squiggle.h | 46 ++++++++ 3 files changed, 158 insertions(+) create mode 100644 src/display/control/canvas-item-squiggle.cpp create mode 100644 src/display/control/canvas-item-squiggle.h diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt index 30e13e3c3f..b3e3fcddea 100644 --- a/src/display/CMakeLists.txt +++ b/src/display/CMakeLists.txt @@ -61,6 +61,7 @@ set(display_SRC control/canvas-item-guideline.cpp control/canvas-item-quad.cpp control/canvas-item-rect.cpp + control/canvas-item-squiggle.cpp control/canvas-item-text.cpp control/canvas-page.cpp @@ -137,6 +138,7 @@ set(display_SRC control/canvas-item-ptr.h control/canvas-item-quad.h control/canvas-item-rect.h + control/canvas-item-squiggle.h control/canvas-item-text.h control/canvas-page.h ) diff --git a/src/display/control/canvas-item-squiggle.cpp b/src/display/control/canvas-item-squiggle.cpp new file mode 100644 index 0000000000..a3f74e8c35 --- /dev/null +++ b/src/display/control/canvas-item-squiggle.cpp @@ -0,0 +1,110 @@ +#include "canvas-item-squiggle.h" + +#include + +#include "display/cairo-utils.h" + +namespace Inkscape { + +CanvasItemSquiggle::CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point start, Geom::Point end, uint32_t color) + : CanvasItem(group) + , _start(start) + , _end(end) + , _color(color) +{ + _name = "CanvasItemSquiggle"; + _pickable = false; + request_update(); +} + +void CanvasItemSquiggle::set_points(Geom::Point start, Geom::Point end) +{ + if (_start != start || _end != end) { + _start = start; + _end = end; + request_update(); + } +} + +void CanvasItemSquiggle::set_color(uint32_t color) +{ + if (_color != color) { + _color = color; + request_redraw(); + } +} + +void CanvasItemSquiggle::_rebuild_squiggle() +{ + // Transform start and end from document to canvas units + Geom::Affine aff = affine(); + Geom::Point s = _start * aff; + Geom::Point e = _end * aff; + + // Minimum length in canvas units to draw squiggle + constexpr double min_canvas_len = 20.0; + double len = Geom::L2(e - s); + + _squiggle_path.clear(); + + if (len < min_canvas_len) { + return; + } + + // Parameters for squiggle in canvas units (screen size) + double amplitude = 3.0; + double wavelength = 8.0; + int n = std::max(1, int(len / wavelength)); + double step = len / n; + + Geom::Point dir = (e - s) / len; + Geom::Point perp(-dir[1], dir[0]); + Geom::Path path; + path.start(s); + + for (int i = 1; i <= n; ++i) { + double t = i * step; + Geom::Point next = s + dir * t; + double sign = (i % 2 == 0) ? 1.0 : -1.0; + Geom::Point ctrl = s + dir * (t - step / 2) + perp * (amplitude * sign); + path.appendNew(ctrl, ctrl, next); + } + _squiggle_path.push_back(path); +} + +void CanvasItemSquiggle::_update(bool) +{ + _rebuild_squiggle(); + + // Set bounds (just a box around the squiggle) + Geom::Rect bounds_doc(_start, _end); + bounds_doc.expandBy(5.0 / affine().descrim()); // Expand by 5 canvas units, convert to doc units + _bounds = bounds_doc; + + request_redraw(); + + request_redraw(); +} + +void CanvasItemSquiggle::_render(CanvasItemBuffer &buf) const +{ + if (_squiggle_path.empty()) { + return; + } + + buf.cr->save(); + + buf.cr->set_tolerance(0.5); + buf.cr->begin_new_path(); + + // Draw in screen coordinates but no affine transformation cause it is already in canvas coordinates + feed_pathvector_to_cairo(buf.cr->cobj(), _squiggle_path, Geom::Affine(), buf.rect, true, 0); + + ink_cairo_set_source_rgba32(buf.cr, _color); + buf.cr->set_line_width(1.5); + buf.cr->stroke(); + + buf.cr->restore(); +} + +} // namespace Inkscape \ No newline at end of file diff --git a/src/display/control/canvas-item-squiggle.h b/src/display/control/canvas-item-squiggle.h new file mode 100644 index 0000000000..dd6377c862 --- /dev/null +++ b/src/display/control/canvas-item-squiggle.h @@ -0,0 +1,46 @@ +#ifndef SEEN_CANVAS_ITEM_SQUIGGLE_H +#define SEEN_CANVAS_ITEM_SQUIGGLE_H + +/** + * A class to represent squiggles + */ + +#include +#include <2geom/pathvector.h> +#include <2geom/point.h> + +#include "canvas-item.h" + +namespace Inkscape { + +class CanvasItemSquiggle : public CanvasItem +{ +public: + CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point start, Geom::Point end, uint32_t color = 0xff0000ff); + + // Properties + void set_points(Geom::Point start, Geom::Point end); + void set_color(uint32_t color); + +protected: + ~CanvasItemSquiggle() override = default; + + void _update(bool propagate) override; + void _render(CanvasItemBuffer &buf) const override; + +private: + // Geometry + Geom::Point _start; + Geom::Point _end; + + uint32_t _color; + + Geom::PathVector _squiggle_path; + + // Rebuilding + void _rebuild_squiggle(); +}; + +} // namespace Inkscape + +#endif // SEEN_CANVAS_ITEM_SQUIGGLE_H \ No newline at end of file -- GitLab From a994af3be4edd19d95bc0f37d6c3a34846b4f652 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Thu, 12 Jun 2025 14:52:03 +0530 Subject: [PATCH 02/24] Testing Code for canvas-item-squiggle --- src/display/control/canvas-item-squiggle.cpp | 13 +++++++++++++ src/ui/dialog/spellcheck.cpp | 15 +++++++++++---- src/ui/dialog/spellcheck.h | 3 ++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/display/control/canvas-item-squiggle.cpp b/src/display/control/canvas-item-squiggle.cpp index a3f74e8c35..69c5fe4649 100644 --- a/src/display/control/canvas-item-squiggle.cpp +++ b/src/display/control/canvas-item-squiggle.cpp @@ -12,6 +12,7 @@ CanvasItemSquiggle::CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point start , _end(end) , _color(color) { + std::cout << "CanvasItemSquiggle created" << std::endl; _name = "CanvasItemSquiggle"; _pickable = false; request_update(); @@ -28,6 +29,7 @@ void CanvasItemSquiggle::set_points(Geom::Point start, Geom::Point end) void CanvasItemSquiggle::set_color(uint32_t color) { + std::cout << "CanvasItemSquiggle set_color: " << std::hex << color << std::dec << std::endl; if (_color != color) { _color = color; request_redraw(); @@ -36,15 +38,20 @@ void CanvasItemSquiggle::set_color(uint32_t color) void CanvasItemSquiggle::_rebuild_squiggle() { + std::cout << "CanvasItemSquiggle _rebuild_squiggle" << std::endl; // Transform start and end from document to canvas units Geom::Affine aff = affine(); Geom::Point s = _start * aff; Geom::Point e = _end * aff; + std::cout << "CanvasItemSquiggle _rebuild_squiggle: start = " << s << ", end = " << e << std::endl; + // Minimum length in canvas units to draw squiggle constexpr double min_canvas_len = 20.0; double len = Geom::L2(e - s); + std::cout << "CanvasItemSquiggle _rebuild_squiggle: length = " << len << std::endl; + _squiggle_path.clear(); if (len < min_canvas_len) { @@ -57,6 +64,8 @@ void CanvasItemSquiggle::_rebuild_squiggle() int n = std::max(1, int(len / wavelength)); double step = len / n; + std::cout << "CanvasItemSquiggle _rebuild_squiggle: n = " << n << ", step = " << step << std::endl; + Geom::Point dir = (e - s) / len; Geom::Point perp(-dir[1], dir[0]); Geom::Path path; @@ -74,6 +83,7 @@ void CanvasItemSquiggle::_rebuild_squiggle() void CanvasItemSquiggle::_update(bool) { + std::cout << "CanvasItemSquiggle _update" << std::endl; _rebuild_squiggle(); // Set bounds (just a box around the squiggle) @@ -88,10 +98,13 @@ void CanvasItemSquiggle::_update(bool) void CanvasItemSquiggle::_render(CanvasItemBuffer &buf) const { + std::cout << "CanvasItemSquiggle _render" << std::endl; if (_squiggle_path.empty()) { return; } + std::cout << "CanvasItemSquiggle _render: _squiggle_path not empty" << std::endl; + buf.cr->save(); buf.cr->set_tolerance(0.5); diff --git a/src/ui/dialog/spellcheck.cpp b/src/ui/dialog/spellcheck.cpp index b83de6fb58..e1ffaaae2a 100644 --- a/src/ui/dialog/spellcheck.cpp +++ b/src/ui/dialog/spellcheck.cpp @@ -30,6 +30,7 @@ #include "selection-chemistry.h" #include "text-editing.h" #include "display/control/canvas-item-rect.h" +#include "display/control/canvas-item-squiggle.h" #include "object/sp-defs.h" #include "object/sp-flowtext.h" #include "object/sp-object.h" @@ -407,10 +408,16 @@ bool SpellCheck::nextWord() area.expandBy(std::max(0.05 * mindim, 1.0)); // Create canvas item rect with red stroke. (TODO: a quad could allow non-axis aligned rects.) - auto rect = new Inkscape::CanvasItemRect(desktop->getCanvasSketch(), area); - rect->set_stroke(0xff0000ff); - rect->set_visible(true); - _rects.emplace_back(rect); + // auto rect = new Inkscape::CanvasItemRect(desktop->getCanvasSketch(), area); + // rect->set_stroke(0xff0000ff); + // rect->set_visible(true); + // _rects.emplace_back(rect); + + auto squiggle = new Inkscape::CanvasItemSquiggle(desktop->getCanvasSketch(), area.corner(3), area.corner(2)); + squiggle->set_stroke(0xff0000ff); + squiggle->set_visible(true); + _rects.emplace_back(squiggle); + // scroll to make it all visible Geom::Point const center = desktop->current_center(); diff --git a/src/ui/dialog/spellcheck.h b/src/ui/dialog/spellcheck.h index 5500908300..d992bcf32d 100644 --- a/src/ui/dialog/spellcheck.h +++ b/src/ui/dialog/spellcheck.h @@ -41,6 +41,7 @@ class SPCanvasItem; namespace Inkscape { class Preferences; class CanvasItemRect; +class CanvasItemSquiggle; } // namespace Inkscape { namespace Inkscape::UI::Dialog { @@ -166,7 +167,7 @@ private: /** * list of canvasitems (currently just rects) that mark misspelled things on canvas */ - std::vector> _rects; + std::vector> _rects; /** * list of text objects we have already checked in this session -- GitLab From 82e193fc37d5599ba8833e3b1bcb13b5f5c28831 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Mon, 16 Jun 2025 23:38:12 +0530 Subject: [PATCH 03/24] OnCanvasSpellCheck Class initial commit --- src/ui/CMakeLists.txt | 2 + src/ui/on-canvas-spellcheck.cpp | 106 ++++++++++++++++++++++++++++++++ src/ui/on-canvas-spellcheck.h | 59 ++++++++++++++++++ src/ui/tools/text-tool.cpp | 4 ++ src/ui/tools/text-tool.h | 4 ++ 5 files changed, 175 insertions(+) create mode 100644 src/ui/on-canvas-spellcheck.cpp create mode 100644 src/ui/on-canvas-spellcheck.h diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 5c9d9c3544..20b73a8efb 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -24,6 +24,7 @@ set(ui_SRC tool-factory.cpp util.cpp modifiers.cpp + on-canvas-spellcheck.cpp cache/svg_preview_cache.cpp @@ -336,6 +337,7 @@ set(ui_SRC tool-factory.h util.h modifiers.h + on-canvas-spellcheck.h cache/svg_preview_cache.h diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp new file mode 100644 index 0000000000..51be198d7c --- /dev/null +++ b/src/ui/on-canvas-spellcheck.cpp @@ -0,0 +1,106 @@ +#include "on-canvas-spellcheck.h" +#include "inkscape.h" +#include "document.h" +#include "desktop.h" +#include "layer-manager.h" +#include "ui/libspelling-wrapper.h" +#include "ui/dialog/inkscape-preferences.h" +#include "object/sp-object.h" +#include "object/sp-root.h" +#include "object/sp-defs.h" +#include "object/sp-text.h" +#include "object/sp-flowtext.h" + + + +namespace Inkscape::UI { + + +OnCanvasSpellCheck::OnCanvasSpellCheck() + : _prefs{*Preferences::get()} +{ + // Get the current document root + auto doc = SP_ACTIVE_DOCUMENT; + if (!doc) return; + _root = static_cast(doc->getRoot()); + + // Get the active desktop + auto desktop = SP_ACTIVE_DESKTOP; + if (!desktop) return; + _desktop = desktop; + + // Get the default spelling provider + _provider = spelling_provider_get_default(); + + // Choose a language (for example, the first available) + list_language_names_and_codes(_provider, + [&](auto name, auto code) { _lang_code = code; return false; }); // store first code + + // Create the checker + _checker = GObjectPtr(spelling_checker_new(_provider, _lang_code.c_str())); + + scanDocument(); +} + +void OnCanvasSpellCheck::allTextItems(SPObject *r, std::vector &l, bool hidden, bool locked) +{ + if (is(r)) { + return; // we're not interested in items in defs + } + + if (!std::strcmp(r->getRepr()->name(), "svg:metadata")) { + return; // we're not interested in metadata + } + + if (_desktop) { + for (auto &child: r->children) { + if (auto item = cast(&child)) { + if (!child.cloned && !_desktop->layerManager().isLayer(item)) { + if ((hidden || !_desktop->itemIsHidden(item)) && (locked || !item->isLocked())) { + if (is(item) || is(item)) { + l.push_back(item); + } + } + } + } + allTextItems(&child, l, hidden, locked); + } + } +} + +void OnCanvasSpellCheck::scanDocument() +{ + std::vector items; + // Uses similar logic as SpellCheck::allTextItems to collect all SPText/SPFlowText + allTextItems(_root, items, false, true); + // For each item: + for (auto item : items) { + checkTextItem(item); + } +} + +void OnCanvasSpellCheck::checkTextItem(SPItem* item) +{ + auto layout = te_get_layout(item); + auto it = layout->begin(); + while (it != layout->end()) { + if (!layout->isStartOfWord(it)) { + it.nextStartOfWord(); + if (it == layout->end()) break; + } + auto begin = it; + auto end = it; + end.nextEndOfWord(); + Glib::ustring _word = sp_te_get_string_multiline(item, begin, end); + if (!_word.empty() && !spelling_checker_check_word(_checker.get(), _word.c_str(), _word.length())) { + std::cout<<"Misspelled word: " << _word << std::endl; + _misspelled_words.push_back({item, _word, begin, end}); + } + it = end; + } +} + + +OnCanvasSpellCheck::~OnCanvasSpellCheck() = default; + +} \ No newline at end of file diff --git a/src/ui/on-canvas-spellcheck.h b/src/ui/on-canvas-spellcheck.h new file mode 100644 index 0000000000..091de003b6 --- /dev/null +++ b/src/ui/on-canvas-spellcheck.h @@ -0,0 +1,59 @@ + +#ifndef INKSCAPE_UI_ON_CANVAS_SPELLCHECK_H +#define INKSCAPE_UI_ON_CANVAS_SPELLCHECK_H + +// #include "ui/libspelling-wrapper.h" + +#include +#include + +#include + +#include "text-editing.h" +#include "util/gobjectptr.h" + +class SPObject; +class SPItem; + +namespace Inkscape { +class Preferences; +} //namespace Inkscape + + +namespace Inkscape::UI { + +struct MisspelledWord { + SPItem* item; + Glib::ustring word; + Text::Layout::iterator begin; + Text::Layout::iterator end; +}; + +class OnCanvasSpellCheck +{ +public: + OnCanvasSpellCheck(); + ~OnCanvasSpellCheck(); + +private: + SPObject *_root = nullptr; + SPDesktop *_desktop = nullptr; + + SpellingProvider* _provider = nullptr; + + Util::GObjectPtr _checker; + + Preferences &_prefs; + + std::string _lang_code; + + std::vector _misspelled_words; + + void allTextItems(SPObject *r, std::vector &l, bool hidden, bool locked); + void scanDocument(); + void checkTextItem(SPItem* item); +}; + +} // namespace Inkscape::UI + +#endif // INKSCAPE_UI_ON_CANVAS_SPELLCHECK_H \ No newline at end of file diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp index 4820de0f76..addb47af5f 100644 --- a/src/ui/tools/text-tool.cpp +++ b/src/ui/tools/text-tool.cpp @@ -48,6 +48,8 @@ #include "ui/widget/events/debug.h" #include "util/callback-converter.h" #include "util/units.h" +#include "xml/sp-css-attr.h" +#include "ui/on-canvas-spellcheck.h" using Inkscape::DocumentUndo; @@ -139,6 +141,8 @@ TextTool::TextTool(SPDesktop *desktop) if (prefs->getBool("/tools/text/gradientdrag")) { enableGrDrag(); } + + _spellcheck = std::make_unique(); } TextTool::~TextTool() diff --git a/src/ui/tools/text-tool.h b/src/ui/tools/text-tool.h index 20fe586d04..bf9e80f4f9 100644 --- a/src/ui/tools/text-tool.h +++ b/src/ui/tools/text-tool.h @@ -23,6 +23,8 @@ #include "ui/tools/tool-base.h" #include "util/delete-with.h" +#include "ui/on-canvas-spellcheck.h" + using GtkIMContext = struct _GtkIMContext; namespace Inkscape { @@ -91,6 +93,8 @@ private: bool creating = false; // dragging rubberband to create flowtext Geom::Point p0; // initial point if the flowtext rect + std::unique_ptr _spellcheck; + sigc::scoped_connection sel_changed_connection; sigc::scoped_connection sel_modified_connection; sigc::scoped_connection style_set_connection; -- GitLab From 9811485d58323a7b6f557b0f5a79f94a3aa990df Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Tue, 17 Jun 2025 04:19:03 +0530 Subject: [PATCH 04/24] Added Squiggle Rendering to OnCanvasSpellCheck Class --- src/ui/on-canvas-spellcheck.cpp | 30 +++++++++++++++++++++++++++++- src/ui/on-canvas-spellcheck.h | 5 +++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index 51be198d7c..9ed1ed917a 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -10,6 +10,7 @@ #include "object/sp-defs.h" #include "object/sp-text.h" #include "object/sp-flowtext.h" +#include "display/control/canvas-item-squiggle.h" @@ -94,12 +95,39 @@ void OnCanvasSpellCheck::checkTextItem(SPItem* item) Glib::ustring _word = sp_te_get_string_multiline(item, begin, end); if (!_word.empty() && !spelling_checker_check_word(_checker.get(), _word.c_str(), _word.length())) { std::cout<<"Misspelled word: " << _word << std::endl; - _misspelled_words.push_back({item, _word, begin, end}); + // auto squiggle = createSquiggle(item, _word, begin, end); + // _misspelled_words.push_back({item, _word, begin, end, std::move(squiggle)}); + _misspelled_words.push_back({item, _word, begin, end, nullptr}); + createSquiggle(_misspelled_words.back()); } it = end; } } +void OnCanvasSpellCheck::createSquiggle(MisspelledWord& misspelled) +{ + auto layout = te_get_layout(misspelled.item); + if (!layout) { + return; // No layout available + } + // Get the selection shape (bounding box) for the word + auto points = layout->createSelectionShape(misspelled.begin, misspelled.end, misspelled.item->i2dt_affine()); + if (points.size() < 4) { + return; // Not enough points to draw a squiggle + } + + // Use the bottom left and bottom right corners for the squiggle + Geom::Point start_doc = points[3]; // bottom left + Geom::Point end_doc = points[2]; // bottom right + + // Create the squiggle (in document coordinates) + misspelled.squiggle = CanvasItemPtr( + new Inkscape::CanvasItemSquiggle(_desktop->getCanvasSketch(), start_doc, end_doc) + ); + misspelled.squiggle->set_color(0xff0000ff); + misspelled.squiggle->set_visible(true); +} + OnCanvasSpellCheck::~OnCanvasSpellCheck() = default; diff --git a/src/ui/on-canvas-spellcheck.h b/src/ui/on-canvas-spellcheck.h index 091de003b6..0c4aa5e592 100644 --- a/src/ui/on-canvas-spellcheck.h +++ b/src/ui/on-canvas-spellcheck.h @@ -11,12 +11,14 @@ #include "text-editing.h" #include "util/gobjectptr.h" +#include "display/control/canvas-item-ptr.h" class SPObject; class SPItem; namespace Inkscape { class Preferences; +class CanvasItemSquiggle; } //namespace Inkscape @@ -27,6 +29,7 @@ struct MisspelledWord { Glib::ustring word; Text::Layout::iterator begin; Text::Layout::iterator end; + CanvasItemPtr squiggle; }; class OnCanvasSpellCheck @@ -52,6 +55,8 @@ private: void allTextItems(SPObject *r, std::vector &l, bool hidden, bool locked); void scanDocument(); void checkTextItem(SPItem* item); + + void createSquiggle(MisspelledWord& misspelled); }; } // namespace Inkscape::UI -- GitLab From b3cecad2071dc0bc29e5928fc1c4d0a1af8fdccc Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Mon, 23 Jun 2025 16:27:23 +0530 Subject: [PATCH 05/24] Made Word detecting live mow --- src/ui/on-canvas-spellcheck.cpp | 72 ++++++++++++++++++++++++++++++--- src/ui/on-canvas-spellcheck.h | 26 ++++++++++-- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index 9ed1ed917a..38f68b37b2 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -82,6 +82,21 @@ void OnCanvasSpellCheck::scanDocument() void OnCanvasSpellCheck::checkTextItem(SPItem* item) { + //Delete Item from TrackedTextItems if it exists + auto it_item = std::find_if(_tracked_items.begin(), _tracked_items.end(), + [item](const TrackedTextItem& tracked) { return tracked.item == item; }); + if (it_item != _tracked_items.end()) { + // Disconnect connections for the item + it_item->modified_connection.disconnect(); + it_item->release_connection.disconnect(); + _tracked_items.erase(it_item); + } + // Create a new tracked item + TrackedTextItem tracked_item; + tracked_item.item = item; + + // Scaning the item for misspelled words + std::vector misspelled_words; auto layout = te_get_layout(item); auto it = layout->begin(); while (it != layout->end()) { @@ -97,21 +112,48 @@ void OnCanvasSpellCheck::checkTextItem(SPItem* item) std::cout<<"Misspelled word: " << _word << std::endl; // auto squiggle = createSquiggle(item, _word, begin, end); // _misspelled_words.push_back({item, _word, begin, end, std::move(squiggle)}); - _misspelled_words.push_back({item, _word, begin, end, nullptr}); - createSquiggle(_misspelled_words.back()); + misspelled_words.push_back({_word, begin, end, nullptr}); + createSquiggle(misspelled_words.back(), item); } it = end; } + + // Add the misspelled words to the tracked item + tracked_item.misspelled_words = std::move(misspelled_words); + + // Add signals for the item + tracked_item.modified_connection = item->connectModified( + [this, item] (auto, auto) { + auto it = std::find_if(_tracked_items.begin(), _tracked_items.end(), + [item](const TrackedTextItem& tracked) { return tracked.item == item; }); + if (it != _tracked_items.end()) { + onObjModified(*it); + } + } + ); + tracked_item.release_connection = item->connectRelease( + [this, item] (auto) { + auto it = std::find_if(_tracked_items.begin(), _tracked_items.end(), + [item](const TrackedTextItem& tracked) { return tracked.item == item; }); + if (it != _tracked_items.end()) { + onObjReleased(*it); + } + } + ); + + // Add the tracked item to the list of tracked items + _tracked_items.push_back(std::move(tracked_item)); + } -void OnCanvasSpellCheck::createSquiggle(MisspelledWord& misspelled) +void OnCanvasSpellCheck::createSquiggle(MisspelledWord& misspelled, SPItem* item) { - auto layout = te_get_layout(misspelled.item); + auto layout = te_get_layout(item); if (!layout) { return; // No layout available } // Get the selection shape (bounding box) for the word - auto points = layout->createSelectionShape(misspelled.begin, misspelled.end, misspelled.item->i2dt_affine()); + auto points = layout->createSelectionShape(misspelled.begin, misspelled.end, item->i2dt_affine()); if (points.size() < 4) { return; // Not enough points to draw a squiggle } @@ -128,6 +170,26 @@ void OnCanvasSpellCheck::createSquiggle(MisspelledWord& misspelled) misspelled.squiggle->set_visible(true); } +void OnCanvasSpellCheck::onObjModified(TrackedTextItem &tracked_item) +{ + // When an object is modified, we need to re-check it for misspelled words + // This will remove the old squiggles and create new ones if necessary + checkTextItem(tracked_item.item); +} + +void OnCanvasSpellCheck::onObjReleased(TrackedTextItem &tracked_item) +{ + // When an object is released, we can remove it from the tracked items + auto it = std::find_if(_tracked_items.begin(), _tracked_items.end(), + [&tracked_item](const TrackedTextItem& tracked) { return tracked.item == tracked_item.item; }); + if (it != _tracked_items.end()) { + // Disconnect connections for the item + it->modified_connection.disconnect(); + it->release_connection.disconnect(); + _tracked_items.erase(it); + } +} + OnCanvasSpellCheck::~OnCanvasSpellCheck() = default; diff --git a/src/ui/on-canvas-spellcheck.h b/src/ui/on-canvas-spellcheck.h index 0c4aa5e592..d1254f1246 100644 --- a/src/ui/on-canvas-spellcheck.h +++ b/src/ui/on-canvas-spellcheck.h @@ -12,6 +12,7 @@ #include "text-editing.h" #include "util/gobjectptr.h" #include "display/control/canvas-item-ptr.h" +#include class SPObject; class SPItem; @@ -25,13 +26,19 @@ class CanvasItemSquiggle; namespace Inkscape::UI { struct MisspelledWord { - SPItem* item; Glib::ustring word; Text::Layout::iterator begin; Text::Layout::iterator end; CanvasItemPtr squiggle; }; +struct TrackedTextItem { + SPItem* item; + sigc::scoped_connection modified_connection; + sigc::scoped_connection release_connection; + std::vector misspelled_words; +}; + class OnCanvasSpellCheck { public: @@ -50,13 +57,26 @@ private: std::string _lang_code; - std::vector _misspelled_words; + std::vector _tracked_items; + // Get all text items in the document void allTextItems(SPObject *r, std::vector &l, bool hidden, bool locked); + + // Scanning the document for misspelled words void scanDocument(); + + // Check a specific text item for misspelled words void checkTextItem(SPItem* item); - void createSquiggle(MisspelledWord& misspelled); + // Create a squiggle for a misspelled word + void createSquiggle(MisspelledWord& misspelled, SPItem* item); + + // Object Modified handler + void onObjModified(TrackedTextItem &tracked_item); + + // Object Released handler + void onObjReleased(TrackedTextItem &tracked_item); + }; } // namespace Inkscape::UI -- GitLab From d27660bf5bf701c231c8c299837a58001b5091d7 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Mon, 23 Jun 2025 17:22:54 +0530 Subject: [PATCH 06/24] Added Live SpellCheck for new Text Item --- src/ui/on-canvas-spellcheck.cpp | 5 +++++ src/ui/on-canvas-spellcheck.h | 3 +++ src/ui/tools/text-tool.cpp | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index 38f68b37b2..ecfa8b2cdf 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -190,6 +190,11 @@ void OnCanvasSpellCheck::onObjReleased(TrackedTextItem &tracked_item) } } +void OnCanvasSpellCheck::addTrackedItem(SPItem* item) +{ + checkTextItem(item); +} + OnCanvasSpellCheck::~OnCanvasSpellCheck() = default; diff --git a/src/ui/on-canvas-spellcheck.h b/src/ui/on-canvas-spellcheck.h index d1254f1246..d32cfdc405 100644 --- a/src/ui/on-canvas-spellcheck.h +++ b/src/ui/on-canvas-spellcheck.h @@ -77,6 +77,9 @@ private: // Object Released handler void onObjReleased(TrackedTextItem &tracked_item); +public: + void addTrackedItem(SPItem* item); + }; } // namespace Inkscape::UI diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp index addb47af5f..0672462448 100644 --- a/src/ui/tools/text-tool.cpp +++ b/src/ui/tools/text-tool.cpp @@ -275,6 +275,11 @@ void TextTool::_setupText() text_item->updateRepr(); text_item->doWriteTransform(text_item->transform, nullptr, true); DocumentUndo::done(_desktop->getDocument(), _("Create text"), INKSCAPE_ICON("draw-text")); + + if(_spellcheck) + { + _spellcheck->addTrackedItem(text_item); + } } /** -- GitLab From a9daba49961c2b77c31d22bf38536fafab8df0fc Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Fri, 27 Jun 2025 04:23:06 +0530 Subject: [PATCH 07/24] Context Menu shows corrections with replacement working --- src/ui/contextmenu.cpp | 30 +++++++++++++++++++++++++ src/ui/on-canvas-spellcheck.cpp | 39 +++++++++++++++++++++++++++++++++ src/ui/on-canvas-spellcheck.h | 9 ++++++++ src/ui/tools/text-tool.h | 2 ++ 4 files changed, 80 insertions(+) diff --git a/src/ui/contextmenu.cpp b/src/ui/contextmenu.cpp index 5f5fd70120..2e524112be 100644 --- a/src/ui/contextmenu.cpp +++ b/src/ui/contextmenu.cpp @@ -42,6 +42,9 @@ #include "selection.h" #include "ui/desktop/menu-set-tooltips-shift-icons.h" #include "ui/util.h" +#include "ui/widget/desktop-widget.h" +#include "ui/tools/text-tool.h" +#include "ui/on-canvas-spellcheck.h" static void AppendItemFromAction(Glib::RefPtr const &gmenu, @@ -257,6 +260,33 @@ ContextMenu::ContextMenu(SPDesktop *desktop, SPObject *object, std::vector(item)) { AppendItemFromAction( gmenu_dialogs, "win.dialog-open('Text')", _("_Text and Font..."), "dialog-text-and-font" ); AppendItemFromAction( gmenu_dialogs, "win.dialog-open('Spellcheck')", _("Check Spellin_g..."), "tools-check-spelling" ); + + auto text_tool = dynamic_cast(desktop->getTool()); + + if(text_tool) + { + auto sel_start = text_tool->text_sel_start; + auto sel_end = text_tool->text_sel_end; + + auto spellcheck = text_tool->getSpellcheck(); + + if(spellcheck && spellcheck->isMisspelled(item, sel_start, sel_end)) + { + auto corrections = spellcheck->getCorrections(item, sel_start, sel_end); + + if (!corrections.empty()) { + int count = 0; + for (auto const &correction : corrections) { + count++; + if(count > 5) break; + auto label = Glib::ustring::compose(_("Replace with '%1'"), correction); + auto action_name = Glib::ustring::compose("spellcheck-replace-%1", correction); + action_group->add_action(action_name, sigc::bind(sigc::mem_fun(*spellcheck, &Inkscape::UI::OnCanvasSpellCheck::replaceWord), item, sel_start, sel_end, correction)); + AppendItemFromAction(gmenu_dialogs, "ctx." + action_name, label); + } + } + } + } } gmenu->append_section(gmenu_dialogs); // We might add to it later... diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index ecfa8b2cdf..34584a3e15 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -195,6 +195,45 @@ void OnCanvasSpellCheck::addTrackedItem(SPItem* item) checkTextItem(item); } +bool OnCanvasSpellCheck::isMisspelled(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end) const +{ + // Check if the word is misspelled in the tracked items + auto it = std::find_if(_tracked_items.begin(), _tracked_items.end(), + [item](const TrackedTextItem& tracked) { return tracked.item == item; }); + if (it != _tracked_items.end()) { + for (const auto& misspelled : it->misspelled_words) { + if (misspelled.begin == begin && misspelled.end == end) { + return true; // Found a match + } + } + } + return false; // Not found +} + +std::vector OnCanvasSpellCheck::getCorrections(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end) const +{ + if(!isMisspelled(item, begin, end)) + { + return {}; // No corrections if the word is not misspelled + } + + auto word = sp_te_get_string_multiline(item, begin, end); + + auto corrections = list_corrections(_checker.get(), word.c_str()); + + return corrections; + +} + +void OnCanvasSpellCheck::replaceWord(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end, const Glib::ustring &replacement) +{ + // Replace the word in the text item + sp_te_replace(item, begin, end, replacement.c_str()); + + // After replacing, we need to re-check the item for misspelled words + checkTextItem(item); +} + OnCanvasSpellCheck::~OnCanvasSpellCheck() = default; diff --git a/src/ui/on-canvas-spellcheck.h b/src/ui/on-canvas-spellcheck.h index d32cfdc405..9be86f399f 100644 --- a/src/ui/on-canvas-spellcheck.h +++ b/src/ui/on-canvas-spellcheck.h @@ -80,6 +80,15 @@ private: public: void addTrackedItem(SPItem* item); + // Check is passed word exists in our MispelledWords vector + bool isMisspelled(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end) const; + + // Get List of Correct Words for SpellCheck + std::vector getCorrections(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end) const; + + void replaceWord(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end, const Glib::ustring &replacement); + + }; } // namespace Inkscape::UI diff --git a/src/ui/tools/text-tool.h b/src/ui/tools/text-tool.h index bf9e80f4f9..10c044c908 100644 --- a/src/ui/tools/text-tool.h +++ b/src/ui/tools/text-tool.h @@ -49,6 +49,8 @@ public: bool deleteSelection(); void deleteSelected(); + Inkscape::UI::OnCanvasSpellCheck* getSpellcheck() { return _spellcheck.get();} + SPItem *textItem() const { return text; } // Insertion point position -- GitLab From c679d2627ae70643b62dee07fc00014dd7cde3e0 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Fri, 27 Jun 2025 15:26:19 +0530 Subject: [PATCH 08/24] ContextMenu Quickfix --- src/ui/contextmenu.cpp | 76 ++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/src/ui/contextmenu.cpp b/src/ui/contextmenu.cpp index 2e524112be..3ee0d0de57 100644 --- a/src/ui/contextmenu.cpp +++ b/src/ui/contextmenu.cpp @@ -156,6 +156,51 @@ ContextMenu::ContextMenu(SPDesktop *desktop, SPObject *object, std::vector(item)) { + + auto text_tool = dynamic_cast(desktop->getTool()); + + if(text_tool) + { + auto sel_start = text_tool->text_sel_start; + auto sel_end = text_tool->text_sel_end; + + auto spellcheck = text_tool->getSpellcheck(); + + if(spellcheck && spellcheck->isMisspelled(item, sel_start, sel_end)) + { + auto corrections = spellcheck->getCorrections(item, sel_start, sel_end); + + if (!corrections.empty()) { + spellcheck_enabled = true; + gmenu_section = Gio::Menu::create(); + int count = 0; + for (auto const &correction : corrections) { + count++; + if(count > 5) break; + auto label = Glib::ustring::compose(_("%1"), correction); + auto action_name = Glib::ustring::compose("spellcheck-replace-%1", correction); + action_group->add_action(action_name, sigc::bind(sigc::mem_fun(*spellcheck, &Inkscape::UI::OnCanvasSpellCheck::replaceWord), item, sel_start, sel_end, correction)); + AppendItemFromAction(gmenu_section, "ctx." + action_name, label); + } + gmenu->append_section(gmenu_section); + } + + gmenu->append_section(create_clipboard_actions()); + + gmenu_section = Gio::Menu::create(); + AppendItemFromAction(gmenu_section, "app.duplicate", _("Duplic_ate"), "edit-duplicate"); + AppendItemFromAction(gmenu_section, "app.clone", _("_Clone"), "edit-clone"); + AppendItemFromAction(gmenu_section, "app.delete-selection", _("_Delete"), "edit-delete"); + gmenu->append_section(gmenu_section); + } + } + } + + std::cout<append_section(gmenu_section); - } else if (!layer || desktop->getSelection()->includes(layer)) { + } else if (!spellcheck_enabled && !layer || desktop->getSelection()->includes(layer)) { // "item" is the object that was under the mouse when right-clicked. It determines what is shown // in the menu thus it makes the most sense that it is either selected or part of the current // selection. @@ -260,33 +305,6 @@ ContextMenu::ContextMenu(SPDesktop *desktop, SPObject *object, std::vector(item)) { AppendItemFromAction( gmenu_dialogs, "win.dialog-open('Text')", _("_Text and Font..."), "dialog-text-and-font" ); AppendItemFromAction( gmenu_dialogs, "win.dialog-open('Spellcheck')", _("Check Spellin_g..."), "tools-check-spelling" ); - - auto text_tool = dynamic_cast(desktop->getTool()); - - if(text_tool) - { - auto sel_start = text_tool->text_sel_start; - auto sel_end = text_tool->text_sel_end; - - auto spellcheck = text_tool->getSpellcheck(); - - if(spellcheck && spellcheck->isMisspelled(item, sel_start, sel_end)) - { - auto corrections = spellcheck->getCorrections(item, sel_start, sel_end); - - if (!corrections.empty()) { - int count = 0; - for (auto const &correction : corrections) { - count++; - if(count > 5) break; - auto label = Glib::ustring::compose(_("Replace with '%1'"), correction); - auto action_name = Glib::ustring::compose("spellcheck-replace-%1", correction); - action_group->add_action(action_name, sigc::bind(sigc::mem_fun(*spellcheck, &Inkscape::UI::OnCanvasSpellCheck::replaceWord), item, sel_start, sel_end, correction)); - AppendItemFromAction(gmenu_dialogs, "ctx." + action_name, label); - } - } - } - } } gmenu->append_section(gmenu_dialogs); // We might add to it later... @@ -368,7 +386,7 @@ ContextMenu::ContextMenu(SPDesktop *desktop, SPObject *object, std::vectorappend_section(gmenu_section); - } else { + } else if(!spellcheck_enabled){ // Layers: Only used in "Layers and Objects" dialog. gmenu_section = Gio::Menu::create(); -- GitLab From cc50aef96bfdf6edb324825e92dc3be0b55056fc Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 2 Jul 2025 23:35:01 +0530 Subject: [PATCH 09/24] Added ContextMenu Options --- src/ui/contextmenu.cpp | 12 +++ src/ui/on-canvas-spellcheck.cpp | 127 ++++++++++++++++++++++++++++++-- src/ui/on-canvas-spellcheck.h | 12 ++- src/ui/tools/text-tool.cpp | 2 +- 4 files changed, 143 insertions(+), 10 deletions(-) diff --git a/src/ui/contextmenu.cpp b/src/ui/contextmenu.cpp index 3ee0d0de57..1c15403b5a 100644 --- a/src/ui/contextmenu.cpp +++ b/src/ui/contextmenu.cpp @@ -188,6 +188,18 @@ ContextMenu::ContextMenu(SPDesktop *desktop, SPObject *object, std::vectorappend_section(gmenu_section); } + gmenu_section = Gio::Menu::create(); + action_group->add_action("spellcheck-ignore-once", sigc::bind(sigc::mem_fun(*spellcheck, &Inkscape::UI::OnCanvasSpellCheck::ignoreOnce), item, sel_start, sel_end)); + AppendItemFromAction(gmenu_section, "ctx.spellcheck-ignore-once", _("Ignore Once"), "edit-ignore-once"); + + action_group->add_action("spellcheck-ignore", sigc::bind(sigc::mem_fun(*spellcheck, &Inkscape::UI::OnCanvasSpellCheck::ignore), item, sel_start, sel_end)); + AppendItemFromAction(gmenu_section, "ctx.spellcheck-ignore", _("Ignore"), "edit-ignore"); + + action_group->add_action("spellcheck-add-to-dictionary", sigc::bind(sigc::mem_fun(*spellcheck, &Inkscape::UI::OnCanvasSpellCheck::addToDictionary), item, sel_start, sel_end)); + AppendItemFromAction(gmenu_section, "ctx.spellcheck-add-to-dictionary", _("Add to Dictionary"), "edit-add-to-dictionary"); + + gmenu->append_section(gmenu_section); + gmenu->append_section(create_clipboard_actions()); gmenu_section = Gio::Menu::create(); diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index 34584a3e15..d8d7c76095 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -1,3 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + #include "on-canvas-spellcheck.h" #include "inkscape.h" #include "document.h" @@ -16,20 +21,35 @@ namespace Inkscape::UI { +namespace { + +std::string get_user_ignore_path() +{ + return Glib::build_filename(Glib::get_user_config_dir(), "inkscape/ignored-words.txt"); +} + +std::string get_user_dict_path() +{ + return Glib::build_filename(Glib::get_user_config_dir(), "inkscape/accepted-words.txt"); +} + +void ensure_file_exists(const std::string& path) +{ + std::ofstream f(path, std::ios::app); +} + +} + -OnCanvasSpellCheck::OnCanvasSpellCheck() - : _prefs{*Preferences::get()} +OnCanvasSpellCheck::OnCanvasSpellCheck(SPDesktop *desktop) + : _prefs{*Preferences::get()}, + _desktop(desktop) { // Get the current document root - auto doc = SP_ACTIVE_DOCUMENT; + auto doc = desktop->getDocument(); if (!doc) return; _root = static_cast(doc->getRoot()); - // Get the active desktop - auto desktop = SP_ACTIVE_DESKTOP; - if (!desktop) return; - _desktop = desktop; - // Get the default spelling provider _provider = spelling_provider_get_default(); @@ -40,6 +60,34 @@ OnCanvasSpellCheck::OnCanvasSpellCheck() // Create the checker _checker = GObjectPtr(spelling_checker_new(_provider, _lang_code.c_str())); + // Open Ignored Words File and add ignored words to checker + ensure_file_exists(get_user_ignore_path()); + std::ifstream ignore_file(get_user_ignore_path()); + if (ignore_file.is_open()) { + std::string word; + while (std::getline(ignore_file, word)) { + if (!word.empty()) { + _ignored_words.push_back(word); + spelling_checker_ignore_word(_checker.get(), word.c_str()); + } + } + ignore_file.close(); + } + + // Open Accepted Words File and add accepted words to checker + ensure_file_exists(get_user_dict_path()); + std::ifstream dict_file(get_user_dict_path()); + if (dict_file.is_open()) { + std::string word; + while (std::getline(dict_file, word)) { + if (!word.empty()) { + _added_words.push_back(word); + spelling_checker_add_word(_checker.get(), word.c_str()); + } + } + dict_file.close(); + } + scanDocument(); } @@ -71,6 +119,9 @@ void OnCanvasSpellCheck::allTextItems(SPObject *r, std::vector &l, boo void OnCanvasSpellCheck::scanDocument() { + // Clear any previously tracked items + _tracked_items.clear(); + std::vector items; // Uses similar logic as SpellCheck::allTextItems to collect all SPText/SPFlowText allTextItems(_root, items, false, true); @@ -234,6 +285,66 @@ void OnCanvasSpellCheck::replaceWord(SPItem *item, Text::Layout::iterator begin, checkTextItem(item); } +void OnCanvasSpellCheck::ignoreOnce(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end) +{ + // Ignore the word once + auto it = std::find_if(_tracked_items.begin(), _tracked_items.end(), + [item](const TrackedTextItem& tracked) { return tracked.item == item; }); + if (it != _tracked_items.end()) { + + auto mispelled_it = std::find_if(it->misspelled_words.begin(), it->misspelled_words.end(), + [begin, end](const MisspelledWord& misspelled) { + return misspelled.begin == begin && misspelled.end == end; + }); + if (mispelled_it != it->misspelled_words.end()) { + // Remove the squiggle + if (mispelled_it->squiggle) { + mispelled_it->squiggle->set_visible(false); + mispelled_it->squiggle.reset(); + } + // Remove the misspelled word from the list + it->misspelled_words.erase(mispelled_it); + } + } +} + +void OnCanvasSpellCheck::ignore(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end) +{ + // Ignore the word permanently + auto word = sp_te_get_string_multiline(item, begin, end); + if (std::find(_ignored_words.begin(), _ignored_words.end(), word) == _ignored_words.end()) { + _ignored_words.push_back(word); + spelling_checker_ignore_word(_checker.get(), word.c_str()); + + // Save the ignored word to the file + std::ofstream ignore_file(get_user_ignore_path(), std::ios::app); + if (ignore_file.is_open()) { + ignore_file << word << std::endl; + ignore_file.close(); + } + } + + scanDocument(); // Re-scan the document to update the squiggles +} + +void OnCanvasSpellCheck::addToDictionary(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end) +{ + // Add the word to the dictionary + auto word = sp_te_get_string_multiline(item, begin, end); + if (std::find(_added_words.begin(), _added_words.end(), word) == _added_words.end()) { + _added_words.push_back(word); + spelling_checker_add_word(_checker.get(), word.c_str()); + + // Save the accepted word to the file + std::ofstream dict_file(get_user_dict_path(), std::ios::app); + if (dict_file.is_open()) { + dict_file << word << std::endl; + dict_file.close(); + } + } + + scanDocument(); +} OnCanvasSpellCheck::~OnCanvasSpellCheck() = default; diff --git a/src/ui/on-canvas-spellcheck.h b/src/ui/on-canvas-spellcheck.h index 9be86f399f..fb467fa326 100644 --- a/src/ui/on-canvas-spellcheck.h +++ b/src/ui/on-canvas-spellcheck.h @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later #ifndef INKSCAPE_UI_ON_CANVAS_SPELLCHECK_H #define INKSCAPE_UI_ON_CANVAS_SPELLCHECK_H @@ -42,7 +43,7 @@ struct TrackedTextItem { class OnCanvasSpellCheck { public: - OnCanvasSpellCheck(); + OnCanvasSpellCheck(SPDesktop *desktop); ~OnCanvasSpellCheck(); private: @@ -59,6 +60,10 @@ private: std::vector _tracked_items; + std::vector _ignored_words; + + std::vector _added_words; + // Get all text items in the document void allTextItems(SPObject *r, std::vector &l, bool hidden, bool locked); @@ -88,6 +93,11 @@ public: void replaceWord(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end, const Glib::ustring &replacement); + void ignoreOnce(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end); + + void ignore(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end); + + void addToDictionary(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end); }; diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp index 0672462448..e5cdce0d11 100644 --- a/src/ui/tools/text-tool.cpp +++ b/src/ui/tools/text-tool.cpp @@ -142,7 +142,7 @@ TextTool::TextTool(SPDesktop *desktop) enableGrDrag(); } - _spellcheck = std::make_unique(); + _spellcheck = std::make_unique(_desktop); } TextTool::~TextTool() -- GitLab From 513ee120bac95858a7ab392a8cd7674789de77a2 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Thu, 3 Jul 2025 23:19:00 +0530 Subject: [PATCH 10/24] Added Licenses for Canvas Item --- src/display/control/canvas-item-squiggle.cpp | 2 ++ src/display/control/canvas-item-squiggle.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/display/control/canvas-item-squiggle.cpp b/src/display/control/canvas-item-squiggle.cpp index 69c5fe4649..0f01943d22 100644 --- a/src/display/control/canvas-item-squiggle.cpp +++ b/src/display/control/canvas-item-squiggle.cpp @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + #include "canvas-item-squiggle.h" #include diff --git a/src/display/control/canvas-item-squiggle.h b/src/display/control/canvas-item-squiggle.h index dd6377c862..edf6d7c611 100644 --- a/src/display/control/canvas-item-squiggle.h +++ b/src/display/control/canvas-item-squiggle.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + #ifndef SEEN_CANVAS_ITEM_SQUIGGLE_H #define SEEN_CANVAS_ITEM_SQUIGGLE_H -- GitLab From 4afe5b9b97941a15db78863fd19b46434d91d7ea Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 9 Jul 2025 00:13:18 +0530 Subject: [PATCH 11/24] Added better word finding --- src/ui/on-canvas-spellcheck.cpp | 47 +++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index d8d7c76095..cdda779422 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -42,8 +42,8 @@ void ensure_file_exists(const std::string& path) OnCanvasSpellCheck::OnCanvasSpellCheck(SPDesktop *desktop) - : _prefs{*Preferences::get()}, - _desktop(desktop) + : _prefs{*Preferences::get()} + , _desktop(desktop) { // Get the current document root auto doc = desktop->getDocument(); @@ -158,7 +158,50 @@ void OnCanvasSpellCheck::checkTextItem(SPItem* item) auto begin = it; auto end = it; end.nextEndOfWord(); + + // Try to link with the next word if separated by an apostrophe + { + SPObject *char_item = nullptr; + Glib::ustring::iterator text_iter; + layout->getSourceOfCharacter(end, &char_item, &text_iter); + if (char_item && is(char_item)) { + int ch = *text_iter; + if (ch == '\'' || ch == 0x2019) { + auto tempEnd = end; + tempEnd.nextCharacter(); + layout->getSourceOfCharacter(tempEnd, &char_item, &text_iter); + if (char_item && is(char_item)) { + int nextChar = *text_iter; + if (g_ascii_isalpha(nextChar)) + end.nextEndOfWord(); + } + } + } + } + Glib::ustring _word = sp_te_get_string_multiline(item, begin, end); + + // Check if word is empty + if(_word.empty()) + { + it = end; + continue; + } + + // Skip words containing digits + if(_prefs.getInt("/dialogs/spellcheck/ignorenumbers") != 0 && + std::any_of(_word.begin(), _word.end(), [](gchar c) { return g_unichar_isdigit(c); })) { + it = end; + continue; + } + + // Skip ALL-CAPS words + if(_prefs.getInt("/dialogs/spellcheck/ignoreallcaps") != 0 && + std::all_of(_word.begin(), _word.end(), [](gchar c) { return g_unichar_isupper(c); })) { + it = end; + continue; + } + if (!_word.empty() && !spelling_checker_check_word(_checker.get(), _word.c_str(), _word.length())) { std::cout<<"Misspelled word: " << _word << std::endl; // auto squiggle = createSquiggle(item, _word, begin, end); -- GitLab From 03d07acbd2a99b0e543e8ebfd0215a8c9dda3946 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 9 Jul 2025 01:46:50 +0530 Subject: [PATCH 12/24] Added Live SpellCheck Preference --- src/ui/contextmenu.cpp | 5 +++-- src/ui/dialog/inkscape-preferences.cpp | 3 +++ src/ui/dialog/inkscape-preferences.h | 1 + src/ui/on-canvas-spellcheck.cpp | 5 ++++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ui/contextmenu.cpp b/src/ui/contextmenu.cpp index 1c15403b5a..e9b3847099 100644 --- a/src/ui/contextmenu.cpp +++ b/src/ui/contextmenu.cpp @@ -158,7 +158,8 @@ ContextMenu::ContextMenu(SPDesktop *desktop, SPObject *object, std::vector(item)) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if(item && is(item) && prefs->getBool("/dialogs/spellcheck/live", false)) { auto text_tool = dynamic_cast(desktop->getTool()); @@ -438,7 +439,7 @@ ContextMenu::ContextMenu(SPDesktop *desktop, SPObject *object, std::vectorgetInt("/theme/shiftIcons", true); set_tooltips_and_shift_icons(*this, shift_icons); // Set the style and icon theme of the new menu based on the desktop diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index 438712c27c..5f8a28948d 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -3756,6 +3756,9 @@ void InkscapePreferences::initPageSpellcheck() _spell_ignoreallcaps.init(_("Ignore words in ALL CAPITALS"), "/dialogs/spellcheck/ignoreallcaps", false); _page_spellcheck.add_line(false, "", _spell_ignoreallcaps, "", _("Ignore words in all capitals, such as \"IUPAC\""), true); + _spell_live.init(_("Turn On live spellchecking"), "/dialogs/spellcheck/live", true); + _page_spellcheck.add_line(false, "", _spell_live, "", _("Enable live spellchecking while typing"), true); + AddPage(_page_spellcheck, _("Spellcheck"), PREFS_PAGE_SPELLCHECK); #endif } diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index 1d47aef6df..ad1ecf964f 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -472,6 +472,7 @@ protected: UI::Widget::PrefCombo _spell_language3; UI::Widget::PrefCheckButton _spell_ignorenumbers; UI::Widget::PrefCheckButton _spell_ignoreallcaps; + UI::Widget::PrefCheckButton _spell_live; // Bitmaps UI::Widget::PrefCombo _misc_overs_bitmap; diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index cdda779422..6a7674289f 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -88,7 +88,10 @@ OnCanvasSpellCheck::OnCanvasSpellCheck(SPDesktop *desktop) dict_file.close(); } - scanDocument(); + // If live spellcheck is enabled, start scanning the document + if(_prefs.getBool("/dialogs/spellcheck/live", false)) { + scanDocument(); + } } void OnCanvasSpellCheck::allTextItems(SPObject *r, std::vector &l, bool hidden, bool locked) -- GitLab From 6e9e6da33cf41e7c6e213c1f0024b7dcc5cf7961 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 16 Jul 2025 18:34:12 +0530 Subject: [PATCH 13/24] Minor Fix --- src/display/control/canvas-item-squiggle.cpp | 10 +++++----- src/display/control/canvas-item-squiggle.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/display/control/canvas-item-squiggle.cpp b/src/display/control/canvas-item-squiggle.cpp index 0f01943d22..6b36e11143 100644 --- a/src/display/control/canvas-item-squiggle.cpp +++ b/src/display/control/canvas-item-squiggle.cpp @@ -8,7 +8,7 @@ namespace Inkscape { -CanvasItemSquiggle::CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point start, Geom::Point end, uint32_t color) +CanvasItemSquiggle::CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point const &start, Geom::Point const &end, uint32_t color) : CanvasItem(group) , _start(start) , _end(end) @@ -85,7 +85,9 @@ void CanvasItemSquiggle::_rebuild_squiggle() void CanvasItemSquiggle::_update(bool) { - std::cout << "CanvasItemSquiggle _update" << std::endl; + request_redraw(); + + // std::cout << "CanvasItemSquiggle _update" << std::endl; _rebuild_squiggle(); // Set bounds (just a box around the squiggle) @@ -94,8 +96,6 @@ void CanvasItemSquiggle::_update(bool) _bounds = bounds_doc; request_redraw(); - - request_redraw(); } void CanvasItemSquiggle::_render(CanvasItemBuffer &buf) const @@ -115,7 +115,7 @@ void CanvasItemSquiggle::_render(CanvasItemBuffer &buf) const // Draw in screen coordinates but no affine transformation cause it is already in canvas coordinates feed_pathvector_to_cairo(buf.cr->cobj(), _squiggle_path, Geom::Affine(), buf.rect, true, 0); - ink_cairo_set_source_rgba32(buf.cr, _color); + ink_cairo_set_source_color(buf.cr, Colors::Color(_color)); buf.cr->set_line_width(1.5); buf.cr->stroke(); diff --git a/src/display/control/canvas-item-squiggle.h b/src/display/control/canvas-item-squiggle.h index edf6d7c611..c6500010ca 100644 --- a/src/display/control/canvas-item-squiggle.h +++ b/src/display/control/canvas-item-squiggle.h @@ -18,7 +18,7 @@ namespace Inkscape { class CanvasItemSquiggle : public CanvasItem { public: - CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point start, Geom::Point end, uint32_t color = 0xff0000ff); + CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point const &start, Geom::Point const &end, uint32_t color = 0xff0000ff); // Properties void set_points(Geom::Point start, Geom::Point end); -- GitLab From d4ed8d4f130b220cd3df304b58b092f715d6e60d Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 16 Jul 2025 21:54:13 +0530 Subject: [PATCH 14/24] Rendering Bug Fix --- src/display/control/canvas-item-squiggle.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/display/control/canvas-item-squiggle.cpp b/src/display/control/canvas-item-squiggle.cpp index 6b36e11143..103f36c42f 100644 --- a/src/display/control/canvas-item-squiggle.cpp +++ b/src/display/control/canvas-item-squiggle.cpp @@ -92,9 +92,11 @@ void CanvasItemSquiggle::_update(bool) // Set bounds (just a box around the squiggle) Geom::Rect bounds_doc(_start, _end); - bounds_doc.expandBy(5.0 / affine().descrim()); // Expand by 5 canvas units, convert to doc units + bounds_doc.expandBy(5.0); // Expand by 5 canvas units, convert to doc units _bounds = bounds_doc; + *_bounds *= affine(); + request_redraw(); } -- GitLab From 7a8b10809485ac5f2b9d249f8bfd9fb68f918bd8 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 16 Jul 2025 21:58:44 +0530 Subject: [PATCH 15/24] Debugging Code from canvas-item-squiggle --- src/display/control/canvas-item-squiggle.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/display/control/canvas-item-squiggle.cpp b/src/display/control/canvas-item-squiggle.cpp index 103f36c42f..61774806ea 100644 --- a/src/display/control/canvas-item-squiggle.cpp +++ b/src/display/control/canvas-item-squiggle.cpp @@ -14,7 +14,6 @@ CanvasItemSquiggle::CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point const , _end(end) , _color(color) { - std::cout << "CanvasItemSquiggle created" << std::endl; _name = "CanvasItemSquiggle"; _pickable = false; request_update(); @@ -31,7 +30,6 @@ void CanvasItemSquiggle::set_points(Geom::Point start, Geom::Point end) void CanvasItemSquiggle::set_color(uint32_t color) { - std::cout << "CanvasItemSquiggle set_color: " << std::hex << color << std::dec << std::endl; if (_color != color) { _color = color; request_redraw(); @@ -40,20 +38,15 @@ void CanvasItemSquiggle::set_color(uint32_t color) void CanvasItemSquiggle::_rebuild_squiggle() { - std::cout << "CanvasItemSquiggle _rebuild_squiggle" << std::endl; // Transform start and end from document to canvas units Geom::Affine aff = affine(); Geom::Point s = _start * aff; Geom::Point e = _end * aff; - std::cout << "CanvasItemSquiggle _rebuild_squiggle: start = " << s << ", end = " << e << std::endl; - // Minimum length in canvas units to draw squiggle constexpr double min_canvas_len = 20.0; double len = Geom::L2(e - s); - std::cout << "CanvasItemSquiggle _rebuild_squiggle: length = " << len << std::endl; - _squiggle_path.clear(); if (len < min_canvas_len) { @@ -66,8 +59,6 @@ void CanvasItemSquiggle::_rebuild_squiggle() int n = std::max(1, int(len / wavelength)); double step = len / n; - std::cout << "CanvasItemSquiggle _rebuild_squiggle: n = " << n << ", step = " << step << std::endl; - Geom::Point dir = (e - s) / len; Geom::Point perp(-dir[1], dir[0]); Geom::Path path; @@ -87,7 +78,6 @@ void CanvasItemSquiggle::_update(bool) { request_redraw(); - // std::cout << "CanvasItemSquiggle _update" << std::endl; _rebuild_squiggle(); // Set bounds (just a box around the squiggle) @@ -102,13 +92,10 @@ void CanvasItemSquiggle::_update(bool) void CanvasItemSquiggle::_render(CanvasItemBuffer &buf) const { - std::cout << "CanvasItemSquiggle _render" << std::endl; if (_squiggle_path.empty()) { return; } - std::cout << "CanvasItemSquiggle _render: _squiggle_path not empty" << std::endl; - buf.cr->save(); buf.cr->set_tolerance(0.5); -- GitLab From 5257ea402dcfdee2610c0476ce85d4b89b17ab07 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 16 Jul 2025 22:59:33 +0530 Subject: [PATCH 16/24] Minor refactoring --- src/ui/on-canvas-spellcheck.cpp | 39 ++++++++++++++++++--------------- src/ui/on-canvas-spellcheck.h | 2 -- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index 6a7674289f..20d40b73d0 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -23,14 +23,19 @@ namespace Inkscape::UI { namespace { +const char *IGNORED_WORDS_FILE = "inkscape/ignored-words.txt"; +const char *ACCEPTED_WORDS_FILE = "inkscape/accepted-words.txt"; + +constexpr uint32_t SQUIGGLE_COLOR_RED = 0xff0000ff; // Color for squiggles + std::string get_user_ignore_path() { - return Glib::build_filename(Glib::get_user_config_dir(), "inkscape/ignored-words.txt"); + return Glib::build_filename(Glib::get_user_config_dir(), IGNORED_WORDS_FILE); } std::string get_user_dict_path() { - return Glib::build_filename(Glib::get_user_config_dir(), "inkscape/accepted-words.txt"); + return Glib::build_filename(Glib::get_user_config_dir(), ACCEPTED_WORDS_FILE); } void ensure_file_exists(const std::string& path) @@ -42,8 +47,7 @@ void ensure_file_exists(const std::string& path) OnCanvasSpellCheck::OnCanvasSpellCheck(SPDesktop *desktop) - : _prefs{*Preferences::get()} - , _desktop(desktop) + : _desktop(desktop) { // Get the current document root auto doc = desktop->getDocument(); @@ -89,33 +93,30 @@ OnCanvasSpellCheck::OnCanvasSpellCheck(SPDesktop *desktop) } // If live spellcheck is enabled, start scanning the document - if(_prefs.getBool("/dialogs/spellcheck/live", false)) { + auto prefs = Inkscape::Preferences::get(); + if(prefs->getBool("/dialogs/spellcheck/live", false)) { scanDocument(); } } -void OnCanvasSpellCheck::allTextItems(SPObject *r, std::vector &l, bool hidden, bool locked) +void OnCanvasSpellCheck::allTextItems(SPObject *root, std::vector &list, bool hidden, bool locked) { - if (is(r)) { - return; // we're not interested in items in defs - } - - if (!std::strcmp(r->getRepr()->name(), "svg:metadata")) { - return; // we're not interested in metadata + if (is(root) || !std::strcmp(root->getRepr()->name(), "svg:metadata")) { + return; // we're not interested in items in defs and metadata } if (_desktop) { - for (auto &child: r->children) { + for (auto &child: root->children) { if (auto item = cast(&child)) { if (!child.cloned && !_desktop->layerManager().isLayer(item)) { if ((hidden || !_desktop->itemIsHidden(item)) && (locked || !item->isLocked())) { if (is(item) || is(item)) { - l.push_back(item); + list.push_back(item); } } } } - allTextItems(&child, l, hidden, locked); + allTextItems(&child, list, hidden, locked); } } } @@ -191,15 +192,17 @@ void OnCanvasSpellCheck::checkTextItem(SPItem* item) continue; } + auto prefs = Inkscape::Preferences::get(); + // Skip words containing digits - if(_prefs.getInt("/dialogs/spellcheck/ignorenumbers") != 0 && + if(prefs->getInt("/dialogs/spellcheck/ignorenumbers") != 0 && std::any_of(_word.begin(), _word.end(), [](gchar c) { return g_unichar_isdigit(c); })) { it = end; continue; } // Skip ALL-CAPS words - if(_prefs.getInt("/dialogs/spellcheck/ignoreallcaps") != 0 && + if(prefs->getInt("/dialogs/spellcheck/ignoreallcaps") != 0 && std::all_of(_word.begin(), _word.end(), [](gchar c) { return g_unichar_isupper(c); })) { it = end; continue; @@ -263,7 +266,7 @@ void OnCanvasSpellCheck::createSquiggle(MisspelledWord& misspelled, SPItem* item misspelled.squiggle = CanvasItemPtr( new Inkscape::CanvasItemSquiggle(_desktop->getCanvasSketch(), start_doc, end_doc) ); - misspelled.squiggle->set_color(0xff0000ff); + misspelled.squiggle->set_color(SQUIGGLE_COLOR_RED); misspelled.squiggle->set_visible(true); } diff --git a/src/ui/on-canvas-spellcheck.h b/src/ui/on-canvas-spellcheck.h index fb467fa326..a357f08d10 100644 --- a/src/ui/on-canvas-spellcheck.h +++ b/src/ui/on-canvas-spellcheck.h @@ -54,8 +54,6 @@ private: Util::GObjectPtr _checker; - Preferences &_prefs; - std::string _lang_code; std::vector _tracked_items; -- GitLab From 81f6da9bfb5d46c9c066d4d524e0e02604d7a355 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 13 Aug 2025 18:58:48 +0530 Subject: [PATCH 17/24] Added Language Change Settings in Preference Dialog --- src/ui/dialog/inkscape-preferences.cpp | 33 ++++++++++++++++++++++++++ src/ui/dialog/inkscape-preferences.h | 1 + src/ui/on-canvas-spellcheck.cpp | 5 ++-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index 5f8a28948d..d03a810ef8 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -82,6 +82,8 @@ #include "util/trim.h" #include "widgets/spw-utilities.h" +#include "ui/libspelling-wrapper.h" + namespace Inkscape::UI::Dialog { using Inkscape::UI::Widget::DialogPage; @@ -3759,6 +3761,37 @@ void InkscapePreferences::initPageSpellcheck() _spell_live.init(_("Turn On live spellchecking"), "/dialogs/spellcheck/live", true); _page_spellcheck.add_line(false, "", _spell_live, "", _("Enable live spellchecking while typing"), true); + std::vector lang_labels; + std::vector lang_codes; + auto provider = spelling_provider_get_default(); + Inkscape::UI::list_language_names_and_codes(provider, [&](auto name, auto code) { + lang_labels.push_back(name); + lang_codes.push_back(code); + return false; + }); + + // Get saved language code from preferences + auto prefs = Inkscape::Preferences::get(); + Glib::ustring saved_code = prefs->getString("/dialogs/spellcheck/live_lang"); + + // Initialize PrefCombo + _spell_live_lang.init("/dialogs/spellcheck/live_lang", + std::span(lang_labels.data(), lang_labels.size()), + std::span(lang_codes.data(), lang_codes.size()), + saved_code); + + // Save selection to preferences when changed + _spell_live_lang.signal_changed().connect([prefs, lang_labels, lang_codes, this]() { + int idx = _spell_live_lang.get_selected(); + if (idx >= 0 && idx < (int)lang_codes.size()) { + prefs->setString("/dialogs/spellcheck/live_lang", lang_codes[idx]); + } + }); + + // Add to preferences page + _page_spellcheck.add_line(false, _("Live Spellcheck language:"), _spell_live_lang, "", + _("Select the language used for live spellchecking."), true); + AddPage(_page_spellcheck, _("Spellcheck"), PREFS_PAGE_SPELLCHECK); #endif } diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index ad1ecf964f..3112dd4d4a 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -473,6 +473,7 @@ protected: UI::Widget::PrefCheckButton _spell_ignorenumbers; UI::Widget::PrefCheckButton _spell_ignoreallcaps; UI::Widget::PrefCheckButton _spell_live; + UI::Widget::PrefCombo _spell_live_lang; // Bitmaps UI::Widget::PrefCombo _misc_overs_bitmap; diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index 20d40b73d0..0a5e6fd746 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -58,8 +58,8 @@ OnCanvasSpellCheck::OnCanvasSpellCheck(SPDesktop *desktop) _provider = spelling_provider_get_default(); // Choose a language (for example, the first available) - list_language_names_and_codes(_provider, - [&](auto name, auto code) { _lang_code = code; return false; }); // store first code + auto prefs = Inkscape::Preferences::get(); + _lang_code = prefs->getString("/dialogs/spellcheck/live_lang", "en_US"); // Create the checker _checker = GObjectPtr(spelling_checker_new(_provider, _lang_code.c_str())); @@ -93,7 +93,6 @@ OnCanvasSpellCheck::OnCanvasSpellCheck(SPDesktop *desktop) } // If live spellcheck is enabled, start scanning the document - auto prefs = Inkscape::Preferences::get(); if(prefs->getBool("/dialogs/spellcheck/live", false)) { scanDocument(); } -- GitLab From ed6eb84b82def4d2089e373f2c7ef973efafd821 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 27 Aug 2025 19:18:57 +0530 Subject: [PATCH 18/24] Text-on-Path Initial Commit --- src/ui/on-canvas-spellcheck.cpp | 49 ++++++++++++++++++++++----------- src/ui/on-canvas-spellcheck.h | 17 ++++++++++-- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index 0a5e6fd746..24b274ea8c 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -211,8 +211,8 @@ void OnCanvasSpellCheck::checkTextItem(SPItem* item) std::cout<<"Misspelled word: " << _word << std::endl; // auto squiggle = createSquiggle(item, _word, begin, end); // _misspelled_words.push_back({item, _word, begin, end, std::move(squiggle)}); - misspelled_words.push_back({_word, begin, end, nullptr}); - createSquiggle(misspelled_words.back(), item); + misspelled_words.push_back(MisspelledWord{_word, begin, end, {}}); + createSquiggle(misspelled_words.back(), item, layout); } it = end; } @@ -245,9 +245,8 @@ void OnCanvasSpellCheck::checkTextItem(SPItem* item) } -void OnCanvasSpellCheck::createSquiggle(MisspelledWord& misspelled, SPItem* item) +void OnCanvasSpellCheck::createSquiggle(MisspelledWord& misspelled, SPItem* item, const Text::Layout* layout) { - auto layout = te_get_layout(item); if (!layout) { return; // No layout available } @@ -257,16 +256,32 @@ void OnCanvasSpellCheck::createSquiggle(MisspelledWord& misspelled, SPItem* item return; // Not enough points to draw a squiggle } - // Use the bottom left and bottom right corners for the squiggle - Geom::Point start_doc = points[3]; // bottom left - Geom::Point end_doc = points[2]; // bottom right + // Initially get the bottom left and bottom right points + std::vector squiggle_points_temp; + for(int i=0; i( - new Inkscape::CanvasItemSquiggle(_desktop->getCanvasSketch(), start_doc, end_doc) - ); - misspelled.squiggle->set_color(SQUIGGLE_COLOR_RED); - misspelled.squiggle->set_visible(true); + // Replace Bottom Left and Bottom Right points with midpoints to create smooth squiggle + std::vector squiggle_points; + squiggle_points.push_back(squiggle_points_temp[0]); + + for(int i=1; i( + new Inkscape::CanvasItemSquiggle(_desktop->getCanvasSketch(), squiggle_points[i], squiggle_points[i+1]) + )); + misspelled.squiggle.back()->set_color(SQUIGGLE_COLOR_RED); + misspelled.squiggle.back()->set_visible(true); + } } void OnCanvasSpellCheck::onObjModified(TrackedTextItem &tracked_item) @@ -346,9 +361,11 @@ void OnCanvasSpellCheck::ignoreOnce(SPItem *item, Text::Layout::iterator begin, }); if (mispelled_it != it->misspelled_words.end()) { // Remove the squiggle - if (mispelled_it->squiggle) { - mispelled_it->squiggle->set_visible(false); - mispelled_it->squiggle.reset(); + if (mispelled_it->squiggle.size() > 0) { + for(auto &squiggle_part: mispelled_it->squiggle) { + squiggle_part->set_visible(false); + } + mispelled_it->squiggle.clear(); } // Remove the misspelled word from the list it->misspelled_words.erase(mispelled_it); diff --git a/src/ui/on-canvas-spellcheck.h b/src/ui/on-canvas-spellcheck.h index a357f08d10..d3f4d6442f 100644 --- a/src/ui/on-canvas-spellcheck.h +++ b/src/ui/on-canvas-spellcheck.h @@ -14,6 +14,7 @@ #include "util/gobjectptr.h" #include "display/control/canvas-item-ptr.h" #include +#include "display/control/canvas-item-squiggle.h" class SPObject; class SPItem; @@ -30,7 +31,19 @@ struct MisspelledWord { Glib::ustring word; Text::Layout::iterator begin; Text::Layout::iterator end; - CanvasItemPtr squiggle; + std::vector< CanvasItemPtr > squiggle; + + // Constructor for initialization + MisspelledWord(Glib::ustring w, Text::Layout::iterator b, Text::Layout::iterator e, + std::vector> s = {}) + : word(std::move(w)), begin(b), end(e), squiggle(std::move(s)) {} + + // Move-only + MisspelledWord() = default; + MisspelledWord(MisspelledWord&&) = default; + MisspelledWord& operator=(MisspelledWord&&) = default; + MisspelledWord(const MisspelledWord&) = delete; + MisspelledWord& operator=(const MisspelledWord&) = delete; }; struct TrackedTextItem { @@ -72,7 +85,7 @@ private: void checkTextItem(SPItem* item); // Create a squiggle for a misspelled word - void createSquiggle(MisspelledWord& misspelled, SPItem* item); + void createSquiggle(MisspelledWord& misspelled, SPItem* item, const Text::Layout* layout); // Object Modified handler void onObjModified(TrackedTextItem &tracked_item); -- GitLab From 7e97e325881a1c02899687ed8eab4273d23d538e Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 27 Aug 2025 19:34:48 +0530 Subject: [PATCH 19/24] Minor Rendering Bug Fix --- src/display/control/canvas-item-squiggle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/display/control/canvas-item-squiggle.cpp b/src/display/control/canvas-item-squiggle.cpp index 61774806ea..f31dcdc35b 100644 --- a/src/display/control/canvas-item-squiggle.cpp +++ b/src/display/control/canvas-item-squiggle.cpp @@ -44,7 +44,7 @@ void CanvasItemSquiggle::_rebuild_squiggle() Geom::Point e = _end * aff; // Minimum length in canvas units to draw squiggle - constexpr double min_canvas_len = 20.0; + constexpr double min_canvas_len = 4.0; double len = Geom::L2(e - s); _squiggle_path.clear(); -- GitLab From 51a67420440852b9b09c9c38a8c178171c531719 Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Tue, 30 Sep 2025 01:38:51 +0530 Subject: [PATCH 20/24] Catmull-Rom spline initial commit --- src/display/control/canvas-item-squiggle.cpp | 2 +- src/ui/on-canvas-spellcheck.cpp | 58 ++++++++++++++++++-- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/display/control/canvas-item-squiggle.cpp b/src/display/control/canvas-item-squiggle.cpp index f31dcdc35b..5173b60db7 100644 --- a/src/display/control/canvas-item-squiggle.cpp +++ b/src/display/control/canvas-item-squiggle.cpp @@ -54,7 +54,7 @@ void CanvasItemSquiggle::_rebuild_squiggle() } // Parameters for squiggle in canvas units (screen size) - double amplitude = 3.0; + double amplitude = 3.5; double wavelength = 8.0; int n = std::max(1, int(len / wavelength)); double step = len / n; diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index 24b274ea8c..56b3d5b2bd 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -43,6 +43,44 @@ void ensure_file_exists(const std::string& path) std::ofstream f(path, std::ios::app); } +static inline double safe_pow(double x, double p) { + return std::pow(x < 0.0 ? 0.0 : x, p); +} + +Geom::Point catmull_rom_centripetal(const Geom::Point &P0, + const Geom::Point &P1, + const Geom::Point &P2, + const Geom::Point &P3, + double u, + double alpha = 0.5) +{ + // knot parameters + double t0 = 0.0; + double t1 = t0 + safe_pow(Geom::distance(P0, P1), alpha); + double t2 = t1 + safe_pow(Geom::distance(P1, P2), alpha); + double t3 = t2 + safe_pow(Geom::distance(P2, P3), alpha); + + // avoid degenerate denominators + const double eps = 1e-8; + if (t1 - t0 < eps) t1 = t0 + eps; + if (t2 - t1 < eps) t2 = t1 + eps; + if (t3 - t2 < eps) t3 = t2 + eps; + + // tangents (Hermite form) + Geom::Point m1 = ((P2 - P1) / (t2 - t1) - (P1 - P0) / (t1 - t0)) * ((t2 - t1) / (t2 - t0)); + Geom::Point m2 = ((P3 - P2) / (t3 - t2) - (P2 - P1) / (t2 - t1)) * ((t2 - t1) / (t3 - t1)); + + // Hermite basis + double u2 = u * u; + double u3 = u2 * u; + double h1 = 2.0*u3 - 3.0*u2 + 1.0; + double h2 = -2.0*u3 + 3.0*u2; + double h3 = u3 - 2.0*u2 + u; + double h4 = u3 - u2; + + return P1 * h1 + P2 * h2 + m1 * h3 + m2 * h4; +} + } @@ -265,15 +303,27 @@ void OnCanvasSpellCheck::createSquiggle(MisspelledWord& misspelled, SPItem* item } // Replace Bottom Left and Bottom Right points with midpoints to create smooth squiggle - std::vector squiggle_points; - squiggle_points.push_back(squiggle_points_temp[0]); + std::vector squiggle_points_temp2; + squiggle_points_temp2.push_back(squiggle_points_temp[0]); for(int i=1; i squiggle_points; + // Create Catmull-Rom spline points for smooth squiggle + squiggle_points.push_back(squiggle_points_temp2[0]); + for(int i=1; i( -- GitLab From 9f0a736e55204f83acec1ad9301e597696d64d3e Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Wed, 1 Oct 2025 15:28:45 +0530 Subject: [PATCH 21/24] Fix critical warning in contextmenu --- src/ui/contextmenu.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ui/contextmenu.cpp b/src/ui/contextmenu.cpp index e9b3847099..7860c0a341 100644 --- a/src/ui/contextmenu.cpp +++ b/src/ui/contextmenu.cpp @@ -212,8 +212,6 @@ ContextMenu::ContextMenu(SPDesktop *desktop, SPObject *object, std::vectorappend_section(gmenu_section); - } else if (!spellcheck_enabled && !layer || desktop->getSelection()->includes(layer)) { + } else if (!spellcheck_enabled && (!layer || desktop->getSelection()->includes(layer))) { // "item" is the object that was under the mouse when right-clicked. It determines what is shown // in the menu thus it makes the most sense that it is either selected or part of the current // selection. -- GitLab From 494869dedc9519468a46d328fa561655d00c3d9e Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Sat, 4 Oct 2025 00:04:24 +0530 Subject: [PATCH 22/24] Text-on-Path Squiggle Rendering compatibility added in canvas-item-squiggle --- src/display/control/canvas-item-squiggle.cpp | 317 +++++++++++++++++-- src/display/control/canvas-item-squiggle.h | 8 + src/ui/on-canvas-spellcheck.cpp | 60 +--- 3 files changed, 310 insertions(+), 75 deletions(-) diff --git a/src/display/control/canvas-item-squiggle.cpp b/src/display/control/canvas-item-squiggle.cpp index 5173b60db7..929a3bd42e 100644 --- a/src/display/control/canvas-item-squiggle.cpp +++ b/src/display/control/canvas-item-squiggle.cpp @@ -8,6 +8,184 @@ namespace Inkscape { +namespace { + +// Helper Functions + +// Evaluate cubic Bezier at t in [0..1] +static inline Geom::Point eval_cubic(const Geom::Point &p0, const Geom::Point &c1, const Geom::Point &c2, const Geom::Point &p3, double t) +{ + double u = 1.0 - t; + + // Bernstein form + return p0 * (u*u*u) + c1 * (3.0 * u*u * t) + c2 * (3.0 * u * t*t) + p3 * (t*t*t); +} + +/* Build a sequence of cubic bezier segments that approximate a Centripetal Catmull-Rom spline through the input points. + Uses alpha = 0.5. + Converts each Catmull segment (P0,P1,P2,P3) into a cubic Bezier (B0,B1,B2,B3) via Hermite tangents -> Bezier control points. + + If knot distances are degenerate (very small) we use Uniform Catmull-Rom conversion for robustness. +*/ +void _build_catmull_beziers_from_points(const std::vector &pts, std::vector> &out_beziers) +{ + out_beziers.clear(); + if (pts.size() < 2) return; + + // If only two points, just make a straight cubic (control points on the line). + if (pts.size() == 2) { + Geom::Point p0 = pts[0]; + Geom::Point p1 = pts[1]; + Geom::Point c1 = p0 + (p1 - p0) * (1.0 / 3.0); + Geom::Point c2 = p0 + (p1 - p0) * (2.0 / 3.0); + out_beziers.push_back({ p0, c1, c2, p1 }); + return; + } + + constexpr double alpha = 0.5; // centripetal + constexpr double eps = 1e-8; // small epsilon to avoid /0 + + size_t n = pts.size(); + for (size_t i = 0; i < n - 1; ++i) { + // neighbors (clamped at ends) + Geom::Point P0 = (i == 0) ? pts[i] : pts[i - 1]; + Geom::Point P1 = pts[i]; + Geom::Point P2 = pts[i + 1]; + Geom::Point P3 = (i + 2 < n) ? pts[i + 2] : pts[i + 1]; + + // Compute chord lengths ^ alpha (centripetal knots) + double d01 = Geom::L2(P1 - P0); + double d12 = Geom::L2(P2 - P1); + double d23 = Geom::L2(P3 - P2); + + // Avoid exact zeros (which would make knot differences zero) + if (d01 < eps) d01 = eps; + if (d12 < eps) d12 = eps; + if (d23 < eps) d23 = eps; + + double t0 = 0.0; + double t1 = t0 + std::pow(d01, alpha); + double t2 = t1 + std::pow(d12, alpha); + double t3 = t2 + std::pow(d23, alpha); + + // If denominators are too small or knots collapsed, use Uniform Catmull-Rom formula. + bool small_denom = ( (t2 - t0) < eps ) || ( (t3 - t1) < eps ); + + Geom::Point B0 = P1; + Geom::Point B3 = P2; + Geom::Point B1, B2; + + if (small_denom) { + // Uniform Catmull-Rom + B1 = P1 + (P2 - P0) * (1.0 / 6.0); + B2 = P2 - (P3 - P1) * (1.0 / 6.0); + } else { + // Compute tangent (Hermite) vectors M1 and M2 for interval [t1,t2] + // Use stable coefficient formulas + double denom1 = (t1 - t0); + double denom2 = (t2 - t1); + double denom3 = (t3 - t2); + double denomA = (t2 - t0); // for c1,c2 + double denomB = (t3 - t1); // for d1,d2 + + // Precompute finite-difference velocities where safe + Geom::Point v10 = (denom1 > eps) ? ((P1 - P0) / denom1) : Geom::Point(0.0, 0.0); + Geom::Point v21 = (denom2 > eps) ? ((P2 - P1) / denom2) : Geom::Point(0.0, 0.0); + Geom::Point v32 = (denom3 > eps) ? ((P3 - P2) / denom3) : Geom::Point(0.0, 0.0); + + // coefficients + double c1 = (denomA > eps) ? ((t2 - t1) / denomA) : 0.0; + double c2 = (denomA > eps) ? ((t1 - t0) / denomA) : 0.0; + + double d1 = (denomB > eps) ? ((t3 - t2) / denomB) : 0.0; + double d2 = (denomB > eps) ? ((t2 - t1) / denomB) : 0.0; + + // M1 and M2 scaled to the interval [t1,t2] (see formulas in description) + Geom::Point M1 = (v10 * c1 + v21 * c2) * (t2 - t1); + Geom::Point M2 = (v21 * d1 + v32 * d2) * (t2 - t1); + + // Convert Hermite (P1,P2,M1,M2) to Bezier control points + B1 = P1 + M1 * (1.0 / 3.0); + B2 = P2 - M2 * (1.0 / 3.0); + } + + out_beziers.push_back({ B0, B1, B2, B3 }); + } +} + +// Flatten a list of cubic beziers to dense polyline (sampled points), also produce cumulated lengths +void _flatten_beziers(const std::vector> &beziers, std::vector &out_poly, std::vector &out_cumlen, double dt) +{ + out_poly.clear(); + out_cumlen.clear(); + if (beziers.empty()) return; + + // sample each bezier + out_poly.push_back(beziers[0][0]); + out_cumlen.push_back(0.0); + + for (const auto &bz : beziers) { + // sample t from dt..1.0 inclusive (first t=0 already added) + for (double t = dt; t <= 1.0 + 1e-12; t += dt) { + double tt = t; + if (tt > 1.0) tt = 1.0; + Geom::Point pt = eval_cubic(bz[0], bz[1], bz[2], bz[3], tt); + if (pt != out_poly.back()) { + double seg = Geom::L2(pt - out_poly.back()); + out_poly.push_back(pt); + out_cumlen.push_back(out_cumlen.back() + seg); + } + } + } +} + +/* sample on the flattened polyline by arc-length s (0..total_len) + produce interpolated point and tangent (approx from neighboring samples) */ +void _sample_poly_by_arc(const std::vector &poly, const std::vector &cumlen, double s, Geom::Point &out_pt, Geom::Point &out_tangent) +{ + if (poly.empty()) { + out_pt = Geom::Point(0,0); + out_tangent = Geom::Point(1,0); + return; + } + + double total = cumlen.back(); + if (s <= 0.0) { + out_pt = poly.front(); + out_tangent = (poly.size() > 1) ? (poly[1] - poly[0]) : Geom::Point(1,0); + return; + } + if (s >= total) { + out_pt = poly.back(); + out_tangent = (poly.size() > 1) ? (poly.back() - poly[poly.size()-2]) : Geom::Point(1,0); + return; + } + + // binary search for segment + auto it = std::lower_bound(cumlen.begin(), cumlen.end(), s); + size_t idx = std::distance(cumlen.begin(), it); + if (idx == 0) idx = 1; + + // interpolate between idx-1 and idx + double s0 = cumlen[idx-1]; + double s1 = cumlen[idx]; + double frac = (s1 - s0) > 1e-12 ? (s - s0) / (s1 - s0) : 0.0; + Geom::Point p0 = poly[idx-1]; + Geom::Point p1 = poly[idx]; + out_pt = p0 + (p1 - p0) * frac; + + // tangent approx by neighbor + Geom::Point ahead, behind; + if (idx + 1 < poly.size()) ahead = poly[idx+1]; + else ahead = p1; + if (idx >= 2) behind = poly[idx-2]; + else behind = p0; + + out_tangent = (ahead - behind); +} + +} + CanvasItemSquiggle::CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point const &start, Geom::Point const &end, uint32_t color) : CanvasItem(group) , _start(start) @@ -16,11 +194,34 @@ CanvasItemSquiggle::CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point const { _name = "CanvasItemSquiggle"; _pickable = false; + + // defaults + _amplitude = 3.5; + _wavelength = 8.0; + _sample_dt = 0.02; // sampling step for flattening beziers + + request_update(); +} + +CanvasItemSquiggle::CanvasItemSquiggle(CanvasItemGroup *group, std::vector const &points, uint32_t color) + : CanvasItem(group) + , _points(points) + , _color(color) +{ + _name = "CanvasItemSquiggle"; + _pickable = false; + + // defaults + _amplitude = 3.5; + _wavelength = 8.0; + _sample_dt = 0.02; + request_update(); } void CanvasItemSquiggle::set_points(Geom::Point start, Geom::Point end) { + _points.clear(); if (_start != start || _end != end) { _start = start; _end = end; @@ -28,6 +229,22 @@ void CanvasItemSquiggle::set_points(Geom::Point start, Geom::Point end) } } +void CanvasItemSquiggle::set_points(std::vector const &points) +{ + if (_points != points) { + _points = points; + request_update(); + } +} + +void CanvasItemSquiggle::set_squiggle_params(double amplitude, double wavelength, double sample_dt) +{ + _amplitude = amplitude; + _wavelength = wavelength; + _sample_dt = sample_dt > 0.0 ? sample_dt : 0.02; + request_update(); +} + void CanvasItemSquiggle::set_color(uint32_t color) { if (_color != color) { @@ -38,39 +255,90 @@ void CanvasItemSquiggle::set_color(uint32_t color) void CanvasItemSquiggle::_rebuild_squiggle() { - // Transform start and end from document to canvas units + // Transform start/end/points from document to canvas units Geom::Affine aff = affine(); - Geom::Point s = _start * aff; - Geom::Point e = _end * aff; - // Minimum length in canvas units to draw squiggle - constexpr double min_canvas_len = 4.0; - double len = Geom::L2(e - s); + // collect base points in canvas coords + std::vector base_pts; + if (!_points.empty()) { + base_pts.reserve(_points.size()); + for (auto const &p : _points) base_pts.push_back(p * aff); + } else { + Geom::Point s = _start * aff; + Geom::Point e = _end * aff; + base_pts.push_back(s); + base_pts.push_back(e); + } _squiggle_path.clear(); - if (len < min_canvas_len) { + // minimum length threshold + constexpr double min_canvas_len = 4.0; + // compute straight-line length as a quick check + double approx_len = 0.0; + for (size_t i = 1; i < base_pts.size(); ++i) approx_len += Geom::L2(base_pts[i] - base_pts[i-1]); + if (approx_len < min_canvas_len) { + return; + } + + // 1) Build Catmull-Rom -> cubic beziers + std::vector> beziers; + _build_catmull_beziers_from_points(base_pts, beziers); + + // 2) Flatten to a dense polyline and arc-length table + std::vector poly; + std::vector cumlen; + _flatten_beziers(beziers, poly, cumlen, _sample_dt); + + if (poly.size() < 2) { return; } - // Parameters for squiggle in canvas units (screen size) - double amplitude = 3.5; - double wavelength = 8.0; - int n = std::max(1, int(len / wavelength)); - double step = len / n; + double total_len = cumlen.back(); + + // squiggle params (already in canvas/screen units) + double amplitude = _amplitude; // in canvas units + double wavelength = _wavelength; // in canvas units + int n = std::max(1, int(total_len / wavelength)); + double step = total_len / n; - Geom::Point dir = (e - s) / len; - Geom::Point perp(-dir[1], dir[0]); + // Build squiggle by sampling along baseline and offsetting perpendicular Geom::Path path; - path.start(s); + // start at baseline first point (no offset) + Geom::Point first_base = poly.front(); + path.start(first_base); + + // previous baseline point (for computing ctrl midpoints) + Geom::Point prev_base = first_base; + // previous offset point — for the first segment it equals first_base (no offset) + Geom::Point prev_offset = first_base; for (int i = 1; i <= n; ++i) { - double t = i * step; - Geom::Point next = s + dir * t; + double s = i * step; + Geom::Point base_pt, tangent; + _sample_poly_by_arc(poly, cumlen, s, base_pt, tangent); + + // compute perpendicular; normalize tangent first + double tlen = Geom::L2(tangent); + Geom::Point dir = (tlen > 1e-8) ? (tangent / tlen) : Geom::Point(1.0, 0.0); + Geom::Point perp(-dir[1], dir[0]); + double sign = (i % 2 == 0) ? 1.0 : -1.0; - Geom::Point ctrl = s + dir * (t - step / 2) + perp * (amplitude * sign); - path.appendNew(ctrl, ctrl, next); + Geom::Point offset_pt = base_pt + perp * (amplitude * sign); + + // control point placed at mid baseline between prev_base and base_pt and shifted by same sign*amplitude + Geom::Point baseline_mid = prev_base + (base_pt - prev_base) * 0.5; + Geom::Point ctrl = baseline_mid + perp * (amplitude * sign); + + // create a cubic using ctrl repeated as symmetric control points (like you had earlier) + path.appendNew(ctrl, ctrl, offset_pt); + + // advance + prev_base = base_pt; + prev_offset = offset_pt; } + + // store the result path (in canvas coords) _squiggle_path.push_back(path); } @@ -80,8 +348,15 @@ void CanvasItemSquiggle::_update(bool) _rebuild_squiggle(); - // Set bounds (just a box around the squiggle) - Geom::Rect bounds_doc(_start, _end); + // Set bounds based on either _points or _start/_end + Geom::Rect bounds_doc; + if (!_points.empty()) { + bounds_doc = Geom::Rect(_points.front(), _points.back()); + for (auto const &p : _points) bounds_doc.unionWith(Geom::Rect(p, p)); + } else { + bounds_doc = Geom::Rect(_start, _end); + } + bounds_doc.expandBy(5.0); // Expand by 5 canvas units, convert to doc units _bounds = bounds_doc; diff --git a/src/display/control/canvas-item-squiggle.h b/src/display/control/canvas-item-squiggle.h index c6500010ca..eabc21ec96 100644 --- a/src/display/control/canvas-item-squiggle.h +++ b/src/display/control/canvas-item-squiggle.h @@ -19,9 +19,12 @@ class CanvasItemSquiggle : public CanvasItem { public: CanvasItemSquiggle(CanvasItemGroup *group, Geom::Point const &start, Geom::Point const &end, uint32_t color = 0xff0000ff); + CanvasItemSquiggle(CanvasItemGroup *group, std::vector const &points, uint32_t color = 0xff0000ff); // Properties void set_points(Geom::Point start, Geom::Point end); + void set_points(std::vector const &points); + void set_squiggle_params(double amplitude = 3.5, double wavelength = 8.0, double sample_dt = 0.02); void set_color(uint32_t color); protected: @@ -35,7 +38,12 @@ private: Geom::Point _start; Geom::Point _end; + std::vector _points; + uint32_t _color; + double _amplitude; // Amplitude of the squiggle in canvas units + double _wavelength; // Wavelength of the squiggle in canvas units + double _sample_dt; // Sampling step for drawing the squiggle Geom::PathVector _squiggle_path; diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index 56b3d5b2bd..a43588e049 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -47,40 +47,6 @@ static inline double safe_pow(double x, double p) { return std::pow(x < 0.0 ? 0.0 : x, p); } -Geom::Point catmull_rom_centripetal(const Geom::Point &P0, - const Geom::Point &P1, - const Geom::Point &P2, - const Geom::Point &P3, - double u, - double alpha = 0.5) -{ - // knot parameters - double t0 = 0.0; - double t1 = t0 + safe_pow(Geom::distance(P0, P1), alpha); - double t2 = t1 + safe_pow(Geom::distance(P1, P2), alpha); - double t3 = t2 + safe_pow(Geom::distance(P2, P3), alpha); - - // avoid degenerate denominators - const double eps = 1e-8; - if (t1 - t0 < eps) t1 = t0 + eps; - if (t2 - t1 < eps) t2 = t1 + eps; - if (t3 - t2 < eps) t3 = t2 + eps; - - // tangents (Hermite form) - Geom::Point m1 = ((P2 - P1) / (t2 - t1) - (P1 - P0) / (t1 - t0)) * ((t2 - t1) / (t2 - t0)); - Geom::Point m2 = ((P3 - P2) / (t3 - t2) - (P2 - P1) / (t2 - t1)) * ((t2 - t1) / (t3 - t1)); - - // Hermite basis - double u2 = u * u; - double u3 = u2 * u; - double h1 = 2.0*u3 - 3.0*u2 + 1.0; - double h2 = -2.0*u3 + 3.0*u2; - double h3 = u3 - 2.0*u2 + u; - double h4 = u3 - u2; - - return P1 * h1 + P2 * h2 + m1 * h3 + m2 * h4; -} - } @@ -312,26 +278,12 @@ void OnCanvasSpellCheck::createSquiggle(MisspelledWord& misspelled, SPItem* item } squiggle_points_temp2.push_back(squiggle_points_temp.back()); - std::vector squiggle_points; - // Create Catmull-Rom spline points for smooth squiggle - squiggle_points.push_back(squiggle_points_temp2[0]); - for(int i=1; i( - new Inkscape::CanvasItemSquiggle(_desktop->getCanvasSketch(), squiggle_points[i], squiggle_points[i+1]) - )); - misspelled.squiggle.back()->set_color(SQUIGGLE_COLOR_RED); - misspelled.squiggle.back()->set_visible(true); - } + // Create the squiggle + misspelled.squiggle.push_back( CanvasItemPtr( + new Inkscape::CanvasItemSquiggle(_desktop->getCanvasSketch(), squiggle_points_temp2, SQUIGGLE_COLOR_RED) + )); + misspelled.squiggle.back()->set_squiggle_params(3.0, 8.0, 0.10); // Amplitude, Wavelength, Sampling + misspelled.squiggle.back()->set_visible(true); } void OnCanvasSpellCheck::onObjModified(TrackedTextItem &tracked_item) -- GitLab From a25502bda8dc5c036951f5580e5f7fb1f0bc77cd Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Sat, 4 Oct 2025 18:22:20 +0530 Subject: [PATCH 23/24] Added Undo Integration --- src/ui/on-canvas-spellcheck.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index a43588e049..1ad37b183d 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include "on-canvas-spellcheck.h" @@ -16,8 +17,8 @@ #include "object/sp-text.h" #include "object/sp-flowtext.h" #include "display/control/canvas-item-squiggle.h" - - +#include "document-undo.h" +#include "ui/icon-names.h" namespace Inkscape::UI { @@ -212,7 +213,6 @@ void OnCanvasSpellCheck::checkTextItem(SPItem* item) } if (!_word.empty() && !spelling_checker_check_word(_checker.get(), _word.c_str(), _word.length())) { - std::cout<<"Misspelled word: " << _word << std::endl; // auto squiggle = createSquiggle(item, _word, begin, end); // _misspelled_words.push_back({item, _word, begin, end, std::move(squiggle)}); misspelled_words.push_back(MisspelledWord{_word, begin, end, {}}); @@ -346,6 +346,9 @@ void OnCanvasSpellCheck::replaceWord(SPItem *item, Text::Layout::iterator begin, // Replace the word in the text item sp_te_replace(item, begin, end, replacement.c_str()); + // Store the undo action + DocumentUndo::done(_desktop->getDocument(), _("Fix spelling (live)"), INKSCAPE_ICON("draw-text")); + // After replacing, we need to re-check the item for misspelled words checkTextItem(item); } -- GitLab From a1461c25aab5258d294818ccc3352c057e6eaa5c Mon Sep 17 00:00:00 2001 From: avee12x2 Date: Mon, 6 Oct 2025 12:03:51 +0530 Subject: [PATCH 24/24] Fix to reflect changes in SpellCheck Preferences immediately --- src/ui/dialog/inkscape-preferences.cpp | 26 ++++++++++++++++++++++++++ src/ui/dialog/inkscape-preferences.h | 1 + src/ui/on-canvas-spellcheck.cpp | 17 +++++++++++++++++ src/ui/on-canvas-spellcheck.h | 5 +++++ 4 files changed, 49 insertions(+) diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index d03a810ef8..31b73410de 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -83,6 +83,7 @@ #include "widgets/spw-utilities.h" #include "ui/libspelling-wrapper.h" +#include "ui/tools/text-tool.h" namespace Inkscape::UI::Dialog { @@ -3749,17 +3750,41 @@ void InkscapePreferences::onKBListKeyboardShortcuts() } } +void InkscapePreferences::spellcheckPreferencesChanged() +{ +#if WITH_LIBSPELLING + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + auto text_tool = dynamic_cast(desktop->getTool()); + + if (!text_tool) { + return; + } + + auto spellcheck = text_tool->getSpellcheck(); + if (!spellcheck) { + return; + } + + spellcheck->reinitialize(); +#endif +} + void InkscapePreferences::initPageSpellcheck() { #if WITH_LIBSPELLING _spell_ignorenumbers.init(_("Ignore words with digits"), "/dialogs/spellcheck/ignorenumbers", true); _page_spellcheck.add_line(false, "", _spell_ignorenumbers, "", _("Ignore words containing digits, such as \"R2D2\""), true); + _spell_ignorenumbers.signal_toggled().connect(sigc::mem_fun(*this, &InkscapePreferences::spellcheckPreferencesChanged)); _spell_ignoreallcaps.init(_("Ignore words in ALL CAPITALS"), "/dialogs/spellcheck/ignoreallcaps", false); _page_spellcheck.add_line(false, "", _spell_ignoreallcaps, "", _("Ignore words in all capitals, such as \"IUPAC\""), true); + _spell_ignoreallcaps.signal_toggled().connect(sigc::mem_fun(*this, &InkscapePreferences::spellcheckPreferencesChanged)); _spell_live.init(_("Turn On live spellchecking"), "/dialogs/spellcheck/live", true); _page_spellcheck.add_line(false, "", _spell_live, "", _("Enable live spellchecking while typing"), true); + _spell_live.signal_toggled().connect(sigc::mem_fun(*this, &InkscapePreferences::spellcheckPreferencesChanged)); + std::vector lang_labels; std::vector lang_codes; @@ -3785,6 +3810,7 @@ void InkscapePreferences::initPageSpellcheck() int idx = _spell_live_lang.get_selected(); if (idx >= 0 && idx < (int)lang_codes.size()) { prefs->setString("/dialogs/spellcheck/live_lang", lang_codes[idx]); + this->spellcheckPreferencesChanged(); } }); diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index 3112dd4d4a..c8dff03d3b 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -703,6 +703,7 @@ private: void resetIconsColorsWrapper(); void get_highlight_colors(guint32 &colorsetbase, guint32 &colorsetsuccess, guint32 &colorsetwarning, guint32 &colorseterror); + void spellcheckPreferencesChanged(); std::map dark_themes; bool _init; diff --git a/src/ui/on-canvas-spellcheck.cpp b/src/ui/on-canvas-spellcheck.cpp index 1ad37b183d..e4d29d7706 100644 --- a/src/ui/on-canvas-spellcheck.cpp +++ b/src/ui/on-canvas-spellcheck.cpp @@ -59,6 +59,11 @@ OnCanvasSpellCheck::OnCanvasSpellCheck(SPDesktop *desktop) if (!doc) return; _root = static_cast(doc->getRoot()); + initialize(); +} + +void OnCanvasSpellCheck::initialize() +{ // Get the default spelling provider _provider = spelling_provider_get_default(); @@ -103,6 +108,18 @@ OnCanvasSpellCheck::OnCanvasSpellCheck(SPDesktop *desktop) } } +void OnCanvasSpellCheck::reinitialize() +{ + // Clear previous data + _ignored_words.clear(); + _added_words.clear(); + _tracked_items.clear(); + _checker.reset(); + + // Re-initialize + initialize(); +} + void OnCanvasSpellCheck::allTextItems(SPObject *root, std::vector &list, bool hidden, bool locked) { if (is(root) || !std::strcmp(root->getRepr()->name(), "svg:metadata")) { diff --git a/src/ui/on-canvas-spellcheck.h b/src/ui/on-canvas-spellcheck.h index d3f4d6442f..d399707961 100644 --- a/src/ui/on-canvas-spellcheck.h +++ b/src/ui/on-canvas-spellcheck.h @@ -75,6 +75,9 @@ private: std::vector _added_words; + // Initialize spell checker and tracked items and start spellchecking + void initialize(); + // Get all text items in the document void allTextItems(SPObject *r, std::vector &l, bool hidden, bool locked); @@ -110,6 +113,8 @@ public: void addToDictionary(SPItem *item, Text::Layout::iterator begin, Text::Layout::iterator end); + void reinitialize(); + }; } // namespace Inkscape::UI -- GitLab