Inkscape
Vector Graphics Editor
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages Concepts
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};
81
85std::unordered_map<std::string, CanvasItemCtrlShape> const ctrl_shape_map = {
87 {"'diamond'", CANVAS_ITEM_CTRL_SHAPE_DIAMOND},
89 {"'triangle'", CANVAS_ITEM_CTRL_SHAPE_TRIANGLE},
90 {"'triangle-angled'", CANVAS_ITEM_CTRL_SHAPE_TRIANGLE_ANGLED},
96 {"'skew-arrow'", CANVAS_ITEM_CTRL_SHAPE_SARROW},
97 {"'curved-arrow'", CANVAS_ITEM_CTRL_SHAPE_CARROW},
98 {"'side-align'", CANVAS_ITEM_CTRL_SHAPE_SALIGN},
99 {"'corner-align'", CANVAS_ITEM_CTRL_SHAPE_CALIGN},
100 {"'middle-align'", CANVAS_ITEM_CTRL_SHAPE_MALIGN}
101};
102
103struct Exception
104{
105 Glib::ustring msg;
106};
107
108void log_error(Glib::ustring const &err, CRParsingLocation const &loc)
109{
110 std::cerr << loc.line << ':' << loc.column << ": " << err << std::endl;
111}
112
113std::string get_string(CRTerm const *term)
114{
115 auto const cstr = delete_with<g_free>(cr_term_to_string(term));
116 if (!cstr) {
117 throw Exception{_("Empty or improper value, skipped")};
118 }
119 return reinterpret_cast<char *>(cstr.get());
120}
121
122CanvasItemCtrlShape parse_shape(CRTerm const *term)
123{
124 auto const str = get_string(term);
125 auto const it = ctrl_shape_map.find(str);
126 if (it == ctrl_shape_map.end()) {
127 throw Exception{Glib::ustring::compose(_("Unrecognized shape '%1'"), str)};
128 }
129 return it->second;
130}
131
132uint32_t parse_rgb(CRTerm const *term)
133{
134 auto const rgb = delete_with<cr_rgb_destroy>(cr_rgb_new());
135 auto const status = cr_rgb_set_from_term(rgb.get(), term);
136 if (status != CR_OK) {
137 throw Exception{Glib::ustring::compose(_("Unrecognized color '%1'"), get_string(term))};
138 }
139 return Display::AssembleARGB32(255, rgb->red, rgb->green, rgb->blue);
140}
141
142float parse_opacity(CRTerm const *term)
143{
144 auto const num = term->content.num;
145 if (!num) {
146 throw Exception{Glib::ustring::compose(_("Invalid opacity '%1'"), get_string(term))};
147 }
148
149 double value;
150 if (num->type == NUM_PERCENTAGE) {
151 value = num->val / 100.0f;
152 } else if (num->type == NUM_GENERIC) {
153 value = num->val;
154 } else {
155 throw Exception{Glib::ustring::compose(_("Invalid opacity units '%1'"), get_string(term))};
156 }
157
158 if (value > 1 || value < 0) {
159 throw Exception{Glib::ustring::compose(_("Opacity '%1' out of range"), get_string(term))};
160 }
161
162 return value;
163}
164
165float parse_width(CRTerm const *term)
166{
167 // Assuming px value only, which stays the same regardless of the size of the handles.
168 auto const num = term->content.num;
169 if (!num) {
170 throw Exception{Glib::ustring::compose(_("Invalid width '%1'"), get_string(term))};
171 }
172
173 float value;
174 if (num->type == NUM_LENGTH_PX) {
175 value = static_cast<float>(num->val);
176 } else {
177 throw Exception{Glib::ustring::compose(_("Invalid width units '%1'"), get_string(term))};
178 }
179
180 return value;
181}
182
183float parse_scale(CRTerm const *term)
184{
185 auto const num = term->content.num;
186 if (!num) {
187 throw Exception{Glib::ustring::compose(_("Invalid scale '%1'"), get_string(term))};
188 }
189
190 double value;
191 if (num->type == NUM_PERCENTAGE) {
192 value = num->val / 100.0f;
193 } else if (num->type == NUM_GENERIC) {
194 value = num->val;
195 } else {
196 throw Exception{Glib::ustring::compose(_("Invalid scale units '%1'"), get_string(term))};
197 }
198
199 if (value > 100 || value < 0) {
200 throw Exception{Glib::ustring::compose(_("Scale '%1' out of range"), get_string(term))};
201 }
202
203 return value;
204}
205
206template <auto parse, auto member>
207auto setter = +[] (CRDocHandler *handler, CRTerm const *term, bool important)
208{
209 auto &state = get_parsing_state(handler);
210 auto const value = parse(term);
211 for (auto &[handle, specificity] : state.selected_handles) {
212 (handle->*member).setProperty(value, specificity + 100000 * important);
213 }
214};
215
219std::unordered_map<std::string, void(*)(CRDocHandler *, CRTerm const *, bool)> const property_map = {
220 {"shape", setter<parse_shape, &Style::shape>},
221 {"fill", setter<parse_rgb, &Style::fill>},
222 {"stroke", setter<parse_rgb, &Style::stroke>},
223 {"outline", setter<parse_rgb, &Style::outline>},
224 {"opacity", setter<parse_opacity, &Style::opacity>},
225 {"fill-opacity", setter<parse_opacity, &Style::fill_opacity>},
226 {"stroke-opacity", setter<parse_opacity, &Style::stroke_opacity>},
227 {"outline-opacity", setter<parse_opacity, &Style::outline_opacity>},
228 {"stroke-width", setter<parse_width, &Style::stroke_width>},
229 {"outline-width", setter<parse_width, &Style::outline_width>},
230 {"scale", setter<parse_scale, &Style::scale>},
231 {"size-extra", setter<parse_width, &Style::size_extra>},
232 {"stroke-scale", setter<parse_scale, &Style::stroke_scale>},
233};
234
238std::optional<std::pair<TypeState, int>> configure_selector(CRSelector *a_selector)
239{
240 auto log_unrecognised = [&] (char const *selector) {
241 log_error(Glib::ustring::compose(_("Unrecognized selector '%1'"), selector),
242 a_selector->location);
243 };
244
245 cr_simple_sel_compute_specificity(a_selector->simple_sel);
246 int specificity = a_selector->simple_sel->specificity;
247
248 auto const selector_str = reinterpret_cast<char const *>(cr_simple_sel_one_to_string(a_selector->simple_sel));
249 auto const tokens = Glib::Regex::split_simple(":", selector_str);
250 auto const type_it = tokens.empty() ? ctrl_type_map.end() : ctrl_type_map.find(tokens.front());
251 if (type_it == ctrl_type_map.end()) {
252 log_unrecognised(selector_str);
253 return {};
254 }
255
256 auto selector = TypeState{type_it->second};
257 // for (auto &tok : tokens | std::views::drop(1)) { // Todo: When supported by CI Apple Clang.
258 for (int i = 1; i < tokens.size(); i++) {
259 auto &tok = tokens[i];
260 if (tok == "*") {
261 continue;
262 } else if (tok == "selected") {
263 selector.selected = true;
264 } else if (tok == "hover") {
265 specificity++;
266 selector.hover = true;
267 } else if (tok == "click") {
268 specificity++;
269 selector.click = true;
270 } else {
271 log_unrecognised(tok.c_str());
272 return {};
273 }
274 }
275
276 return {{ selector, specificity }};
277}
278
279bool fits(TypeState const &selector, TypeState const &handle)
280{
281 // Type must match for non-default selectors.
282 if (selector.type != CANVAS_ITEM_CTRL_TYPE_DEFAULT && selector.type != handle.type) {
283 return false;
284 }
285 // Any state set in selector must be set in handle.
286 return !((selector.selected && !handle.selected) ||
287 (selector.hover && !handle.hover) ||
288 (selector.click && !handle.click));
289}
290
294void set_selectors(CRDocHandler *a_handler, CRSelector *a_selector, bool is_users)
295{
296 auto &state = get_parsing_state(a_handler);
297 while (a_selector) {
298 if (auto const ret = configure_selector(a_selector)) {
299 auto const &[selector, specificity] = *ret;
300 for (auto &[handle, style] : state.result.style_map) {
301 if (fits(selector, handle)) {
302 state.selected_handles.emplace_back(&style, specificity + 10000 * is_users);
303 }
304 }
305 }
306 a_selector = a_selector->next;
307 }
308}
309
310template <bool is_users>
311void set_selectors(CRDocHandler *a_handler, CRSelector *a_selector)
312{
313 set_selectors(a_handler, a_selector, is_users);
314}
315
319void set_properties(CRDocHandler *a_handler, CRString *a_name, CRTerm *a_value, gboolean a_important)
320{
321 auto log_error_local = [&] (Glib::ustring const &err) {
322 log_error(err, a_value->location);
323 };
324
325 auto const property = cr_string_peek_raw_str(a_name);
326 if (!property) {
327 log_error_local(_("Empty or improper property, skipped."));
328 return;
329 }
330
331 auto const it = property_map.find(property);
332 if (it == property_map.end()) {
333 log_error_local(Glib::ustring::compose(_("Unrecognized property '%1'"), property));
334 return;
335 }
336
337 try {
338 it->second(a_handler, a_value, a_important);
339 } catch (Exception const &e) {
340 log_error_local(e.msg);
341 }
342}
343
347void clear_selectors(CRDocHandler *a_handler, CRSelector *a_selector)
348{
349 auto &state = get_parsing_state(a_handler);
350 state.selected_handles.clear();
351}
352
353uint32_t combine_rgb_a(uint32_t rgb, float a)
354{
355 EXTRACT_ARGB32(rgb, _, r, g, b)
356 return Display::AssembleARGB32(r, g, b, a * 255);
357}
358
359} // namespace
360
361uint32_t Style::getFill() const
362{
363 return combine_rgb_a(fill(), fill_opacity() * opacity());
364}
365
366uint32_t Style::getStroke() const
367{
368 return combine_rgb_a(stroke(), stroke_opacity() * opacity());
369}
370
371uint32_t Style::getOutline() const
372{
373 return combine_rgb_a(outline(), outline_opacity() * opacity());
374}
375
376Css parse_css(const std::string& css_file_name)
377{
378 Css result;
379
380 for (int type_i = CANVAS_ITEM_CTRL_TYPE_DEFAULT; type_i < LAST_ITEM_CANVAS_ITEM_CTRL_TYPE; type_i++) {
381 auto type = static_cast<CanvasItemCtrlType>(type_i);
382 for (auto state_bits = 0; state_bits < 8; state_bits++) {
383 bool selected = state_bits & (1 << 2);
384 bool hover = state_bits & (1 << 1);
385 bool click = state_bits & (1 << 0);
386 result.style_map[TypeState{type, selected, hover, click}] = {};
387 }
388 }
389
390 ParsingState state{result};
391
392 auto sac = delete_with<cr_doc_handler_destroy>(cr_doc_handler_new());
393 sac->app_data = &state;
394 sac->property = set_properties;
395 sac->end_selector = clear_selectors;
396
397 auto parse = [&] (IO::Resource::Domain domain) {
398 auto const css_path = IO::Resource::get_path_string(domain, IO::Resource::UIS, css_file_name.c_str());
399 if (Glib::file_test(css_path, Glib::FileTest::EXISTS)) {
400 auto parser = delete_with<cr_parser_destroy>(cr_parser_new_from_file(reinterpret_cast<unsigned char const *>(css_path.c_str()), CR_UTF_8));
401 cr_parser_set_sac_handler(parser.get(), sac.get());
402 cr_parser_parse(parser.get());
403 }
404 };
405
406 auto import = +[](CRDocHandler* a_handler, GList* a_media_list, CRString* a_uri, CRString* a_uri_default_ns, CRParsingLocation* a_location){
407 g_return_if_fail(a_handler && a_uri && a_uri->stryng && a_uri->stryng->str);
408 auto domain = IO::Resource::SYSTEM; // import files form installation folder
409 auto fname = a_uri->stryng->str;
410 auto const css_path = IO::Resource::get_path_string(domain, IO::Resource::UIS, fname);
411 if (Glib::file_test(css_path, Glib::FileTest::EXISTS)) {
412 auto parser = delete_with<cr_parser_destroy>(cr_parser_new_from_file(reinterpret_cast<unsigned char const *>(css_path.c_str()), CR_UTF_8));
413 cr_parser_set_sac_handler(parser.get(), a_handler);
414 cr_parser_parse(parser.get());
415 }
416 };
417
418 sac->import_style = import;
419
420 sac->start_selector = set_selectors<false>;
422
423 sac->start_selector = set_selectors<true>;
424 parse(IO::Resource::USER);
425
426 return result;
427}
428
429} // namespace Inkscape::Handles
430
431/*
432 Local Variables:
433 mode:c++
434 c-file-style:"stroustrup"
435 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
436 indent-tabs-mode:nil
437 fill-column:99
438 End:
439*/
440// 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:19
@ 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
@ 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
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.