Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
ctrl-handle-styling.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <optional>
5#include <boost/functional/hash.hpp>
6#include <glibmm/fileutils.h>
7#include <glibmm/i18n.h>
8#include <glibmm/regex.h>
9
17
18#include "display/cairo-utils.h" // argb32_from_rgba()
20#include "io/resource.h"
21#include "util/delete-with.h"
23
24namespace Inkscape::Handles {
25namespace {
26
30struct ParsingState
31{
32 Css &result;
33 std::vector<std::pair<Style *, int>> selected_handles;
34};
35
39ParsingState &get_parsing_state(CRDocHandler *a_handler)
40{
41 return *reinterpret_cast<ParsingState *>(a_handler->app_data);
42}
43
47std::unordered_map<std::string, CanvasItemCtrlType> const ctrl_type_map = {
49 {".inkscape-adj-handle", CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE},
50 {".inkscape-adj-skew", CANVAS_ITEM_CTRL_TYPE_ADJ_SKEW},
51 {".inkscape-adj-rotate", CANVAS_ITEM_CTRL_TYPE_ADJ_ROTATE},
52 {".inkscape-adj-center", CANVAS_ITEM_CTRL_TYPE_ADJ_CENTER},
53 {".inkscape-adj-salign", CANVAS_ITEM_CTRL_TYPE_ADJ_SALIGN},
54 {".inkscape-adj-calign", CANVAS_ITEM_CTRL_TYPE_ADJ_CALIGN},
55 {".inkscape-adj-malign", CANVAS_ITEM_CTRL_TYPE_ADJ_MALIGN},
56 {".inkscape-anchor", CANVAS_ITEM_CTRL_TYPE_ANCHOR},
57 {".inkscape-point", CANVAS_ITEM_CTRL_TYPE_POINT},
58 {".inkscape-rotate", CANVAS_ITEM_CTRL_TYPE_ROTATE},
59 {".inkscape-margin", CANVAS_ITEM_CTRL_TYPE_MARGIN},
60 {".inkscape-center", CANVAS_ITEM_CTRL_TYPE_CENTER},
61 {".inkscape-sizer", CANVAS_ITEM_CTRL_TYPE_SIZER},
62 {".inkscape-shaper", CANVAS_ITEM_CTRL_TYPE_SHAPER},
63 {".inkscape-marker", CANVAS_ITEM_CTRL_TYPE_MARKER},
64 {".inkscape-lpe", CANVAS_ITEM_CTRL_TYPE_LPE},
65 {".inkscape-node-auto", CANVAS_ITEM_CTRL_TYPE_NODE_AUTO},
66 {".inkscape-node-cusp", CANVAS_ITEM_CTRL_TYPE_NODE_CUSP},
67 {".inkscape-node-smooth", CANVAS_ITEM_CTRL_TYPE_NODE_SMOOTH},
68 {".inkscape-node-symmetrical", CANVAS_ITEM_CTRL_TYPE_NODE_SYMMETRICAL},
69 {".inkscape-mesh", CANVAS_ITEM_CTRL_TYPE_MESH},
70 {".inkscape-invisible", CANVAS_ITEM_CTRL_TYPE_INVISIPOINT},
71 {".inkscape-guide-handle", CANVAS_ITEM_CTRL_TYPE_GUIDE_HANDLE},
72 {".inkscape-pointer", CANVAS_ITEM_CTRL_TYPE_POINTER},
73 {".inkscape-move", CANVAS_ITEM_CTRL_TYPE_MOVE},
74 {".inkscape-selection-rect", RUBBERBAND_RECT},
75 {".inkscape-selection-lasso", RUBBERBAND_TOUCHPATH},
76 {".inkscape-selection-path.selector", RUBBERBAND_TOUCHPATH_SELECT},
77 {".inkscape-selection-path.eraser", RUBBERBAND_TOUCHPATH_ERASER},
78 {".inkscape-selection-path.paint-bucket", RUBBERBAND_TOUCHPATH_FLOOD},
79 {".inkscape-selection-touchrect", RUBBERBAND_TOUCHRECT},
80 {".inkscape-selection-deselect", RUBBERBAND_DESELECT},
81 {".inkscape-selection-deselect.selector", RUBBERBAND_TOUCHPATH_DESELECT},
82 {".inkscape-selection-invert", RUBBERBAND_INVERT},
83 {".inkscape-selection-invert.selector", RUBBERBAND_TOUCHPATH_INVERT},
84};
85
89std::unordered_map<std::string, CanvasItemCtrlShape> const ctrl_shape_map = {
91 {"'diamond'", CANVAS_ITEM_CTRL_SHAPE_DIAMOND},
93 {"'triangle'", CANVAS_ITEM_CTRL_SHAPE_TRIANGLE},
94 {"'triangle-angled'", CANVAS_ITEM_CTRL_SHAPE_TRIANGLE_ANGLED},
100 {"'skew-arrow'", CANVAS_ITEM_CTRL_SHAPE_SARROW},
101 {"'curved-arrow'", CANVAS_ITEM_CTRL_SHAPE_CARROW},
102 {"'side-align'", CANVAS_ITEM_CTRL_SHAPE_SALIGN},
103 {"'corner-align'", CANVAS_ITEM_CTRL_SHAPE_CALIGN},
104 {"'middle-align'", CANVAS_ITEM_CTRL_SHAPE_MALIGN}
105};
106
107struct Exception
108{
109 Glib::ustring msg;
110};
111
112void log_error(Glib::ustring const &err, CRParsingLocation const &loc)
113{
114 std::cerr << loc.line << ':' << loc.column << ": " << err << std::endl;
115}
116
117std::string get_string(CRTerm const *term)
118{
119 auto const cstr = delete_with<g_free>(cr_term_to_string(term));
120 if (!cstr) {
121 throw Exception{_("Empty or improper value, skipped")};
122 }
123 return reinterpret_cast<char *>(cstr.get());
124}
125
126CanvasItemCtrlShape parse_shape(CRTerm const *term)
127{
128 auto const str = get_string(term);
129 auto const it = ctrl_shape_map.find(str);
130 if (it == ctrl_shape_map.end()) {
131 throw Exception{Glib::ustring::compose(_("Unrecognized shape '%1'"), str)};
132 }
133 return it->second;
134}
135
136uint32_t parse_rgb(CRTerm const *term)
137{
138 auto const rgb = delete_with<cr_rgb_destroy>(cr_rgb_new());
139 auto const status = cr_rgb_set_from_term(rgb.get(), term);
140 if (status != CR_OK) {
141 throw Exception{Glib::ustring::compose(_("Unrecognized color '%1'"), get_string(term))};
142 }
143 return Display::AssembleARGB32(255, rgb->red, rgb->green, rgb->blue);
144}
145
146float parse_opacity(CRTerm const *term)
147{
148 auto const num = term->content.num;
149 if (!num) {
150 throw Exception{Glib::ustring::compose(_("Invalid opacity '%1'"), get_string(term))};
151 }
152
153 double value;
154 if (num->type == NUM_PERCENTAGE) {
155 value = num->val / 100.0f;
156 } else if (num->type == NUM_GENERIC) {
157 value = num->val;
158 } else {
159 throw Exception{Glib::ustring::compose(_("Invalid opacity units '%1'"), get_string(term))};
160 }
161
162 if (value > 1 || value < 0) {
163 throw Exception{Glib::ustring::compose(_("Opacity '%1' out of range"), get_string(term))};
164 }
165
166 return value;
167}
168
169float parse_width(CRTerm const *term)
170{
171 // Assuming px value only, which stays the same regardless of the size of the handles.
172 auto const num = term->content.num;
173 if (!num) {
174 throw Exception{Glib::ustring::compose(_("Invalid width '%1'"), get_string(term))};
175 }
176
177 float value;
178 if (num->type == NUM_LENGTH_PX) {
179 value = static_cast<float>(num->val);
180 } else {
181 throw Exception{Glib::ustring::compose(_("Invalid width units '%1'"), get_string(term))};
182 }
183
184 return value;
185}
186
187float parse_scale(CRTerm const *term)
188{
189 auto const num = term->content.num;
190 if (!num) {
191 throw Exception{Glib::ustring::compose(_("Invalid scale '%1'"), get_string(term))};
192 }
193
194 double value;
195 if (num->type == NUM_PERCENTAGE) {
196 value = num->val / 100.0f;
197 } else if (num->type == NUM_GENERIC) {
198 value = num->val;
199 } else {
200 throw Exception{Glib::ustring::compose(_("Invalid scale units '%1'"), get_string(term))};
201 }
202
203 if (value > 100 || value < 0) {
204 throw Exception{Glib::ustring::compose(_("Scale '%1' out of range"), get_string(term))};
205 }
206
207 return value;
208}
209
210template <auto parse, auto member>
211auto setter = +[] (CRDocHandler *handler, CRTerm const *term, bool important)
212{
213 auto &state = get_parsing_state(handler);
214 auto const value = parse(term);
215 for (auto &[handle, specificity] : state.selected_handles) {
216 (handle->*member).setProperty(value, specificity + 100000 * important);
217 }
218};
219
223std::unordered_map<std::string, void(*)(CRDocHandler *, CRTerm const *, bool)> const property_map = {
224 {"shape", setter<parse_shape, &Style::shape>},
225 {"fill", setter<parse_rgb, &Style::fill>},
226 {"stroke", setter<parse_rgb, &Style::stroke>},
227 {"outline", setter<parse_rgb, &Style::outline>},
228 {"opacity", setter<parse_opacity, &Style::opacity>},
229 {"fill-opacity", setter<parse_opacity, &Style::fill_opacity>},
230 {"stroke-opacity", setter<parse_opacity, &Style::stroke_opacity>},
231 {"outline-opacity", setter<parse_opacity, &Style::outline_opacity>},
232 {"stroke-width", setter<parse_width, &Style::stroke_width>},
233 {"outline-width", setter<parse_width, &Style::outline_width>},
234 {"scale", setter<parse_scale, &Style::scale>},
235 {"size-extra", setter<parse_width, &Style::size_extra>},
236 {"stroke-scale", setter<parse_scale, &Style::stroke_scale>},
237};
238
242std::optional<std::pair<TypeState, int>> configure_selector(CRSelector *a_selector)
243{
244 auto log_unrecognised = [&] (char const *selector) {
245 log_error(Glib::ustring::compose(_("Unrecognized selector '%1'"), selector),
246 a_selector->location);
247 };
248
249 cr_simple_sel_compute_specificity(a_selector->simple_sel);
250 int specificity = a_selector->simple_sel->specificity;
251
252 auto const selector_str = reinterpret_cast<char const *>(cr_simple_sel_one_to_string(a_selector->simple_sel));
253 auto const tokens = Glib::Regex::split_simple(":", selector_str);
254 auto const type_it = tokens.empty() ? ctrl_type_map.end() : ctrl_type_map.find(tokens.front());
255 if (type_it == ctrl_type_map.end()) {
256 log_unrecognised(selector_str);
257 return {};
258 }
259
260 auto selector = TypeState{type_it->second};
261 // for (auto &tok : tokens | std::views::drop(1)) { // Todo: When supported by CI Apple Clang.
262 for (int i = 1; i < tokens.size(); i++) {
263 auto &tok = tokens[i];
264 if (tok == "*") {
265 continue;
266 } else if (tok == "selected") {
267 selector.selected = true;
268 } else if (tok == "hover") {
269 specificity++;
270 selector.hover = true;
271 } else if (tok == "click") {
272 specificity++;
273 selector.click = true;
274 } else {
275 log_unrecognised(tok.c_str());
276 return {};
277 }
278 }
279
280 return {{ selector, specificity }};
281}
282
283bool fits(TypeState const &selector, TypeState const &handle)
284{
285 // Type must match for non-default selectors.
286 if (selector.type != CANVAS_ITEM_CTRL_TYPE_DEFAULT && selector.type != handle.type) {
287 return false;
288 }
289 // Any state set in selector must be set in handle.
290 return !((selector.selected && !handle.selected) ||
291 (selector.hover && !handle.hover) ||
292 (selector.click && !handle.click));
293}
294
298void set_selectors(CRDocHandler *a_handler, CRSelector *a_selector, bool is_users)
299{
300 auto &state = get_parsing_state(a_handler);
301 while (a_selector) {
302 if (auto const ret = configure_selector(a_selector)) {
303 auto const &[selector, specificity] = *ret;
304 for (auto &[handle, style] : state.result.style_map) {
305 if (fits(selector, handle)) {
306 state.selected_handles.emplace_back(&style, specificity + 10000 * is_users);
307 }
308 }
309 }
310 a_selector = a_selector->next;
311 }
312}
313
314template <bool is_users>
315void set_selectors(CRDocHandler *a_handler, CRSelector *a_selector)
316{
317 set_selectors(a_handler, a_selector, is_users);
318}
319
323void set_properties(CRDocHandler *a_handler, CRString *a_name, CRTerm *a_value, gboolean a_important)
324{
325 auto log_error_local = [&] (Glib::ustring const &err) {
326 log_error(err, a_value->location);
327 };
328
329 auto const property = cr_string_peek_raw_str(a_name);
330 if (!property) {
331 log_error_local(_("Empty or improper property, skipped."));
332 return;
333 }
334
335 auto const it = property_map.find(property);
336 if (it == property_map.end()) {
337 log_error_local(Glib::ustring::compose(_("Unrecognized property '%1'"), property));
338 return;
339 }
340
341 try {
342 it->second(a_handler, a_value, a_important);
343 } catch (Exception const &e) {
344 log_error_local(e.msg);
345 }
346}
347
351void clear_selectors(CRDocHandler *a_handler, CRSelector *a_selector)
352{
353 auto &state = get_parsing_state(a_handler);
354 state.selected_handles.clear();
355}
356
357uint32_t combine_rgb_a(uint32_t rgb, float a)
358{
359 EXTRACT_ARGB32(rgb, _, r, g, b)
360 return Display::AssembleARGB32(r, g, b, a * 255);
361}
362
363} // namespace
364
365uint32_t Style::getFill() const
366{
367 return combine_rgb_a(fill(), fill_opacity() * opacity());
368}
369
370uint32_t Style::getStroke() const
371{
372 return combine_rgb_a(stroke(), stroke_opacity() * opacity());
373}
374
375uint32_t Style::getOutline() const
376{
377 return combine_rgb_a(outline(), outline_opacity() * opacity());
378}
379
380Css parse_css(const std::string& css_file_name)
381{
382 Css result;
383
384 for (int type_i = CANVAS_ITEM_CTRL_TYPE_DEFAULT; type_i < LAST_ITEM_CANVAS_ITEM_CTRL_TYPE; type_i++) {
385 auto type = static_cast<CanvasItemCtrlType>(type_i);
386 for (auto state_bits = 0; state_bits < 8; state_bits++) {
387 bool selected = state_bits & (1 << 2);
388 bool hover = state_bits & (1 << 1);
389 bool click = state_bits & (1 << 0);
390 result.style_map[TypeState{type, selected, hover, click}] = {};
391 }
392 }
393
394 ParsingState state{result};
395
396 auto sac = delete_with<cr_doc_handler_destroy>(cr_doc_handler_new());
397 sac->app_data = &state;
398 sac->property = set_properties;
399 sac->end_selector = clear_selectors;
400
401 auto parse = [&] (IO::Resource::Domain domain) {
402 auto const css_path = IO::Resource::get_path_string(domain, IO::Resource::UIS, css_file_name.c_str());
403 if (Glib::file_test(css_path, Glib::FileTest::EXISTS)) {
404 auto parser = delete_with<cr_parser_destroy>(cr_parser_new_from_file(reinterpret_cast<unsigned char const *>(css_path.c_str()), CR_UTF_8));
405 cr_parser_set_sac_handler(parser.get(), sac.get());
406 cr_parser_parse(parser.get());
407 }
408 };
409
410 auto import = +[](CRDocHandler* a_handler, GList* a_media_list, CRString* a_uri, CRString* a_uri_default_ns, CRParsingLocation* a_location){
411 g_return_if_fail(a_handler && a_uri && a_uri->stryng && a_uri->stryng->str);
412 auto domain = IO::Resource::SYSTEM; // import files form installation folder
413 auto fname = a_uri->stryng->str;
414 auto const css_path = IO::Resource::get_path_string(domain, IO::Resource::UIS, fname);
415 if (Glib::file_test(css_path, Glib::FileTest::EXISTS)) {
416 auto parser = delete_with<cr_parser_destroy>(cr_parser_new_from_file(reinterpret_cast<unsigned char const *>(css_path.c_str()), CR_UTF_8));
417 cr_parser_set_sac_handler(parser.get(), a_handler);
418 cr_parser_parse(parser.get());
419 }
420 };
421
422 sac->import_style = import;
423
424 sac->start_selector = set_selectors<false>;
426
427 sac->start_selector = set_selectors<true>;
428 parse(IO::Resource::USER);
429
430 return result;
431}
432
433} // namespace Inkscape::Handles
434
435/*
436 Local Variables:
437 mode:c++
438 c-file-style:"stroustrup"
439 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
440 indent-tabs-mode:nil
441 fill-column:99
442 End:
443*/
444// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Cairo integration helpers.
Enums for CanvasItems.
The declaration of the #CRDocumentHandler class.
CRDocHandler * cr_doc_handler_new(void)
typedefG_BEGIN_DECLS struct _CRDocHandler CRDocHandler
@ NUM_GENERIC
Definition cr-num.h:57
@ NUM_LENGTH_PX
Definition cr-num.h:60
@ NUM_PERCENTAGE
Definition cr-num.h:73
The declaration file of the CRParser class.
CRParser * cr_parser_new_from_file(const guchar *a_file_uri, enum CREncoding a_enc)
enum CRStatus cr_parser_parse(CRParser *a_this)
enum CRStatus cr_parser_set_sac_handler(CRParser *a_this, CRDocHandler *a_handler)
CRRgb * cr_rgb_new(void)
enum CRStatus cr_rgb_set_from_term(CRRgb *a_this, const struct _CRTerm *a_value)
The declaration file of the CRSelector file.
typedefG_BEGIN_DECLS struct _CRSelector CRSelector
Definition cr-selector.h:40
guchar * cr_simple_sel_one_to_string(CRSimpleSel const *a_this)
enum CRStatus cr_simple_sel_compute_specificity(CRSimpleSel *a_this)
Declaration file of the CRString class.
typedefG_BEGIN_DECLS struct _CRString CRString
Definition cr-string.h:37
const gchar * cr_string_peek_raw_str(CRString const *a_this)
Declaration of the CRTerm class.
guchar * cr_term_to_string(CRTerm const *a_this)
The Croco library basic types definitions And global definitions.
@ CR_OK
Definition cr-utils.h:43
@ CR_UTF_8
Definition cr-utils.h:89
Css & result
std::vector< std::pair< Style *, int > > selected_handles
Glib::ustring msg
Ad-hoc smart pointer useful when interfacing with C code.
guint AssembleARGB32(guint32 a, guint32 r, guint32 g, guint32 b)
Classes related to control handle styling.
Css parse_css(const std::string &css_file_name)
std::string get_path_string(Domain domain, Type type, char const *filename, char const *extra)
Definition resource.cpp:148
auto delete_with(T *p)
Wrap a raw pointer in a std::unique_ptr with a custom function as the deleter.
Definition delete-with.h:26
@ CANVAS_ITEM_CTRL_SHAPE_SQUARE
@ CANVAS_ITEM_CTRL_SHAPE_TRIANGLE
@ CANVAS_ITEM_CTRL_SHAPE_PLUS
@ CANVAS_ITEM_CTRL_SHAPE_CROSS
@ CANVAS_ITEM_CTRL_SHAPE_CARROW
@ CANVAS_ITEM_CTRL_SHAPE_SALIGN
@ CANVAS_ITEM_CTRL_SHAPE_CALIGN
@ CANVAS_ITEM_CTRL_SHAPE_TRIANGLE_ANGLED
@ CANVAS_ITEM_CTRL_SHAPE_PIVOT
@ CANVAS_ITEM_CTRL_SHAPE_DIAMOND
@ CANVAS_ITEM_CTRL_SHAPE_DARROW
@ CANVAS_ITEM_CTRL_SHAPE_SARROW
@ CANVAS_ITEM_CTRL_SHAPE_CIRCLE
@ CANVAS_ITEM_CTRL_SHAPE_MALIGN
@ CANVAS_ITEM_CTRL_TYPE_ANCHOR
@ CANVAS_ITEM_CTRL_TYPE_CENTER
@ CANVAS_ITEM_CTRL_TYPE_GUIDE_HANDLE
@ CANVAS_ITEM_CTRL_TYPE_POINTER
@ CANVAS_ITEM_CTRL_TYPE_NODE_AUTO
@ CANVAS_ITEM_CTRL_TYPE_ADJ_ROTATE
@ CANVAS_ITEM_CTRL_TYPE_POINT
@ CANVAS_ITEM_CTRL_TYPE_NODE_CUSP
@ RUBBERBAND_TOUCHPATH_SELECT
@ CANVAS_ITEM_CTRL_TYPE_MARGIN
@ CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE
@ CANVAS_ITEM_CTRL_TYPE_SIZER
@ RUBBERBAND_TOUCHPATH_DESELECT
@ CANVAS_ITEM_CTRL_TYPE_MARKER
@ CANVAS_ITEM_CTRL_TYPE_NODE_SMOOTH
@ CANVAS_ITEM_CTRL_TYPE_INVISIPOINT
@ CANVAS_ITEM_CTRL_TYPE_ADJ_SALIGN
@ CANVAS_ITEM_CTRL_TYPE_NODE_SYMMETRICAL
@ CANVAS_ITEM_CTRL_TYPE_ADJ_CALIGN
@ CANVAS_ITEM_CTRL_TYPE_SHAPER
@ CANVAS_ITEM_CTRL_TYPE_MOVE
@ RUBBERBAND_TOUCHPATH_FLOOD
@ RUBBERBAND_TOUCHPATH_ERASER
@ CANVAS_ITEM_CTRL_TYPE_MESH
@ CANVAS_ITEM_CTRL_TYPE_ADJ_SKEW
@ CANVAS_ITEM_CTRL_TYPE_LPE
@ CANVAS_ITEM_CTRL_TYPE_ROTATE
@ CANVAS_ITEM_CTRL_TYPE_ADJ_MALIGN
@ CANVAS_ITEM_CTRL_TYPE_ADJ_CENTER
@ LAST_ITEM_CANVAS_ITEM_CTRL_TYPE
@ CANVAS_ITEM_CTRL_TYPE_DEFAULT
@ RUBBERBAND_TOUCHPATH_INVERT
RGB rgb
Definition quantize.cpp:36
Inkscape::IO::Resource - simple resource API.
int num
Definition scribble.cpp:47
The result of parsing the handle styling CSS files, containing all information needed to style a give...
std::unordered_map< TypeState, Style > style_map
Property< uint32_t > stroke
Property< float > outline_opacity
Property< uint32_t > outline
Property< uint32_t > fill
Struct to manage type and state.
gdouble val
Definition cr-num.h:93
An abstraction of a css2 term as defined in the CSS2 spec in appendix D.1: term ::= [ NUMBER S* | PER...
Definition cr-term.h:83
CRParsingLocation location
Definition cr-term.h:150
CRNum * num
Definition cr-term.h:108
union _CRTerm::@7 content
The content of the term.