cataract 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.clang-tidy +30 -0
- data/.github/workflows/ci-macos.yml +12 -0
- data/.github/workflows/ci.yml +77 -0
- data/.github/workflows/test.yml +76 -0
- data/.gitignore +45 -0
- data/.overcommit.yml +38 -0
- data/.rubocop.yml +83 -0
- data/BENCHMARKS.md +201 -0
- data/CHANGELOG.md +1 -0
- data/Gemfile +27 -0
- data/LICENSE +21 -0
- data/RAGEL_MIGRATION.md +60 -0
- data/README.md +292 -0
- data/Rakefile +209 -0
- data/benchmarks/benchmark_harness.rb +193 -0
- data/benchmarks/benchmark_merging.rb +121 -0
- data/benchmarks/benchmark_optimization_comparison.rb +168 -0
- data/benchmarks/benchmark_parsing.rb +153 -0
- data/benchmarks/benchmark_ragel_removal.rb +56 -0
- data/benchmarks/benchmark_runner.rb +70 -0
- data/benchmarks/benchmark_serialization.rb +180 -0
- data/benchmarks/benchmark_shorthand.rb +109 -0
- data/benchmarks/benchmark_shorthand_expansion.rb +176 -0
- data/benchmarks/benchmark_specificity.rb +124 -0
- data/benchmarks/benchmark_string_allocation.rb +151 -0
- data/benchmarks/benchmark_stylesheet_to_s.rb +62 -0
- data/benchmarks/benchmark_to_s_cached.rb +55 -0
- data/benchmarks/benchmark_value_splitter.rb +54 -0
- data/benchmarks/benchmark_yjit.rb +158 -0
- data/benchmarks/benchmark_yjit_workers.rb +61 -0
- data/benchmarks/profile_to_s.rb +23 -0
- data/benchmarks/speedup_calculator.rb +83 -0
- data/benchmarks/system_metadata.rb +81 -0
- data/benchmarks/templates/benchmarks.md.erb +221 -0
- data/benchmarks/yjit_tests.rb +141 -0
- data/cataract.gemspec +34 -0
- data/cliff.toml +92 -0
- data/examples/color_conversion_visual_test/color_conversion_test.html +3603 -0
- data/examples/color_conversion_visual_test/generate.rb +202 -0
- data/examples/color_conversion_visual_test/template.html.erb +259 -0
- data/examples/css_analyzer/analyzer.rb +164 -0
- data/examples/css_analyzer/analyzers/base.rb +33 -0
- data/examples/css_analyzer/analyzers/colors.rb +133 -0
- data/examples/css_analyzer/analyzers/important.rb +88 -0
- data/examples/css_analyzer/analyzers/properties.rb +61 -0
- data/examples/css_analyzer/analyzers/specificity.rb +68 -0
- data/examples/css_analyzer/templates/report.html.erb +575 -0
- data/examples/css_analyzer.rb +69 -0
- data/examples/github_analysis.html +5343 -0
- data/ext/cataract/cataract.c +1086 -0
- data/ext/cataract/cataract.h +174 -0
- data/ext/cataract/css_parser.c +1435 -0
- data/ext/cataract/extconf.rb +48 -0
- data/ext/cataract/import_scanner.c +174 -0
- data/ext/cataract/merge.c +973 -0
- data/ext/cataract/shorthand_expander.c +902 -0
- data/ext/cataract/specificity.c +213 -0
- data/ext/cataract/value_splitter.c +116 -0
- data/ext/cataract_color/cataract_color.c +16 -0
- data/ext/cataract_color/color_conversion.c +1687 -0
- data/ext/cataract_color/color_conversion.h +136 -0
- data/ext/cataract_color/color_conversion_lab.c +571 -0
- data/ext/cataract_color/color_conversion_named.c +259 -0
- data/ext/cataract_color/color_conversion_oklab.c +547 -0
- data/ext/cataract_color/extconf.rb +23 -0
- data/ext/cataract_old/cataract.c +393 -0
- data/ext/cataract_old/cataract.h +250 -0
- data/ext/cataract_old/css_parser.c +933 -0
- data/ext/cataract_old/extconf.rb +67 -0
- data/ext/cataract_old/import_scanner.c +174 -0
- data/ext/cataract_old/merge.c +776 -0
- data/ext/cataract_old/shorthand_expander.c +902 -0
- data/ext/cataract_old/specificity.c +213 -0
- data/ext/cataract_old/stylesheet.c +290 -0
- data/ext/cataract_old/value_splitter.c +116 -0
- data/lib/cataract/at_rule.rb +97 -0
- data/lib/cataract/color_conversion.rb +18 -0
- data/lib/cataract/declarations.rb +332 -0
- data/lib/cataract/import_resolver.rb +210 -0
- data/lib/cataract/rule.rb +131 -0
- data/lib/cataract/stylesheet.rb +716 -0
- data/lib/cataract/stylesheet_scope.rb +257 -0
- data/lib/cataract/version.rb +5 -0
- data/lib/cataract.rb +107 -0
- data/lib/tasks/gem.rake +158 -0
- data/scripts/fuzzer/run.rb +828 -0
- data/scripts/fuzzer/worker.rb +99 -0
- data/scripts/generate_benchmarks_md.rb +155 -0
- metadata +135 -0
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
#include "cataract.h"
|
|
2
|
+
|
|
3
|
+
// Cache frequently used symbol IDs (initialized in init_merge_constants)
|
|
4
|
+
static ID id_value = 0;
|
|
5
|
+
static ID id_specificity = 0;
|
|
6
|
+
static ID id_important = 0;
|
|
7
|
+
static ID id_struct_class = 0;
|
|
8
|
+
|
|
9
|
+
// Cached property name strings (frozen, never GC'd)
|
|
10
|
+
// Initialized in init_merge_constants() at module load time
|
|
11
|
+
static VALUE str_margin = Qnil;
|
|
12
|
+
static VALUE str_margin_top = Qnil;
|
|
13
|
+
static VALUE str_margin_right = Qnil;
|
|
14
|
+
static VALUE str_margin_bottom = Qnil;
|
|
15
|
+
static VALUE str_margin_left = Qnil;
|
|
16
|
+
static VALUE str_padding = Qnil;
|
|
17
|
+
static VALUE str_padding_top = Qnil;
|
|
18
|
+
static VALUE str_padding_right = Qnil;
|
|
19
|
+
static VALUE str_padding_bottom = Qnil;
|
|
20
|
+
static VALUE str_padding_left = Qnil;
|
|
21
|
+
static VALUE str_border_width = Qnil;
|
|
22
|
+
static VALUE str_border_top_width = Qnil;
|
|
23
|
+
static VALUE str_border_right_width = Qnil;
|
|
24
|
+
static VALUE str_border_bottom_width = Qnil;
|
|
25
|
+
static VALUE str_border_left_width = Qnil;
|
|
26
|
+
static VALUE str_border_style = Qnil;
|
|
27
|
+
static VALUE str_border_top_style = Qnil;
|
|
28
|
+
static VALUE str_border_right_style = Qnil;
|
|
29
|
+
static VALUE str_border_bottom_style = Qnil;
|
|
30
|
+
static VALUE str_border_left_style = Qnil;
|
|
31
|
+
static VALUE str_border_color = Qnil;
|
|
32
|
+
static VALUE str_border_top_color = Qnil;
|
|
33
|
+
static VALUE str_border_right_color = Qnil;
|
|
34
|
+
static VALUE str_border_bottom_color = Qnil;
|
|
35
|
+
static VALUE str_border_left_color = Qnil;
|
|
36
|
+
static VALUE str_border = Qnil;
|
|
37
|
+
static VALUE str_font = Qnil;
|
|
38
|
+
static VALUE str_font_style = Qnil;
|
|
39
|
+
static VALUE str_font_variant = Qnil;
|
|
40
|
+
static VALUE str_font_weight = Qnil;
|
|
41
|
+
static VALUE str_font_size = Qnil;
|
|
42
|
+
static VALUE str_line_height = Qnil;
|
|
43
|
+
static VALUE str_font_family = Qnil;
|
|
44
|
+
static VALUE str_list_style = Qnil;
|
|
45
|
+
static VALUE str_list_style_type = Qnil;
|
|
46
|
+
static VALUE str_list_style_position = Qnil;
|
|
47
|
+
static VALUE str_list_style_image = Qnil;
|
|
48
|
+
static VALUE str_background = Qnil;
|
|
49
|
+
static VALUE str_background_color = Qnil;
|
|
50
|
+
static VALUE str_background_image = Qnil;
|
|
51
|
+
static VALUE str_background_repeat = Qnil;
|
|
52
|
+
static VALUE str_background_attachment = Qnil;
|
|
53
|
+
static VALUE str_background_position = Qnil;
|
|
54
|
+
|
|
55
|
+
// Context for expanded property iteration
|
|
56
|
+
struct expand_context {
|
|
57
|
+
VALUE properties_hash;
|
|
58
|
+
int specificity;
|
|
59
|
+
VALUE important;
|
|
60
|
+
VALUE declaration_struct;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Callback for rb_hash_foreach - process expanded properties and apply cascade
|
|
64
|
+
static int merge_expanded_callback(VALUE exp_prop, VALUE exp_value, VALUE ctx_val) {
|
|
65
|
+
struct expand_context *ctx = (struct expand_context *)ctx_val;
|
|
66
|
+
|
|
67
|
+
// Expanded properties from shorthand expanders are already lowercase
|
|
68
|
+
// No need to lowercase again
|
|
69
|
+
int is_important = RTEST(ctx->important);
|
|
70
|
+
|
|
71
|
+
// Apply cascade rules for expanded property
|
|
72
|
+
VALUE existing = rb_hash_aref(ctx->properties_hash, exp_prop);
|
|
73
|
+
|
|
74
|
+
if (NIL_P(existing)) {
|
|
75
|
+
VALUE prop_data = rb_hash_new();
|
|
76
|
+
rb_hash_aset(prop_data, ID2SYM(id_value), exp_value);
|
|
77
|
+
rb_hash_aset(prop_data, ID2SYM(id_specificity), INT2NUM(ctx->specificity));
|
|
78
|
+
rb_hash_aset(prop_data, ID2SYM(id_important), ctx->important);
|
|
79
|
+
rb_hash_aset(prop_data, ID2SYM(id_struct_class), ctx->declaration_struct);
|
|
80
|
+
rb_hash_aset(ctx->properties_hash, exp_prop, prop_data);
|
|
81
|
+
} else {
|
|
82
|
+
VALUE existing_spec = rb_hash_aref(existing, ID2SYM(id_specificity));
|
|
83
|
+
VALUE existing_important = rb_hash_aref(existing, ID2SYM(id_important));
|
|
84
|
+
|
|
85
|
+
int existing_spec_int = NUM2INT(existing_spec);
|
|
86
|
+
int existing_is_important = RTEST(existing_important);
|
|
87
|
+
|
|
88
|
+
int should_replace = 0;
|
|
89
|
+
if (is_important) {
|
|
90
|
+
if (!existing_is_important || existing_spec_int <= ctx->specificity) {
|
|
91
|
+
should_replace = 1;
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
if (!existing_is_important && existing_spec_int <= ctx->specificity) {
|
|
95
|
+
should_replace = 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (should_replace) {
|
|
100
|
+
rb_hash_aset(existing, ID2SYM(id_value), exp_value);
|
|
101
|
+
rb_hash_aset(existing, ID2SYM(id_specificity), INT2NUM(ctx->specificity));
|
|
102
|
+
rb_hash_aset(existing, ID2SYM(id_important), ctx->important);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
RB_GC_GUARD(exp_prop);
|
|
107
|
+
RB_GC_GUARD(exp_value);
|
|
108
|
+
return ST_CONTINUE;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Callback for rb_hash_foreach - builds result array from properties hash
|
|
112
|
+
static int merge_build_result_callback(VALUE property, VALUE prop_data, VALUE result_ary) {
|
|
113
|
+
// Get Declaration struct class (cached in prop_data for efficiency)
|
|
114
|
+
VALUE declaration_struct = rb_hash_aref(prop_data, ID2SYM(id_struct_class));
|
|
115
|
+
VALUE value = rb_hash_aref(prop_data, ID2SYM(id_value));
|
|
116
|
+
VALUE important = rb_hash_aref(prop_data, ID2SYM(id_important));
|
|
117
|
+
|
|
118
|
+
// Create Declaration struct
|
|
119
|
+
VALUE decl_struct = rb_struct_new(declaration_struct, property, value, important);
|
|
120
|
+
rb_ary_push(result_ary, decl_struct);
|
|
121
|
+
|
|
122
|
+
return ST_CONTINUE;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Initialize cached property strings (called once at module init)
|
|
126
|
+
void init_merge_constants(void) {
|
|
127
|
+
// Initialize symbol IDs
|
|
128
|
+
id_value = rb_intern("value");
|
|
129
|
+
id_specificity = rb_intern("specificity");
|
|
130
|
+
id_important = rb_intern("important");
|
|
131
|
+
id_struct_class = rb_intern("_struct_class");
|
|
132
|
+
|
|
133
|
+
// Margin properties
|
|
134
|
+
str_margin = rb_str_freeze(USASCII_STR("margin"));
|
|
135
|
+
str_margin_top = rb_str_freeze(USASCII_STR("margin-top"));
|
|
136
|
+
str_margin_right = rb_str_freeze(USASCII_STR("margin-right"));
|
|
137
|
+
str_margin_bottom = rb_str_freeze(USASCII_STR("margin-bottom"));
|
|
138
|
+
str_margin_left = rb_str_freeze(USASCII_STR("margin-left"));
|
|
139
|
+
|
|
140
|
+
// Padding properties
|
|
141
|
+
str_padding = rb_str_freeze(USASCII_STR("padding"));
|
|
142
|
+
str_padding_top = rb_str_freeze(USASCII_STR("padding-top"));
|
|
143
|
+
str_padding_right = rb_str_freeze(USASCII_STR("padding-right"));
|
|
144
|
+
str_padding_bottom = rb_str_freeze(USASCII_STR("padding-bottom"));
|
|
145
|
+
str_padding_left = rb_str_freeze(USASCII_STR("padding-left"));
|
|
146
|
+
|
|
147
|
+
// Border-width properties
|
|
148
|
+
str_border_width = rb_str_freeze(USASCII_STR("border-width"));
|
|
149
|
+
str_border_top_width = rb_str_freeze(USASCII_STR("border-top-width"));
|
|
150
|
+
str_border_right_width = rb_str_freeze(USASCII_STR("border-right-width"));
|
|
151
|
+
str_border_bottom_width = rb_str_freeze(USASCII_STR("border-bottom-width"));
|
|
152
|
+
str_border_left_width = rb_str_freeze(USASCII_STR("border-left-width"));
|
|
153
|
+
|
|
154
|
+
// Border-style properties
|
|
155
|
+
str_border_style = rb_str_freeze(USASCII_STR("border-style"));
|
|
156
|
+
str_border_top_style = rb_str_freeze(USASCII_STR("border-top-style"));
|
|
157
|
+
str_border_right_style = rb_str_freeze(USASCII_STR("border-right-style"));
|
|
158
|
+
str_border_bottom_style = rb_str_freeze(USASCII_STR("border-bottom-style"));
|
|
159
|
+
str_border_left_style = rb_str_freeze(USASCII_STR("border-left-style"));
|
|
160
|
+
|
|
161
|
+
// Border-color properties
|
|
162
|
+
str_border_color = rb_str_freeze(USASCII_STR("border-color"));
|
|
163
|
+
str_border_top_color = rb_str_freeze(USASCII_STR("border-top-color"));
|
|
164
|
+
str_border_right_color = rb_str_freeze(USASCII_STR("border-right-color"));
|
|
165
|
+
str_border_bottom_color = rb_str_freeze(USASCII_STR("border-bottom-color"));
|
|
166
|
+
str_border_left_color = rb_str_freeze(USASCII_STR("border-left-color"));
|
|
167
|
+
|
|
168
|
+
// Border shorthand
|
|
169
|
+
str_border = rb_str_freeze(USASCII_STR("border"));
|
|
170
|
+
|
|
171
|
+
// Font properties
|
|
172
|
+
str_font = rb_str_freeze(USASCII_STR("font"));
|
|
173
|
+
str_font_style = rb_str_freeze(USASCII_STR("font-style"));
|
|
174
|
+
str_font_variant = rb_str_freeze(USASCII_STR("font-variant"));
|
|
175
|
+
str_font_weight = rb_str_freeze(USASCII_STR("font-weight"));
|
|
176
|
+
str_font_size = rb_str_freeze(USASCII_STR("font-size"));
|
|
177
|
+
str_line_height = rb_str_freeze(USASCII_STR("line-height"));
|
|
178
|
+
str_font_family = rb_str_freeze(USASCII_STR("font-family"));
|
|
179
|
+
|
|
180
|
+
// List-style properties
|
|
181
|
+
str_list_style = rb_str_freeze(USASCII_STR("list-style"));
|
|
182
|
+
str_list_style_type = rb_str_freeze(USASCII_STR("list-style-type"));
|
|
183
|
+
str_list_style_position = rb_str_freeze(USASCII_STR("list-style-position"));
|
|
184
|
+
str_list_style_image = rb_str_freeze(USASCII_STR("list-style-image"));
|
|
185
|
+
|
|
186
|
+
// Background properties
|
|
187
|
+
str_background = rb_str_freeze(USASCII_STR("background"));
|
|
188
|
+
str_background_color = rb_str_freeze(USASCII_STR("background-color"));
|
|
189
|
+
str_background_image = rb_str_freeze(USASCII_STR("background-image"));
|
|
190
|
+
str_background_repeat = rb_str_freeze(USASCII_STR("background-repeat"));
|
|
191
|
+
str_background_attachment = rb_str_freeze(USASCII_STR("background-attachment"));
|
|
192
|
+
str_background_position = rb_str_freeze(USASCII_STR("background-position"));
|
|
193
|
+
|
|
194
|
+
// Register all strings with GC so they're never collected
|
|
195
|
+
rb_gc_register_mark_object(str_margin);
|
|
196
|
+
rb_gc_register_mark_object(str_margin_top);
|
|
197
|
+
rb_gc_register_mark_object(str_margin_right);
|
|
198
|
+
rb_gc_register_mark_object(str_margin_bottom);
|
|
199
|
+
rb_gc_register_mark_object(str_margin_left);
|
|
200
|
+
rb_gc_register_mark_object(str_padding);
|
|
201
|
+
rb_gc_register_mark_object(str_padding_top);
|
|
202
|
+
rb_gc_register_mark_object(str_padding_right);
|
|
203
|
+
rb_gc_register_mark_object(str_padding_bottom);
|
|
204
|
+
rb_gc_register_mark_object(str_padding_left);
|
|
205
|
+
rb_gc_register_mark_object(str_border_width);
|
|
206
|
+
rb_gc_register_mark_object(str_border_top_width);
|
|
207
|
+
rb_gc_register_mark_object(str_border_right_width);
|
|
208
|
+
rb_gc_register_mark_object(str_border_bottom_width);
|
|
209
|
+
rb_gc_register_mark_object(str_border_left_width);
|
|
210
|
+
rb_gc_register_mark_object(str_border_style);
|
|
211
|
+
rb_gc_register_mark_object(str_border_top_style);
|
|
212
|
+
rb_gc_register_mark_object(str_border_right_style);
|
|
213
|
+
rb_gc_register_mark_object(str_border_bottom_style);
|
|
214
|
+
rb_gc_register_mark_object(str_border_left_style);
|
|
215
|
+
rb_gc_register_mark_object(str_border_color);
|
|
216
|
+
rb_gc_register_mark_object(str_border_top_color);
|
|
217
|
+
rb_gc_register_mark_object(str_border_right_color);
|
|
218
|
+
rb_gc_register_mark_object(str_border_bottom_color);
|
|
219
|
+
rb_gc_register_mark_object(str_border_left_color);
|
|
220
|
+
rb_gc_register_mark_object(str_border);
|
|
221
|
+
rb_gc_register_mark_object(str_font);
|
|
222
|
+
rb_gc_register_mark_object(str_font_style);
|
|
223
|
+
rb_gc_register_mark_object(str_font_variant);
|
|
224
|
+
rb_gc_register_mark_object(str_font_weight);
|
|
225
|
+
rb_gc_register_mark_object(str_font_size);
|
|
226
|
+
rb_gc_register_mark_object(str_line_height);
|
|
227
|
+
rb_gc_register_mark_object(str_font_family);
|
|
228
|
+
rb_gc_register_mark_object(str_list_style);
|
|
229
|
+
rb_gc_register_mark_object(str_list_style_type);
|
|
230
|
+
rb_gc_register_mark_object(str_list_style_position);
|
|
231
|
+
rb_gc_register_mark_object(str_list_style_image);
|
|
232
|
+
rb_gc_register_mark_object(str_background);
|
|
233
|
+
rb_gc_register_mark_object(str_background_color);
|
|
234
|
+
rb_gc_register_mark_object(str_background_image);
|
|
235
|
+
rb_gc_register_mark_object(str_background_repeat);
|
|
236
|
+
rb_gc_register_mark_object(str_background_attachment);
|
|
237
|
+
rb_gc_register_mark_object(str_background_position);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Helper macros to extract property data from properties_hash
|
|
241
|
+
// Note: These use id_value, id_specificity, id_important which are initialized in cataract_merge
|
|
242
|
+
#define GET_PROP_VALUE(hash, prop_name) \
|
|
243
|
+
({ VALUE pd = rb_hash_aref(hash, USASCII_STR(prop_name)); \
|
|
244
|
+
NIL_P(pd) ? Qnil : rb_hash_aref(pd, ID2SYM(id_value)); })
|
|
245
|
+
|
|
246
|
+
#define GET_PROP_DATA(hash, prop_name) \
|
|
247
|
+
rb_hash_aref(hash, USASCII_STR(prop_name))
|
|
248
|
+
|
|
249
|
+
// Versions that accept cached VALUE strings instead of string literals
|
|
250
|
+
#define GET_PROP_VALUE_STR(hash, str_prop) \
|
|
251
|
+
({ VALUE pd = rb_hash_aref(hash, str_prop); \
|
|
252
|
+
NIL_P(pd) ? Qnil : rb_hash_aref(pd, ID2SYM(id_value)); })
|
|
253
|
+
|
|
254
|
+
#define GET_PROP_DATA_STR(hash, str_prop) \
|
|
255
|
+
rb_hash_aref(hash, str_prop)
|
|
256
|
+
|
|
257
|
+
// Helper macro to check if a property's !important flag matches a reference
|
|
258
|
+
#define CHECK_IMPORTANT_MATCH(hash, str_prop, ref_important) \
|
|
259
|
+
({ VALUE _pd = GET_PROP_DATA_STR(hash, str_prop); \
|
|
260
|
+
NIL_P(_pd) ? 1 : (RTEST(rb_hash_aref(_pd, ID2SYM(id_important))) == (ref_important)); })
|
|
261
|
+
|
|
262
|
+
// Macro to create shorthand from 4-sided properties (margin, padding, border-width/style/color)
|
|
263
|
+
// Reduces repetitive code by encapsulating the common pattern:
|
|
264
|
+
// 1. Get 4 longhand values (top, right, bottom, left)
|
|
265
|
+
// 2. Check if all 4 exist
|
|
266
|
+
// 3. Call shorthand creator function
|
|
267
|
+
// 4. Add shorthand to properties_hash and remove longhands
|
|
268
|
+
// Note: Uses cached static strings (VALUE) for property names - no runtime allocation
|
|
269
|
+
#define TRY_CREATE_FOUR_SIDED_SHORTHAND(hash, str_top, str_right, str_bottom, str_left, str_shorthand, creator_func, vstruct) \
|
|
270
|
+
do { \
|
|
271
|
+
VALUE _top = GET_PROP_VALUE_STR(hash, str_top); \
|
|
272
|
+
VALUE _right = GET_PROP_VALUE_STR(hash, str_right); \
|
|
273
|
+
VALUE _bottom = GET_PROP_VALUE_STR(hash, str_bottom); \
|
|
274
|
+
VALUE _left = GET_PROP_VALUE_STR(hash, str_left); \
|
|
275
|
+
\
|
|
276
|
+
if (!NIL_P(_top) && !NIL_P(_right) && !NIL_P(_bottom) && !NIL_P(_left)) { \
|
|
277
|
+
/* Check that all properties have the same !important flag */ \
|
|
278
|
+
VALUE _top_data = GET_PROP_DATA_STR(hash, str_top); \
|
|
279
|
+
VALUE _right_data = GET_PROP_DATA_STR(hash, str_right); \
|
|
280
|
+
VALUE _bottom_data = GET_PROP_DATA_STR(hash, str_bottom); \
|
|
281
|
+
VALUE _left_data = GET_PROP_DATA_STR(hash, str_left); \
|
|
282
|
+
\
|
|
283
|
+
VALUE _top_imp = rb_hash_aref(_top_data, ID2SYM(id_important)); \
|
|
284
|
+
VALUE _right_imp = rb_hash_aref(_right_data, ID2SYM(id_important)); \
|
|
285
|
+
VALUE _bottom_imp = rb_hash_aref(_bottom_data, ID2SYM(id_important)); \
|
|
286
|
+
VALUE _left_imp = rb_hash_aref(_left_data, ID2SYM(id_important)); \
|
|
287
|
+
\
|
|
288
|
+
int _top_is_imp = RTEST(_top_imp); \
|
|
289
|
+
int _right_is_imp = RTEST(_right_imp); \
|
|
290
|
+
int _bottom_is_imp = RTEST(_bottom_imp); \
|
|
291
|
+
int _left_is_imp = RTEST(_left_imp); \
|
|
292
|
+
\
|
|
293
|
+
/* Only create shorthand if all have same !important flag */ \
|
|
294
|
+
if (_top_is_imp == _right_is_imp && _top_is_imp == _bottom_is_imp && _top_is_imp == _left_is_imp) { \
|
|
295
|
+
VALUE _props = rb_hash_new(); \
|
|
296
|
+
rb_hash_aset(_props, str_top, _top); \
|
|
297
|
+
rb_hash_aset(_props, str_right, _right); \
|
|
298
|
+
rb_hash_aset(_props, str_bottom, _bottom); \
|
|
299
|
+
rb_hash_aset(_props, str_left, _left); \
|
|
300
|
+
\
|
|
301
|
+
VALUE _shorthand_value = creator_func(Qnil, _props); \
|
|
302
|
+
if (!NIL_P(_shorthand_value)) { \
|
|
303
|
+
int _specificity = NUM2INT(rb_hash_aref(_top_data, ID2SYM(id_specificity))); \
|
|
304
|
+
\
|
|
305
|
+
VALUE _shorthand_data = rb_hash_new(); \
|
|
306
|
+
rb_hash_aset(_shorthand_data, ID2SYM(id_value), _shorthand_value); \
|
|
307
|
+
rb_hash_aset(_shorthand_data, ID2SYM(id_specificity), INT2NUM(_specificity)); \
|
|
308
|
+
rb_hash_aset(_shorthand_data, ID2SYM(id_important), _top_imp); \
|
|
309
|
+
rb_hash_aset(_shorthand_data, ID2SYM(id_struct_class), vstruct); \
|
|
310
|
+
rb_hash_aset(hash, str_shorthand, _shorthand_data); \
|
|
311
|
+
\
|
|
312
|
+
rb_hash_delete(hash, str_top); \
|
|
313
|
+
rb_hash_delete(hash, str_right); \
|
|
314
|
+
rb_hash_delete(hash, str_bottom); \
|
|
315
|
+
rb_hash_delete(hash, str_left); \
|
|
316
|
+
\
|
|
317
|
+
RB_GC_GUARD(_shorthand_value); \
|
|
318
|
+
} \
|
|
319
|
+
RB_GC_GUARD(_props); \
|
|
320
|
+
} \
|
|
321
|
+
} \
|
|
322
|
+
} while(0)
|
|
323
|
+
|
|
324
|
+
// Merge CSS rules according to cascade rules
|
|
325
|
+
// Input: array of parsed rules from parse_css
|
|
326
|
+
// Output: array of Declaration structs (merged and with shorthand recreated)
|
|
327
|
+
VALUE cataract_merge(VALUE self, VALUE rules_array) {
|
|
328
|
+
Check_Type(rules_array, T_ARRAY);
|
|
329
|
+
|
|
330
|
+
// Initialize cached symbol IDs on first call (thread-safe since GVL is held)
|
|
331
|
+
if (id_value == 0) {
|
|
332
|
+
id_value = rb_intern("value");
|
|
333
|
+
id_specificity = rb_intern("specificity");
|
|
334
|
+
id_important = rb_intern("important");
|
|
335
|
+
id_struct_class = rb_intern("_struct_class");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
long num_rules = RARRAY_LEN(rules_array);
|
|
339
|
+
if (num_rules == 0) {
|
|
340
|
+
return rb_ary_new();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Get Declaration struct class once
|
|
344
|
+
VALUE cataract_module = rb_const_get(rb_cObject, rb_intern("Cataract"));
|
|
345
|
+
VALUE declaration_struct = rb_const_get(cataract_module, rb_intern("Declaration"));
|
|
346
|
+
|
|
347
|
+
// Use Ruby hash for temporary storage: property => {value:, specificity:, important:, _struct_class:}
|
|
348
|
+
VALUE properties_hash = rb_hash_new();
|
|
349
|
+
|
|
350
|
+
// Iterate through each rule
|
|
351
|
+
for (long i = 0; i < num_rules; i++) {
|
|
352
|
+
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
353
|
+
Check_Type(rule, T_STRUCT);
|
|
354
|
+
|
|
355
|
+
// Extract selector, declarations, specificity from Rule struct
|
|
356
|
+
VALUE selector = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR));
|
|
357
|
+
VALUE declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
358
|
+
VALUE specificity_val = rb_struct_aref(rule, INT2FIX(RULE_SPECIFICITY));
|
|
359
|
+
|
|
360
|
+
// Calculate specificity if not provided (lazy)
|
|
361
|
+
int specificity = 0;
|
|
362
|
+
if (NIL_P(specificity_val)) {
|
|
363
|
+
specificity_val = calculate_specificity(Qnil, selector);
|
|
364
|
+
// Cache the calculated value back to the struct
|
|
365
|
+
rb_struct_aset(rule, INT2FIX(RULE_SPECIFICITY), specificity_val);
|
|
366
|
+
}
|
|
367
|
+
specificity = NUM2INT(specificity_val);
|
|
368
|
+
|
|
369
|
+
// Process each declaration in this rule
|
|
370
|
+
Check_Type(declarations, T_ARRAY);
|
|
371
|
+
long num_decls = RARRAY_LEN(declarations);
|
|
372
|
+
|
|
373
|
+
for (long j = 0; j < num_decls; j++) {
|
|
374
|
+
VALUE decl = RARRAY_AREF(declarations, j);
|
|
375
|
+
|
|
376
|
+
// Extract property, value, important from Declaration struct
|
|
377
|
+
VALUE property = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
378
|
+
VALUE value = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
379
|
+
VALUE important = rb_struct_aref(decl, INT2FIX(DECL_IMPORTANT));
|
|
380
|
+
|
|
381
|
+
// Properties are already lowercased during parsing (see css_parser.c)
|
|
382
|
+
// No need to lowercase again
|
|
383
|
+
int is_important = RTEST(important);
|
|
384
|
+
|
|
385
|
+
// Expand shorthand properties if needed
|
|
386
|
+
const char *prop_str = StringValueCStr(property);
|
|
387
|
+
VALUE expanded = Qnil;
|
|
388
|
+
|
|
389
|
+
if (strcmp(prop_str, "margin") == 0) {
|
|
390
|
+
expanded = cataract_expand_margin(Qnil, value);
|
|
391
|
+
} else if (strcmp(prop_str, "padding") == 0) {
|
|
392
|
+
expanded = cataract_expand_padding(Qnil, value);
|
|
393
|
+
} else if (strcmp(prop_str, "border") == 0) {
|
|
394
|
+
expanded = cataract_expand_border(Qnil, value);
|
|
395
|
+
} else if (strcmp(prop_str, "border-color") == 0) {
|
|
396
|
+
expanded = cataract_expand_border_color(Qnil, value);
|
|
397
|
+
} else if (strcmp(prop_str, "border-style") == 0) {
|
|
398
|
+
expanded = cataract_expand_border_style(Qnil, value);
|
|
399
|
+
} else if (strcmp(prop_str, "border-width") == 0) {
|
|
400
|
+
expanded = cataract_expand_border_width(Qnil, value);
|
|
401
|
+
} else if (strcmp(prop_str, "border-top") == 0) {
|
|
402
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("top"), value);
|
|
403
|
+
} else if (strcmp(prop_str, "border-right") == 0) {
|
|
404
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("right"), value);
|
|
405
|
+
} else if (strcmp(prop_str, "border-bottom") == 0) {
|
|
406
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("bottom"), value);
|
|
407
|
+
} else if (strcmp(prop_str, "border-left") == 0) {
|
|
408
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("left"), value);
|
|
409
|
+
} else if (strcmp(prop_str, "font") == 0) {
|
|
410
|
+
expanded = cataract_expand_font(Qnil, value);
|
|
411
|
+
} else if (strcmp(prop_str, "list-style") == 0) {
|
|
412
|
+
expanded = cataract_expand_list_style(Qnil, value);
|
|
413
|
+
} else if (strcmp(prop_str, "background") == 0) {
|
|
414
|
+
expanded = cataract_expand_background(Qnil, value);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// If property was expanded, iterate and apply cascade using rb_hash_foreach
|
|
418
|
+
if (!NIL_P(expanded)) {
|
|
419
|
+
Check_Type(expanded, T_HASH);
|
|
420
|
+
|
|
421
|
+
struct expand_context ctx;
|
|
422
|
+
ctx.properties_hash = properties_hash;
|
|
423
|
+
ctx.specificity = specificity;
|
|
424
|
+
ctx.important = important;
|
|
425
|
+
ctx.declaration_struct = declaration_struct;
|
|
426
|
+
|
|
427
|
+
rb_hash_foreach(expanded, merge_expanded_callback, (VALUE)&ctx);
|
|
428
|
+
|
|
429
|
+
RB_GC_GUARD(expanded);
|
|
430
|
+
continue; // Skip processing the original shorthand property
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Apply CSS cascade rules
|
|
434
|
+
VALUE existing = rb_hash_aref(properties_hash, property);
|
|
435
|
+
|
|
436
|
+
if (NIL_P(existing)) {
|
|
437
|
+
// New property - add it
|
|
438
|
+
VALUE prop_data = rb_hash_new();
|
|
439
|
+
rb_hash_aset(prop_data, ID2SYM(id_value), value);
|
|
440
|
+
rb_hash_aset(prop_data, ID2SYM(id_specificity), INT2NUM(specificity));
|
|
441
|
+
rb_hash_aset(prop_data, ID2SYM(id_important), important);
|
|
442
|
+
rb_hash_aset(prop_data, ID2SYM(id_struct_class), declaration_struct);
|
|
443
|
+
rb_hash_aset(properties_hash, property, prop_data);
|
|
444
|
+
} else {
|
|
445
|
+
// Property exists - check cascade rules
|
|
446
|
+
VALUE existing_spec = rb_hash_aref(existing, ID2SYM(id_specificity));
|
|
447
|
+
VALUE existing_important = rb_hash_aref(existing, ID2SYM(id_important));
|
|
448
|
+
|
|
449
|
+
int existing_spec_int = NUM2INT(existing_spec);
|
|
450
|
+
int existing_is_important = RTEST(existing_important);
|
|
451
|
+
|
|
452
|
+
int should_replace = 0;
|
|
453
|
+
|
|
454
|
+
if (is_important) {
|
|
455
|
+
// New is !important - wins if existing is NOT important OR equal/higher specificity
|
|
456
|
+
if (!existing_is_important || existing_spec_int <= specificity) {
|
|
457
|
+
should_replace = 1;
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
// New is NOT important - only wins if existing is also NOT important AND equal/higher specificity
|
|
461
|
+
if (!existing_is_important && existing_spec_int <= specificity) {
|
|
462
|
+
should_replace = 1;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (should_replace) {
|
|
467
|
+
rb_hash_aset(existing, ID2SYM(id_value), value);
|
|
468
|
+
rb_hash_aset(existing, ID2SYM(id_specificity), INT2NUM(specificity));
|
|
469
|
+
rb_hash_aset(existing, ID2SYM(id_important), important);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
RB_GC_GUARD(property);
|
|
474
|
+
RB_GC_GUARD(value);
|
|
475
|
+
RB_GC_GUARD(decl);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
RB_GC_GUARD(selector);
|
|
479
|
+
RB_GC_GUARD(declarations);
|
|
480
|
+
RB_GC_GUARD(rule);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Create shorthand from longhand properties
|
|
484
|
+
// Uses cached static strings to avoid runtime allocation
|
|
485
|
+
|
|
486
|
+
// Try to create margin shorthand
|
|
487
|
+
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
488
|
+
str_margin_top, str_margin_right, str_margin_bottom, str_margin_left,
|
|
489
|
+
str_margin, cataract_create_margin_shorthand, declaration_struct);
|
|
490
|
+
|
|
491
|
+
// Try to create padding shorthand
|
|
492
|
+
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
493
|
+
str_padding_top, str_padding_right, str_padding_bottom, str_padding_left,
|
|
494
|
+
str_padding, cataract_create_padding_shorthand, declaration_struct);
|
|
495
|
+
|
|
496
|
+
// Create border-width from individual sides
|
|
497
|
+
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
498
|
+
str_border_top_width, str_border_right_width, str_border_bottom_width, str_border_left_width,
|
|
499
|
+
str_border_width, cataract_create_border_width_shorthand, declaration_struct);
|
|
500
|
+
|
|
501
|
+
// Create border-style from individual sides
|
|
502
|
+
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
503
|
+
str_border_top_style, str_border_right_style, str_border_bottom_style, str_border_left_style,
|
|
504
|
+
str_border_style, cataract_create_border_style_shorthand, declaration_struct);
|
|
505
|
+
|
|
506
|
+
// Create border-color from individual sides
|
|
507
|
+
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
508
|
+
str_border_top_color, str_border_right_color, str_border_bottom_color, str_border_left_color,
|
|
509
|
+
str_border_color, cataract_create_border_color_shorthand, declaration_struct);
|
|
510
|
+
|
|
511
|
+
// Now create border shorthand from border-{width,style,color}
|
|
512
|
+
VALUE border_width = GET_PROP_VALUE_STR(properties_hash, str_border_width);
|
|
513
|
+
VALUE border_style = GET_PROP_VALUE_STR(properties_hash, str_border_style);
|
|
514
|
+
VALUE border_color = GET_PROP_VALUE_STR(properties_hash, str_border_color);
|
|
515
|
+
|
|
516
|
+
if (!NIL_P(border_width) || !NIL_P(border_style) || !NIL_P(border_color)) {
|
|
517
|
+
// Use first available property's metadata as reference
|
|
518
|
+
VALUE border_data_src = !NIL_P(border_width) ? GET_PROP_DATA_STR(properties_hash, str_border_width) :
|
|
519
|
+
!NIL_P(border_style) ? GET_PROP_DATA_STR(properties_hash, str_border_style) :
|
|
520
|
+
GET_PROP_DATA_STR(properties_hash, str_border_color);
|
|
521
|
+
VALUE border_important = rb_hash_aref(border_data_src, ID2SYM(id_important));
|
|
522
|
+
int border_is_important = RTEST(border_important);
|
|
523
|
+
|
|
524
|
+
// Check that all present properties have the same !important flag
|
|
525
|
+
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_border_width, border_is_important) &&
|
|
526
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_border_style, border_is_important) &&
|
|
527
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_border_color, border_is_important);
|
|
528
|
+
|
|
529
|
+
if (important_match) {
|
|
530
|
+
VALUE border_props = rb_hash_new();
|
|
531
|
+
if (!NIL_P(border_width)) rb_hash_aset(border_props, str_border_width, border_width);
|
|
532
|
+
if (!NIL_P(border_style)) rb_hash_aset(border_props, str_border_style, border_style);
|
|
533
|
+
if (!NIL_P(border_color)) rb_hash_aset(border_props, str_border_color, border_color);
|
|
534
|
+
|
|
535
|
+
VALUE border_shorthand = cataract_create_border_shorthand(Qnil, border_props);
|
|
536
|
+
if (!NIL_P(border_shorthand)) {
|
|
537
|
+
int border_spec = NUM2INT(rb_hash_aref(border_data_src, ID2SYM(id_specificity)));
|
|
538
|
+
|
|
539
|
+
VALUE border_data = rb_hash_new();
|
|
540
|
+
rb_hash_aset(border_data, ID2SYM(id_value), border_shorthand);
|
|
541
|
+
rb_hash_aset(border_data, ID2SYM(id_specificity), INT2NUM(border_spec));
|
|
542
|
+
rb_hash_aset(border_data, ID2SYM(id_important), border_important);
|
|
543
|
+
rb_hash_aset(border_data, ID2SYM(id_struct_class), declaration_struct);
|
|
544
|
+
rb_hash_aset(properties_hash, str_border, border_data);
|
|
545
|
+
|
|
546
|
+
if (!NIL_P(border_width)) rb_hash_delete(properties_hash, str_border_width);
|
|
547
|
+
if (!NIL_P(border_style)) rb_hash_delete(properties_hash, str_border_style);
|
|
548
|
+
if (!NIL_P(border_color)) rb_hash_delete(properties_hash, str_border_color);
|
|
549
|
+
}
|
|
550
|
+
RB_GC_GUARD(border_props);
|
|
551
|
+
RB_GC_GUARD(border_shorthand);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Try to create font shorthand
|
|
556
|
+
VALUE font_size = GET_PROP_VALUE_STR(properties_hash, str_font_size);
|
|
557
|
+
VALUE font_family = GET_PROP_VALUE_STR(properties_hash, str_font_family);
|
|
558
|
+
|
|
559
|
+
// Font shorthand requires at least font-size and font-family
|
|
560
|
+
if (!NIL_P(font_size) && !NIL_P(font_family)) {
|
|
561
|
+
VALUE font_style = GET_PROP_VALUE_STR(properties_hash, str_font_style);
|
|
562
|
+
VALUE font_variant = GET_PROP_VALUE_STR(properties_hash, str_font_variant);
|
|
563
|
+
VALUE font_weight = GET_PROP_VALUE_STR(properties_hash, str_font_weight);
|
|
564
|
+
VALUE line_height = GET_PROP_VALUE_STR(properties_hash, str_line_height);
|
|
565
|
+
|
|
566
|
+
// Get metadata from font-size as reference
|
|
567
|
+
VALUE size_data = GET_PROP_DATA_STR(properties_hash, str_font_size);
|
|
568
|
+
VALUE font_important = rb_hash_aref(size_data, ID2SYM(id_important));
|
|
569
|
+
int font_is_important = RTEST(font_important);
|
|
570
|
+
|
|
571
|
+
// Check that all present properties have the same !important flag
|
|
572
|
+
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_font_style, font_is_important) &&
|
|
573
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_font_variant, font_is_important) &&
|
|
574
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_font_weight, font_is_important) &&
|
|
575
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_line_height, font_is_important) &&
|
|
576
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_font_family, font_is_important);
|
|
577
|
+
|
|
578
|
+
if (important_match) {
|
|
579
|
+
VALUE font_props = rb_hash_new();
|
|
580
|
+
if (!NIL_P(font_style)) rb_hash_aset(font_props, str_font_style, font_style);
|
|
581
|
+
if (!NIL_P(font_variant)) rb_hash_aset(font_props, str_font_variant, font_variant);
|
|
582
|
+
if (!NIL_P(font_weight)) rb_hash_aset(font_props, str_font_weight, font_weight);
|
|
583
|
+
rb_hash_aset(font_props, str_font_size, font_size);
|
|
584
|
+
if (!NIL_P(line_height)) rb_hash_aset(font_props, str_line_height, line_height);
|
|
585
|
+
rb_hash_aset(font_props, str_font_family, font_family);
|
|
586
|
+
|
|
587
|
+
VALUE font_shorthand = cataract_create_font_shorthand(Qnil, font_props);
|
|
588
|
+
if (!NIL_P(font_shorthand)) {
|
|
589
|
+
int font_spec = NUM2INT(rb_hash_aref(size_data, ID2SYM(id_specificity)));
|
|
590
|
+
|
|
591
|
+
VALUE font_data = rb_hash_new();
|
|
592
|
+
rb_hash_aset(font_data, ID2SYM(id_value), font_shorthand);
|
|
593
|
+
rb_hash_aset(font_data, ID2SYM(id_specificity), INT2NUM(font_spec));
|
|
594
|
+
rb_hash_aset(font_data, ID2SYM(id_important), font_important);
|
|
595
|
+
rb_hash_aset(font_data, ID2SYM(id_struct_class), declaration_struct);
|
|
596
|
+
rb_hash_aset(properties_hash, str_font, font_data);
|
|
597
|
+
|
|
598
|
+
// Remove longhand properties
|
|
599
|
+
if (!NIL_P(font_style)) rb_hash_delete(properties_hash, str_font_style);
|
|
600
|
+
if (!NIL_P(font_variant)) rb_hash_delete(properties_hash, str_font_variant);
|
|
601
|
+
if (!NIL_P(font_weight)) rb_hash_delete(properties_hash, str_font_weight);
|
|
602
|
+
rb_hash_delete(properties_hash, str_font_size);
|
|
603
|
+
if (!NIL_P(line_height)) rb_hash_delete(properties_hash, str_line_height);
|
|
604
|
+
rb_hash_delete(properties_hash, str_font_family);
|
|
605
|
+
}
|
|
606
|
+
RB_GC_GUARD(font_props);
|
|
607
|
+
RB_GC_GUARD(font_shorthand);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Try to create list-style shorthand
|
|
612
|
+
VALUE list_style_type = GET_PROP_VALUE_STR(properties_hash, str_list_style_type);
|
|
613
|
+
VALUE list_style_position = GET_PROP_VALUE_STR(properties_hash, str_list_style_position);
|
|
614
|
+
VALUE list_style_image = GET_PROP_VALUE_STR(properties_hash, str_list_style_image);
|
|
615
|
+
|
|
616
|
+
// List-style shorthand requires at least 2 properties
|
|
617
|
+
int list_style_count = (!NIL_P(list_style_type) ? 1 : 0) +
|
|
618
|
+
(!NIL_P(list_style_position) ? 1 : 0) +
|
|
619
|
+
(!NIL_P(list_style_image) ? 1 : 0);
|
|
620
|
+
|
|
621
|
+
if (list_style_count >= 2) {
|
|
622
|
+
// Use first available property's metadata as reference
|
|
623
|
+
VALUE list_style_data_src = !NIL_P(list_style_type) ? GET_PROP_DATA_STR(properties_hash, str_list_style_type) :
|
|
624
|
+
!NIL_P(list_style_position) ? GET_PROP_DATA_STR(properties_hash, str_list_style_position) :
|
|
625
|
+
GET_PROP_DATA_STR(properties_hash, str_list_style_image);
|
|
626
|
+
VALUE list_style_important = rb_hash_aref(list_style_data_src, ID2SYM(id_important));
|
|
627
|
+
int list_style_is_important = RTEST(list_style_important);
|
|
628
|
+
|
|
629
|
+
// Check that all present properties have the same !important flag
|
|
630
|
+
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_list_style_type, list_style_is_important) &&
|
|
631
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_list_style_position, list_style_is_important) &&
|
|
632
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_list_style_image, list_style_is_important);
|
|
633
|
+
|
|
634
|
+
if (important_match) {
|
|
635
|
+
VALUE list_style_props = rb_hash_new();
|
|
636
|
+
if (!NIL_P(list_style_type)) rb_hash_aset(list_style_props, str_list_style_type, list_style_type);
|
|
637
|
+
if (!NIL_P(list_style_position)) rb_hash_aset(list_style_props, str_list_style_position, list_style_position);
|
|
638
|
+
if (!NIL_P(list_style_image)) rb_hash_aset(list_style_props, str_list_style_image, list_style_image);
|
|
639
|
+
|
|
640
|
+
VALUE list_style_shorthand = cataract_create_list_style_shorthand(Qnil, list_style_props);
|
|
641
|
+
if (!NIL_P(list_style_shorthand)) {
|
|
642
|
+
int list_style_spec = NUM2INT(rb_hash_aref(list_style_data_src, ID2SYM(id_specificity)));
|
|
643
|
+
|
|
644
|
+
VALUE list_style_data = rb_hash_new();
|
|
645
|
+
rb_hash_aset(list_style_data, ID2SYM(id_value), list_style_shorthand);
|
|
646
|
+
rb_hash_aset(list_style_data, ID2SYM(id_specificity), INT2NUM(list_style_spec));
|
|
647
|
+
rb_hash_aset(list_style_data, ID2SYM(id_important), list_style_important);
|
|
648
|
+
rb_hash_aset(list_style_data, ID2SYM(id_struct_class), declaration_struct);
|
|
649
|
+
rb_hash_aset(properties_hash, str_list_style, list_style_data);
|
|
650
|
+
|
|
651
|
+
// Remove longhand properties
|
|
652
|
+
if (!NIL_P(list_style_type)) rb_hash_delete(properties_hash, str_list_style_type);
|
|
653
|
+
if (!NIL_P(list_style_position)) rb_hash_delete(properties_hash, str_list_style_position);
|
|
654
|
+
if (!NIL_P(list_style_image)) rb_hash_delete(properties_hash, str_list_style_image);
|
|
655
|
+
}
|
|
656
|
+
RB_GC_GUARD(list_style_props);
|
|
657
|
+
RB_GC_GUARD(list_style_shorthand);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Try to create background shorthand
|
|
662
|
+
VALUE background_color = GET_PROP_VALUE_STR(properties_hash, str_background_color);
|
|
663
|
+
VALUE background_image = GET_PROP_VALUE_STR(properties_hash, str_background_image);
|
|
664
|
+
VALUE background_repeat = GET_PROP_VALUE_STR(properties_hash, str_background_repeat);
|
|
665
|
+
VALUE background_attachment = GET_PROP_VALUE_STR(properties_hash, str_background_attachment);
|
|
666
|
+
VALUE background_position = GET_PROP_VALUE_STR(properties_hash, str_background_position);
|
|
667
|
+
|
|
668
|
+
// Background shorthand requires at least 2 properties
|
|
669
|
+
int background_count = (!NIL_P(background_color) ? 1 : 0) +
|
|
670
|
+
(!NIL_P(background_image) ? 1 : 0) +
|
|
671
|
+
(!NIL_P(background_repeat) ? 1 : 0) +
|
|
672
|
+
(!NIL_P(background_attachment) ? 1 : 0) +
|
|
673
|
+
(!NIL_P(background_position) ? 1 : 0);
|
|
674
|
+
|
|
675
|
+
if (background_count >= 2) {
|
|
676
|
+
// Use first available property's metadata as reference
|
|
677
|
+
VALUE background_data_src = !NIL_P(background_color) ? GET_PROP_DATA_STR(properties_hash, str_background_color) :
|
|
678
|
+
!NIL_P(background_image) ? GET_PROP_DATA_STR(properties_hash, str_background_image) :
|
|
679
|
+
!NIL_P(background_repeat) ? GET_PROP_DATA_STR(properties_hash, str_background_repeat) :
|
|
680
|
+
!NIL_P(background_attachment) ? GET_PROP_DATA_STR(properties_hash, str_background_attachment) :
|
|
681
|
+
GET_PROP_DATA_STR(properties_hash, str_background_position);
|
|
682
|
+
VALUE background_important = rb_hash_aref(background_data_src, ID2SYM(id_important));
|
|
683
|
+
int background_is_important = RTEST(background_important);
|
|
684
|
+
|
|
685
|
+
// Check that all present properties have the same !important flag
|
|
686
|
+
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_background_color, background_is_important) &&
|
|
687
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_background_image, background_is_important) &&
|
|
688
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_background_repeat, background_is_important) &&
|
|
689
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_background_attachment, background_is_important) &&
|
|
690
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_background_position, background_is_important);
|
|
691
|
+
|
|
692
|
+
if (important_match) {
|
|
693
|
+
VALUE background_props = rb_hash_new();
|
|
694
|
+
if (!NIL_P(background_color)) rb_hash_aset(background_props, str_background_color, background_color);
|
|
695
|
+
if (!NIL_P(background_image)) rb_hash_aset(background_props, str_background_image, background_image);
|
|
696
|
+
if (!NIL_P(background_repeat)) rb_hash_aset(background_props, str_background_repeat, background_repeat);
|
|
697
|
+
if (!NIL_P(background_attachment)) rb_hash_aset(background_props, str_background_attachment, background_attachment);
|
|
698
|
+
if (!NIL_P(background_position)) rb_hash_aset(background_props, str_background_position, background_position);
|
|
699
|
+
|
|
700
|
+
VALUE background_shorthand = cataract_create_background_shorthand(Qnil, background_props);
|
|
701
|
+
if (!NIL_P(background_shorthand)) {
|
|
702
|
+
int background_spec = NUM2INT(rb_hash_aref(background_data_src, ID2SYM(id_specificity)));
|
|
703
|
+
|
|
704
|
+
VALUE background_data = rb_hash_new();
|
|
705
|
+
rb_hash_aset(background_data, ID2SYM(id_value), background_shorthand);
|
|
706
|
+
rb_hash_aset(background_data, ID2SYM(id_specificity), INT2NUM(background_spec));
|
|
707
|
+
rb_hash_aset(background_data, ID2SYM(id_important), background_important);
|
|
708
|
+
rb_hash_aset(background_data, ID2SYM(id_struct_class), declaration_struct);
|
|
709
|
+
rb_hash_aset(properties_hash, str_background, background_data);
|
|
710
|
+
|
|
711
|
+
// Remove longhand properties
|
|
712
|
+
if (!NIL_P(background_color)) rb_hash_delete(properties_hash, str_background_color);
|
|
713
|
+
if (!NIL_P(background_image)) rb_hash_delete(properties_hash, str_background_image);
|
|
714
|
+
if (!NIL_P(background_repeat)) rb_hash_delete(properties_hash, str_background_repeat);
|
|
715
|
+
if (!NIL_P(background_attachment)) rb_hash_delete(properties_hash, str_background_attachment);
|
|
716
|
+
if (!NIL_P(background_position)) rb_hash_delete(properties_hash, str_background_position);
|
|
717
|
+
}
|
|
718
|
+
RB_GC_GUARD(background_props);
|
|
719
|
+
RB_GC_GUARD(background_shorthand);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
#undef GET_PROP_VALUE
|
|
724
|
+
#undef GET_PROP_DATA
|
|
725
|
+
|
|
726
|
+
// Build result array by iterating properties_hash using rb_hash_foreach
|
|
727
|
+
VALUE result = rb_ary_new();
|
|
728
|
+
rb_hash_foreach(properties_hash, merge_build_result_callback, result);
|
|
729
|
+
|
|
730
|
+
RB_GC_GUARD(properties_hash);
|
|
731
|
+
RB_GC_GUARD(result);
|
|
732
|
+
RB_GC_GUARD(rules_array);
|
|
733
|
+
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Context for flattening hash structure callback
|
|
738
|
+
struct flatten_hash_ctx {
|
|
739
|
+
VALUE rules_array;
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
// Callback to flatten {query_string => {media_types: [...], rules: [...]}} to array
|
|
743
|
+
static int flatten_hash_callback(VALUE query_string, VALUE group_hash, VALUE arg) {
|
|
744
|
+
struct flatten_hash_ctx *ctx = (struct flatten_hash_ctx *)arg;
|
|
745
|
+
|
|
746
|
+
VALUE rules = rb_hash_aref(group_hash, ID2SYM(rb_intern("rules")));
|
|
747
|
+
if (!NIL_P(rules) && TYPE(rules) == T_ARRAY) {
|
|
748
|
+
long rules_len = RARRAY_LEN(rules);
|
|
749
|
+
for (long i = 0; i < rules_len; i++) {
|
|
750
|
+
rb_ary_push(ctx->rules_array, RARRAY_AREF(rules, i));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return ST_CONTINUE;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Wrapper function that accepts either array or hash structure
|
|
758
|
+
// This is called from Ruby as Cataract.merge_rules
|
|
759
|
+
VALUE cataract_merge_wrapper(VALUE self, VALUE input) {
|
|
760
|
+
// Check if input is a hash (new structure from Stylesheet)
|
|
761
|
+
if (TYPE(input) == T_HASH) {
|
|
762
|
+
// Flatten hash structure to array
|
|
763
|
+
VALUE rules_array = rb_ary_new();
|
|
764
|
+
struct flatten_hash_ctx ctx = { rules_array };
|
|
765
|
+
rb_hash_foreach(input, flatten_hash_callback, (VALUE)&ctx);
|
|
766
|
+
|
|
767
|
+
// Call the original merge function
|
|
768
|
+
VALUE result = cataract_merge(self, rules_array);
|
|
769
|
+
|
|
770
|
+
RB_GC_GUARD(rules_array);
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Input is already an array - call original function directly
|
|
775
|
+
return cataract_merge(self, input);
|
|
776
|
+
}
|