cataract 0.1.4 → 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 +18 -1
- data/.rubocop.yml +36 -6
- data/.rubocop_todo.yml +7 -7
- data/BENCHMARKS.md +30 -30
- data/CHANGELOG.md +10 -0
- data/RAGEL_MIGRATION.md +2 -2
- data/README.md +7 -2
- data/Rakefile +24 -11
- data/cataract.gemspec +1 -1
- data/ext/cataract/cataract.c +12 -3
- data/ext/cataract/cataract.h +5 -3
- data/ext/cataract/css_parser.c +156 -32
- data/ext/cataract/extconf.rb +2 -2
- data/ext/cataract/{merge.c → flatten.c} +520 -468
- data/ext/cataract/shorthand_expander.c +164 -115
- data/lib/cataract/import_resolver.rb +60 -39
- data/lib/cataract/import_statement.rb +49 -0
- data/lib/cataract/pure/{merge.rb → flatten.rb} +39 -40
- data/lib/cataract/pure/imports.rb +13 -0
- data/lib/cataract/pure/parser.rb +108 -4
- data/lib/cataract/pure.rb +32 -9
- data/lib/cataract/rule.rb +51 -6
- data/lib/cataract/stylesheet.rb +343 -41
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +28 -24
- metadata +4 -3
|
@@ -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"));
|
|
@@ -950,18 +1007,18 @@ static VALUE merge_rules_for_selector(VALUE rules_array, VALUE rule_indices, VAL
|
|
|
950
1007
|
// The output order is roughly source order but may vary when properties are
|
|
951
1008
|
// overridden by later rules with higher specificity or importance.
|
|
952
1009
|
VALUE merged_decls = rb_ary_new();
|
|
953
|
-
rb_hash_foreach(properties_hash,
|
|
1010
|
+
rb_hash_foreach(properties_hash, flatten_build_result_callback, merged_decls);
|
|
954
1011
|
|
|
955
|
-
DEBUG_PRINTF(" [
|
|
1012
|
+
DEBUG_PRINTF(" [flatten_rules_for_selector] Result: %ld merged declarations\n",
|
|
956
1013
|
RARRAY_LEN(merged_decls));
|
|
957
1014
|
|
|
958
1015
|
return merged_decls;
|
|
959
1016
|
}
|
|
960
1017
|
|
|
961
|
-
//
|
|
1018
|
+
// Flatten CSS rules by applying cascade rules
|
|
962
1019
|
// Input: Stylesheet object or CSS string
|
|
963
|
-
// Output: Stylesheet with
|
|
964
|
-
VALUE
|
|
1020
|
+
// Output: Stylesheet with flattened declarations (cascade applied)
|
|
1021
|
+
VALUE cataract_flatten(VALUE self, VALUE input) {
|
|
965
1022
|
VALUE rules_array;
|
|
966
1023
|
|
|
967
1024
|
// Handle different input types
|
|
@@ -987,14 +1044,6 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
987
1044
|
has_nesting = RTEST(has_nesting_ivar);
|
|
988
1045
|
}
|
|
989
1046
|
|
|
990
|
-
// Initialize cached symbol IDs on first call (thread-safe since GVL is held)
|
|
991
|
-
// This only happens once, so unlikely
|
|
992
|
-
if (id_value == 0) {
|
|
993
|
-
id_value = rb_intern("value");
|
|
994
|
-
id_specificity = rb_intern("specificity");
|
|
995
|
-
id_important = rb_intern("important");
|
|
996
|
-
}
|
|
997
|
-
|
|
998
1047
|
long num_rules = RARRAY_LEN(rules_array);
|
|
999
1048
|
// Empty stylesheets are rare
|
|
1000
1049
|
if (num_rules == 0) {
|
|
@@ -1005,13 +1054,13 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1005
1054
|
|
|
1006
1055
|
/*
|
|
1007
1056
|
* ============================================================================
|
|
1008
|
-
*
|
|
1057
|
+
* FLATTEN ALGORITHM - Rules and Implementation Notes
|
|
1009
1058
|
* ============================================================================
|
|
1010
1059
|
*
|
|
1011
|
-
* CORE PRINCIPLE: Group rules by selector,
|
|
1060
|
+
* CORE PRINCIPLE: Group rules by selector, flatten declarations within each group
|
|
1012
1061
|
*
|
|
1013
1062
|
* Different selectors (.test vs #test) target different elements and must stay separate.
|
|
1014
|
-
* Same selectors should
|
|
1063
|
+
* Same selectors should flatten into one rule to reduce output size.
|
|
1015
1064
|
*
|
|
1016
1065
|
* ALGORITHM STEPS:
|
|
1017
1066
|
* 1. Group rules by selector (.test, #test, etc.)
|
|
@@ -1031,7 +1080,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1031
1080
|
* This ensures declarations within the same rule maintain relative order.
|
|
1032
1081
|
*
|
|
1033
1082
|
* SHORTHAND EXPANSION:
|
|
1034
|
-
* When
|
|
1083
|
+
* When flattening, all shorthands must be expanded to longhands first.
|
|
1035
1084
|
* Example: "background: blue" expands to:
|
|
1036
1085
|
* - background-color: blue
|
|
1037
1086
|
* - background-image: none
|
|
@@ -1061,9 +1110,9 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1061
1110
|
* (The "none", "repeat", etc. are just defaults from expansion)
|
|
1062
1111
|
*
|
|
1063
1112
|
* EDGE CASES:
|
|
1064
|
-
* - Empty rules (no declarations): Skip during
|
|
1113
|
+
* - Empty rules (no declarations): Skip during flatten
|
|
1065
1114
|
* - Nested CSS: Parent rules with children are containers only, skip their declarations
|
|
1066
|
-
* - Mixed !important: Properties with different importance cannot
|
|
1115
|
+
* - Mixed !important: Properties with different importance cannot flatten into shorthand
|
|
1067
1116
|
* - Single property: Don't create shorthand (e.g., background-color alone stays as-is)
|
|
1068
1117
|
* Reason: "background: blue" resets all other background properties to defaults,
|
|
1069
1118
|
* which is semantically different from just setting background-color.
|
|
@@ -1071,16 +1120,16 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1071
1120
|
* PERFORMANCE NOTES:
|
|
1072
1121
|
* - Use cached static strings (VALUE) for property names (no allocation)
|
|
1073
1122
|
* - Group by selector in single pass (O(n) hash building)
|
|
1074
|
-
* -
|
|
1123
|
+
* - Flatten within groups (O(n*m) where m is avg declarations per rule)
|
|
1075
1124
|
* ============================================================================
|
|
1076
1125
|
*/
|
|
1077
1126
|
|
|
1078
1127
|
// For nested CSS: identify parent rules (rules that have children)
|
|
1079
|
-
// These should be skipped during
|
|
1128
|
+
// These should be skipped during flatten, even if they have declarations
|
|
1080
1129
|
// Use Ruby hash as a set: parent_id => true
|
|
1081
1130
|
VALUE parent_ids = Qnil;
|
|
1082
1131
|
if (has_nesting) {
|
|
1083
|
-
DEBUG_PRINTF("\n===
|
|
1132
|
+
DEBUG_PRINTF("\n=== FLATTEN: has_nesting=true, num_rules=%ld ===\n", num_rules);
|
|
1084
1133
|
parent_ids = rb_hash_new();
|
|
1085
1134
|
for (long i = 0; i < num_rules; i++) {
|
|
1086
1135
|
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
@@ -1097,7 +1146,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1097
1146
|
}
|
|
1098
1147
|
}
|
|
1099
1148
|
|
|
1100
|
-
// ALWAYS build selector groups - this is the core of
|
|
1149
|
+
// ALWAYS build selector groups - this is the core of flatten logic
|
|
1101
1150
|
// Group rules by selector: different selectors stay separate
|
|
1102
1151
|
// selector => [rule indices]
|
|
1103
1152
|
DEBUG_PRINTF("\n=== Building selector groups (has_nesting=%d) ===\n", has_nesting);
|
|
@@ -1181,32 +1230,18 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1181
1230
|
VALUE merged_rules = rb_ary_new();
|
|
1182
1231
|
int rule_id_counter = 0;
|
|
1183
1232
|
|
|
1184
|
-
// Iterate through each selector group
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
// Merge all rules in this selector group
|
|
1197
|
-
VALUE merged_decls = merge_rules_for_selector(rules_array, group_indices, selector);
|
|
1198
|
-
|
|
1199
|
-
// Create new rule with this selector and merged declarations
|
|
1200
|
-
VALUE new_rule = rb_struct_new(cRule,
|
|
1201
|
-
INT2FIX(rule_id_counter++),
|
|
1202
|
-
selector,
|
|
1203
|
-
merged_decls,
|
|
1204
|
-
Qnil, // specificity
|
|
1205
|
-
Qnil, // parent_rule_id
|
|
1206
|
-
Qnil // nesting_style
|
|
1207
|
-
);
|
|
1208
|
-
rb_ary_push(merged_rules, new_rule);
|
|
1209
|
-
}
|
|
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);
|
|
1210
1245
|
|
|
1211
1246
|
// Add passthrough AtRules to output (preserve @keyframes, @font-face, etc.)
|
|
1212
1247
|
long num_passthrough = RARRAY_LEN(passthrough_rules);
|
|
@@ -1246,6 +1281,9 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1246
1281
|
VALUE first_selector_value = Qnil;
|
|
1247
1282
|
int all_same_selector = 1;
|
|
1248
1283
|
|
|
1284
|
+
// Track source order for cascade rules
|
|
1285
|
+
long source_order = 0;
|
|
1286
|
+
|
|
1249
1287
|
// Iterate through each rule
|
|
1250
1288
|
for (long i = 0; i < num_rules; i++) {
|
|
1251
1289
|
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
@@ -1317,45 +1355,59 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1317
1355
|
const char *prop_str = StringValueCStr(property);
|
|
1318
1356
|
VALUE expanded = Qnil;
|
|
1319
1357
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
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
|
+
}
|
|
1346
1390
|
}
|
|
1391
|
+
// If first_char doesn't match, expanded stays Qnil
|
|
1347
1392
|
|
|
1348
|
-
// If property was expanded, iterate and apply cascade
|
|
1393
|
+
// If property was expanded, iterate array and apply cascade
|
|
1349
1394
|
// Expansion is rare (most properties are not shorthands)
|
|
1350
1395
|
if (!NIL_P(expanded)) {
|
|
1351
|
-
Check_Type(expanded,
|
|
1396
|
+
Check_Type(expanded, T_ARRAY);
|
|
1352
1397
|
|
|
1353
1398
|
struct expand_context ctx;
|
|
1354
1399
|
ctx.properties_hash = properties_hash;
|
|
1400
|
+
ctx.source_order = source_order;
|
|
1355
1401
|
ctx.specificity = specificity;
|
|
1356
1402
|
ctx.important = important;
|
|
1357
1403
|
|
|
1358
|
-
|
|
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
|
+
}
|
|
1359
1411
|
|
|
1360
1412
|
RB_GC_GUARD(expanded);
|
|
1361
1413
|
continue; // Skip processing the original shorthand property
|
|
@@ -1367,47 +1419,51 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1367
1419
|
// In merge scenarios, properties often collide (same property in multiple rules)
|
|
1368
1420
|
// so existing property is the common case
|
|
1369
1421
|
if (NIL_P(existing)) {
|
|
1370
|
-
// New property - add it
|
|
1371
|
-
VALUE prop_data =
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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);
|
|
1376
1428
|
rb_hash_aset(properties_hash, property, prop_data);
|
|
1377
1429
|
} else {
|
|
1378
1430
|
// Property exists - check cascade rules
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
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);
|
|
1383
1434
|
int existing_is_important = RTEST(existing_important);
|
|
1384
1435
|
|
|
1385
1436
|
int should_replace = 0;
|
|
1386
1437
|
|
|
1387
1438
|
// Most declarations are NOT !important
|
|
1388
1439
|
if (is_important) {
|
|
1389
|
-
// New is !important - wins if existing is NOT important OR
|
|
1390
|
-
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)) {
|
|
1391
1443
|
should_replace = 1;
|
|
1392
1444
|
}
|
|
1393
1445
|
} else {
|
|
1394
|
-
// New is NOT important - only wins if existing is also NOT important AND
|
|
1395
|
-
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))) {
|
|
1396
1450
|
should_replace = 1;
|
|
1397
1451
|
}
|
|
1398
1452
|
}
|
|
1399
1453
|
|
|
1400
1454
|
// Replacement is common in merge scenarios
|
|
1401
1455
|
if (should_replace) {
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
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);
|
|
1405
1460
|
}
|
|
1406
1461
|
}
|
|
1407
1462
|
|
|
1408
1463
|
RB_GC_GUARD(property);
|
|
1409
1464
|
RB_GC_GUARD(value);
|
|
1410
1465
|
RB_GC_GUARD(decl);
|
|
1466
|
+
source_order++;
|
|
1411
1467
|
}
|
|
1412
1468
|
|
|
1413
1469
|
RB_GC_GUARD(selector);
|
|
@@ -1453,7 +1509,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1453
1509
|
VALUE border_data_src = !NIL_P(border_width) ? GET_PROP_DATA_STR(properties_hash, str_border_width) :
|
|
1454
1510
|
!NIL_P(border_style) ? GET_PROP_DATA_STR(properties_hash, str_border_style) :
|
|
1455
1511
|
GET_PROP_DATA_STR(properties_hash, str_border_color);
|
|
1456
|
-
VALUE border_important =
|
|
1512
|
+
VALUE border_important = RARRAY_AREF(border_data_src, PROP_IMPORTANT);
|
|
1457
1513
|
int border_is_important = RTEST(border_important);
|
|
1458
1514
|
|
|
1459
1515
|
// Check that all present properties have the same !important flag
|
|
@@ -1469,12 +1525,11 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1469
1525
|
|
|
1470
1526
|
VALUE border_shorthand = cataract_create_border_shorthand(Qnil, border_props);
|
|
1471
1527
|
if (!NIL_P(border_shorthand)) {
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
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);
|
|
1478
1533
|
rb_hash_aset(properties_hash, str_border, border_data);
|
|
1479
1534
|
|
|
1480
1535
|
if (!NIL_P(border_width)) rb_hash_delete(properties_hash, str_border_width);
|
|
@@ -1499,7 +1554,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1499
1554
|
|
|
1500
1555
|
// Get metadata from font-size as reference
|
|
1501
1556
|
VALUE size_data = GET_PROP_DATA_STR(properties_hash, str_font_size);
|
|
1502
|
-
VALUE font_important =
|
|
1557
|
+
VALUE font_important = RARRAY_AREF(size_data, PROP_IMPORTANT);
|
|
1503
1558
|
int font_is_important = RTEST(font_important);
|
|
1504
1559
|
|
|
1505
1560
|
// Check that all present properties have the same !important flag
|
|
@@ -1520,12 +1575,11 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1520
1575
|
|
|
1521
1576
|
VALUE font_shorthand = cataract_create_font_shorthand(Qnil, font_props);
|
|
1522
1577
|
if (!NIL_P(font_shorthand)) {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
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);
|
|
1529
1583
|
rb_hash_aset(properties_hash, str_font, font_data);
|
|
1530
1584
|
|
|
1531
1585
|
// Remove longhand properties
|
|
@@ -1556,7 +1610,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1556
1610
|
VALUE list_style_data_src = !NIL_P(list_style_type) ? GET_PROP_DATA_STR(properties_hash, str_list_style_type) :
|
|
1557
1611
|
!NIL_P(list_style_position) ? GET_PROP_DATA_STR(properties_hash, str_list_style_position) :
|
|
1558
1612
|
GET_PROP_DATA_STR(properties_hash, str_list_style_image);
|
|
1559
|
-
VALUE list_style_important =
|
|
1613
|
+
VALUE list_style_important = RARRAY_AREF(list_style_data_src, PROP_IMPORTANT);
|
|
1560
1614
|
int list_style_is_important = RTEST(list_style_important);
|
|
1561
1615
|
|
|
1562
1616
|
// Check that all present properties have the same !important flag
|
|
@@ -1572,12 +1626,11 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1572
1626
|
|
|
1573
1627
|
VALUE list_style_shorthand = cataract_create_list_style_shorthand(Qnil, list_style_props);
|
|
1574
1628
|
if (!NIL_P(list_style_shorthand)) {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
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);
|
|
1581
1634
|
rb_hash_aset(properties_hash, str_list_style, list_style_data);
|
|
1582
1635
|
|
|
1583
1636
|
// Remove longhand properties
|
|
@@ -1611,7 +1664,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1611
1664
|
!NIL_P(background_repeat) ? GET_PROP_DATA_STR(properties_hash, str_background_repeat) :
|
|
1612
1665
|
!NIL_P(background_attachment) ? GET_PROP_DATA_STR(properties_hash, str_background_attachment) :
|
|
1613
1666
|
GET_PROP_DATA_STR(properties_hash, str_background_position);
|
|
1614
|
-
VALUE background_important =
|
|
1667
|
+
VALUE background_important = RARRAY_AREF(background_data_src, PROP_IMPORTANT);
|
|
1615
1668
|
int background_is_important = RTEST(background_important);
|
|
1616
1669
|
|
|
1617
1670
|
// Check that all present properties have the same !important flag
|
|
@@ -1631,12 +1684,11 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1631
1684
|
|
|
1632
1685
|
VALUE background_shorthand = cataract_create_background_shorthand(Qnil, background_props);
|
|
1633
1686
|
if (!NIL_P(background_shorthand)) {
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
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);
|
|
1640
1692
|
rb_hash_aset(properties_hash, str_background, background_data);
|
|
1641
1693
|
|
|
1642
1694
|
// Remove longhand properties
|
|
@@ -1656,7 +1708,7 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
1656
1708
|
|
|
1657
1709
|
// Build merged declarations array
|
|
1658
1710
|
VALUE merged_declarations = rb_ary_new();
|
|
1659
|
-
rb_hash_foreach(properties_hash,
|
|
1711
|
+
rb_hash_foreach(properties_hash, flatten_build_result_callback, merged_declarations);
|
|
1660
1712
|
|
|
1661
1713
|
// Determine final selector (allocate only once at the end)
|
|
1662
1714
|
VALUE final_selector;
|