cataract 0.1.3 → 0.2.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 +4 -4
- data/.github/workflows/ci-manual-rubies.yml +44 -0
- data/.overcommit.yml +1 -1
- data/.rubocop.yml +96 -4
- data/.rubocop_todo.yml +186 -0
- data/BENCHMARKS.md +62 -141
- data/CHANGELOG.md +20 -0
- data/RAGEL_MIGRATION.md +2 -2
- data/README.md +37 -4
- data/Rakefile +72 -32
- data/cataract.gemspec +4 -1
- data/ext/cataract/cataract.c +59 -50
- data/ext/cataract/cataract.h +5 -3
- data/ext/cataract/css_parser.c +173 -65
- data/ext/cataract/extconf.rb +2 -2
- data/ext/cataract/{merge.c → flatten.c} +526 -468
- data/ext/cataract/shorthand_expander.c +164 -115
- data/lib/cataract/at_rule.rb +8 -9
- data/lib/cataract/declaration.rb +18 -0
- data/lib/cataract/import_resolver.rb +63 -43
- data/lib/cataract/import_statement.rb +49 -0
- data/lib/cataract/pure/byte_constants.rb +69 -0
- data/lib/cataract/pure/flatten.rb +1145 -0
- data/lib/cataract/pure/helpers.rb +35 -0
- data/lib/cataract/pure/imports.rb +268 -0
- data/lib/cataract/pure/parser.rb +1340 -0
- data/lib/cataract/pure/serializer.rb +590 -0
- data/lib/cataract/pure/specificity.rb +206 -0
- data/lib/cataract/pure.rb +153 -0
- data/lib/cataract/rule.rb +69 -15
- data/lib/cataract/stylesheet.rb +356 -49
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +43 -26
- metadata +14 -26
- data/benchmarks/benchmark_harness.rb +0 -193
- data/benchmarks/benchmark_merging.rb +0 -121
- data/benchmarks/benchmark_optimization_comparison.rb +0 -168
- data/benchmarks/benchmark_parsing.rb +0 -153
- data/benchmarks/benchmark_ragel_removal.rb +0 -56
- data/benchmarks/benchmark_runner.rb +0 -70
- data/benchmarks/benchmark_serialization.rb +0 -180
- data/benchmarks/benchmark_shorthand.rb +0 -109
- data/benchmarks/benchmark_shorthand_expansion.rb +0 -176
- data/benchmarks/benchmark_specificity.rb +0 -124
- data/benchmarks/benchmark_string_allocation.rb +0 -151
- data/benchmarks/benchmark_stylesheet_to_s.rb +0 -62
- data/benchmarks/benchmark_to_s_cached.rb +0 -55
- data/benchmarks/benchmark_value_splitter.rb +0 -54
- data/benchmarks/benchmark_yjit.rb +0 -158
- data/benchmarks/benchmark_yjit_workers.rb +0 -61
- data/benchmarks/profile_to_s.rb +0 -23
- data/benchmarks/speedup_calculator.rb +0 -83
- data/benchmarks/system_metadata.rb +0 -81
- data/benchmarks/templates/benchmarks.md.erb +0 -221
- data/benchmarks/yjit_tests.rb +0 -141
- data/scripts/fuzzer/run.rb +0 -828
- data/scripts/fuzzer/worker.rb +0 -99
- data/scripts/generate_benchmarks_md.rb +0 -155
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
#include "cataract.h"
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
// NOTE: This file was previously called merge.c and the functions were named cataract_merge_*
|
|
4
|
+
// The terminology was changed to "flatten" to better represent CSS cascade behavior.
|
|
5
|
+
|
|
6
|
+
// Array indices for property metadata: [source_order, specificity, important, value]
|
|
7
|
+
#define PROP_SOURCE_ORDER 0
|
|
8
|
+
#define PROP_SPECIFICITY 1
|
|
9
|
+
#define PROP_IMPORTANT 2
|
|
10
|
+
#define PROP_VALUE 3
|
|
11
|
+
|
|
12
|
+
// Cache frequently used symbol IDs (initialized in init_flatten_constants)
|
|
7
13
|
static ID id_all = 0;
|
|
8
14
|
|
|
9
15
|
// Cached ivar IDs for Stylesheet
|
|
@@ -13,8 +19,40 @@ static ID id_ivar_media_index = 0;
|
|
|
13
19
|
// Cached "merged" selector string
|
|
14
20
|
static VALUE str_merged_selector = Qnil;
|
|
15
21
|
|
|
22
|
+
/*
|
|
23
|
+
* Shorthand recreation mapping: defines how to recreate shorthands from longhand properties
|
|
24
|
+
*
|
|
25
|
+
* We cache VALUE objects for property names to avoid repeated string allocations during
|
|
26
|
+
* hash lookups. These are initialized once in init_flatten_constants().
|
|
27
|
+
*/
|
|
28
|
+
struct shorthand_mapping {
|
|
29
|
+
const char *shorthand_name; // e.g., "border-width"
|
|
30
|
+
size_t shorthand_name_len; // Pre-computed strlen(shorthand_name)
|
|
31
|
+
VALUE shorthand_name_val; // Cached Ruby string (initialized at load time)
|
|
32
|
+
const char *prop_top; // e.g., "border-top-width"
|
|
33
|
+
VALUE prop_top_val; // Cached Ruby string
|
|
34
|
+
const char *prop_right; // e.g., "border-right-width"
|
|
35
|
+
VALUE prop_right_val; // Cached Ruby string
|
|
36
|
+
const char *prop_bottom; // e.g., "border-bottom-width"
|
|
37
|
+
VALUE prop_bottom_val; // Cached Ruby string
|
|
38
|
+
const char *prop_left; // e.g., "border-left-width"
|
|
39
|
+
VALUE prop_left_val; // Cached Ruby string
|
|
40
|
+
VALUE (*creator_func)(VALUE, VALUE); // Function pointer to shorthand creator
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Static mapping table for all 4-sided shorthand properties
|
|
44
|
+
// The _val fields are initialized to Qnil here and populated in init_flatten_constants()
|
|
45
|
+
static struct shorthand_mapping SHORTHAND_MAPPINGS[] = {
|
|
46
|
+
{"margin", 6, Qnil, "margin-top", Qnil, "margin-right", Qnil, "margin-bottom", Qnil, "margin-left", Qnil, cataract_create_margin_shorthand},
|
|
47
|
+
{"padding", 7, Qnil, "padding-top", Qnil, "padding-right", Qnil, "padding-bottom", Qnil, "padding-left", Qnil, cataract_create_padding_shorthand},
|
|
48
|
+
{"border-width", 12, Qnil, "border-top-width", Qnil, "border-right-width", Qnil, "border-bottom-width", Qnil, "border-left-width", Qnil, cataract_create_border_width_shorthand},
|
|
49
|
+
{"border-style", 12, Qnil, "border-top-style", Qnil, "border-right-style", Qnil, "border-bottom-style", Qnil, "border-left-style", Qnil, cataract_create_border_style_shorthand},
|
|
50
|
+
{"border-color", 12, Qnil, "border-top-color", Qnil, "border-right-color", Qnil, "border-bottom-color", Qnil, "border-left-color", Qnil, cataract_create_border_color_shorthand},
|
|
51
|
+
{NULL, 0, Qnil, NULL, Qnil, NULL, Qnil, NULL, Qnil, NULL, Qnil, NULL} // Sentinel to mark end of array
|
|
52
|
+
};
|
|
53
|
+
|
|
16
54
|
// Cached property name strings (frozen, never GC'd)
|
|
17
|
-
// Initialized in
|
|
55
|
+
// Initialized in init_flatten_constants() at module load time
|
|
18
56
|
static VALUE str_margin = Qnil;
|
|
19
57
|
static VALUE str_margin_top = Qnil;
|
|
20
58
|
static VALUE str_margin_right = Qnil;
|
|
@@ -62,12 +100,13 @@ static VALUE str_background_position = Qnil;
|
|
|
62
100
|
// Context for expanded property iteration
|
|
63
101
|
struct expand_context {
|
|
64
102
|
VALUE properties_hash;
|
|
103
|
+
long source_order;
|
|
65
104
|
int specificity;
|
|
66
105
|
VALUE important;
|
|
67
106
|
};
|
|
68
107
|
|
|
69
108
|
// Callback for rb_hash_foreach - process expanded properties and apply cascade
|
|
70
|
-
static int
|
|
109
|
+
static int flatten_expanded_callback(VALUE exp_prop, VALUE exp_value, VALUE ctx_val) {
|
|
71
110
|
struct expand_context *ctx = (struct expand_context *)ctx_val;
|
|
72
111
|
|
|
73
112
|
// Expanded properties from shorthand expanders are already lowercase
|
|
@@ -78,34 +117,40 @@ static int merge_expanded_callback(VALUE exp_prop, VALUE exp_value, VALUE ctx_va
|
|
|
78
117
|
VALUE existing = rb_hash_aref(ctx->properties_hash, exp_prop);
|
|
79
118
|
|
|
80
119
|
if (NIL_P(existing)) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
120
|
+
// Create array: [source_order, specificity, important, value]
|
|
121
|
+
VALUE prop_data = rb_ary_new_capa(4);
|
|
122
|
+
rb_ary_push(prop_data, LONG2NUM(ctx->source_order));
|
|
123
|
+
rb_ary_push(prop_data, INT2NUM(ctx->specificity));
|
|
124
|
+
rb_ary_push(prop_data, ctx->important);
|
|
125
|
+
rb_ary_push(prop_data, exp_value);
|
|
86
126
|
rb_hash_aset(ctx->properties_hash, exp_prop, prop_data);
|
|
87
127
|
} else {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
128
|
+
// Access array elements directly
|
|
129
|
+
long existing_order = NUM2LONG(RARRAY_AREF(existing, PROP_SOURCE_ORDER));
|
|
130
|
+
int existing_spec_int = NUM2INT(RARRAY_AREF(existing, PROP_SPECIFICITY));
|
|
131
|
+
VALUE existing_important = RARRAY_AREF(existing, PROP_IMPORTANT);
|
|
92
132
|
int existing_is_important = RTEST(existing_important);
|
|
93
133
|
|
|
94
134
|
int should_replace = 0;
|
|
95
135
|
if (is_important) {
|
|
96
|
-
if (!existing_is_important || existing_spec_int
|
|
136
|
+
if (!existing_is_important || existing_spec_int < ctx->specificity ||
|
|
137
|
+
(existing_spec_int == ctx->specificity && existing_order <= ctx->source_order)) {
|
|
97
138
|
should_replace = 1;
|
|
98
139
|
}
|
|
99
140
|
} else {
|
|
100
|
-
if (!existing_is_important &&
|
|
141
|
+
if (!existing_is_important &&
|
|
142
|
+
(existing_spec_int < ctx->specificity ||
|
|
143
|
+
(existing_spec_int == ctx->specificity && existing_order <= ctx->source_order))) {
|
|
101
144
|
should_replace = 1;
|
|
102
145
|
}
|
|
103
146
|
}
|
|
104
147
|
|
|
105
148
|
if (should_replace) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
149
|
+
// Update array elements
|
|
150
|
+
RARRAY_ASET(existing, PROP_SOURCE_ORDER, LONG2NUM(ctx->source_order));
|
|
151
|
+
RARRAY_ASET(existing, PROP_SPECIFICITY, INT2NUM(ctx->specificity));
|
|
152
|
+
RARRAY_ASET(existing, PROP_IMPORTANT, ctx->important);
|
|
153
|
+
RARRAY_ASET(existing, PROP_VALUE, exp_value);
|
|
109
154
|
}
|
|
110
155
|
}
|
|
111
156
|
|
|
@@ -115,10 +160,10 @@ static int merge_expanded_callback(VALUE exp_prop, VALUE exp_value, VALUE ctx_va
|
|
|
115
160
|
}
|
|
116
161
|
|
|
117
162
|
// Callback for rb_hash_foreach - builds result array from properties hash
|
|
118
|
-
static int
|
|
119
|
-
// Extract value and important flag from
|
|
120
|
-
VALUE value =
|
|
121
|
-
VALUE important =
|
|
163
|
+
static int flatten_build_result_callback(VALUE property, VALUE prop_data, VALUE result_ary) {
|
|
164
|
+
// Extract value and important flag from array: [source_order, specificity, important, value]
|
|
165
|
+
VALUE value = RARRAY_AREF(prop_data, PROP_VALUE);
|
|
166
|
+
VALUE important = RARRAY_AREF(prop_data, PROP_IMPORTANT);
|
|
122
167
|
|
|
123
168
|
// Create Declaration struct (use global cDeclaration)
|
|
124
169
|
VALUE decl_struct = rb_struct_new(cDeclaration, property, value, important);
|
|
@@ -128,11 +173,8 @@ static int merge_build_result_callback(VALUE property, VALUE prop_data, VALUE re
|
|
|
128
173
|
}
|
|
129
174
|
|
|
130
175
|
// Initialize cached property strings (called once at module init)
|
|
131
|
-
void
|
|
176
|
+
void init_flatten_constants(void) {
|
|
132
177
|
// Initialize symbol IDs
|
|
133
|
-
id_value = rb_intern("value");
|
|
134
|
-
id_specificity = rb_intern("specificity");
|
|
135
|
-
id_important = rb_intern("important");
|
|
136
178
|
id_all = rb_intern("all");
|
|
137
179
|
|
|
138
180
|
// Initialize ivar IDs for Stylesheet
|
|
@@ -248,13 +290,45 @@ void init_merge_constants(void) {
|
|
|
248
290
|
// Cached "merged" selector string
|
|
249
291
|
str_merged_selector = rb_str_freeze(USASCII_STR("merged"));
|
|
250
292
|
rb_gc_register_mark_object(str_merged_selector);
|
|
293
|
+
|
|
294
|
+
// Populate the shorthand mapping table with cached string VALUEs
|
|
295
|
+
// This avoids allocating new strings on every hash lookup
|
|
296
|
+
SHORTHAND_MAPPINGS[0].shorthand_name_val = str_margin;
|
|
297
|
+
SHORTHAND_MAPPINGS[0].prop_top_val = str_margin_top;
|
|
298
|
+
SHORTHAND_MAPPINGS[0].prop_right_val = str_margin_right;
|
|
299
|
+
SHORTHAND_MAPPINGS[0].prop_bottom_val = str_margin_bottom;
|
|
300
|
+
SHORTHAND_MAPPINGS[0].prop_left_val = str_margin_left;
|
|
301
|
+
|
|
302
|
+
SHORTHAND_MAPPINGS[1].shorthand_name_val = str_padding;
|
|
303
|
+
SHORTHAND_MAPPINGS[1].prop_top_val = str_padding_top;
|
|
304
|
+
SHORTHAND_MAPPINGS[1].prop_right_val = str_padding_right;
|
|
305
|
+
SHORTHAND_MAPPINGS[1].prop_bottom_val = str_padding_bottom;
|
|
306
|
+
SHORTHAND_MAPPINGS[1].prop_left_val = str_padding_left;
|
|
307
|
+
|
|
308
|
+
SHORTHAND_MAPPINGS[2].shorthand_name_val = str_border_width;
|
|
309
|
+
SHORTHAND_MAPPINGS[2].prop_top_val = str_border_top_width;
|
|
310
|
+
SHORTHAND_MAPPINGS[2].prop_right_val = str_border_right_width;
|
|
311
|
+
SHORTHAND_MAPPINGS[2].prop_bottom_val = str_border_bottom_width;
|
|
312
|
+
SHORTHAND_MAPPINGS[2].prop_left_val = str_border_left_width;
|
|
313
|
+
|
|
314
|
+
SHORTHAND_MAPPINGS[3].shorthand_name_val = str_border_style;
|
|
315
|
+
SHORTHAND_MAPPINGS[3].prop_top_val = str_border_top_style;
|
|
316
|
+
SHORTHAND_MAPPINGS[3].prop_right_val = str_border_right_style;
|
|
317
|
+
SHORTHAND_MAPPINGS[3].prop_bottom_val = str_border_bottom_style;
|
|
318
|
+
SHORTHAND_MAPPINGS[3].prop_left_val = str_border_left_style;
|
|
319
|
+
|
|
320
|
+
SHORTHAND_MAPPINGS[4].shorthand_name_val = str_border_color;
|
|
321
|
+
SHORTHAND_MAPPINGS[4].prop_top_val = str_border_top_color;
|
|
322
|
+
SHORTHAND_MAPPINGS[4].prop_right_val = str_border_right_color;
|
|
323
|
+
SHORTHAND_MAPPINGS[4].prop_bottom_val = str_border_bottom_color;
|
|
324
|
+
SHORTHAND_MAPPINGS[4].prop_left_val = str_border_left_color;
|
|
251
325
|
}
|
|
252
326
|
|
|
253
327
|
// Helper macros to extract property data from properties_hash
|
|
254
|
-
//
|
|
328
|
+
// Properties are stored as arrays: [source_order, specificity, important, value]
|
|
255
329
|
#define GET_PROP_VALUE(hash, prop_name) \
|
|
256
330
|
({ VALUE pd = rb_hash_aref(hash, USASCII_STR(prop_name)); \
|
|
257
|
-
NIL_P(pd) ? Qnil :
|
|
331
|
+
NIL_P(pd) ? Qnil : RARRAY_AREF(pd, PROP_VALUE); })
|
|
258
332
|
|
|
259
333
|
#define GET_PROP_DATA(hash, prop_name) \
|
|
260
334
|
rb_hash_aref(hash, USASCII_STR(prop_name))
|
|
@@ -262,7 +336,7 @@ void init_merge_constants(void) {
|
|
|
262
336
|
// Versions that accept cached VALUE strings instead of string literals
|
|
263
337
|
#define GET_PROP_VALUE_STR(hash, str_prop) \
|
|
264
338
|
({ VALUE pd = rb_hash_aref(hash, str_prop); \
|
|
265
|
-
NIL_P(pd) ? Qnil :
|
|
339
|
+
NIL_P(pd) ? Qnil : RARRAY_AREF(pd, PROP_VALUE); })
|
|
266
340
|
|
|
267
341
|
#define GET_PROP_DATA_STR(hash, str_prop) \
|
|
268
342
|
rb_hash_aref(hash, str_prop)
|
|
@@ -270,7 +344,7 @@ void init_merge_constants(void) {
|
|
|
270
344
|
// Helper macro to check if a property's !important flag matches a reference
|
|
271
345
|
#define CHECK_IMPORTANT_MATCH(hash, str_prop, ref_important) \
|
|
272
346
|
({ VALUE _pd = GET_PROP_DATA_STR(hash, str_prop); \
|
|
273
|
-
NIL_P(_pd) ? 1 : (RTEST(
|
|
347
|
+
NIL_P(_pd) ? 1 : (RTEST(RARRAY_AREF(_pd, PROP_IMPORTANT)) == (ref_important)); })
|
|
274
348
|
|
|
275
349
|
// Macro to create shorthand from 4-sided properties (margin, padding, border-width/style/color)
|
|
276
350
|
// Reduces repetitive code by encapsulating the common pattern:
|
|
@@ -293,10 +367,10 @@ void init_merge_constants(void) {
|
|
|
293
367
|
VALUE _bottom_data = GET_PROP_DATA_STR(hash, str_bottom); \
|
|
294
368
|
VALUE _left_data = GET_PROP_DATA_STR(hash, str_left); \
|
|
295
369
|
\
|
|
296
|
-
VALUE _top_imp =
|
|
297
|
-
VALUE _right_imp =
|
|
298
|
-
VALUE _bottom_imp =
|
|
299
|
-
VALUE _left_imp =
|
|
370
|
+
VALUE _top_imp = RARRAY_AREF(_top_data, PROP_IMPORTANT); \
|
|
371
|
+
VALUE _right_imp = RARRAY_AREF(_right_data, PROP_IMPORTANT); \
|
|
372
|
+
VALUE _bottom_imp = RARRAY_AREF(_bottom_data, PROP_IMPORTANT); \
|
|
373
|
+
VALUE _left_imp = RARRAY_AREF(_left_data, PROP_IMPORTANT); \
|
|
300
374
|
\
|
|
301
375
|
int _top_is_imp = RTEST(_top_imp); \
|
|
302
376
|
int _right_is_imp = RTEST(_right_imp); \
|
|
@@ -313,12 +387,14 @@ void init_merge_constants(void) {
|
|
|
313
387
|
\
|
|
314
388
|
VALUE _shorthand_value = creator_func(Qnil, _props); \
|
|
315
389
|
if (!NIL_P(_shorthand_value)) { \
|
|
316
|
-
|
|
390
|
+
long _source_order = NUM2LONG(RARRAY_AREF(_top_data, PROP_SOURCE_ORDER)); \
|
|
391
|
+
int _specificity = NUM2INT(RARRAY_AREF(_top_data, PROP_SPECIFICITY)); \
|
|
317
392
|
\
|
|
318
|
-
VALUE _shorthand_data =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
393
|
+
VALUE _shorthand_data = rb_ary_new_capa(4); \
|
|
394
|
+
rb_ary_push(_shorthand_data, LONG2NUM(_source_order)); \
|
|
395
|
+
rb_ary_push(_shorthand_data, INT2NUM(_specificity)); \
|
|
396
|
+
rb_ary_push(_shorthand_data, _top_imp); \
|
|
397
|
+
rb_ary_push(_shorthand_data, _shorthand_value); \
|
|
322
398
|
rb_hash_aset(hash, str_shorthand, _shorthand_data); \
|
|
323
399
|
\
|
|
324
400
|
rb_hash_delete(hash, str_top); \
|
|
@@ -349,24 +425,25 @@ void init_merge_constants(void) {
|
|
|
349
425
|
VALUE _left_data = rb_hash_aref(hash, STR_NEW_CSTR(_left_name)); \
|
|
350
426
|
\
|
|
351
427
|
if (!NIL_P(_top_data) && !NIL_P(_right_data) && !NIL_P(_bottom_data) && !NIL_P(_left_data)) { \
|
|
352
|
-
VALUE _top_imp =
|
|
353
|
-
VALUE _right_imp =
|
|
354
|
-
VALUE _bottom_imp =
|
|
355
|
-
VALUE _left_imp =
|
|
428
|
+
VALUE _top_imp = RARRAY_AREF(_top_data, PROP_IMPORTANT); \
|
|
429
|
+
VALUE _right_imp = RARRAY_AREF(_right_data, PROP_IMPORTANT); \
|
|
430
|
+
VALUE _bottom_imp = RARRAY_AREF(_bottom_data, PROP_IMPORTANT); \
|
|
431
|
+
VALUE _left_imp = RARRAY_AREF(_left_data, PROP_IMPORTANT); \
|
|
356
432
|
\
|
|
357
433
|
if (RTEST(_top_imp) == RTEST(_right_imp) && RTEST(_top_imp) == RTEST(_bottom_imp) && RTEST(_top_imp) == RTEST(_left_imp)) { \
|
|
358
434
|
VALUE _props = rb_hash_new(); \
|
|
359
|
-
rb_hash_aset(_props, STR_NEW_CSTR(_top_name),
|
|
360
|
-
rb_hash_aset(_props, STR_NEW_CSTR(_right_name),
|
|
361
|
-
rb_hash_aset(_props, STR_NEW_CSTR(_bottom_name),
|
|
362
|
-
rb_hash_aset(_props, STR_NEW_CSTR(_left_name),
|
|
435
|
+
rb_hash_aset(_props, STR_NEW_CSTR(_top_name), RARRAY_AREF(_top_data, PROP_VALUE)); \
|
|
436
|
+
rb_hash_aset(_props, STR_NEW_CSTR(_right_name), RARRAY_AREF(_right_data, PROP_VALUE)); \
|
|
437
|
+
rb_hash_aset(_props, STR_NEW_CSTR(_bottom_name), RARRAY_AREF(_bottom_data, PROP_VALUE)); \
|
|
438
|
+
rb_hash_aset(_props, STR_NEW_CSTR(_left_name), RARRAY_AREF(_left_data, PROP_VALUE)); \
|
|
363
439
|
\
|
|
364
440
|
VALUE _shorthand_value = creator_func(Qnil, _props); \
|
|
365
441
|
if (!NIL_P(_shorthand_value)) { \
|
|
366
|
-
VALUE _shorthand_data =
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
442
|
+
VALUE _shorthand_data = rb_ary_new_capa(4); \
|
|
443
|
+
rb_ary_push(_shorthand_data, RARRAY_AREF(_top_data, PROP_SOURCE_ORDER)); \
|
|
444
|
+
rb_ary_push(_shorthand_data, RARRAY_AREF(_top_data, PROP_SPECIFICITY)); \
|
|
445
|
+
rb_ary_push(_shorthand_data, _top_imp); \
|
|
446
|
+
rb_ary_push(_shorthand_data, _shorthand_value); \
|
|
370
447
|
rb_hash_aset(hash, rb_usascii_str_new(prefix, strlen(prefix)), _shorthand_data); \
|
|
371
448
|
\
|
|
372
449
|
rb_hash_delete(hash, STR_NEW_CSTR(_top_name)); \
|
|
@@ -379,23 +456,83 @@ void init_merge_constants(void) {
|
|
|
379
456
|
} \
|
|
380
457
|
} while(0)
|
|
381
458
|
|
|
459
|
+
// Helper function: Try to recreate a shorthand property from its longhand components
|
|
460
|
+
// Uses cached VALUE objects for property names to avoid repeated string allocations
|
|
461
|
+
static inline void try_recreate_shorthand(VALUE properties_hash, const struct shorthand_mapping *mapping) {
|
|
462
|
+
VALUE top_data = rb_hash_aref(properties_hash, mapping->prop_top_val);
|
|
463
|
+
VALUE right_data = rb_hash_aref(properties_hash, mapping->prop_right_val);
|
|
464
|
+
VALUE bottom_data = rb_hash_aref(properties_hash, mapping->prop_bottom_val);
|
|
465
|
+
VALUE left_data = rb_hash_aref(properties_hash, mapping->prop_left_val);
|
|
466
|
+
|
|
467
|
+
// All four sides must be present
|
|
468
|
+
if (NIL_P(top_data) || NIL_P(right_data) || NIL_P(bottom_data) || NIL_P(left_data)) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// All four sides must have the same !important flag
|
|
473
|
+
VALUE top_imp = RARRAY_AREF(top_data, PROP_IMPORTANT);
|
|
474
|
+
VALUE right_imp = RARRAY_AREF(right_data, PROP_IMPORTANT);
|
|
475
|
+
VALUE bottom_imp = RARRAY_AREF(bottom_data, PROP_IMPORTANT);
|
|
476
|
+
VALUE left_imp = RARRAY_AREF(left_data, PROP_IMPORTANT);
|
|
477
|
+
|
|
478
|
+
if (RTEST(top_imp) != RTEST(right_imp) ||
|
|
479
|
+
RTEST(top_imp) != RTEST(bottom_imp) ||
|
|
480
|
+
RTEST(top_imp) != RTEST(left_imp)) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Build a hash of property values for the creator function
|
|
485
|
+
VALUE props = rb_hash_new();
|
|
486
|
+
rb_hash_aset(props, mapping->prop_top_val, RARRAY_AREF(top_data, PROP_VALUE));
|
|
487
|
+
rb_hash_aset(props, mapping->prop_right_val, RARRAY_AREF(right_data, PROP_VALUE));
|
|
488
|
+
rb_hash_aset(props, mapping->prop_bottom_val, RARRAY_AREF(bottom_data, PROP_VALUE));
|
|
489
|
+
rb_hash_aset(props, mapping->prop_left_val, RARRAY_AREF(left_data, PROP_VALUE));
|
|
490
|
+
|
|
491
|
+
// Call the creator function
|
|
492
|
+
VALUE shorthand_value = mapping->creator_func(Qnil, props);
|
|
493
|
+
if (NIL_P(shorthand_value)) {
|
|
494
|
+
return; // Creator decided not to create shorthand
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Create the shorthand property data array
|
|
498
|
+
VALUE shorthand_data = rb_ary_new_capa(4);
|
|
499
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(top_data, PROP_SOURCE_ORDER));
|
|
500
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(top_data, PROP_SPECIFICITY));
|
|
501
|
+
rb_ary_push(shorthand_data, top_imp);
|
|
502
|
+
rb_ary_push(shorthand_data, shorthand_value);
|
|
503
|
+
|
|
504
|
+
// Add shorthand and remove longhand properties
|
|
505
|
+
rb_hash_aset(properties_hash, mapping->shorthand_name_val, shorthand_data);
|
|
506
|
+
rb_hash_delete(properties_hash, mapping->prop_top_val);
|
|
507
|
+
rb_hash_delete(properties_hash, mapping->prop_right_val);
|
|
508
|
+
rb_hash_delete(properties_hash, mapping->prop_bottom_val);
|
|
509
|
+
rb_hash_delete(properties_hash, mapping->prop_left_val);
|
|
510
|
+
|
|
511
|
+
DEBUG_PRINTF(" -> Recreated %s shorthand\n", mapping->shorthand_name);
|
|
512
|
+
}
|
|
513
|
+
|
|
382
514
|
/*
|
|
383
515
|
* Helper struct: For processing expanded properties during merge
|
|
384
516
|
*/
|
|
385
517
|
struct expand_property_data {
|
|
386
518
|
VALUE properties_hash; // Target hash to store properties
|
|
387
|
-
|
|
519
|
+
VALUE selector; // Selector string (for lazy specificity calculation)
|
|
520
|
+
int specificity; // Cached specificity (-1 if not yet calculated)
|
|
388
521
|
int is_important; // Whether the original declaration was !important
|
|
389
522
|
long source_order; // Source order of the original declaration
|
|
390
523
|
};
|
|
391
524
|
|
|
392
525
|
/*
|
|
393
526
|
* Callback: Process each expanded property and apply cascade rules
|
|
527
|
+
*
|
|
528
|
+
* Optimization: Specificity is calculated lazily only when needed for cascade comparison.
|
|
529
|
+
* This avoids expensive specificity calculation when:
|
|
530
|
+
* - Property doesn't exist yet (no comparison needed)
|
|
531
|
+
* - Importance levels differ (!important always wins, regardless of specificity)
|
|
394
532
|
*/
|
|
395
533
|
static int process_expanded_property(VALUE prop_name, VALUE prop_value, VALUE arg) {
|
|
396
534
|
struct expand_property_data *data = (struct expand_property_data *)arg;
|
|
397
535
|
VALUE properties_hash = data->properties_hash;
|
|
398
|
-
int specificity = data->specificity;
|
|
399
536
|
int is_important = data->is_important;
|
|
400
537
|
long source_order = data->source_order;
|
|
401
538
|
|
|
@@ -407,55 +544,113 @@ static int process_expanded_property(VALUE prop_name, VALUE prop_value, VALUE ar
|
|
|
407
544
|
VALUE existing = rb_hash_aref(properties_hash, prop_name);
|
|
408
545
|
if (NIL_P(existing)) {
|
|
409
546
|
DEBUG_PRINTF(" -> NEW property\n");
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
547
|
+
// Calculate specificity on first use (lazy initialization)
|
|
548
|
+
if (data->specificity == -1) {
|
|
549
|
+
data->specificity = NUM2INT(calculate_specificity(Qnil, data->selector));
|
|
550
|
+
}
|
|
551
|
+
// Create array: [source_order, specificity, important, value]
|
|
552
|
+
VALUE prop_data = rb_ary_new_capa(4);
|
|
553
|
+
rb_ary_push(prop_data, LONG2NUM(source_order));
|
|
554
|
+
rb_ary_push(prop_data, INT2NUM(data->specificity));
|
|
555
|
+
rb_ary_push(prop_data, is_important ? Qtrue : Qfalse);
|
|
556
|
+
rb_ary_push(prop_data, prop_value);
|
|
415
557
|
rb_hash_aset(properties_hash, prop_name, prop_data);
|
|
416
558
|
} else {
|
|
417
559
|
// Property exists - apply CSS cascade rules
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
560
|
+
long existing_source_order = NUM2LONG(RARRAY_AREF(existing, PROP_SOURCE_ORDER));
|
|
561
|
+
int existing_spec = NUM2INT(RARRAY_AREF(existing, PROP_SPECIFICITY));
|
|
562
|
+
VALUE existing_important = RARRAY_AREF(existing, PROP_IMPORTANT);
|
|
421
563
|
int existing_is_important = RTEST(existing_important);
|
|
422
|
-
long existing_source_order = NUM2LONG(existing_source_order_val);
|
|
423
|
-
|
|
424
|
-
DEBUG_PRINTF(" -> COLLISION: existing important=%d source_order=%ld, new important=%d source_order=%ld\n",
|
|
425
|
-
existing_is_important, existing_source_order, is_important, source_order);
|
|
426
564
|
|
|
427
565
|
int should_replace = 0;
|
|
428
566
|
|
|
429
567
|
// Apply CSS cascade rules:
|
|
430
|
-
// 1. !important always wins over non-!important
|
|
431
|
-
// 2. Higher specificity wins (
|
|
568
|
+
// 1. !important always wins over non-!important (no specificity check needed)
|
|
569
|
+
// 2. Higher specificity wins (only check when importance is same)
|
|
432
570
|
// 3. Later source order wins
|
|
433
571
|
if (is_important && !existing_is_important) {
|
|
434
|
-
// New declaration is !important, existing is not - replace
|
|
572
|
+
// New declaration is !important, existing is not - replace (no specificity needed)
|
|
435
573
|
should_replace = 1;
|
|
436
574
|
DEBUG_PRINTF(" -> REPLACE (new is !important, existing is not)\n");
|
|
437
575
|
} else if (!is_important && existing_is_important) {
|
|
438
|
-
// Existing declaration is !important, new is not - keep existing
|
|
576
|
+
// Existing declaration is !important, new is not - keep existing (no specificity needed)
|
|
439
577
|
should_replace = 0;
|
|
440
578
|
DEBUG_PRINTF(" -> KEEP (existing is !important, new is not)\n");
|
|
441
579
|
} else {
|
|
442
|
-
// Same importance level -
|
|
443
|
-
|
|
444
|
-
|
|
580
|
+
// Same importance level - NOW we need specificity
|
|
581
|
+
// Calculate specificity on first use (lazy initialization)
|
|
582
|
+
if (data->specificity == -1) {
|
|
583
|
+
data->specificity = NUM2INT(calculate_specificity(Qnil, data->selector));
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
DEBUG_PRINTF(" -> COLLISION: existing spec=%d important=%d source_order=%ld, new spec=%d important=%d source_order=%ld\n",
|
|
587
|
+
existing_spec, existing_is_important, existing_source_order,
|
|
588
|
+
data->specificity, is_important, source_order);
|
|
589
|
+
|
|
590
|
+
// Same importance level - check specificity then source order
|
|
591
|
+
if (data->specificity > existing_spec) {
|
|
592
|
+
should_replace = 1;
|
|
593
|
+
} else if (data->specificity == existing_spec) {
|
|
594
|
+
should_replace = source_order > existing_source_order;
|
|
595
|
+
}
|
|
596
|
+
DEBUG_PRINTF(" -> %s (same importance, spec=%d vs %d, order=%ld vs %ld)\n",
|
|
445
597
|
should_replace ? "REPLACE" : "KEEP",
|
|
446
|
-
|
|
598
|
+
data->specificity, existing_spec, source_order, existing_source_order);
|
|
447
599
|
}
|
|
448
600
|
|
|
449
601
|
if (should_replace) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
602
|
+
// Calculate specificity if we haven't yet (edge case: importance differs but we're replacing)
|
|
603
|
+
if (data->specificity == -1) {
|
|
604
|
+
data->specificity = NUM2INT(calculate_specificity(Qnil, data->selector));
|
|
605
|
+
}
|
|
606
|
+
RARRAY_ASET(existing, PROP_SOURCE_ORDER, LONG2NUM(source_order));
|
|
607
|
+
RARRAY_ASET(existing, PROP_SPECIFICITY, INT2NUM(data->specificity));
|
|
608
|
+
RARRAY_ASET(existing, PROP_IMPORTANT, is_important ? Qtrue : Qfalse);
|
|
609
|
+
RARRAY_ASET(existing, PROP_VALUE, prop_value);
|
|
453
610
|
}
|
|
454
611
|
}
|
|
455
612
|
|
|
456
613
|
return ST_CONTINUE;
|
|
457
614
|
}
|
|
458
615
|
|
|
616
|
+
// Context for flatten_selector_group_callback
|
|
617
|
+
struct flatten_selectors_context {
|
|
618
|
+
VALUE merged_rules;
|
|
619
|
+
VALUE rules_array;
|
|
620
|
+
int *rule_id_counter;
|
|
621
|
+
long selector_index;
|
|
622
|
+
long total_selectors;
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
// Forward declaration
|
|
626
|
+
static VALUE flatten_rules_for_selector(VALUE rules_array, VALUE rule_indices, VALUE selector);
|
|
627
|
+
|
|
628
|
+
// Callback for rb_hash_foreach when merging selector groups
|
|
629
|
+
static int flatten_selector_group_callback(VALUE selector, VALUE group_indices, VALUE arg) {
|
|
630
|
+
struct flatten_selectors_context *ctx = (struct flatten_selectors_context *)arg;
|
|
631
|
+
ctx->selector_index++;
|
|
632
|
+
|
|
633
|
+
DEBUG_PRINTF("\n[Selector %ld/%ld] '%s' - %ld rules in group\n",
|
|
634
|
+
ctx->selector_index, ctx->total_selectors,
|
|
635
|
+
RSTRING_PTR(selector), RARRAY_LEN(group_indices));
|
|
636
|
+
|
|
637
|
+
// Merge all rules in this selector group
|
|
638
|
+
VALUE merged_decls = flatten_rules_for_selector(ctx->rules_array, group_indices, selector);
|
|
639
|
+
|
|
640
|
+
// Create new rule with this selector and merged declarations
|
|
641
|
+
VALUE new_rule = rb_struct_new(cRule,
|
|
642
|
+
INT2FIX((*ctx->rule_id_counter)++),
|
|
643
|
+
selector,
|
|
644
|
+
merged_decls,
|
|
645
|
+
Qnil, // specificity
|
|
646
|
+
Qnil, // parent_rule_id
|
|
647
|
+
Qnil // nesting_style
|
|
648
|
+
);
|
|
649
|
+
rb_ary_push(ctx->merged_rules, new_rule);
|
|
650
|
+
|
|
651
|
+
return ST_CONTINUE;
|
|
652
|
+
}
|
|
653
|
+
|
|
459
654
|
/*
|
|
460
655
|
* Helper function: Merge multiple rules with the same selector
|
|
461
656
|
*
|
|
@@ -464,17 +659,13 @@ static int process_expanded_property(VALUE prop_name, VALUE prop_value, VALUE ar
|
|
|
464
659
|
*
|
|
465
660
|
* Returns: Array of merged Declaration structs
|
|
466
661
|
*/
|
|
467
|
-
static VALUE
|
|
662
|
+
static VALUE flatten_rules_for_selector(VALUE rules_array, VALUE rule_indices, VALUE selector) {
|
|
468
663
|
long num_rules_in_group = RARRAY_LEN(rule_indices);
|
|
469
664
|
VALUE properties_hash = rb_hash_new();
|
|
470
665
|
|
|
471
|
-
DEBUG_PRINTF(" [
|
|
666
|
+
DEBUG_PRINTF(" [flatten_rules_for_selector] Merging %ld rules for selector '%s'\n",
|
|
472
667
|
num_rules_in_group, RSTRING_PTR(selector));
|
|
473
668
|
|
|
474
|
-
// Calculate specificity once for this selector
|
|
475
|
-
VALUE specificity_val = calculate_specificity(Qnil, selector);
|
|
476
|
-
int specificity = NUM2INT(specificity_val);
|
|
477
|
-
|
|
478
669
|
// Process each rule in this selector group
|
|
479
670
|
for (long g = 0; g < num_rules_in_group; g++) {
|
|
480
671
|
long rule_idx = FIX2LONG(rb_ary_entry(rule_indices, g));
|
|
@@ -512,66 +703,81 @@ static VALUE merge_rules_for_selector(VALUE rules_array, VALUE rule_indices, VAL
|
|
|
512
703
|
is_important ? " !important" : "", source_order);
|
|
513
704
|
|
|
514
705
|
// Expand shorthands (margin, padding, background, font, etc.)
|
|
515
|
-
// The expand functions return
|
|
706
|
+
// The expand functions return an array of Declaration structs
|
|
516
707
|
const char *prop_cstr = RSTRING_PTR(property);
|
|
517
708
|
VALUE expanded = Qnil;
|
|
518
709
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
710
|
+
// Early exit: shorthand properties only start with m, p, b, f, or l
|
|
711
|
+
char first_char = prop_cstr[0];
|
|
712
|
+
if (first_char == 'm' || first_char == 'p' || first_char == 'b' ||
|
|
713
|
+
first_char == 'f' || first_char == 'l') {
|
|
714
|
+
// Potentially a shorthand - check specific property names
|
|
715
|
+
if (strcmp(prop_cstr, "margin") == 0) {
|
|
716
|
+
expanded = cataract_expand_margin(Qnil, value);
|
|
717
|
+
DEBUG_PRINTF(" -> Expanding margin shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
718
|
+
} else if (strcmp(prop_cstr, "padding") == 0) {
|
|
719
|
+
expanded = cataract_expand_padding(Qnil, value);
|
|
720
|
+
DEBUG_PRINTF(" -> Expanding padding shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
721
|
+
} else if (strcmp(prop_cstr, "background") == 0) {
|
|
722
|
+
expanded = cataract_expand_background(Qnil, value);
|
|
723
|
+
DEBUG_PRINTF(" -> Expanding background shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
724
|
+
} else if (strcmp(prop_cstr, "font") == 0) {
|
|
725
|
+
expanded = cataract_expand_font(Qnil, value);
|
|
726
|
+
DEBUG_PRINTF(" -> Expanding font shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
727
|
+
} else if (strcmp(prop_cstr, "border") == 0) {
|
|
728
|
+
expanded = cataract_expand_border(Qnil, value);
|
|
729
|
+
DEBUG_PRINTF(" -> Expanding border shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
730
|
+
} else if (strcmp(prop_cstr, "border-color") == 0) {
|
|
731
|
+
expanded = cataract_expand_border_color(Qnil, value);
|
|
732
|
+
DEBUG_PRINTF(" -> Expanding border-color shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
733
|
+
} else if (strcmp(prop_cstr, "border-style") == 0) {
|
|
734
|
+
expanded = cataract_expand_border_style(Qnil, value);
|
|
735
|
+
DEBUG_PRINTF(" -> Expanding border-style shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
736
|
+
} else if (strcmp(prop_cstr, "border-width") == 0) {
|
|
737
|
+
expanded = cataract_expand_border_width(Qnil, value);
|
|
738
|
+
DEBUG_PRINTF(" -> Expanding border-width shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
739
|
+
} else if (strcmp(prop_cstr, "list-style") == 0) {
|
|
740
|
+
expanded = cataract_expand_list_style(Qnil, value);
|
|
741
|
+
DEBUG_PRINTF(" -> Expanding list-style shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
742
|
+
} else if (strcmp(prop_cstr, "border-top") == 0) {
|
|
743
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("top"), value);
|
|
744
|
+
DEBUG_PRINTF(" -> Expanding border-top shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
745
|
+
} else if (strcmp(prop_cstr, "border-right") == 0) {
|
|
746
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("right"), value);
|
|
747
|
+
DEBUG_PRINTF(" -> Expanding border-right shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
748
|
+
} else if (strcmp(prop_cstr, "border-bottom") == 0) {
|
|
749
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("bottom"), value);
|
|
750
|
+
DEBUG_PRINTF(" -> Expanding border-bottom shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
751
|
+
} else if (strcmp(prop_cstr, "border-left") == 0) {
|
|
752
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("left"), value);
|
|
753
|
+
DEBUG_PRINTF(" -> Expanding border-left shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
754
|
+
}
|
|
558
755
|
}
|
|
756
|
+
// If first_char doesn't match, expanded stays Qnil and we skip to processing original property
|
|
559
757
|
|
|
560
758
|
// Process expanded properties or the original property
|
|
561
|
-
if (!NIL_P(expanded) &&
|
|
562
|
-
//
|
|
759
|
+
if (!NIL_P(expanded) && RARRAY_LEN(expanded) > 0) {
|
|
760
|
+
// Iterate over expanded Declaration array
|
|
563
761
|
struct expand_property_data expand_data = {
|
|
564
762
|
.properties_hash = properties_hash,
|
|
565
|
-
.
|
|
763
|
+
.selector = selector,
|
|
764
|
+
.specificity = -1, // Lazy: calculated only when needed
|
|
566
765
|
.is_important = is_important,
|
|
567
766
|
.source_order = source_order
|
|
568
767
|
};
|
|
569
|
-
|
|
768
|
+
long expanded_len = RARRAY_LEN(expanded);
|
|
769
|
+
for (long i = 0; i < expanded_len; i++) {
|
|
770
|
+
VALUE decl = rb_ary_entry(expanded, i);
|
|
771
|
+
VALUE prop = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
772
|
+
VALUE val = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
773
|
+
process_expanded_property(prop, val, (VALUE)&expand_data);
|
|
774
|
+
}
|
|
570
775
|
} else {
|
|
571
776
|
// No expansion - process the original property directly
|
|
572
777
|
struct expand_property_data expand_data = {
|
|
573
778
|
.properties_hash = properties_hash,
|
|
574
|
-
.
|
|
779
|
+
.selector = selector,
|
|
780
|
+
.specificity = -1, // Lazy: calculated only when needed
|
|
575
781
|
.is_important = is_important,
|
|
576
782
|
.source_order = source_order
|
|
577
783
|
};
|
|
@@ -586,164 +792,11 @@ static VALUE merge_rules_for_selector(VALUE rules_array, VALUE rule_indices, VAL
|
|
|
586
792
|
}
|
|
587
793
|
|
|
588
794
|
// Recreate shorthands where possible (reduces output size)
|
|
589
|
-
DEBUG_PRINTF(" [
|
|
590
|
-
|
|
591
|
-
// Try to recreate margin shorthand (if all 4 sides present)
|
|
592
|
-
RECREATE_DIMENSION_SHORTHAND(properties_hash, "margin", cataract_create_margin_shorthand);
|
|
593
|
-
|
|
594
|
-
// Try to recreate padding shorthand (if all 4 sides present)
|
|
595
|
-
RECREATE_DIMENSION_SHORTHAND(properties_hash, "padding", cataract_create_padding_shorthand);
|
|
596
|
-
|
|
597
|
-
// Try to recreate border-width shorthand (if all 4 sides present)
|
|
598
|
-
{
|
|
599
|
-
VALUE top = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-top-width"));
|
|
600
|
-
VALUE right = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-right-width"));
|
|
601
|
-
VALUE bottom = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-bottom-width"));
|
|
602
|
-
VALUE left = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-left-width"));
|
|
603
|
-
|
|
604
|
-
if (!NIL_P(top) && !NIL_P(right) && !NIL_P(bottom) && !NIL_P(left)) {
|
|
605
|
-
VALUE top_imp = rb_hash_aref(top, ID2SYM(id_important));
|
|
606
|
-
VALUE right_imp = rb_hash_aref(right, ID2SYM(id_important));
|
|
607
|
-
VALUE bottom_imp = rb_hash_aref(bottom, ID2SYM(id_important));
|
|
608
|
-
VALUE left_imp = rb_hash_aref(left, ID2SYM(id_important));
|
|
609
|
-
|
|
610
|
-
if (RTEST(top_imp) == RTEST(right_imp) && RTEST(top_imp) == RTEST(bottom_imp) && RTEST(top_imp) == RTEST(left_imp)) {
|
|
611
|
-
VALUE props = rb_hash_new();
|
|
612
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-top-width"), rb_hash_aref(top, ID2SYM(id_value)));
|
|
613
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-right-width"), rb_hash_aref(right, ID2SYM(id_value)));
|
|
614
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-bottom-width"), rb_hash_aref(bottom, ID2SYM(id_value)));
|
|
615
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-left-width"), rb_hash_aref(left, ID2SYM(id_value)));
|
|
616
|
-
|
|
617
|
-
VALUE shorthand_value = cataract_create_border_width_shorthand(Qnil, props);
|
|
618
|
-
if (!NIL_P(shorthand_value)) {
|
|
619
|
-
VALUE shorthand_data = rb_hash_new();
|
|
620
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
621
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(top, ID2SYM(id_specificity)));
|
|
622
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_important), top_imp);
|
|
623
|
-
rb_hash_aset(properties_hash, USASCII_STR("border-width"), shorthand_data);
|
|
624
|
-
|
|
625
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-top-width"));
|
|
626
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-right-width"));
|
|
627
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-bottom-width"));
|
|
628
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-left-width"));
|
|
629
|
-
DEBUG_PRINTF(" -> Recreated border-width shorthand\n");
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// Try to recreate border-style shorthand (if all 4 sides present)
|
|
636
|
-
{
|
|
637
|
-
VALUE top = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-top-style"));
|
|
638
|
-
VALUE right = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-right-style"));
|
|
639
|
-
VALUE bottom = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-bottom-style"));
|
|
640
|
-
VALUE left = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-left-style"));
|
|
641
|
-
|
|
642
|
-
if (!NIL_P(top) && !NIL_P(right) && !NIL_P(bottom) && !NIL_P(left)) {
|
|
643
|
-
VALUE top_imp = rb_hash_aref(top, ID2SYM(id_important));
|
|
644
|
-
VALUE right_imp = rb_hash_aref(right, ID2SYM(id_important));
|
|
645
|
-
VALUE bottom_imp = rb_hash_aref(bottom, ID2SYM(id_important));
|
|
646
|
-
VALUE left_imp = rb_hash_aref(left, ID2SYM(id_important));
|
|
647
|
-
|
|
648
|
-
if (RTEST(top_imp) == RTEST(right_imp) && RTEST(top_imp) == RTEST(bottom_imp) && RTEST(top_imp) == RTEST(left_imp)) {
|
|
649
|
-
VALUE props = rb_hash_new();
|
|
650
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-top-style"), rb_hash_aref(top, ID2SYM(id_value)));
|
|
651
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-right-style"), rb_hash_aref(right, ID2SYM(id_value)));
|
|
652
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-bottom-style"), rb_hash_aref(bottom, ID2SYM(id_value)));
|
|
653
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-left-style"), rb_hash_aref(left, ID2SYM(id_value)));
|
|
795
|
+
DEBUG_PRINTF(" [flatten_rules_for_selector] Recreating shorthands...\n");
|
|
654
796
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
659
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(top, ID2SYM(id_specificity)));
|
|
660
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_important), top_imp);
|
|
661
|
-
rb_hash_aset(properties_hash, USASCII_STR("border-style"), shorthand_data);
|
|
662
|
-
|
|
663
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-top-style"));
|
|
664
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-right-style"));
|
|
665
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-bottom-style"));
|
|
666
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-left-style"));
|
|
667
|
-
DEBUG_PRINTF(" -> Recreated border-style shorthand\n");
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// Try to recreate border-color shorthand (if all 4 sides present)
|
|
674
|
-
{
|
|
675
|
-
VALUE top = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-top-color"));
|
|
676
|
-
VALUE right = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-right-color"));
|
|
677
|
-
VALUE bottom = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-bottom-color"));
|
|
678
|
-
VALUE left = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-left-color"));
|
|
679
|
-
|
|
680
|
-
if (!NIL_P(top) && !NIL_P(right) && !NIL_P(bottom) && !NIL_P(left)) {
|
|
681
|
-
VALUE top_imp = rb_hash_aref(top, ID2SYM(id_important));
|
|
682
|
-
VALUE right_imp = rb_hash_aref(right, ID2SYM(id_important));
|
|
683
|
-
VALUE bottom_imp = rb_hash_aref(bottom, ID2SYM(id_important));
|
|
684
|
-
VALUE left_imp = rb_hash_aref(left, ID2SYM(id_important));
|
|
685
|
-
|
|
686
|
-
if (RTEST(top_imp) == RTEST(right_imp) && RTEST(top_imp) == RTEST(bottom_imp) && RTEST(top_imp) == RTEST(left_imp)) {
|
|
687
|
-
VALUE props = rb_hash_new();
|
|
688
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-top-color"), rb_hash_aref(top, ID2SYM(id_value)));
|
|
689
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-right-color"), rb_hash_aref(right, ID2SYM(id_value)));
|
|
690
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-bottom-color"), rb_hash_aref(bottom, ID2SYM(id_value)));
|
|
691
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-left-color"), rb_hash_aref(left, ID2SYM(id_value)));
|
|
692
|
-
|
|
693
|
-
VALUE shorthand_value = cataract_create_border_color_shorthand(Qnil, props);
|
|
694
|
-
if (!NIL_P(shorthand_value)) {
|
|
695
|
-
VALUE shorthand_data = rb_hash_new();
|
|
696
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
697
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(top, ID2SYM(id_specificity)));
|
|
698
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_important), top_imp);
|
|
699
|
-
rb_hash_aset(properties_hash, USASCII_STR("border-color"), shorthand_data);
|
|
700
|
-
|
|
701
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-top-color"));
|
|
702
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-right-color"));
|
|
703
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-bottom-color"));
|
|
704
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-left-color"));
|
|
705
|
-
DEBUG_PRINTF(" -> Recreated border-color shorthand\n");
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// Try to recreate border-style shorthand (if all 4 sides present)
|
|
712
|
-
{
|
|
713
|
-
VALUE top = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-top-style"));
|
|
714
|
-
VALUE right = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-right-style"));
|
|
715
|
-
VALUE bottom = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-bottom-style"));
|
|
716
|
-
VALUE left = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-left-style"));
|
|
717
|
-
|
|
718
|
-
if (!NIL_P(top) && !NIL_P(right) && !NIL_P(bottom) && !NIL_P(left)) {
|
|
719
|
-
VALUE top_imp = rb_hash_aref(top, ID2SYM(id_important));
|
|
720
|
-
VALUE right_imp = rb_hash_aref(right, ID2SYM(id_important));
|
|
721
|
-
VALUE bottom_imp = rb_hash_aref(bottom, ID2SYM(id_important));
|
|
722
|
-
VALUE left_imp = rb_hash_aref(left, ID2SYM(id_important));
|
|
723
|
-
|
|
724
|
-
if (RTEST(top_imp) == RTEST(right_imp) && RTEST(top_imp) == RTEST(bottom_imp) && RTEST(top_imp) == RTEST(left_imp)) {
|
|
725
|
-
VALUE props = rb_hash_new();
|
|
726
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-top-style"), rb_hash_aref(top, ID2SYM(id_value)));
|
|
727
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-right-style"), rb_hash_aref(right, ID2SYM(id_value)));
|
|
728
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-bottom-style"), rb_hash_aref(bottom, ID2SYM(id_value)));
|
|
729
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-left-style"), rb_hash_aref(left, ID2SYM(id_value)));
|
|
730
|
-
|
|
731
|
-
VALUE shorthand_value = cataract_create_border_style_shorthand(Qnil, props);
|
|
732
|
-
if (!NIL_P(shorthand_value)) {
|
|
733
|
-
VALUE shorthand_data = rb_hash_new();
|
|
734
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
735
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(top, ID2SYM(id_specificity)));
|
|
736
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_important), top_imp);
|
|
737
|
-
rb_hash_aset(properties_hash, USASCII_STR("border-style"), shorthand_data);
|
|
738
|
-
|
|
739
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-top-style"));
|
|
740
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-right-style"));
|
|
741
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-bottom-style"));
|
|
742
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-left-style"));
|
|
743
|
-
DEBUG_PRINTF(" -> Recreated border-style shorthand\n");
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
797
|
+
// Try to recreate all 4-sided shorthands using the mapping table
|
|
798
|
+
for (const struct shorthand_mapping *mapping = SHORTHAND_MAPPINGS; mapping->shorthand_name != NULL; mapping++) {
|
|
799
|
+
try_recreate_shorthand(properties_hash, mapping);
|
|
747
800
|
}
|
|
748
801
|
|
|
749
802
|
// Try to recreate full border shorthand (if border-width, border-style, border-color present)
|
|
@@ -755,23 +808,24 @@ static VALUE merge_rules_for_selector(VALUE rules_array, VALUE rule_indices, VAL
|
|
|
755
808
|
// Need at least style (border shorthand requires style)
|
|
756
809
|
if (!NIL_P(style)) {
|
|
757
810
|
// Check all have same !important flag
|
|
758
|
-
VALUE style_imp =
|
|
811
|
+
VALUE style_imp = RARRAY_AREF(style, PROP_IMPORTANT);
|
|
759
812
|
int same_importance = 1;
|
|
760
|
-
if (!NIL_P(width)) same_importance = same_importance && (RTEST(style_imp) == RTEST(
|
|
761
|
-
if (!NIL_P(color)) same_importance = same_importance && (RTEST(style_imp) == RTEST(
|
|
813
|
+
if (!NIL_P(width)) same_importance = same_importance && (RTEST(style_imp) == RTEST(RARRAY_AREF(width, PROP_IMPORTANT)));
|
|
814
|
+
if (!NIL_P(color)) same_importance = same_importance && (RTEST(style_imp) == RTEST(RARRAY_AREF(color, PROP_IMPORTANT)));
|
|
762
815
|
|
|
763
816
|
if (same_importance) {
|
|
764
817
|
VALUE props = rb_hash_new();
|
|
765
|
-
if (!NIL_P(width)) rb_hash_aset(props, STR_NEW_CSTR("border-width"),
|
|
766
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-style"),
|
|
767
|
-
if (!NIL_P(color)) rb_hash_aset(props, STR_NEW_CSTR("border-color"),
|
|
818
|
+
if (!NIL_P(width)) rb_hash_aset(props, STR_NEW_CSTR("border-width"), RARRAY_AREF(width, PROP_VALUE));
|
|
819
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-style"), RARRAY_AREF(style, PROP_VALUE));
|
|
820
|
+
if (!NIL_P(color)) rb_hash_aset(props, STR_NEW_CSTR("border-color"), RARRAY_AREF(color, PROP_VALUE));
|
|
768
821
|
|
|
769
822
|
VALUE shorthand_value = cataract_create_border_shorthand(Qnil, props);
|
|
770
823
|
if (!NIL_P(shorthand_value)) {
|
|
771
|
-
VALUE shorthand_data =
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
824
|
+
VALUE shorthand_data = rb_ary_new_capa(4);
|
|
825
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(style, PROP_SOURCE_ORDER));
|
|
826
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(style, PROP_SPECIFICITY));
|
|
827
|
+
rb_ary_push(shorthand_data, style_imp);
|
|
828
|
+
rb_ary_push(shorthand_data, shorthand_value);
|
|
775
829
|
rb_hash_aset(properties_hash, USASCII_STR("border"), shorthand_data);
|
|
776
830
|
|
|
777
831
|
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-width"));
|
|
@@ -799,28 +853,29 @@ static VALUE merge_rules_for_selector(VALUE rules_array, VALUE rule_indices, VAL
|
|
|
799
853
|
if (list_count >= 2) {
|
|
800
854
|
// Check all have same !important flag
|
|
801
855
|
VALUE first_imp = Qnil;
|
|
802
|
-
if (!NIL_P(type)) first_imp =
|
|
803
|
-
else if (!NIL_P(position)) first_imp =
|
|
804
|
-
else if (!NIL_P(image)) first_imp =
|
|
856
|
+
if (!NIL_P(type)) first_imp = RARRAY_AREF(type, PROP_IMPORTANT);
|
|
857
|
+
else if (!NIL_P(position)) first_imp = RARRAY_AREF(position, PROP_IMPORTANT);
|
|
858
|
+
else if (!NIL_P(image)) first_imp = RARRAY_AREF(image, PROP_IMPORTANT);
|
|
805
859
|
|
|
806
860
|
int same_importance = 1;
|
|
807
|
-
if (!NIL_P(type)) same_importance = same_importance && (RTEST(first_imp) == RTEST(
|
|
808
|
-
if (!NIL_P(position)) same_importance = same_importance && (RTEST(first_imp) == RTEST(
|
|
809
|
-
if (!NIL_P(image)) same_importance = same_importance && (RTEST(first_imp) == RTEST(
|
|
861
|
+
if (!NIL_P(type)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(type, PROP_IMPORTANT)));
|
|
862
|
+
if (!NIL_P(position)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(position, PROP_IMPORTANT)));
|
|
863
|
+
if (!NIL_P(image)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(image, PROP_IMPORTANT)));
|
|
810
864
|
|
|
811
865
|
if (same_importance) {
|
|
812
866
|
VALUE props = rb_hash_new();
|
|
813
|
-
if (!NIL_P(type)) rb_hash_aset(props, STR_NEW_CSTR("list-style-type"),
|
|
814
|
-
if (!NIL_P(position)) rb_hash_aset(props, STR_NEW_CSTR("list-style-position"),
|
|
815
|
-
if (!NIL_P(image)) rb_hash_aset(props, STR_NEW_CSTR("list-style-image"),
|
|
867
|
+
if (!NIL_P(type)) rb_hash_aset(props, STR_NEW_CSTR("list-style-type"), RARRAY_AREF(type, PROP_VALUE));
|
|
868
|
+
if (!NIL_P(position)) rb_hash_aset(props, STR_NEW_CSTR("list-style-position"), RARRAY_AREF(position, PROP_VALUE));
|
|
869
|
+
if (!NIL_P(image)) rb_hash_aset(props, STR_NEW_CSTR("list-style-image"), RARRAY_AREF(image, PROP_VALUE));
|
|
816
870
|
|
|
817
871
|
VALUE shorthand_value = cataract_create_list_style_shorthand(Qnil, props);
|
|
818
872
|
if (!NIL_P(shorthand_value)) {
|
|
819
|
-
VALUE shorthand_data = rb_hash_new();
|
|
820
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
821
873
|
VALUE first_prop = !NIL_P(type) ? type : (!NIL_P(position) ? position : image);
|
|
822
|
-
|
|
823
|
-
|
|
874
|
+
VALUE shorthand_data = rb_ary_new_capa(4);
|
|
875
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(first_prop, PROP_SOURCE_ORDER));
|
|
876
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(first_prop, PROP_SPECIFICITY));
|
|
877
|
+
rb_ary_push(shorthand_data, first_imp);
|
|
878
|
+
rb_ary_push(shorthand_data, shorthand_value);
|
|
824
879
|
rb_hash_aset(properties_hash, USASCII_STR("list-style"), shorthand_data);
|
|
825
880
|
|
|
826
881
|
rb_hash_delete(properties_hash, STR_NEW_CSTR("list-style-type"));
|
|
@@ -844,30 +899,31 @@ static VALUE merge_rules_for_selector(VALUE rules_array, VALUE rule_indices, VAL
|
|
|
844
899
|
VALUE line_height = rb_hash_aref(properties_hash, STR_NEW_CSTR("line-height"));
|
|
845
900
|
|
|
846
901
|
// Check all font properties have same !important flag
|
|
847
|
-
VALUE size_imp =
|
|
848
|
-
VALUE family_imp =
|
|
902
|
+
VALUE size_imp = RARRAY_AREF(size, PROP_IMPORTANT);
|
|
903
|
+
VALUE family_imp = RARRAY_AREF(family, PROP_IMPORTANT);
|
|
849
904
|
|
|
850
905
|
int same_importance = (RTEST(size_imp) == RTEST(family_imp));
|
|
851
|
-
if (!NIL_P(style)) same_importance = same_importance && (RTEST(size_imp) == RTEST(
|
|
852
|
-
if (!NIL_P(variant)) same_importance = same_importance && (RTEST(size_imp) == RTEST(
|
|
853
|
-
if (!NIL_P(weight)) same_importance = same_importance && (RTEST(size_imp) == RTEST(
|
|
854
|
-
if (!NIL_P(line_height)) same_importance = same_importance && (RTEST(size_imp) == RTEST(
|
|
906
|
+
if (!NIL_P(style)) same_importance = same_importance && (RTEST(size_imp) == RTEST(RARRAY_AREF(style, PROP_IMPORTANT)));
|
|
907
|
+
if (!NIL_P(variant)) same_importance = same_importance && (RTEST(size_imp) == RTEST(RARRAY_AREF(variant, PROP_IMPORTANT)));
|
|
908
|
+
if (!NIL_P(weight)) same_importance = same_importance && (RTEST(size_imp) == RTEST(RARRAY_AREF(weight, PROP_IMPORTANT)));
|
|
909
|
+
if (!NIL_P(line_height)) same_importance = same_importance && (RTEST(size_imp) == RTEST(RARRAY_AREF(line_height, PROP_IMPORTANT)));
|
|
855
910
|
|
|
856
911
|
if (same_importance) {
|
|
857
912
|
VALUE props = rb_hash_new();
|
|
858
|
-
rb_hash_aset(props, STR_NEW_CSTR("font-size"),
|
|
859
|
-
rb_hash_aset(props, STR_NEW_CSTR("font-family"),
|
|
860
|
-
if (!NIL_P(style)) rb_hash_aset(props, STR_NEW_CSTR("font-style"),
|
|
861
|
-
if (!NIL_P(variant)) rb_hash_aset(props, STR_NEW_CSTR("font-variant"),
|
|
862
|
-
if (!NIL_P(weight)) rb_hash_aset(props, STR_NEW_CSTR("font-weight"),
|
|
863
|
-
if (!NIL_P(line_height)) rb_hash_aset(props, STR_NEW_CSTR("line-height"),
|
|
913
|
+
rb_hash_aset(props, STR_NEW_CSTR("font-size"), RARRAY_AREF(size, PROP_VALUE));
|
|
914
|
+
rb_hash_aset(props, STR_NEW_CSTR("font-family"), RARRAY_AREF(family, PROP_VALUE));
|
|
915
|
+
if (!NIL_P(style)) rb_hash_aset(props, STR_NEW_CSTR("font-style"), RARRAY_AREF(style, PROP_VALUE));
|
|
916
|
+
if (!NIL_P(variant)) rb_hash_aset(props, STR_NEW_CSTR("font-variant"), RARRAY_AREF(variant, PROP_VALUE));
|
|
917
|
+
if (!NIL_P(weight)) rb_hash_aset(props, STR_NEW_CSTR("font-weight"), RARRAY_AREF(weight, PROP_VALUE));
|
|
918
|
+
if (!NIL_P(line_height)) rb_hash_aset(props, STR_NEW_CSTR("line-height"), RARRAY_AREF(line_height, PROP_VALUE));
|
|
864
919
|
|
|
865
920
|
VALUE shorthand_value = cataract_create_font_shorthand(Qnil, props);
|
|
866
921
|
if (!NIL_P(shorthand_value)) {
|
|
867
|
-
VALUE shorthand_data =
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
922
|
+
VALUE shorthand_data = rb_ary_new_capa(4);
|
|
923
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(size, PROP_SOURCE_ORDER));
|
|
924
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(size, PROP_SPECIFICITY));
|
|
925
|
+
rb_ary_push(shorthand_data, size_imp);
|
|
926
|
+
rb_ary_push(shorthand_data, shorthand_value);
|
|
871
927
|
rb_hash_aset(properties_hash, USASCII_STR("font"), shorthand_data);
|
|
872
928
|
|
|
873
929
|
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-size"));
|
|
@@ -901,34 +957,35 @@ static VALUE merge_rules_for_selector(VALUE rules_array, VALUE rule_indices, VAL
|
|
|
901
957
|
if (bg_count >= 2) {
|
|
902
958
|
// Check all have same !important flag
|
|
903
959
|
VALUE first_imp = Qnil;
|
|
904
|
-
if (!NIL_P(color)) first_imp =
|
|
905
|
-
else if (!NIL_P(image)) first_imp =
|
|
906
|
-
else if (!NIL_P(repeat)) first_imp =
|
|
907
|
-
else if (!NIL_P(position)) first_imp =
|
|
908
|
-
else if (!NIL_P(attachment)) first_imp =
|
|
960
|
+
if (!NIL_P(color)) first_imp = RARRAY_AREF(color, PROP_IMPORTANT);
|
|
961
|
+
else if (!NIL_P(image)) first_imp = RARRAY_AREF(image, PROP_IMPORTANT);
|
|
962
|
+
else if (!NIL_P(repeat)) first_imp = RARRAY_AREF(repeat, PROP_IMPORTANT);
|
|
963
|
+
else if (!NIL_P(position)) first_imp = RARRAY_AREF(position, PROP_IMPORTANT);
|
|
964
|
+
else if (!NIL_P(attachment)) first_imp = RARRAY_AREF(attachment, PROP_IMPORTANT);
|
|
909
965
|
|
|
910
966
|
int same_importance = 1;
|
|
911
|
-
if (!NIL_P(color)) same_importance = same_importance && (RTEST(first_imp) == RTEST(
|
|
912
|
-
if (!NIL_P(image)) same_importance = same_importance && (RTEST(first_imp) == RTEST(
|
|
913
|
-
if (!NIL_P(repeat)) same_importance = same_importance && (RTEST(first_imp) == RTEST(
|
|
914
|
-
if (!NIL_P(position)) same_importance = same_importance && (RTEST(first_imp) == RTEST(
|
|
915
|
-
if (!NIL_P(attachment)) same_importance = same_importance && (RTEST(first_imp) == RTEST(
|
|
967
|
+
if (!NIL_P(color)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(color, PROP_IMPORTANT)));
|
|
968
|
+
if (!NIL_P(image)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(image, PROP_IMPORTANT)));
|
|
969
|
+
if (!NIL_P(repeat)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(repeat, PROP_IMPORTANT)));
|
|
970
|
+
if (!NIL_P(position)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(position, PROP_IMPORTANT)));
|
|
971
|
+
if (!NIL_P(attachment)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(attachment, PROP_IMPORTANT)));
|
|
916
972
|
|
|
917
973
|
if (same_importance) {
|
|
918
974
|
VALUE props = rb_hash_new();
|
|
919
|
-
if (!NIL_P(color)) rb_hash_aset(props, STR_NEW_CSTR("background-color"),
|
|
920
|
-
if (!NIL_P(image)) rb_hash_aset(props, STR_NEW_CSTR("background-image"),
|
|
921
|
-
if (!NIL_P(repeat)) rb_hash_aset(props, STR_NEW_CSTR("background-repeat"),
|
|
922
|
-
if (!NIL_P(position)) rb_hash_aset(props, STR_NEW_CSTR("background-position"),
|
|
923
|
-
if (!NIL_P(attachment)) rb_hash_aset(props, STR_NEW_CSTR("background-attachment"),
|
|
975
|
+
if (!NIL_P(color)) rb_hash_aset(props, STR_NEW_CSTR("background-color"), RARRAY_AREF(color, PROP_VALUE));
|
|
976
|
+
if (!NIL_P(image)) rb_hash_aset(props, STR_NEW_CSTR("background-image"), RARRAY_AREF(image, PROP_VALUE));
|
|
977
|
+
if (!NIL_P(repeat)) rb_hash_aset(props, STR_NEW_CSTR("background-repeat"), RARRAY_AREF(repeat, PROP_VALUE));
|
|
978
|
+
if (!NIL_P(position)) rb_hash_aset(props, STR_NEW_CSTR("background-position"), RARRAY_AREF(position, PROP_VALUE));
|
|
979
|
+
if (!NIL_P(attachment)) rb_hash_aset(props, STR_NEW_CSTR("background-attachment"), RARRAY_AREF(attachment, PROP_VALUE));
|
|
924
980
|
|
|
925
981
|
VALUE shorthand_value = cataract_create_background_shorthand(Qnil, props);
|
|
926
982
|
if (!NIL_P(shorthand_value)) {
|
|
927
|
-
VALUE shorthand_data = rb_hash_new();
|
|
928
|
-
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
929
983
|
VALUE first_prop = !NIL_P(color) ? color : (!NIL_P(image) ? image : (!NIL_P(repeat) ? repeat : (!NIL_P(position) ? position : attachment)));
|
|
930
|
-
|
|
931
|
-
|
|
984
|
+
VALUE shorthand_data = rb_ary_new_capa(4);
|
|
985
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(first_prop, PROP_SOURCE_ORDER));
|
|
986
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(first_prop, PROP_SPECIFICITY));
|
|
987
|
+
rb_ary_push(shorthand_data, first_imp);
|
|
988
|
+
rb_ary_push(shorthand_data, shorthand_value);
|
|
932
989
|
rb_hash_aset(properties_hash, USASCII_STR("background"), shorthand_data);
|
|
933
990
|
|
|
934
991
|
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-color"));
|
|
@@ -943,19 +1000,25 @@ static VALUE merge_rules_for_selector(VALUE rules_array, VALUE rule_indices, VAL
|
|
|
943
1000
|
}
|
|
944
1001
|
|
|
945
1002
|
// Build declarations array from properties_hash
|
|
1003
|
+
// NOTE: We don't sort by source_order here because:
|
|
1004
|
+
// 1. Hash iteration order reflects insertion order
|
|
1005
|
+
// 2. Declaration order doesn't affect CSS behavior (cascade is already resolved)
|
|
1006
|
+
// 3. Sorting would add overhead for purely aesthetic output
|
|
1007
|
+
// The output order is roughly source order but may vary when properties are
|
|
1008
|
+
// overridden by later rules with higher specificity or importance.
|
|
946
1009
|
VALUE merged_decls = rb_ary_new();
|
|
947
|
-
rb_hash_foreach(properties_hash,
|
|
1010
|
+
rb_hash_foreach(properties_hash, flatten_build_result_callback, merged_decls);
|
|
948
1011
|
|
|
949
|
-
DEBUG_PRINTF(" [
|
|
1012
|
+
DEBUG_PRINTF(" [flatten_rules_for_selector] Result: %ld merged declarations\n",
|
|
950
1013
|
RARRAY_LEN(merged_decls));
|
|
951
1014
|
|
|
952
1015
|
return merged_decls;
|
|
953
1016
|
}
|
|
954
1017
|
|
|
955
|
-
//
|
|
1018
|
+
// Flatten CSS rules by applying cascade rules
|
|
956
1019
|
// Input: Stylesheet object or CSS string
|
|
957
|
-
// Output: Stylesheet with
|
|
958
|
-
VALUE
|
|
1020
|
+
// Output: Stylesheet with flattened declarations (cascade applied)
|
|
1021
|
+
VALUE cataract_flatten(VALUE self, VALUE input) {
|
|
959
1022
|
VALUE rules_array;
|
|
960
1023
|
|
|
961
1024
|
// Handle different input types
|
|
@@ -981,14 +1044,6 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
981
1044
|
has_nesting = RTEST(has_nesting_ivar);
|
|
982
1045
|
}
|
|
983
1046
|
|
|
984
|
-
// Initialize cached symbol IDs on first call (thread-safe since GVL is held)
|
|
985
|
-
// This only happens once, so unlikely
|
|
986
|
-
if (id_value == 0) {
|
|
987
|
-
id_value = rb_intern("value");
|
|
988
|
-
id_specificity = rb_intern("specificity");
|
|
989
|
-
id_important = rb_intern("important");
|
|
990
|
-
}
|
|
991
|
-
|
|
992
1047
|
long num_rules = RARRAY_LEN(rules_array);
|
|
993
1048
|
// Empty stylesheets are rare
|
|
994
1049
|
if (num_rules == 0) {
|
|
@@ -999,13 +1054,13 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
999
1054
|
|
|
1000
1055
|
/*
|
|
1001
1056
|
* ============================================================================
|
|
1002
|
-
*
|
|
1057
|
+
* FLATTEN ALGORITHM - Rules and Implementation Notes
|
|
1003
1058
|
* ============================================================================
|
|
1004
1059
|
*
|
|
1005
|
-
* CORE PRINCIPLE: Group rules by selector,
|
|
1060
|
+
* CORE PRINCIPLE: Group rules by selector, flatten declarations within each group
|
|
1006
1061
|
*
|
|
1007
1062
|
* Different selectors (.test vs #test) target different elements and must stay separate.
|
|
1008
|
-
* Same selectors should
|
|
1063
|
+
* Same selectors should flatten into one rule to reduce output size.
|
|
1009
1064
|
*
|
|
1010
1065
|
* ALGORITHM STEPS:
|
|
1011
1066
|
* 1. Group rules by selector (.test, #test, etc.)
|
|
@@ -1025,7 +1080,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1025
1080
|
* This ensures declarations within the same rule maintain relative order.
|
|
1026
1081
|
*
|
|
1027
1082
|
* SHORTHAND EXPANSION:
|
|
1028
|
-
* When
|
|
1083
|
+
* When flattening, all shorthands must be expanded to longhands first.
|
|
1029
1084
|
* Example: "background: blue" expands to:
|
|
1030
1085
|
* - background-color: blue
|
|
1031
1086
|
* - background-image: none
|
|
@@ -1055,9 +1110,9 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1055
1110
|
* (The "none", "repeat", etc. are just defaults from expansion)
|
|
1056
1111
|
*
|
|
1057
1112
|
* EDGE CASES:
|
|
1058
|
-
* - Empty rules (no declarations): Skip during
|
|
1113
|
+
* - Empty rules (no declarations): Skip during flatten
|
|
1059
1114
|
* - Nested CSS: Parent rules with children are containers only, skip their declarations
|
|
1060
|
-
* - Mixed !important: Properties with different importance cannot
|
|
1115
|
+
* - Mixed !important: Properties with different importance cannot flatten into shorthand
|
|
1061
1116
|
* - Single property: Don't create shorthand (e.g., background-color alone stays as-is)
|
|
1062
1117
|
* Reason: "background: blue" resets all other background properties to defaults,
|
|
1063
1118
|
* which is semantically different from just setting background-color.
|
|
@@ -1065,16 +1120,16 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1065
1120
|
* PERFORMANCE NOTES:
|
|
1066
1121
|
* - Use cached static strings (VALUE) for property names (no allocation)
|
|
1067
1122
|
* - Group by selector in single pass (O(n) hash building)
|
|
1068
|
-
* -
|
|
1123
|
+
* - Flatten within groups (O(n*m) where m is avg declarations per rule)
|
|
1069
1124
|
* ============================================================================
|
|
1070
1125
|
*/
|
|
1071
1126
|
|
|
1072
1127
|
// For nested CSS: identify parent rules (rules that have children)
|
|
1073
|
-
// These should be skipped during
|
|
1128
|
+
// These should be skipped during flatten, even if they have declarations
|
|
1074
1129
|
// Use Ruby hash as a set: parent_id => true
|
|
1075
1130
|
VALUE parent_ids = Qnil;
|
|
1076
1131
|
if (has_nesting) {
|
|
1077
|
-
DEBUG_PRINTF("\n===
|
|
1132
|
+
DEBUG_PRINTF("\n=== FLATTEN: has_nesting=true, num_rules=%ld ===\n", num_rules);
|
|
1078
1133
|
parent_ids = rb_hash_new();
|
|
1079
1134
|
for (long i = 0; i < num_rules; i++) {
|
|
1080
1135
|
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
@@ -1091,7 +1146,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1091
1146
|
}
|
|
1092
1147
|
}
|
|
1093
1148
|
|
|
1094
|
-
// ALWAYS build selector groups - this is the core of
|
|
1149
|
+
// ALWAYS build selector groups - this is the core of flatten logic
|
|
1095
1150
|
// Group rules by selector: different selectors stay separate
|
|
1096
1151
|
// selector => [rule indices]
|
|
1097
1152
|
DEBUG_PRINTF("\n=== Building selector groups (has_nesting=%d) ===\n", has_nesting);
|
|
@@ -1175,32 +1230,18 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1175
1230
|
VALUE merged_rules = rb_ary_new();
|
|
1176
1231
|
int rule_id_counter = 0;
|
|
1177
1232
|
|
|
1178
|
-
// Iterate through each selector group
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
// Merge all rules in this selector group
|
|
1191
|
-
VALUE merged_decls = merge_rules_for_selector(rules_array, group_indices, selector);
|
|
1192
|
-
|
|
1193
|
-
// Create new rule with this selector and merged declarations
|
|
1194
|
-
VALUE new_rule = rb_struct_new(cRule,
|
|
1195
|
-
INT2FIX(rule_id_counter++),
|
|
1196
|
-
selector,
|
|
1197
|
-
merged_decls,
|
|
1198
|
-
Qnil, // specificity
|
|
1199
|
-
Qnil, // parent_rule_id
|
|
1200
|
-
Qnil // nesting_style
|
|
1201
|
-
);
|
|
1202
|
-
rb_ary_push(merged_rules, new_rule);
|
|
1203
|
-
}
|
|
1233
|
+
// Iterate through each selector group using rb_hash_foreach
|
|
1234
|
+
// to avoid rb_funcall in hot path
|
|
1235
|
+
struct flatten_selectors_context merge_ctx;
|
|
1236
|
+
merge_ctx.merged_rules = merged_rules;
|
|
1237
|
+
merge_ctx.rules_array = rules_array;
|
|
1238
|
+
merge_ctx.rule_id_counter = &rule_id_counter;
|
|
1239
|
+
merge_ctx.selector_index = 0;
|
|
1240
|
+
merge_ctx.total_selectors = RHASH_SIZE(selector_groups);
|
|
1241
|
+
|
|
1242
|
+
DEBUG_PRINTF("\n=== Processing %ld selector groups ===\n", merge_ctx.total_selectors);
|
|
1243
|
+
|
|
1244
|
+
rb_hash_foreach(selector_groups, flatten_selector_group_callback, (VALUE)&merge_ctx);
|
|
1204
1245
|
|
|
1205
1246
|
// Add passthrough AtRules to output (preserve @keyframes, @font-face, etc.)
|
|
1206
1247
|
long num_passthrough = RARRAY_LEN(passthrough_rules);
|
|
@@ -1240,6 +1281,9 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1240
1281
|
VALUE first_selector_value = Qnil;
|
|
1241
1282
|
int all_same_selector = 1;
|
|
1242
1283
|
|
|
1284
|
+
// Track source order for cascade rules
|
|
1285
|
+
long source_order = 0;
|
|
1286
|
+
|
|
1243
1287
|
// Iterate through each rule
|
|
1244
1288
|
for (long i = 0; i < num_rules; i++) {
|
|
1245
1289
|
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
@@ -1311,45 +1355,59 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1311
1355
|
const char *prop_str = StringValueCStr(property);
|
|
1312
1356
|
VALUE expanded = Qnil;
|
|
1313
1357
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1358
|
+
// Early exit: shorthand properties only start with m, p, b, f, or l
|
|
1359
|
+
char first_char = prop_str[0];
|
|
1360
|
+
if (first_char == 'm' || first_char == 'p' || first_char == 'b' ||
|
|
1361
|
+
first_char == 'f' || first_char == 'l') {
|
|
1362
|
+
// Potentially a shorthand - check specific property names
|
|
1363
|
+
if (strcmp(prop_str, "margin") == 0) {
|
|
1364
|
+
expanded = cataract_expand_margin(Qnil, value);
|
|
1365
|
+
} else if (strcmp(prop_str, "padding") == 0) {
|
|
1366
|
+
expanded = cataract_expand_padding(Qnil, value);
|
|
1367
|
+
} else if (strcmp(prop_str, "border") == 0) {
|
|
1368
|
+
expanded = cataract_expand_border(Qnil, value);
|
|
1369
|
+
} else if (strcmp(prop_str, "border-color") == 0) {
|
|
1370
|
+
expanded = cataract_expand_border_color(Qnil, value);
|
|
1371
|
+
} else if (strcmp(prop_str, "border-style") == 0) {
|
|
1372
|
+
expanded = cataract_expand_border_style(Qnil, value);
|
|
1373
|
+
} else if (strcmp(prop_str, "border-width") == 0) {
|
|
1374
|
+
expanded = cataract_expand_border_width(Qnil, value);
|
|
1375
|
+
} else if (strcmp(prop_str, "border-top") == 0) {
|
|
1376
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("top"), value);
|
|
1377
|
+
} else if (strcmp(prop_str, "border-right") == 0) {
|
|
1378
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("right"), value);
|
|
1379
|
+
} else if (strcmp(prop_str, "border-bottom") == 0) {
|
|
1380
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("bottom"), value);
|
|
1381
|
+
} else if (strcmp(prop_str, "border-left") == 0) {
|
|
1382
|
+
expanded = cataract_expand_border_side(Qnil, USASCII_STR("left"), value);
|
|
1383
|
+
} else if (strcmp(prop_str, "font") == 0) {
|
|
1384
|
+
expanded = cataract_expand_font(Qnil, value);
|
|
1385
|
+
} else if (strcmp(prop_str, "list-style") == 0) {
|
|
1386
|
+
expanded = cataract_expand_list_style(Qnil, value);
|
|
1387
|
+
} else if (strcmp(prop_str, "background") == 0) {
|
|
1388
|
+
expanded = cataract_expand_background(Qnil, value);
|
|
1389
|
+
}
|
|
1340
1390
|
}
|
|
1391
|
+
// If first_char doesn't match, expanded stays Qnil
|
|
1341
1392
|
|
|
1342
|
-
// If property was expanded, iterate and apply cascade
|
|
1393
|
+
// If property was expanded, iterate array and apply cascade
|
|
1343
1394
|
// Expansion is rare (most properties are not shorthands)
|
|
1344
1395
|
if (!NIL_P(expanded)) {
|
|
1345
|
-
Check_Type(expanded,
|
|
1396
|
+
Check_Type(expanded, T_ARRAY);
|
|
1346
1397
|
|
|
1347
1398
|
struct expand_context ctx;
|
|
1348
1399
|
ctx.properties_hash = properties_hash;
|
|
1400
|
+
ctx.source_order = source_order;
|
|
1349
1401
|
ctx.specificity = specificity;
|
|
1350
1402
|
ctx.important = important;
|
|
1351
1403
|
|
|
1352
|
-
|
|
1404
|
+
long expanded_len = RARRAY_LEN(expanded);
|
|
1405
|
+
for (long i = 0; i < expanded_len; i++) {
|
|
1406
|
+
VALUE decl = rb_ary_entry(expanded, i);
|
|
1407
|
+
VALUE prop = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
1408
|
+
VALUE val = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
1409
|
+
flatten_expanded_callback(prop, val, (VALUE)&ctx);
|
|
1410
|
+
}
|
|
1353
1411
|
|
|
1354
1412
|
RB_GC_GUARD(expanded);
|
|
1355
1413
|
continue; // Skip processing the original shorthand property
|
|
@@ -1361,47 +1419,51 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1361
1419
|
// In merge scenarios, properties often collide (same property in multiple rules)
|
|
1362
1420
|
// so existing property is the common case
|
|
1363
1421
|
if (NIL_P(existing)) {
|
|
1364
|
-
// New property - add it
|
|
1365
|
-
VALUE prop_data =
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1422
|
+
// New property - add it as array: [source_order, specificity, important, value]
|
|
1423
|
+
VALUE prop_data = rb_ary_new_capa(4);
|
|
1424
|
+
rb_ary_push(prop_data, LONG2NUM(source_order));
|
|
1425
|
+
rb_ary_push(prop_data, INT2NUM(specificity));
|
|
1426
|
+
rb_ary_push(prop_data, important);
|
|
1427
|
+
rb_ary_push(prop_data, value);
|
|
1370
1428
|
rb_hash_aset(properties_hash, property, prop_data);
|
|
1371
1429
|
} else {
|
|
1372
1430
|
// Property exists - check cascade rules
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
int existing_spec_int = NUM2INT(existing_spec);
|
|
1431
|
+
long existing_order = NUM2LONG(RARRAY_AREF(existing, PROP_SOURCE_ORDER));
|
|
1432
|
+
int existing_spec_int = NUM2INT(RARRAY_AREF(existing, PROP_SPECIFICITY));
|
|
1433
|
+
VALUE existing_important = RARRAY_AREF(existing, PROP_IMPORTANT);
|
|
1377
1434
|
int existing_is_important = RTEST(existing_important);
|
|
1378
1435
|
|
|
1379
1436
|
int should_replace = 0;
|
|
1380
1437
|
|
|
1381
1438
|
// Most declarations are NOT !important
|
|
1382
1439
|
if (is_important) {
|
|
1383
|
-
// New is !important - wins if existing is NOT important OR
|
|
1384
|
-
if (!existing_is_important || existing_spec_int
|
|
1440
|
+
// New is !important - wins if existing is NOT important OR higher specificity OR (equal specificity AND later order)
|
|
1441
|
+
if (!existing_is_important || existing_spec_int < specificity ||
|
|
1442
|
+
(existing_spec_int == specificity && existing_order <= source_order)) {
|
|
1385
1443
|
should_replace = 1;
|
|
1386
1444
|
}
|
|
1387
1445
|
} else {
|
|
1388
|
-
// New is NOT important - only wins if existing is also NOT important AND
|
|
1389
|
-
if (!existing_is_important &&
|
|
1446
|
+
// New is NOT important - only wins if existing is also NOT important AND (higher specificity OR equal specificity with later order)
|
|
1447
|
+
if (!existing_is_important &&
|
|
1448
|
+
(existing_spec_int < specificity ||
|
|
1449
|
+
(existing_spec_int == specificity && existing_order <= source_order))) {
|
|
1390
1450
|
should_replace = 1;
|
|
1391
1451
|
}
|
|
1392
1452
|
}
|
|
1393
1453
|
|
|
1394
1454
|
// Replacement is common in merge scenarios
|
|
1395
1455
|
if (should_replace) {
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1456
|
+
RARRAY_ASET(existing, PROP_SOURCE_ORDER, LONG2NUM(source_order));
|
|
1457
|
+
RARRAY_ASET(existing, PROP_SPECIFICITY, INT2NUM(specificity));
|
|
1458
|
+
RARRAY_ASET(existing, PROP_IMPORTANT, important);
|
|
1459
|
+
RARRAY_ASET(existing, PROP_VALUE, value);
|
|
1399
1460
|
}
|
|
1400
1461
|
}
|
|
1401
1462
|
|
|
1402
1463
|
RB_GC_GUARD(property);
|
|
1403
1464
|
RB_GC_GUARD(value);
|
|
1404
1465
|
RB_GC_GUARD(decl);
|
|
1466
|
+
source_order++;
|
|
1405
1467
|
}
|
|
1406
1468
|
|
|
1407
1469
|
RB_GC_GUARD(selector);
|
|
@@ -1447,7 +1509,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1447
1509
|
VALUE border_data_src = !NIL_P(border_width) ? GET_PROP_DATA_STR(properties_hash, str_border_width) :
|
|
1448
1510
|
!NIL_P(border_style) ? GET_PROP_DATA_STR(properties_hash, str_border_style) :
|
|
1449
1511
|
GET_PROP_DATA_STR(properties_hash, str_border_color);
|
|
1450
|
-
VALUE border_important =
|
|
1512
|
+
VALUE border_important = RARRAY_AREF(border_data_src, PROP_IMPORTANT);
|
|
1451
1513
|
int border_is_important = RTEST(border_important);
|
|
1452
1514
|
|
|
1453
1515
|
// Check that all present properties have the same !important flag
|
|
@@ -1463,12 +1525,11 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1463
1525
|
|
|
1464
1526
|
VALUE border_shorthand = cataract_create_border_shorthand(Qnil, border_props);
|
|
1465
1527
|
if (!NIL_P(border_shorthand)) {
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
rb_hash_aset(border_data, ID2SYM(id_important), border_important);
|
|
1528
|
+
VALUE border_data = rb_ary_new_capa(4);
|
|
1529
|
+
rb_ary_push(border_data, RARRAY_AREF(border_data_src, PROP_SOURCE_ORDER));
|
|
1530
|
+
rb_ary_push(border_data, RARRAY_AREF(border_data_src, PROP_SPECIFICITY));
|
|
1531
|
+
rb_ary_push(border_data, border_important);
|
|
1532
|
+
rb_ary_push(border_data, border_shorthand);
|
|
1472
1533
|
rb_hash_aset(properties_hash, str_border, border_data);
|
|
1473
1534
|
|
|
1474
1535
|
if (!NIL_P(border_width)) rb_hash_delete(properties_hash, str_border_width);
|
|
@@ -1493,7 +1554,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1493
1554
|
|
|
1494
1555
|
// Get metadata from font-size as reference
|
|
1495
1556
|
VALUE size_data = GET_PROP_DATA_STR(properties_hash, str_font_size);
|
|
1496
|
-
VALUE font_important =
|
|
1557
|
+
VALUE font_important = RARRAY_AREF(size_data, PROP_IMPORTANT);
|
|
1497
1558
|
int font_is_important = RTEST(font_important);
|
|
1498
1559
|
|
|
1499
1560
|
// Check that all present properties have the same !important flag
|
|
@@ -1514,12 +1575,11 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1514
1575
|
|
|
1515
1576
|
VALUE font_shorthand = cataract_create_font_shorthand(Qnil, font_props);
|
|
1516
1577
|
if (!NIL_P(font_shorthand)) {
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
rb_hash_aset(font_data, ID2SYM(id_important), font_important);
|
|
1578
|
+
VALUE font_data = rb_ary_new_capa(4);
|
|
1579
|
+
rb_ary_push(font_data, RARRAY_AREF(size_data, PROP_SOURCE_ORDER));
|
|
1580
|
+
rb_ary_push(font_data, RARRAY_AREF(size_data, PROP_SPECIFICITY));
|
|
1581
|
+
rb_ary_push(font_data, font_important);
|
|
1582
|
+
rb_ary_push(font_data, font_shorthand);
|
|
1523
1583
|
rb_hash_aset(properties_hash, str_font, font_data);
|
|
1524
1584
|
|
|
1525
1585
|
// Remove longhand properties
|
|
@@ -1550,7 +1610,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1550
1610
|
VALUE list_style_data_src = !NIL_P(list_style_type) ? GET_PROP_DATA_STR(properties_hash, str_list_style_type) :
|
|
1551
1611
|
!NIL_P(list_style_position) ? GET_PROP_DATA_STR(properties_hash, str_list_style_position) :
|
|
1552
1612
|
GET_PROP_DATA_STR(properties_hash, str_list_style_image);
|
|
1553
|
-
VALUE list_style_important =
|
|
1613
|
+
VALUE list_style_important = RARRAY_AREF(list_style_data_src, PROP_IMPORTANT);
|
|
1554
1614
|
int list_style_is_important = RTEST(list_style_important);
|
|
1555
1615
|
|
|
1556
1616
|
// Check that all present properties have the same !important flag
|
|
@@ -1566,12 +1626,11 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1566
1626
|
|
|
1567
1627
|
VALUE list_style_shorthand = cataract_create_list_style_shorthand(Qnil, list_style_props);
|
|
1568
1628
|
if (!NIL_P(list_style_shorthand)) {
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
rb_hash_aset(list_style_data, ID2SYM(id_important), list_style_important);
|
|
1629
|
+
VALUE list_style_data = rb_ary_new_capa(4);
|
|
1630
|
+
rb_ary_push(list_style_data, RARRAY_AREF(list_style_data_src, PROP_SOURCE_ORDER));
|
|
1631
|
+
rb_ary_push(list_style_data, RARRAY_AREF(list_style_data_src, PROP_SPECIFICITY));
|
|
1632
|
+
rb_ary_push(list_style_data, list_style_important);
|
|
1633
|
+
rb_ary_push(list_style_data, list_style_shorthand);
|
|
1575
1634
|
rb_hash_aset(properties_hash, str_list_style, list_style_data);
|
|
1576
1635
|
|
|
1577
1636
|
// Remove longhand properties
|
|
@@ -1605,7 +1664,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1605
1664
|
!NIL_P(background_repeat) ? GET_PROP_DATA_STR(properties_hash, str_background_repeat) :
|
|
1606
1665
|
!NIL_P(background_attachment) ? GET_PROP_DATA_STR(properties_hash, str_background_attachment) :
|
|
1607
1666
|
GET_PROP_DATA_STR(properties_hash, str_background_position);
|
|
1608
|
-
VALUE background_important =
|
|
1667
|
+
VALUE background_important = RARRAY_AREF(background_data_src, PROP_IMPORTANT);
|
|
1609
1668
|
int background_is_important = RTEST(background_important);
|
|
1610
1669
|
|
|
1611
1670
|
// Check that all present properties have the same !important flag
|
|
@@ -1625,12 +1684,11 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1625
1684
|
|
|
1626
1685
|
VALUE background_shorthand = cataract_create_background_shorthand(Qnil, background_props);
|
|
1627
1686
|
if (!NIL_P(background_shorthand)) {
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
rb_hash_aset(background_data, ID2SYM(id_important), background_important);
|
|
1687
|
+
VALUE background_data = rb_ary_new_capa(4);
|
|
1688
|
+
rb_ary_push(background_data, RARRAY_AREF(background_data_src, PROP_SOURCE_ORDER));
|
|
1689
|
+
rb_ary_push(background_data, RARRAY_AREF(background_data_src, PROP_SPECIFICITY));
|
|
1690
|
+
rb_ary_push(background_data, background_important);
|
|
1691
|
+
rb_ary_push(background_data, background_shorthand);
|
|
1634
1692
|
rb_hash_aset(properties_hash, str_background, background_data);
|
|
1635
1693
|
|
|
1636
1694
|
// Remove longhand properties
|
|
@@ -1650,7 +1708,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1650
1708
|
|
|
1651
1709
|
// Build merged declarations array
|
|
1652
1710
|
VALUE merged_declarations = rb_ary_new();
|
|
1653
|
-
rb_hash_foreach(properties_hash,
|
|
1711
|
+
rb_hash_foreach(properties_hash, flatten_build_result_callback, merged_declarations);
|
|
1654
1712
|
|
|
1655
1713
|
// Determine final selector (allocate only once at the end)
|
|
1656
1714
|
VALUE final_selector;
|