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,973 @@
|
|
|
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_all = 0;
|
|
8
|
+
|
|
9
|
+
// Cached ivar IDs for Stylesheet
|
|
10
|
+
static ID id_ivar_rules = 0;
|
|
11
|
+
static ID id_ivar_media_index = 0;
|
|
12
|
+
|
|
13
|
+
// Cached "merged" selector string
|
|
14
|
+
static VALUE str_merged_selector = Qnil;
|
|
15
|
+
|
|
16
|
+
// Cached property name strings (frozen, never GC'd)
|
|
17
|
+
// Initialized in init_merge_constants() at module load time
|
|
18
|
+
static VALUE str_margin = Qnil;
|
|
19
|
+
static VALUE str_margin_top = Qnil;
|
|
20
|
+
static VALUE str_margin_right = Qnil;
|
|
21
|
+
static VALUE str_margin_bottom = Qnil;
|
|
22
|
+
static VALUE str_margin_left = Qnil;
|
|
23
|
+
static VALUE str_padding = Qnil;
|
|
24
|
+
static VALUE str_padding_top = Qnil;
|
|
25
|
+
static VALUE str_padding_right = Qnil;
|
|
26
|
+
static VALUE str_padding_bottom = Qnil;
|
|
27
|
+
static VALUE str_padding_left = Qnil;
|
|
28
|
+
static VALUE str_border_width = Qnil;
|
|
29
|
+
static VALUE str_border_top_width = Qnil;
|
|
30
|
+
static VALUE str_border_right_width = Qnil;
|
|
31
|
+
static VALUE str_border_bottom_width = Qnil;
|
|
32
|
+
static VALUE str_border_left_width = Qnil;
|
|
33
|
+
static VALUE str_border_style = Qnil;
|
|
34
|
+
static VALUE str_border_top_style = Qnil;
|
|
35
|
+
static VALUE str_border_right_style = Qnil;
|
|
36
|
+
static VALUE str_border_bottom_style = Qnil;
|
|
37
|
+
static VALUE str_border_left_style = Qnil;
|
|
38
|
+
static VALUE str_border_color = Qnil;
|
|
39
|
+
static VALUE str_border_top_color = Qnil;
|
|
40
|
+
static VALUE str_border_right_color = Qnil;
|
|
41
|
+
static VALUE str_border_bottom_color = Qnil;
|
|
42
|
+
static VALUE str_border_left_color = Qnil;
|
|
43
|
+
static VALUE str_border = Qnil;
|
|
44
|
+
static VALUE str_font = Qnil;
|
|
45
|
+
static VALUE str_font_style = Qnil;
|
|
46
|
+
static VALUE str_font_variant = Qnil;
|
|
47
|
+
static VALUE str_font_weight = Qnil;
|
|
48
|
+
static VALUE str_font_size = Qnil;
|
|
49
|
+
static VALUE str_line_height = Qnil;
|
|
50
|
+
static VALUE str_font_family = Qnil;
|
|
51
|
+
static VALUE str_list_style = Qnil;
|
|
52
|
+
static VALUE str_list_style_type = Qnil;
|
|
53
|
+
static VALUE str_list_style_position = Qnil;
|
|
54
|
+
static VALUE str_list_style_image = Qnil;
|
|
55
|
+
static VALUE str_background = Qnil;
|
|
56
|
+
static VALUE str_background_color = Qnil;
|
|
57
|
+
static VALUE str_background_image = Qnil;
|
|
58
|
+
static VALUE str_background_repeat = Qnil;
|
|
59
|
+
static VALUE str_background_attachment = Qnil;
|
|
60
|
+
static VALUE str_background_position = Qnil;
|
|
61
|
+
|
|
62
|
+
// Context for expanded property iteration
|
|
63
|
+
struct expand_context {
|
|
64
|
+
VALUE properties_hash;
|
|
65
|
+
int specificity;
|
|
66
|
+
VALUE important;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Callback for rb_hash_foreach - process expanded properties and apply cascade
|
|
70
|
+
static int merge_expanded_callback(VALUE exp_prop, VALUE exp_value, VALUE ctx_val) {
|
|
71
|
+
struct expand_context *ctx = (struct expand_context *)ctx_val;
|
|
72
|
+
|
|
73
|
+
// Expanded properties from shorthand expanders are already lowercase
|
|
74
|
+
// No need to lowercase again
|
|
75
|
+
int is_important = RTEST(ctx->important);
|
|
76
|
+
|
|
77
|
+
// Apply cascade rules for expanded property
|
|
78
|
+
VALUE existing = rb_hash_aref(ctx->properties_hash, exp_prop);
|
|
79
|
+
|
|
80
|
+
if (NIL_P(existing)) {
|
|
81
|
+
VALUE prop_data = rb_hash_new();
|
|
82
|
+
rb_hash_aset(prop_data, ID2SYM(id_value), exp_value);
|
|
83
|
+
rb_hash_aset(prop_data, ID2SYM(id_specificity), INT2NUM(ctx->specificity));
|
|
84
|
+
rb_hash_aset(prop_data, ID2SYM(id_important), ctx->important);
|
|
85
|
+
// Note: declaration_struct not stored - use global cDeclaration instead
|
|
86
|
+
rb_hash_aset(ctx->properties_hash, exp_prop, prop_data);
|
|
87
|
+
} else {
|
|
88
|
+
VALUE existing_spec = rb_hash_aref(existing, ID2SYM(id_specificity));
|
|
89
|
+
VALUE existing_important = rb_hash_aref(existing, ID2SYM(id_important));
|
|
90
|
+
|
|
91
|
+
int existing_spec_int = NUM2INT(existing_spec);
|
|
92
|
+
int existing_is_important = RTEST(existing_important);
|
|
93
|
+
|
|
94
|
+
int should_replace = 0;
|
|
95
|
+
if (is_important) {
|
|
96
|
+
if (!existing_is_important || existing_spec_int <= ctx->specificity) {
|
|
97
|
+
should_replace = 1;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (!existing_is_important && existing_spec_int <= ctx->specificity) {
|
|
101
|
+
should_replace = 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (should_replace) {
|
|
106
|
+
rb_hash_aset(existing, ID2SYM(id_value), exp_value);
|
|
107
|
+
rb_hash_aset(existing, ID2SYM(id_specificity), INT2NUM(ctx->specificity));
|
|
108
|
+
rb_hash_aset(existing, ID2SYM(id_important), ctx->important);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
RB_GC_GUARD(exp_prop);
|
|
113
|
+
RB_GC_GUARD(exp_value);
|
|
114
|
+
return ST_CONTINUE;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Callback for rb_hash_foreach - builds result array from properties hash
|
|
118
|
+
static int merge_build_result_callback(VALUE property, VALUE prop_data, VALUE result_ary) {
|
|
119
|
+
// Extract value and important flag from prop_data
|
|
120
|
+
VALUE value = rb_hash_aref(prop_data, ID2SYM(id_value));
|
|
121
|
+
VALUE important = rb_hash_aref(prop_data, ID2SYM(id_important));
|
|
122
|
+
|
|
123
|
+
// Create Declaration struct (use global cDeclaration)
|
|
124
|
+
VALUE decl_struct = rb_struct_new(cDeclaration, property, value, important);
|
|
125
|
+
rb_ary_push(result_ary, decl_struct);
|
|
126
|
+
|
|
127
|
+
return ST_CONTINUE;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Initialize cached property strings (called once at module init)
|
|
131
|
+
void init_merge_constants(void) {
|
|
132
|
+
// Initialize symbol IDs
|
|
133
|
+
id_value = rb_intern("value");
|
|
134
|
+
id_specificity = rb_intern("specificity");
|
|
135
|
+
id_important = rb_intern("important");
|
|
136
|
+
id_all = rb_intern("all");
|
|
137
|
+
|
|
138
|
+
// Initialize ivar IDs for Stylesheet
|
|
139
|
+
id_ivar_rules = rb_intern("@rules");
|
|
140
|
+
id_ivar_media_index = rb_intern("@_media_index");
|
|
141
|
+
|
|
142
|
+
// Margin properties
|
|
143
|
+
str_margin = rb_str_freeze(USASCII_STR("margin"));
|
|
144
|
+
str_margin_top = rb_str_freeze(USASCII_STR("margin-top"));
|
|
145
|
+
str_margin_right = rb_str_freeze(USASCII_STR("margin-right"));
|
|
146
|
+
str_margin_bottom = rb_str_freeze(USASCII_STR("margin-bottom"));
|
|
147
|
+
str_margin_left = rb_str_freeze(USASCII_STR("margin-left"));
|
|
148
|
+
|
|
149
|
+
// Padding properties
|
|
150
|
+
str_padding = rb_str_freeze(USASCII_STR("padding"));
|
|
151
|
+
str_padding_top = rb_str_freeze(USASCII_STR("padding-top"));
|
|
152
|
+
str_padding_right = rb_str_freeze(USASCII_STR("padding-right"));
|
|
153
|
+
str_padding_bottom = rb_str_freeze(USASCII_STR("padding-bottom"));
|
|
154
|
+
str_padding_left = rb_str_freeze(USASCII_STR("padding-left"));
|
|
155
|
+
|
|
156
|
+
// Border-width properties
|
|
157
|
+
str_border_width = rb_str_freeze(USASCII_STR("border-width"));
|
|
158
|
+
str_border_top_width = rb_str_freeze(USASCII_STR("border-top-width"));
|
|
159
|
+
str_border_right_width = rb_str_freeze(USASCII_STR("border-right-width"));
|
|
160
|
+
str_border_bottom_width = rb_str_freeze(USASCII_STR("border-bottom-width"));
|
|
161
|
+
str_border_left_width = rb_str_freeze(USASCII_STR("border-left-width"));
|
|
162
|
+
|
|
163
|
+
// Border-style properties
|
|
164
|
+
str_border_style = rb_str_freeze(USASCII_STR("border-style"));
|
|
165
|
+
str_border_top_style = rb_str_freeze(USASCII_STR("border-top-style"));
|
|
166
|
+
str_border_right_style = rb_str_freeze(USASCII_STR("border-right-style"));
|
|
167
|
+
str_border_bottom_style = rb_str_freeze(USASCII_STR("border-bottom-style"));
|
|
168
|
+
str_border_left_style = rb_str_freeze(USASCII_STR("border-left-style"));
|
|
169
|
+
|
|
170
|
+
// Border-color properties
|
|
171
|
+
str_border_color = rb_str_freeze(USASCII_STR("border-color"));
|
|
172
|
+
str_border_top_color = rb_str_freeze(USASCII_STR("border-top-color"));
|
|
173
|
+
str_border_right_color = rb_str_freeze(USASCII_STR("border-right-color"));
|
|
174
|
+
str_border_bottom_color = rb_str_freeze(USASCII_STR("border-bottom-color"));
|
|
175
|
+
str_border_left_color = rb_str_freeze(USASCII_STR("border-left-color"));
|
|
176
|
+
|
|
177
|
+
// Border shorthand
|
|
178
|
+
str_border = rb_str_freeze(USASCII_STR("border"));
|
|
179
|
+
|
|
180
|
+
// Font properties
|
|
181
|
+
str_font = rb_str_freeze(USASCII_STR("font"));
|
|
182
|
+
str_font_style = rb_str_freeze(USASCII_STR("font-style"));
|
|
183
|
+
str_font_variant = rb_str_freeze(USASCII_STR("font-variant"));
|
|
184
|
+
str_font_weight = rb_str_freeze(USASCII_STR("font-weight"));
|
|
185
|
+
str_font_size = rb_str_freeze(USASCII_STR("font-size"));
|
|
186
|
+
str_line_height = rb_str_freeze(USASCII_STR("line-height"));
|
|
187
|
+
str_font_family = rb_str_freeze(USASCII_STR("font-family"));
|
|
188
|
+
|
|
189
|
+
// List-style properties
|
|
190
|
+
str_list_style = rb_str_freeze(USASCII_STR("list-style"));
|
|
191
|
+
str_list_style_type = rb_str_freeze(USASCII_STR("list-style-type"));
|
|
192
|
+
str_list_style_position = rb_str_freeze(USASCII_STR("list-style-position"));
|
|
193
|
+
str_list_style_image = rb_str_freeze(USASCII_STR("list-style-image"));
|
|
194
|
+
|
|
195
|
+
// Background properties
|
|
196
|
+
str_background = rb_str_freeze(USASCII_STR("background"));
|
|
197
|
+
str_background_color = rb_str_freeze(USASCII_STR("background-color"));
|
|
198
|
+
str_background_image = rb_str_freeze(USASCII_STR("background-image"));
|
|
199
|
+
str_background_repeat = rb_str_freeze(USASCII_STR("background-repeat"));
|
|
200
|
+
str_background_attachment = rb_str_freeze(USASCII_STR("background-attachment"));
|
|
201
|
+
str_background_position = rb_str_freeze(USASCII_STR("background-position"));
|
|
202
|
+
|
|
203
|
+
// Register all strings with GC so they're never collected
|
|
204
|
+
rb_gc_register_mark_object(str_margin);
|
|
205
|
+
rb_gc_register_mark_object(str_margin_top);
|
|
206
|
+
rb_gc_register_mark_object(str_margin_right);
|
|
207
|
+
rb_gc_register_mark_object(str_margin_bottom);
|
|
208
|
+
rb_gc_register_mark_object(str_margin_left);
|
|
209
|
+
rb_gc_register_mark_object(str_padding);
|
|
210
|
+
rb_gc_register_mark_object(str_padding_top);
|
|
211
|
+
rb_gc_register_mark_object(str_padding_right);
|
|
212
|
+
rb_gc_register_mark_object(str_padding_bottom);
|
|
213
|
+
rb_gc_register_mark_object(str_padding_left);
|
|
214
|
+
rb_gc_register_mark_object(str_border_width);
|
|
215
|
+
rb_gc_register_mark_object(str_border_top_width);
|
|
216
|
+
rb_gc_register_mark_object(str_border_right_width);
|
|
217
|
+
rb_gc_register_mark_object(str_border_bottom_width);
|
|
218
|
+
rb_gc_register_mark_object(str_border_left_width);
|
|
219
|
+
rb_gc_register_mark_object(str_border_style);
|
|
220
|
+
rb_gc_register_mark_object(str_border_top_style);
|
|
221
|
+
rb_gc_register_mark_object(str_border_right_style);
|
|
222
|
+
rb_gc_register_mark_object(str_border_bottom_style);
|
|
223
|
+
rb_gc_register_mark_object(str_border_left_style);
|
|
224
|
+
rb_gc_register_mark_object(str_border_color);
|
|
225
|
+
rb_gc_register_mark_object(str_border_top_color);
|
|
226
|
+
rb_gc_register_mark_object(str_border_right_color);
|
|
227
|
+
rb_gc_register_mark_object(str_border_bottom_color);
|
|
228
|
+
rb_gc_register_mark_object(str_border_left_color);
|
|
229
|
+
rb_gc_register_mark_object(str_border);
|
|
230
|
+
rb_gc_register_mark_object(str_font);
|
|
231
|
+
rb_gc_register_mark_object(str_font_style);
|
|
232
|
+
rb_gc_register_mark_object(str_font_variant);
|
|
233
|
+
rb_gc_register_mark_object(str_font_weight);
|
|
234
|
+
rb_gc_register_mark_object(str_font_size);
|
|
235
|
+
rb_gc_register_mark_object(str_line_height);
|
|
236
|
+
rb_gc_register_mark_object(str_font_family);
|
|
237
|
+
rb_gc_register_mark_object(str_list_style);
|
|
238
|
+
rb_gc_register_mark_object(str_list_style_type);
|
|
239
|
+
rb_gc_register_mark_object(str_list_style_position);
|
|
240
|
+
rb_gc_register_mark_object(str_list_style_image);
|
|
241
|
+
rb_gc_register_mark_object(str_background);
|
|
242
|
+
rb_gc_register_mark_object(str_background_color);
|
|
243
|
+
rb_gc_register_mark_object(str_background_image);
|
|
244
|
+
rb_gc_register_mark_object(str_background_repeat);
|
|
245
|
+
rb_gc_register_mark_object(str_background_attachment);
|
|
246
|
+
rb_gc_register_mark_object(str_background_position);
|
|
247
|
+
|
|
248
|
+
// Cached "merged" selector string
|
|
249
|
+
str_merged_selector = rb_str_freeze(USASCII_STR("merged"));
|
|
250
|
+
rb_gc_register_mark_object(str_merged_selector);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Helper macros to extract property data from properties_hash
|
|
254
|
+
// Note: These use id_value, id_specificity, id_important which are initialized in cataract_merge
|
|
255
|
+
#define GET_PROP_VALUE(hash, prop_name) \
|
|
256
|
+
({ VALUE pd = rb_hash_aref(hash, USASCII_STR(prop_name)); \
|
|
257
|
+
NIL_P(pd) ? Qnil : rb_hash_aref(pd, ID2SYM(id_value)); })
|
|
258
|
+
|
|
259
|
+
#define GET_PROP_DATA(hash, prop_name) \
|
|
260
|
+
rb_hash_aref(hash, USASCII_STR(prop_name))
|
|
261
|
+
|
|
262
|
+
// Versions that accept cached VALUE strings instead of string literals
|
|
263
|
+
#define GET_PROP_VALUE_STR(hash, str_prop) \
|
|
264
|
+
({ VALUE pd = rb_hash_aref(hash, str_prop); \
|
|
265
|
+
NIL_P(pd) ? Qnil : rb_hash_aref(pd, ID2SYM(id_value)); })
|
|
266
|
+
|
|
267
|
+
#define GET_PROP_DATA_STR(hash, str_prop) \
|
|
268
|
+
rb_hash_aref(hash, str_prop)
|
|
269
|
+
|
|
270
|
+
// Helper macro to check if a property's !important flag matches a reference
|
|
271
|
+
#define CHECK_IMPORTANT_MATCH(hash, str_prop, ref_important) \
|
|
272
|
+
({ VALUE _pd = GET_PROP_DATA_STR(hash, str_prop); \
|
|
273
|
+
NIL_P(_pd) ? 1 : (RTEST(rb_hash_aref(_pd, ID2SYM(id_important))) == (ref_important)); })
|
|
274
|
+
|
|
275
|
+
// Macro to create shorthand from 4-sided properties (margin, padding, border-width/style/color)
|
|
276
|
+
// Reduces repetitive code by encapsulating the common pattern:
|
|
277
|
+
// 1. Get 4 longhand values (top, right, bottom, left)
|
|
278
|
+
// 2. Check if all 4 exist
|
|
279
|
+
// 3. Call shorthand creator function
|
|
280
|
+
// 4. Add shorthand to properties_hash and remove longhands
|
|
281
|
+
// Note: Uses cached static strings (VALUE) for property names - no runtime allocation
|
|
282
|
+
#define TRY_CREATE_FOUR_SIDED_SHORTHAND(hash, str_top, str_right, str_bottom, str_left, str_shorthand, creator_func) \
|
|
283
|
+
do { \
|
|
284
|
+
VALUE _top = GET_PROP_VALUE_STR(hash, str_top); \
|
|
285
|
+
VALUE _right = GET_PROP_VALUE_STR(hash, str_right); \
|
|
286
|
+
VALUE _bottom = GET_PROP_VALUE_STR(hash, str_bottom); \
|
|
287
|
+
VALUE _left = GET_PROP_VALUE_STR(hash, str_left); \
|
|
288
|
+
\
|
|
289
|
+
if (!NIL_P(_top) && !NIL_P(_right) && !NIL_P(_bottom) && !NIL_P(_left)) { \
|
|
290
|
+
/* Check that all properties have the same !important flag */ \
|
|
291
|
+
VALUE _top_data = GET_PROP_DATA_STR(hash, str_top); \
|
|
292
|
+
VALUE _right_data = GET_PROP_DATA_STR(hash, str_right); \
|
|
293
|
+
VALUE _bottom_data = GET_PROP_DATA_STR(hash, str_bottom); \
|
|
294
|
+
VALUE _left_data = GET_PROP_DATA_STR(hash, str_left); \
|
|
295
|
+
\
|
|
296
|
+
VALUE _top_imp = rb_hash_aref(_top_data, ID2SYM(id_important)); \
|
|
297
|
+
VALUE _right_imp = rb_hash_aref(_right_data, ID2SYM(id_important)); \
|
|
298
|
+
VALUE _bottom_imp = rb_hash_aref(_bottom_data, ID2SYM(id_important)); \
|
|
299
|
+
VALUE _left_imp = rb_hash_aref(_left_data, ID2SYM(id_important)); \
|
|
300
|
+
\
|
|
301
|
+
int _top_is_imp = RTEST(_top_imp); \
|
|
302
|
+
int _right_is_imp = RTEST(_right_imp); \
|
|
303
|
+
int _bottom_is_imp = RTEST(_bottom_imp); \
|
|
304
|
+
int _left_is_imp = RTEST(_left_imp); \
|
|
305
|
+
\
|
|
306
|
+
/* Only create shorthand if all have same !important flag */ \
|
|
307
|
+
if (_top_is_imp == _right_is_imp && _top_is_imp == _bottom_is_imp && _top_is_imp == _left_is_imp) { \
|
|
308
|
+
VALUE _props = rb_hash_new(); \
|
|
309
|
+
rb_hash_aset(_props, str_top, _top); \
|
|
310
|
+
rb_hash_aset(_props, str_right, _right); \
|
|
311
|
+
rb_hash_aset(_props, str_bottom, _bottom); \
|
|
312
|
+
rb_hash_aset(_props, str_left, _left); \
|
|
313
|
+
\
|
|
314
|
+
VALUE _shorthand_value = creator_func(Qnil, _props); \
|
|
315
|
+
if (!NIL_P(_shorthand_value)) { \
|
|
316
|
+
int _specificity = NUM2INT(rb_hash_aref(_top_data, ID2SYM(id_specificity))); \
|
|
317
|
+
\
|
|
318
|
+
VALUE _shorthand_data = rb_hash_new(); \
|
|
319
|
+
rb_hash_aset(_shorthand_data, ID2SYM(id_value), _shorthand_value); \
|
|
320
|
+
rb_hash_aset(_shorthand_data, ID2SYM(id_specificity), INT2NUM(_specificity)); \
|
|
321
|
+
rb_hash_aset(_shorthand_data, ID2SYM(id_important), _top_imp); \
|
|
322
|
+
rb_hash_aset(hash, str_shorthand, _shorthand_data); \
|
|
323
|
+
\
|
|
324
|
+
rb_hash_delete(hash, str_top); \
|
|
325
|
+
rb_hash_delete(hash, str_right); \
|
|
326
|
+
rb_hash_delete(hash, str_bottom); \
|
|
327
|
+
rb_hash_delete(hash, str_left); \
|
|
328
|
+
\
|
|
329
|
+
RB_GC_GUARD(_shorthand_value); \
|
|
330
|
+
} \
|
|
331
|
+
RB_GC_GUARD(_props); \
|
|
332
|
+
} \
|
|
333
|
+
} \
|
|
334
|
+
} while(0)
|
|
335
|
+
|
|
336
|
+
// Merge CSS rules according to cascade rules
|
|
337
|
+
// Input: Stylesheet object or CSS string
|
|
338
|
+
// Output: Stylesheet with merged declarations
|
|
339
|
+
VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
340
|
+
VALUE rules_array;
|
|
341
|
+
|
|
342
|
+
// Handle different input types
|
|
343
|
+
// Most calls pass Stylesheet (common case), String is rare
|
|
344
|
+
if (TYPE(input) == T_STRING) {
|
|
345
|
+
// Parse CSS string first
|
|
346
|
+
VALUE parsed = parse_css_new(self, input);
|
|
347
|
+
rules_array = rb_hash_aref(parsed, ID2SYM(rb_intern("rules")));
|
|
348
|
+
} else if (rb_obj_is_kind_of(input, cStylesheet)) {
|
|
349
|
+
// Extract @rules from Stylesheet (common case)
|
|
350
|
+
rules_array = rb_ivar_get(input, id_ivar_rules);
|
|
351
|
+
} else {
|
|
352
|
+
rb_raise(rb_eTypeError, "Expected Stylesheet or String, got %s",
|
|
353
|
+
rb_obj_classname(input));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
Check_Type(rules_array, T_ARRAY);
|
|
357
|
+
|
|
358
|
+
// Check if stylesheet has nesting (affects selector rollup)
|
|
359
|
+
int has_nesting = 0;
|
|
360
|
+
if (rb_obj_is_kind_of(input, cStylesheet)) {
|
|
361
|
+
VALUE has_nesting_ivar = rb_ivar_get(input, rb_intern("@_has_nesting"));
|
|
362
|
+
has_nesting = RTEST(has_nesting_ivar);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Initialize cached symbol IDs on first call (thread-safe since GVL is held)
|
|
366
|
+
// This only happens once, so unlikely
|
|
367
|
+
if (id_value == 0) {
|
|
368
|
+
id_value = rb_intern("value");
|
|
369
|
+
id_specificity = rb_intern("specificity");
|
|
370
|
+
id_important = rb_intern("important");
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
long num_rules = RARRAY_LEN(rules_array);
|
|
374
|
+
// Empty stylesheets are rare
|
|
375
|
+
if (num_rules == 0) {
|
|
376
|
+
// Return empty stylesheet
|
|
377
|
+
VALUE empty_sheet = rb_class_new_instance(0, NULL, cStylesheet);
|
|
378
|
+
return empty_sheet;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// For nested CSS: identify parent rules (rules that have children)
|
|
382
|
+
// These should be skipped during merge, even if they have declarations
|
|
383
|
+
// Use Ruby hash as a set: parent_id => true
|
|
384
|
+
VALUE parent_ids = Qnil;
|
|
385
|
+
if (has_nesting) {
|
|
386
|
+
DEBUG_PRINTF("\n=== MERGE: has_nesting=true, num_rules=%ld ===\n", num_rules);
|
|
387
|
+
parent_ids = rb_hash_new();
|
|
388
|
+
for (long i = 0; i < num_rules; i++) {
|
|
389
|
+
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
390
|
+
VALUE parent_rule_id = rb_struct_aref(rule, INT2FIX(RULE_PARENT_RULE_ID));
|
|
391
|
+
DEBUG_PRINTF(" Rule %ld: selector='%s', rule_id=%d, parent_rule_id=%s\n",
|
|
392
|
+
i,
|
|
393
|
+
RSTRING_PTR(rb_struct_aref(rule, INT2FIX(RULE_SELECTOR))),
|
|
394
|
+
FIX2INT(rb_struct_aref(rule, INT2FIX(RULE_ID))),
|
|
395
|
+
NIL_P(parent_rule_id) ? "nil" : RSTRING_PTR(rb_inspect(parent_rule_id)));
|
|
396
|
+
if (!NIL_P(parent_rule_id)) {
|
|
397
|
+
// This rule has a parent, so mark that parent ID
|
|
398
|
+
rb_hash_aset(parent_ids, parent_rule_id, Qtrue);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// For nested CSS with different selectors from SAME parent: group rules by selector
|
|
404
|
+
// Only split into multiple rules if ALL rules share the same parent_rule_id
|
|
405
|
+
// selector => [rule indices]
|
|
406
|
+
VALUE selector_groups = Qnil;
|
|
407
|
+
VALUE common_parent = Qundef; // Qundef = not set yet
|
|
408
|
+
|
|
409
|
+
if (has_nesting) {
|
|
410
|
+
DEBUG_PRINTF("\n=== Building selector groups ===\n");
|
|
411
|
+
selector_groups = rb_hash_new();
|
|
412
|
+
for (long i = 0; i < num_rules; i++) {
|
|
413
|
+
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
414
|
+
VALUE declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
415
|
+
VALUE parent_rule_id = rb_struct_aref(rule, INT2FIX(RULE_PARENT_RULE_ID));
|
|
416
|
+
VALUE selector = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR));
|
|
417
|
+
|
|
418
|
+
// Per W3C spec: parent and child are SEPARATE rules with different selectors
|
|
419
|
+
// Both should be included in merge output
|
|
420
|
+
// Don't skip parent rules - they have their own selector and declarations
|
|
421
|
+
|
|
422
|
+
// Skip empty rules (no declarations)
|
|
423
|
+
if (RARRAY_LEN(declarations) == 0) {
|
|
424
|
+
DEBUG_PRINTF(" Skipping rule %ld: selector='%s' (empty declarations)\n",
|
|
425
|
+
i, RSTRING_PTR(selector));
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
DEBUG_PRINTF(" Processing rule %ld: selector='%s', parent_rule_id=%s\n",
|
|
430
|
+
i, RSTRING_PTR(selector),
|
|
431
|
+
NIL_P(parent_rule_id) ? "nil" : RSTRING_PTR(rb_inspect(parent_rule_id)));
|
|
432
|
+
|
|
433
|
+
// Track if all rules share the same parent
|
|
434
|
+
if (common_parent == Qundef) {
|
|
435
|
+
common_parent = parent_rule_id;
|
|
436
|
+
DEBUG_PRINTF(" Setting common_parent=%s\n",
|
|
437
|
+
NIL_P(common_parent) ? "nil" : RSTRING_PTR(rb_inspect(common_parent)));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
VALUE group = rb_hash_aref(selector_groups, selector);
|
|
441
|
+
if (NIL_P(group)) {
|
|
442
|
+
group = rb_ary_new();
|
|
443
|
+
rb_hash_aset(selector_groups, selector, group);
|
|
444
|
+
DEBUG_PRINTF(" Created new group for selector='%s'\n", RSTRING_PTR(selector));
|
|
445
|
+
}
|
|
446
|
+
rb_ary_push(group, LONG2FIX(i));
|
|
447
|
+
}
|
|
448
|
+
DEBUG_PRINTF(" Total selector groups: %ld\n", RHASH_SIZE(selector_groups));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// If nested CSS with multiple distinct selectors, return separate rules
|
|
452
|
+
// Per W3C spec: each unique selector (parent or child) is a separate rule
|
|
453
|
+
// Example: .parent { color: red; .child { color: blue; } } .other { color: green; }
|
|
454
|
+
// Should return 3 rules: .parent, .parent .child, .other
|
|
455
|
+
DEBUG_PRINTF("\n=== Decision point ===\n");
|
|
456
|
+
DEBUG_PRINTF(" has_nesting=%d\n", has_nesting);
|
|
457
|
+
DEBUG_PRINTF(" selector_groups is nil? %d\n", NIL_P(selector_groups));
|
|
458
|
+
if (!NIL_P(selector_groups)) {
|
|
459
|
+
DEBUG_PRINTF(" selector_groups size=%ld\n", RHASH_SIZE(selector_groups));
|
|
460
|
+
}
|
|
461
|
+
DEBUG_PRINTF(" Condition: has_nesting && !NIL_P(selector_groups) && RHASH_SIZE(selector_groups) > 1 = %d\n",
|
|
462
|
+
has_nesting && !NIL_P(selector_groups) && RHASH_SIZE(selector_groups) > 1);
|
|
463
|
+
|
|
464
|
+
if (has_nesting && !NIL_P(selector_groups) && RHASH_SIZE(selector_groups) > 1) {
|
|
465
|
+
DEBUG_PRINTF(" -> Taking MULTI-SELECTOR path (separate rules)\n");
|
|
466
|
+
VALUE merged_sheet = rb_class_new_instance(0, NULL, cStylesheet);
|
|
467
|
+
VALUE merged_rules = rb_ary_new();
|
|
468
|
+
int rule_id_counter = 0;
|
|
469
|
+
|
|
470
|
+
// Iterate through each selector group
|
|
471
|
+
VALUE selectors = rb_funcall(selector_groups, rb_intern("keys"), 0);
|
|
472
|
+
long num_selectors = RARRAY_LEN(selectors);
|
|
473
|
+
|
|
474
|
+
for (long s = 0; s < num_selectors; s++) {
|
|
475
|
+
VALUE selector = rb_ary_entry(selectors, s);
|
|
476
|
+
VALUE group_indices = rb_hash_aref(selector_groups, selector);
|
|
477
|
+
|
|
478
|
+
// For now, just take first rule from each group (no merging within group)
|
|
479
|
+
// TODO: Merge declarations within same-selector group
|
|
480
|
+
long first_idx = FIX2LONG(rb_ary_entry(group_indices, 0));
|
|
481
|
+
VALUE orig_rule = RARRAY_AREF(rules_array, first_idx);
|
|
482
|
+
VALUE orig_decls = rb_struct_aref(orig_rule, INT2FIX(RULE_DECLARATIONS));
|
|
483
|
+
|
|
484
|
+
// Create new rule with this selector and declarations
|
|
485
|
+
VALUE new_rule = rb_struct_new(cRule,
|
|
486
|
+
INT2FIX(rule_id_counter++),
|
|
487
|
+
selector,
|
|
488
|
+
orig_decls,
|
|
489
|
+
Qnil, // specificity
|
|
490
|
+
Qnil, // parent_rule_id
|
|
491
|
+
Qnil // nesting_style
|
|
492
|
+
);
|
|
493
|
+
rb_ary_push(merged_rules, new_rule);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
rb_ivar_set(merged_sheet, id_ivar_rules, merged_rules);
|
|
497
|
+
|
|
498
|
+
// Set @media_index with :all pointing to all rule IDs
|
|
499
|
+
VALUE media_idx = rb_hash_new();
|
|
500
|
+
VALUE all_ids = rb_ary_new();
|
|
501
|
+
for (int i = 0; i < rule_id_counter; i++) {
|
|
502
|
+
rb_ary_push(all_ids, INT2FIX(i));
|
|
503
|
+
}
|
|
504
|
+
rb_hash_aset(media_idx, ID2SYM(id_all), all_ids);
|
|
505
|
+
rb_ivar_set(merged_sheet, id_ivar_media_index, media_idx);
|
|
506
|
+
|
|
507
|
+
return merged_sheet;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Single-merge path: merge all rules into one
|
|
511
|
+
VALUE properties_hash = rb_hash_new();
|
|
512
|
+
|
|
513
|
+
// Track selector for rollup (minimize allocations)
|
|
514
|
+
// Store pointer + length to first non-parent selector
|
|
515
|
+
// Also keep the VALUE alive since we extract C pointer before allocations
|
|
516
|
+
const char *first_selector_ptr = NULL;
|
|
517
|
+
long first_selector_len = 0;
|
|
518
|
+
VALUE first_selector_value = Qnil;
|
|
519
|
+
int all_same_selector = 1;
|
|
520
|
+
|
|
521
|
+
// Iterate through each rule
|
|
522
|
+
for (long i = 0; i < num_rules; i++) {
|
|
523
|
+
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
524
|
+
Check_Type(rule, T_STRUCT);
|
|
525
|
+
|
|
526
|
+
// Extract rule fields
|
|
527
|
+
VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
528
|
+
VALUE selector = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR));
|
|
529
|
+
VALUE declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
530
|
+
|
|
531
|
+
// Skip parent rules when handling nested CSS
|
|
532
|
+
// Example: .button { color: black; &:hover { color: red; } }
|
|
533
|
+
// - Rule id=0, selector=".button", declarations=[color: black] (SKIP - has children)
|
|
534
|
+
// - Rule id=1, selector=".button:hover", declarations=[color: red] (PROCESS)
|
|
535
|
+
if (has_nesting && !NIL_P(parent_ids)) {
|
|
536
|
+
VALUE is_parent = rb_hash_aref(parent_ids, rule_id);
|
|
537
|
+
if (RTEST(is_parent)) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
long num_decls = RARRAY_LEN(declarations);
|
|
543
|
+
// Skip rules with no declarations (empty parent containers)
|
|
544
|
+
if (num_decls == 0) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Track selectors for rollup (delay allocation)
|
|
549
|
+
const char *sel_ptr = RSTRING_PTR(selector);
|
|
550
|
+
long sel_len = RSTRING_LEN(selector);
|
|
551
|
+
if (first_selector_ptr == NULL) {
|
|
552
|
+
first_selector_ptr = sel_ptr;
|
|
553
|
+
first_selector_len = sel_len;
|
|
554
|
+
first_selector_value = selector; // Keep VALUE alive for RB_GC_GUARD
|
|
555
|
+
} else if (all_same_selector) {
|
|
556
|
+
if (sel_len != first_selector_len || memcmp(sel_ptr, first_selector_ptr, sel_len) != 0) {
|
|
557
|
+
all_same_selector = 0;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
VALUE specificity_val = rb_struct_aref(rule, INT2FIX(RULE_SPECIFICITY));
|
|
562
|
+
|
|
563
|
+
// Calculate specificity if not provided (lazy)
|
|
564
|
+
int specificity = 0;
|
|
565
|
+
if (NIL_P(specificity_val)) {
|
|
566
|
+
specificity_val = calculate_specificity(Qnil, selector);
|
|
567
|
+
// Cache the calculated value back to the struct
|
|
568
|
+
rb_struct_aset(rule, INT2FIX(RULE_SPECIFICITY), specificity_val);
|
|
569
|
+
}
|
|
570
|
+
specificity = NUM2INT(specificity_val);
|
|
571
|
+
|
|
572
|
+
// Process each declaration in this rule
|
|
573
|
+
Check_Type(declarations, T_ARRAY);
|
|
574
|
+
|
|
575
|
+
for (long j = 0; j < num_decls; j++) {
|
|
576
|
+
VALUE decl = RARRAY_AREF(declarations, j);
|
|
577
|
+
|
|
578
|
+
// Extract property, value, important from Declaration struct
|
|
579
|
+
VALUE property = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
580
|
+
VALUE value = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
581
|
+
VALUE important = rb_struct_aref(decl, INT2FIX(DECL_IMPORTANT));
|
|
582
|
+
|
|
583
|
+
// Properties are already lowercased during parsing (see cataract_new.c)
|
|
584
|
+
// No need to lowercase again
|
|
585
|
+
int is_important = RTEST(important);
|
|
586
|
+
|
|
587
|
+
// Expand shorthand properties if needed
|
|
588
|
+
// Most properties are NOT shorthands, so hint compiler accordingly
|
|
589
|
+
const char *prop_str = StringValueCStr(property);
|
|
590
|
+
VALUE expanded = Qnil;
|
|
591
|
+
|
|
592
|
+
if (strcmp(prop_str, "margin") == 0) {
|
|
593
|
+
expanded = cataract_expand_margin(Qnil, value);
|
|
594
|
+
} else if (strcmp(prop_str, "padding") == 0) {
|
|
595
|
+
expanded = cataract_expand_padding(Qnil, value);
|
|
596
|
+
} else if (strcmp(prop_str, "border") == 0) {
|
|
597
|
+
expanded = cataract_expand_border(Qnil, value);
|
|
598
|
+
} else if (strcmp(prop_str, "border-color") == 0) {
|
|
599
|
+
expanded = cataract_expand_border_color(Qnil, value);
|
|
600
|
+
} else if (strcmp(prop_str, "border-style") == 0) {
|
|
601
|
+
expanded = cataract_expand_border_style(Qnil, value);
|
|
602
|
+
} else if (strcmp(prop_str, "border-width") == 0) {
|
|
603
|
+
expanded = cataract_expand_border_width(Qnil, value);
|
|
604
|
+
} else if (strcmp(prop_str, "border-top") == 0) {
|
|
605
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("top"), value);
|
|
606
|
+
} else if (strcmp(prop_str, "border-right") == 0) {
|
|
607
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("right"), value);
|
|
608
|
+
} else if (strcmp(prop_str, "border-bottom") == 0) {
|
|
609
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("bottom"), value);
|
|
610
|
+
} else if (strcmp(prop_str, "border-left") == 0) {
|
|
611
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("left"), value);
|
|
612
|
+
} else if (strcmp(prop_str, "font") == 0) {
|
|
613
|
+
expanded = cataract_expand_font(Qnil, value);
|
|
614
|
+
} else if (strcmp(prop_str, "list-style") == 0) {
|
|
615
|
+
expanded = cataract_expand_list_style(Qnil, value);
|
|
616
|
+
} else if (strcmp(prop_str, "background") == 0) {
|
|
617
|
+
expanded = cataract_expand_background(Qnil, value);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// If property was expanded, iterate and apply cascade using rb_hash_foreach
|
|
621
|
+
// Expansion is rare (most properties are not shorthands)
|
|
622
|
+
if (!NIL_P(expanded)) {
|
|
623
|
+
Check_Type(expanded, T_HASH);
|
|
624
|
+
|
|
625
|
+
struct expand_context ctx;
|
|
626
|
+
ctx.properties_hash = properties_hash;
|
|
627
|
+
ctx.specificity = specificity;
|
|
628
|
+
ctx.important = important;
|
|
629
|
+
|
|
630
|
+
rb_hash_foreach(expanded, merge_expanded_callback, (VALUE)&ctx);
|
|
631
|
+
|
|
632
|
+
RB_GC_GUARD(expanded);
|
|
633
|
+
continue; // Skip processing the original shorthand property
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Apply CSS cascade rules
|
|
637
|
+
VALUE existing = rb_hash_aref(properties_hash, property);
|
|
638
|
+
|
|
639
|
+
// In merge scenarios, properties often collide (same property in multiple rules)
|
|
640
|
+
// so existing property is the common case
|
|
641
|
+
if (NIL_P(existing)) {
|
|
642
|
+
// New property - add it
|
|
643
|
+
VALUE prop_data = rb_hash_new();
|
|
644
|
+
rb_hash_aset(prop_data, ID2SYM(id_value), value);
|
|
645
|
+
rb_hash_aset(prop_data, ID2SYM(id_specificity), INT2NUM(specificity));
|
|
646
|
+
rb_hash_aset(prop_data, ID2SYM(id_important), important);
|
|
647
|
+
// Note: declaration_struct not stored - use global cDeclaration instead
|
|
648
|
+
rb_hash_aset(properties_hash, property, prop_data);
|
|
649
|
+
} else {
|
|
650
|
+
// Property exists - check cascade rules
|
|
651
|
+
VALUE existing_spec = rb_hash_aref(existing, ID2SYM(id_specificity));
|
|
652
|
+
VALUE existing_important = rb_hash_aref(existing, ID2SYM(id_important));
|
|
653
|
+
|
|
654
|
+
int existing_spec_int = NUM2INT(existing_spec);
|
|
655
|
+
int existing_is_important = RTEST(existing_important);
|
|
656
|
+
|
|
657
|
+
int should_replace = 0;
|
|
658
|
+
|
|
659
|
+
// Most declarations are NOT !important
|
|
660
|
+
if (is_important) {
|
|
661
|
+
// New is !important - wins if existing is NOT important OR equal/higher specificity
|
|
662
|
+
if (!existing_is_important || existing_spec_int <= specificity) {
|
|
663
|
+
should_replace = 1;
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
// New is NOT important - only wins if existing is also NOT important AND equal/higher specificity
|
|
667
|
+
if (!existing_is_important && existing_spec_int <= specificity) {
|
|
668
|
+
should_replace = 1;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Replacement is common in merge scenarios
|
|
673
|
+
if (should_replace) {
|
|
674
|
+
rb_hash_aset(existing, ID2SYM(id_value), value);
|
|
675
|
+
rb_hash_aset(existing, ID2SYM(id_specificity), INT2NUM(specificity));
|
|
676
|
+
rb_hash_aset(existing, ID2SYM(id_important), important);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
RB_GC_GUARD(property);
|
|
681
|
+
RB_GC_GUARD(value);
|
|
682
|
+
RB_GC_GUARD(decl);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
RB_GC_GUARD(selector);
|
|
686
|
+
RB_GC_GUARD(declarations);
|
|
687
|
+
RB_GC_GUARD(rule);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Create shorthand from longhand properties
|
|
691
|
+
// Uses cached static strings to avoid runtime allocation
|
|
692
|
+
|
|
693
|
+
// Try to create margin shorthand
|
|
694
|
+
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
695
|
+
str_margin_top, str_margin_right, str_margin_bottom, str_margin_left,
|
|
696
|
+
str_margin, cataract_create_margin_shorthand);
|
|
697
|
+
|
|
698
|
+
// Try to create padding shorthand
|
|
699
|
+
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
700
|
+
str_padding_top, str_padding_right, str_padding_bottom, str_padding_left,
|
|
701
|
+
str_padding, cataract_create_padding_shorthand);
|
|
702
|
+
|
|
703
|
+
// Create border-width from individual sides
|
|
704
|
+
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
705
|
+
str_border_top_width, str_border_right_width, str_border_bottom_width, str_border_left_width,
|
|
706
|
+
str_border_width, cataract_create_border_width_shorthand);
|
|
707
|
+
|
|
708
|
+
// Create border-style from individual sides
|
|
709
|
+
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
710
|
+
str_border_top_style, str_border_right_style, str_border_bottom_style, str_border_left_style,
|
|
711
|
+
str_border_style, cataract_create_border_style_shorthand);
|
|
712
|
+
|
|
713
|
+
// Create border-color from individual sides
|
|
714
|
+
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
715
|
+
str_border_top_color, str_border_right_color, str_border_bottom_color, str_border_left_color,
|
|
716
|
+
str_border_color, cataract_create_border_color_shorthand);
|
|
717
|
+
|
|
718
|
+
// Now create border shorthand from border-{width,style,color}
|
|
719
|
+
VALUE border_width = GET_PROP_VALUE_STR(properties_hash, str_border_width);
|
|
720
|
+
VALUE border_style = GET_PROP_VALUE_STR(properties_hash, str_border_style);
|
|
721
|
+
VALUE border_color = GET_PROP_VALUE_STR(properties_hash, str_border_color);
|
|
722
|
+
|
|
723
|
+
if (!NIL_P(border_width) || !NIL_P(border_style) || !NIL_P(border_color)) {
|
|
724
|
+
// Use first available property's metadata as reference
|
|
725
|
+
VALUE border_data_src = !NIL_P(border_width) ? GET_PROP_DATA_STR(properties_hash, str_border_width) :
|
|
726
|
+
!NIL_P(border_style) ? GET_PROP_DATA_STR(properties_hash, str_border_style) :
|
|
727
|
+
GET_PROP_DATA_STR(properties_hash, str_border_color);
|
|
728
|
+
VALUE border_important = rb_hash_aref(border_data_src, ID2SYM(id_important));
|
|
729
|
+
int border_is_important = RTEST(border_important);
|
|
730
|
+
|
|
731
|
+
// Check that all present properties have the same !important flag
|
|
732
|
+
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_border_width, border_is_important) &&
|
|
733
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_border_style, border_is_important) &&
|
|
734
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_border_color, border_is_important);
|
|
735
|
+
|
|
736
|
+
if (important_match) {
|
|
737
|
+
VALUE border_props = rb_hash_new();
|
|
738
|
+
if (!NIL_P(border_width)) rb_hash_aset(border_props, str_border_width, border_width);
|
|
739
|
+
if (!NIL_P(border_style)) rb_hash_aset(border_props, str_border_style, border_style);
|
|
740
|
+
if (!NIL_P(border_color)) rb_hash_aset(border_props, str_border_color, border_color);
|
|
741
|
+
|
|
742
|
+
VALUE border_shorthand = cataract_create_border_shorthand(Qnil, border_props);
|
|
743
|
+
if (!NIL_P(border_shorthand)) {
|
|
744
|
+
int border_spec = NUM2INT(rb_hash_aref(border_data_src, ID2SYM(id_specificity)));
|
|
745
|
+
|
|
746
|
+
VALUE border_data = rb_hash_new();
|
|
747
|
+
rb_hash_aset(border_data, ID2SYM(id_value), border_shorthand);
|
|
748
|
+
rb_hash_aset(border_data, ID2SYM(id_specificity), INT2NUM(border_spec));
|
|
749
|
+
rb_hash_aset(border_data, ID2SYM(id_important), border_important);
|
|
750
|
+
rb_hash_aset(properties_hash, str_border, border_data);
|
|
751
|
+
|
|
752
|
+
if (!NIL_P(border_width)) rb_hash_delete(properties_hash, str_border_width);
|
|
753
|
+
if (!NIL_P(border_style)) rb_hash_delete(properties_hash, str_border_style);
|
|
754
|
+
if (!NIL_P(border_color)) rb_hash_delete(properties_hash, str_border_color);
|
|
755
|
+
}
|
|
756
|
+
RB_GC_GUARD(border_props);
|
|
757
|
+
RB_GC_GUARD(border_shorthand);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Try to create font shorthand
|
|
762
|
+
VALUE font_size = GET_PROP_VALUE_STR(properties_hash, str_font_size);
|
|
763
|
+
VALUE font_family = GET_PROP_VALUE_STR(properties_hash, str_font_family);
|
|
764
|
+
|
|
765
|
+
// Font shorthand requires at least font-size and font-family
|
|
766
|
+
if (!NIL_P(font_size) && !NIL_P(font_family)) {
|
|
767
|
+
VALUE font_style = GET_PROP_VALUE_STR(properties_hash, str_font_style);
|
|
768
|
+
VALUE font_variant = GET_PROP_VALUE_STR(properties_hash, str_font_variant);
|
|
769
|
+
VALUE font_weight = GET_PROP_VALUE_STR(properties_hash, str_font_weight);
|
|
770
|
+
VALUE line_height = GET_PROP_VALUE_STR(properties_hash, str_line_height);
|
|
771
|
+
|
|
772
|
+
// Get metadata from font-size as reference
|
|
773
|
+
VALUE size_data = GET_PROP_DATA_STR(properties_hash, str_font_size);
|
|
774
|
+
VALUE font_important = rb_hash_aref(size_data, ID2SYM(id_important));
|
|
775
|
+
int font_is_important = RTEST(font_important);
|
|
776
|
+
|
|
777
|
+
// Check that all present properties have the same !important flag
|
|
778
|
+
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_font_style, font_is_important) &&
|
|
779
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_font_variant, font_is_important) &&
|
|
780
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_font_weight, font_is_important) &&
|
|
781
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_line_height, font_is_important) &&
|
|
782
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_font_family, font_is_important);
|
|
783
|
+
|
|
784
|
+
if (important_match) {
|
|
785
|
+
VALUE font_props = rb_hash_new();
|
|
786
|
+
if (!NIL_P(font_style)) rb_hash_aset(font_props, str_font_style, font_style);
|
|
787
|
+
if (!NIL_P(font_variant)) rb_hash_aset(font_props, str_font_variant, font_variant);
|
|
788
|
+
if (!NIL_P(font_weight)) rb_hash_aset(font_props, str_font_weight, font_weight);
|
|
789
|
+
rb_hash_aset(font_props, str_font_size, font_size);
|
|
790
|
+
if (!NIL_P(line_height)) rb_hash_aset(font_props, str_line_height, line_height);
|
|
791
|
+
rb_hash_aset(font_props, str_font_family, font_family);
|
|
792
|
+
|
|
793
|
+
VALUE font_shorthand = cataract_create_font_shorthand(Qnil, font_props);
|
|
794
|
+
if (!NIL_P(font_shorthand)) {
|
|
795
|
+
int font_spec = NUM2INT(rb_hash_aref(size_data, ID2SYM(id_specificity)));
|
|
796
|
+
|
|
797
|
+
VALUE font_data = rb_hash_new();
|
|
798
|
+
rb_hash_aset(font_data, ID2SYM(id_value), font_shorthand);
|
|
799
|
+
rb_hash_aset(font_data, ID2SYM(id_specificity), INT2NUM(font_spec));
|
|
800
|
+
rb_hash_aset(font_data, ID2SYM(id_important), font_important);
|
|
801
|
+
rb_hash_aset(properties_hash, str_font, font_data);
|
|
802
|
+
|
|
803
|
+
// Remove longhand properties
|
|
804
|
+
if (!NIL_P(font_style)) rb_hash_delete(properties_hash, str_font_style);
|
|
805
|
+
if (!NIL_P(font_variant)) rb_hash_delete(properties_hash, str_font_variant);
|
|
806
|
+
if (!NIL_P(font_weight)) rb_hash_delete(properties_hash, str_font_weight);
|
|
807
|
+
rb_hash_delete(properties_hash, str_font_size);
|
|
808
|
+
if (!NIL_P(line_height)) rb_hash_delete(properties_hash, str_line_height);
|
|
809
|
+
rb_hash_delete(properties_hash, str_font_family);
|
|
810
|
+
}
|
|
811
|
+
RB_GC_GUARD(font_props);
|
|
812
|
+
RB_GC_GUARD(font_shorthand);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Try to create list-style shorthand
|
|
817
|
+
VALUE list_style_type = GET_PROP_VALUE_STR(properties_hash, str_list_style_type);
|
|
818
|
+
VALUE list_style_position = GET_PROP_VALUE_STR(properties_hash, str_list_style_position);
|
|
819
|
+
VALUE list_style_image = GET_PROP_VALUE_STR(properties_hash, str_list_style_image);
|
|
820
|
+
|
|
821
|
+
// List-style shorthand requires at least 2 properties
|
|
822
|
+
int list_style_count = (!NIL_P(list_style_type) ? 1 : 0) +
|
|
823
|
+
(!NIL_P(list_style_position) ? 1 : 0) +
|
|
824
|
+
(!NIL_P(list_style_image) ? 1 : 0);
|
|
825
|
+
|
|
826
|
+
if (list_style_count >= 2) {
|
|
827
|
+
// Use first available property's metadata as reference
|
|
828
|
+
VALUE list_style_data_src = !NIL_P(list_style_type) ? GET_PROP_DATA_STR(properties_hash, str_list_style_type) :
|
|
829
|
+
!NIL_P(list_style_position) ? GET_PROP_DATA_STR(properties_hash, str_list_style_position) :
|
|
830
|
+
GET_PROP_DATA_STR(properties_hash, str_list_style_image);
|
|
831
|
+
VALUE list_style_important = rb_hash_aref(list_style_data_src, ID2SYM(id_important));
|
|
832
|
+
int list_style_is_important = RTEST(list_style_important);
|
|
833
|
+
|
|
834
|
+
// Check that all present properties have the same !important flag
|
|
835
|
+
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_list_style_type, list_style_is_important) &&
|
|
836
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_list_style_position, list_style_is_important) &&
|
|
837
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_list_style_image, list_style_is_important);
|
|
838
|
+
|
|
839
|
+
if (important_match) {
|
|
840
|
+
VALUE list_style_props = rb_hash_new();
|
|
841
|
+
if (!NIL_P(list_style_type)) rb_hash_aset(list_style_props, str_list_style_type, list_style_type);
|
|
842
|
+
if (!NIL_P(list_style_position)) rb_hash_aset(list_style_props, str_list_style_position, list_style_position);
|
|
843
|
+
if (!NIL_P(list_style_image)) rb_hash_aset(list_style_props, str_list_style_image, list_style_image);
|
|
844
|
+
|
|
845
|
+
VALUE list_style_shorthand = cataract_create_list_style_shorthand(Qnil, list_style_props);
|
|
846
|
+
if (!NIL_P(list_style_shorthand)) {
|
|
847
|
+
int list_style_spec = NUM2INT(rb_hash_aref(list_style_data_src, ID2SYM(id_specificity)));
|
|
848
|
+
|
|
849
|
+
VALUE list_style_data = rb_hash_new();
|
|
850
|
+
rb_hash_aset(list_style_data, ID2SYM(id_value), list_style_shorthand);
|
|
851
|
+
rb_hash_aset(list_style_data, ID2SYM(id_specificity), INT2NUM(list_style_spec));
|
|
852
|
+
rb_hash_aset(list_style_data, ID2SYM(id_important), list_style_important);
|
|
853
|
+
rb_hash_aset(properties_hash, str_list_style, list_style_data);
|
|
854
|
+
|
|
855
|
+
// Remove longhand properties
|
|
856
|
+
if (!NIL_P(list_style_type)) rb_hash_delete(properties_hash, str_list_style_type);
|
|
857
|
+
if (!NIL_P(list_style_position)) rb_hash_delete(properties_hash, str_list_style_position);
|
|
858
|
+
if (!NIL_P(list_style_image)) rb_hash_delete(properties_hash, str_list_style_image);
|
|
859
|
+
}
|
|
860
|
+
RB_GC_GUARD(list_style_props);
|
|
861
|
+
RB_GC_GUARD(list_style_shorthand);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Try to create background shorthand
|
|
866
|
+
VALUE background_color = GET_PROP_VALUE_STR(properties_hash, str_background_color);
|
|
867
|
+
VALUE background_image = GET_PROP_VALUE_STR(properties_hash, str_background_image);
|
|
868
|
+
VALUE background_repeat = GET_PROP_VALUE_STR(properties_hash, str_background_repeat);
|
|
869
|
+
VALUE background_attachment = GET_PROP_VALUE_STR(properties_hash, str_background_attachment);
|
|
870
|
+
VALUE background_position = GET_PROP_VALUE_STR(properties_hash, str_background_position);
|
|
871
|
+
|
|
872
|
+
// Background shorthand requires at least 2 properties
|
|
873
|
+
int background_count = (!NIL_P(background_color) ? 1 : 0) +
|
|
874
|
+
(!NIL_P(background_image) ? 1 : 0) +
|
|
875
|
+
(!NIL_P(background_repeat) ? 1 : 0) +
|
|
876
|
+
(!NIL_P(background_attachment) ? 1 : 0) +
|
|
877
|
+
(!NIL_P(background_position) ? 1 : 0);
|
|
878
|
+
|
|
879
|
+
if (background_count >= 2) {
|
|
880
|
+
// Use first available property's metadata as reference
|
|
881
|
+
VALUE background_data_src = !NIL_P(background_color) ? GET_PROP_DATA_STR(properties_hash, str_background_color) :
|
|
882
|
+
!NIL_P(background_image) ? GET_PROP_DATA_STR(properties_hash, str_background_image) :
|
|
883
|
+
!NIL_P(background_repeat) ? GET_PROP_DATA_STR(properties_hash, str_background_repeat) :
|
|
884
|
+
!NIL_P(background_attachment) ? GET_PROP_DATA_STR(properties_hash, str_background_attachment) :
|
|
885
|
+
GET_PROP_DATA_STR(properties_hash, str_background_position);
|
|
886
|
+
VALUE background_important = rb_hash_aref(background_data_src, ID2SYM(id_important));
|
|
887
|
+
int background_is_important = RTEST(background_important);
|
|
888
|
+
|
|
889
|
+
// Check that all present properties have the same !important flag
|
|
890
|
+
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_background_color, background_is_important) &&
|
|
891
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_background_image, background_is_important) &&
|
|
892
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_background_repeat, background_is_important) &&
|
|
893
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_background_attachment, background_is_important) &&
|
|
894
|
+
CHECK_IMPORTANT_MATCH(properties_hash, str_background_position, background_is_important);
|
|
895
|
+
|
|
896
|
+
if (important_match) {
|
|
897
|
+
VALUE background_props = rb_hash_new();
|
|
898
|
+
if (!NIL_P(background_color)) rb_hash_aset(background_props, str_background_color, background_color);
|
|
899
|
+
if (!NIL_P(background_image)) rb_hash_aset(background_props, str_background_image, background_image);
|
|
900
|
+
if (!NIL_P(background_repeat)) rb_hash_aset(background_props, str_background_repeat, background_repeat);
|
|
901
|
+
if (!NIL_P(background_attachment)) rb_hash_aset(background_props, str_background_attachment, background_attachment);
|
|
902
|
+
if (!NIL_P(background_position)) rb_hash_aset(background_props, str_background_position, background_position);
|
|
903
|
+
|
|
904
|
+
VALUE background_shorthand = cataract_create_background_shorthand(Qnil, background_props);
|
|
905
|
+
if (!NIL_P(background_shorthand)) {
|
|
906
|
+
int background_spec = NUM2INT(rb_hash_aref(background_data_src, ID2SYM(id_specificity)));
|
|
907
|
+
|
|
908
|
+
VALUE background_data = rb_hash_new();
|
|
909
|
+
rb_hash_aset(background_data, ID2SYM(id_value), background_shorthand);
|
|
910
|
+
rb_hash_aset(background_data, ID2SYM(id_specificity), INT2NUM(background_spec));
|
|
911
|
+
rb_hash_aset(background_data, ID2SYM(id_important), background_important);
|
|
912
|
+
rb_hash_aset(properties_hash, str_background, background_data);
|
|
913
|
+
|
|
914
|
+
// Remove longhand properties
|
|
915
|
+
if (!NIL_P(background_color)) rb_hash_delete(properties_hash, str_background_color);
|
|
916
|
+
if (!NIL_P(background_image)) rb_hash_delete(properties_hash, str_background_image);
|
|
917
|
+
if (!NIL_P(background_repeat)) rb_hash_delete(properties_hash, str_background_repeat);
|
|
918
|
+
if (!NIL_P(background_attachment)) rb_hash_delete(properties_hash, str_background_attachment);
|
|
919
|
+
if (!NIL_P(background_position)) rb_hash_delete(properties_hash, str_background_position);
|
|
920
|
+
}
|
|
921
|
+
RB_GC_GUARD(background_props);
|
|
922
|
+
RB_GC_GUARD(background_shorthand);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
#undef GET_PROP_VALUE
|
|
927
|
+
#undef GET_PROP_DATA
|
|
928
|
+
|
|
929
|
+
// Build merged declarations array
|
|
930
|
+
VALUE merged_declarations = rb_ary_new();
|
|
931
|
+
rb_hash_foreach(properties_hash, merge_build_result_callback, merged_declarations);
|
|
932
|
+
|
|
933
|
+
// Determine final selector (allocate only once at the end)
|
|
934
|
+
VALUE final_selector;
|
|
935
|
+
if (has_nesting && all_same_selector && first_selector_ptr != NULL) {
|
|
936
|
+
// All rules have same selector - use it for rollup
|
|
937
|
+
final_selector = rb_usascii_str_new(first_selector_ptr, first_selector_len);
|
|
938
|
+
} else {
|
|
939
|
+
// Mixed selectors or no nesting - use "merged"
|
|
940
|
+
final_selector = str_merged_selector;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// Create a new Stylesheet with a single merged rule
|
|
944
|
+
// Use rb_class_new_instance instead of rb_funcall for better performance
|
|
945
|
+
VALUE merged_sheet = rb_class_new_instance(0, NULL, cStylesheet);
|
|
946
|
+
|
|
947
|
+
// Create merged rule
|
|
948
|
+
VALUE merged_rule = rb_struct_new(cRule,
|
|
949
|
+
INT2FIX(0), // id
|
|
950
|
+
final_selector, // selector (rolled-up or "merged")
|
|
951
|
+
merged_declarations, // declarations
|
|
952
|
+
Qnil, // specificity (not applicable)
|
|
953
|
+
Qnil, // parent_rule_id (not nested)
|
|
954
|
+
Qnil // nesting_style (not nested)
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
// Set @rules array with single merged rule (use cached ID)
|
|
958
|
+
VALUE rules_ary = rb_ary_new_from_args(1, merged_rule);
|
|
959
|
+
rb_ivar_set(merged_sheet, id_ivar_rules, rules_ary);
|
|
960
|
+
|
|
961
|
+
// Set @media_index with :all pointing to rule 0 (use cached ID)
|
|
962
|
+
VALUE media_idx = rb_hash_new();
|
|
963
|
+
VALUE all_ids = rb_ary_new_from_args(1, INT2FIX(0));
|
|
964
|
+
rb_hash_aset(media_idx, ID2SYM(id_all), all_ids);
|
|
965
|
+
rb_ivar_set(merged_sheet, id_ivar_media_index, media_idx);
|
|
966
|
+
|
|
967
|
+
// Guard first_selector_value: C pointer extracted via RSTRING_PTR during iteration,
|
|
968
|
+
// then used after many allocations (hash operations, shorthand expansions) when
|
|
969
|
+
// creating final_selector with rb_usascii_str_new
|
|
970
|
+
RB_GC_GUARD(first_selector_value);
|
|
971
|
+
|
|
972
|
+
return merged_sheet;
|
|
973
|
+
}
|