cataract 0.2.4 → 0.3.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/test.yml +6 -6
- data/.gitignore +17 -3
- data/.rubocop.yml +1 -0
- data/BENCHMARKS.md +24 -6
- data/CHANGELOG.md +29 -1
- data/Gemfile +2 -2
- data/examples/css_analyzer/analyzer.rb +9 -3
- data/examples/css_analyzer/analyzers/base.rb +1 -1
- data/ext/cataract/cataract.c +251 -458
- data/ext/cataract/cataract.h +127 -16
- data/ext/cataract/css_parser.c +1029 -700
- data/ext/cataract/extconf.rb +1 -2
- data/ext/cataract/flatten.c +279 -991
- data/ext/cataract/shorthand_expander.c +80 -102
- data/ext/cataract_color/color_conversion.c +1 -1
- data/lib/cataract/declarations.rb +13 -7
- data/lib/cataract/error.rb +49 -0
- data/lib/cataract/import_resolver.rb +36 -16
- data/lib/cataract/pure/byte_constants.rb +4 -1
- data/lib/cataract/pure/flatten.rb +143 -145
- data/lib/cataract/pure/parser.rb +623 -388
- data/lib/cataract/pure/serializer.rb +109 -104
- data/lib/cataract/pure/specificity.rb +112 -156
- data/lib/cataract/pure.rb +1 -6
- data/lib/cataract/stylesheet.rb +480 -410
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +1 -0
- metadata +3 -13
- data/ext/cataract/import_scanner.c +0 -174
- data/ext/cataract_old/cataract.c +0 -393
- data/ext/cataract_old/cataract.h +0 -250
- data/ext/cataract_old/css_parser.c +0 -933
- data/ext/cataract_old/extconf.rb +0 -67
- data/ext/cataract_old/import_scanner.c +0 -174
- data/ext/cataract_old/merge.c +0 -776
- data/ext/cataract_old/shorthand_expander.c +0 -902
- data/ext/cataract_old/specificity.c +0 -213
- data/ext/cataract_old/stylesheet.c +0 -290
- data/ext/cataract_old/value_splitter.c +0 -116
data/ext/cataract/flatten.c
CHANGED
|
@@ -51,6 +51,49 @@ static struct shorthand_mapping SHORTHAND_MAPPINGS[] = {
|
|
|
51
51
|
{NULL, 0, Qnil, NULL, Qnil, NULL, Qnil, NULL, Qnil, NULL, Qnil, NULL} // Sentinel to mark end of array
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/*
|
|
55
|
+
* Family shorthand mapping: for shorthands whose longhand components aren't
|
|
56
|
+
* "all N required" like the 4-sided shorthands above, but a mix of required
|
|
57
|
+
* and optional candidates - border only requires border-style (width/color
|
|
58
|
+
* optional); font requires font-size and font-family (4 others optional);
|
|
59
|
+
* list-style and background don't require any specific candidate, just a
|
|
60
|
+
* minimum count of any 2 present.
|
|
61
|
+
*
|
|
62
|
+
* Candidates must be listed with any REQUIRED ones first: the recreation
|
|
63
|
+
* logic uses "first present candidate in this order" as the source of
|
|
64
|
+
* source_order/specificity/importance for the resulting shorthand, which is
|
|
65
|
+
* always the first required candidate when there is one (border-style for
|
|
66
|
+
* border, font-size for font) or the first present optional candidate
|
|
67
|
+
* otherwise (matching what list-style/background use) - see
|
|
68
|
+
* try_recreate_shorthand_family.
|
|
69
|
+
*/
|
|
70
|
+
#define MAX_SHORTHAND_FAMILY_CANDIDATES 6
|
|
71
|
+
|
|
72
|
+
struct shorthand_family_mapping {
|
|
73
|
+
VALUE shorthand_name_val; // Cached Ruby string for the shorthand property name
|
|
74
|
+
VALUE candidate_vals[MAX_SHORTHAND_FAMILY_CANDIDATES]; // Cached candidate longhand property names, populated in init_flatten_constants()
|
|
75
|
+
int num_candidates;
|
|
76
|
+
int num_required; // First `num_required` candidates are mandatory; 0 means "optional-only" (see min_present)
|
|
77
|
+
int min_present; // Only checked when num_required == 0: minimum count of ANY candidates that must be present
|
|
78
|
+
VALUE (*creator_func)(VALUE, VALUE);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Order matches the original inline recreation blocks (border, list-style,
|
|
82
|
+
// font, background) - properties_hash is a Ruby Hash, whose iteration order
|
|
83
|
+
// (and therefore the final declaration output order) follows insertion
|
|
84
|
+
// order, so this table's order is observable behavior, not just style.
|
|
85
|
+
static struct shorthand_family_mapping SHORTHAND_FAMILY_MAPPINGS[] = {
|
|
86
|
+
// border: only border-style is required; border-width/border-color are optional
|
|
87
|
+
{ Qnil, { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil }, 3, 1, 0, cataract_create_border_shorthand },
|
|
88
|
+
// list-style: none individually required, but at least 2 of the 3 must be present
|
|
89
|
+
{ Qnil, { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil }, 3, 0, 2, cataract_create_list_style_shorthand },
|
|
90
|
+
// font: font-size and font-family are both required; the rest are optional
|
|
91
|
+
{ Qnil, { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil }, 6, 2, 0, cataract_create_font_shorthand },
|
|
92
|
+
// background: none individually required, but at least 2 of the 5 must be present
|
|
93
|
+
{ Qnil, { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil }, 5, 0, 2, cataract_create_background_shorthand },
|
|
94
|
+
};
|
|
95
|
+
#define NUM_SHORTHAND_FAMILIES (sizeof(SHORTHAND_FAMILY_MAPPINGS) / sizeof(SHORTHAND_FAMILY_MAPPINGS[0]))
|
|
96
|
+
|
|
54
97
|
// Cached property name strings (frozen, never GC'd)
|
|
55
98
|
// Initialized in init_flatten_constants() at module load time
|
|
56
99
|
static VALUE str_margin = Qnil;
|
|
@@ -97,68 +140,6 @@ static VALUE str_background_repeat = Qnil;
|
|
|
97
140
|
static VALUE str_background_attachment = Qnil;
|
|
98
141
|
static VALUE str_background_position = Qnil;
|
|
99
142
|
|
|
100
|
-
// Context for expanded property iteration
|
|
101
|
-
struct expand_context {
|
|
102
|
-
VALUE properties_hash;
|
|
103
|
-
long source_order;
|
|
104
|
-
int specificity;
|
|
105
|
-
VALUE important;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// Callback for rb_hash_foreach - process expanded properties and apply cascade
|
|
109
|
-
static int flatten_expanded_callback(VALUE exp_prop, VALUE exp_value, VALUE ctx_val) {
|
|
110
|
-
struct expand_context *ctx = (struct expand_context *)ctx_val;
|
|
111
|
-
|
|
112
|
-
// Expanded properties from shorthand expanders are already lowercase
|
|
113
|
-
// No need to lowercase again
|
|
114
|
-
int is_important = RTEST(ctx->important);
|
|
115
|
-
|
|
116
|
-
// Apply cascade rules for expanded property
|
|
117
|
-
VALUE existing = rb_hash_aref(ctx->properties_hash, exp_prop);
|
|
118
|
-
|
|
119
|
-
if (NIL_P(existing)) {
|
|
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);
|
|
126
|
-
rb_hash_aset(ctx->properties_hash, exp_prop, prop_data);
|
|
127
|
-
} else {
|
|
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);
|
|
132
|
-
int existing_is_important = RTEST(existing_important);
|
|
133
|
-
|
|
134
|
-
int should_replace = 0;
|
|
135
|
-
if (is_important) {
|
|
136
|
-
if (!existing_is_important || existing_spec_int < ctx->specificity ||
|
|
137
|
-
(existing_spec_int == ctx->specificity && existing_order <= ctx->source_order)) {
|
|
138
|
-
should_replace = 1;
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
if (!existing_is_important &&
|
|
142
|
-
(existing_spec_int < ctx->specificity ||
|
|
143
|
-
(existing_spec_int == ctx->specificity && existing_order <= ctx->source_order))) {
|
|
144
|
-
should_replace = 1;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (should_replace) {
|
|
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);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
RB_GC_GUARD(exp_prop);
|
|
158
|
-
RB_GC_GUARD(exp_value);
|
|
159
|
-
return ST_CONTINUE;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
143
|
// Callback for rb_hash_foreach - builds result array from properties hash
|
|
163
144
|
static int flatten_build_result_callback(VALUE property, VALUE prop_data, VALUE result_ary) {
|
|
164
145
|
// Extract value and important flag from array: [source_order, specificity, important, value]
|
|
@@ -322,139 +303,34 @@ void init_flatten_constants(void) {
|
|
|
322
303
|
SHORTHAND_MAPPINGS[4].prop_right_val = str_border_right_color;
|
|
323
304
|
SHORTHAND_MAPPINGS[4].prop_bottom_val = str_border_bottom_color;
|
|
324
305
|
SHORTHAND_MAPPINGS[4].prop_left_val = str_border_left_color;
|
|
325
|
-
}
|
|
326
306
|
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
// 4. Add shorthand to properties_hash and remove longhands
|
|
355
|
-
// Note: Uses cached static strings (VALUE) for property names - no runtime allocation
|
|
356
|
-
#define TRY_CREATE_FOUR_SIDED_SHORTHAND(hash, str_top, str_right, str_bottom, str_left, str_shorthand, creator_func) \
|
|
357
|
-
do { \
|
|
358
|
-
VALUE _top = GET_PROP_VALUE_STR(hash, str_top); \
|
|
359
|
-
VALUE _right = GET_PROP_VALUE_STR(hash, str_right); \
|
|
360
|
-
VALUE _bottom = GET_PROP_VALUE_STR(hash, str_bottom); \
|
|
361
|
-
VALUE _left = GET_PROP_VALUE_STR(hash, str_left); \
|
|
362
|
-
\
|
|
363
|
-
if (!NIL_P(_top) && !NIL_P(_right) && !NIL_P(_bottom) && !NIL_P(_left)) { \
|
|
364
|
-
/* Check that all properties have the same !important flag */ \
|
|
365
|
-
VALUE _top_data = GET_PROP_DATA_STR(hash, str_top); \
|
|
366
|
-
VALUE _right_data = GET_PROP_DATA_STR(hash, str_right); \
|
|
367
|
-
VALUE _bottom_data = GET_PROP_DATA_STR(hash, str_bottom); \
|
|
368
|
-
VALUE _left_data = GET_PROP_DATA_STR(hash, str_left); \
|
|
369
|
-
\
|
|
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); \
|
|
374
|
-
\
|
|
375
|
-
int _top_is_imp = RTEST(_top_imp); \
|
|
376
|
-
int _right_is_imp = RTEST(_right_imp); \
|
|
377
|
-
int _bottom_is_imp = RTEST(_bottom_imp); \
|
|
378
|
-
int _left_is_imp = RTEST(_left_imp); \
|
|
379
|
-
\
|
|
380
|
-
/* Only create shorthand if all have same !important flag */ \
|
|
381
|
-
if (_top_is_imp == _right_is_imp && _top_is_imp == _bottom_is_imp && _top_is_imp == _left_is_imp) { \
|
|
382
|
-
VALUE _props = rb_hash_new(); \
|
|
383
|
-
rb_hash_aset(_props, str_top, _top); \
|
|
384
|
-
rb_hash_aset(_props, str_right, _right); \
|
|
385
|
-
rb_hash_aset(_props, str_bottom, _bottom); \
|
|
386
|
-
rb_hash_aset(_props, str_left, _left); \
|
|
387
|
-
\
|
|
388
|
-
VALUE _shorthand_value = creator_func(Qnil, _props); \
|
|
389
|
-
if (!NIL_P(_shorthand_value)) { \
|
|
390
|
-
long _source_order = NUM2LONG(RARRAY_AREF(_top_data, PROP_SOURCE_ORDER)); \
|
|
391
|
-
int _specificity = NUM2INT(RARRAY_AREF(_top_data, PROP_SPECIFICITY)); \
|
|
392
|
-
\
|
|
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); \
|
|
398
|
-
rb_hash_aset(hash, str_shorthand, _shorthand_data); \
|
|
399
|
-
\
|
|
400
|
-
rb_hash_delete(hash, str_top); \
|
|
401
|
-
rb_hash_delete(hash, str_right); \
|
|
402
|
-
rb_hash_delete(hash, str_bottom); \
|
|
403
|
-
rb_hash_delete(hash, str_left); \
|
|
404
|
-
\
|
|
405
|
-
RB_GC_GUARD(_shorthand_value); \
|
|
406
|
-
} \
|
|
407
|
-
RB_GC_GUARD(_props); \
|
|
408
|
-
} \
|
|
409
|
-
} \
|
|
410
|
-
} while(0)
|
|
411
|
-
|
|
412
|
-
// Helper macro: Recreate dimension shorthand (margin, padding, border-width)
|
|
413
|
-
// Takes a property prefix like "margin" and creates "margin" from margin-top/right/bottom/left
|
|
414
|
-
#define RECREATE_DIMENSION_SHORTHAND(hash, prefix, creator_func) \
|
|
415
|
-
do { \
|
|
416
|
-
char _top_name[64], _right_name[64], _bottom_name[64], _left_name[64]; \
|
|
417
|
-
snprintf(_top_name, sizeof(_top_name), "%s-top", prefix); \
|
|
418
|
-
snprintf(_right_name, sizeof(_right_name), "%s-right", prefix); \
|
|
419
|
-
snprintf(_bottom_name, sizeof(_bottom_name), "%s-bottom", prefix); \
|
|
420
|
-
snprintf(_left_name, sizeof(_left_name), "%s-left", prefix); \
|
|
421
|
-
\
|
|
422
|
-
VALUE _top_data = rb_hash_aref(hash, STR_NEW_CSTR(_top_name)); \
|
|
423
|
-
VALUE _right_data = rb_hash_aref(hash, STR_NEW_CSTR(_right_name)); \
|
|
424
|
-
VALUE _bottom_data = rb_hash_aref(hash, STR_NEW_CSTR(_bottom_name)); \
|
|
425
|
-
VALUE _left_data = rb_hash_aref(hash, STR_NEW_CSTR(_left_name)); \
|
|
426
|
-
\
|
|
427
|
-
if (!NIL_P(_top_data) && !NIL_P(_right_data) && !NIL_P(_bottom_data) && !NIL_P(_left_data)) { \
|
|
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); \
|
|
432
|
-
\
|
|
433
|
-
if (RTEST(_top_imp) == RTEST(_right_imp) && RTEST(_top_imp) == RTEST(_bottom_imp) && RTEST(_top_imp) == RTEST(_left_imp)) { \
|
|
434
|
-
VALUE _props = rb_hash_new(); \
|
|
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)); \
|
|
439
|
-
\
|
|
440
|
-
VALUE _shorthand_value = creator_func(Qnil, _props); \
|
|
441
|
-
if (!NIL_P(_shorthand_value)) { \
|
|
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); \
|
|
447
|
-
rb_hash_aset(hash, rb_usascii_str_new(prefix, strlen(prefix)), _shorthand_data); \
|
|
448
|
-
\
|
|
449
|
-
rb_hash_delete(hash, STR_NEW_CSTR(_top_name)); \
|
|
450
|
-
rb_hash_delete(hash, STR_NEW_CSTR(_right_name)); \
|
|
451
|
-
rb_hash_delete(hash, STR_NEW_CSTR(_bottom_name)); \
|
|
452
|
-
rb_hash_delete(hash, STR_NEW_CSTR(_left_name)); \
|
|
453
|
-
DEBUG_PRINTF(" -> Recreated %s shorthand\n", prefix); \
|
|
454
|
-
} \
|
|
455
|
-
} \
|
|
456
|
-
} \
|
|
457
|
-
} while(0)
|
|
307
|
+
// Populate the family shorthand mapping table. Candidate order matches
|
|
308
|
+
// what each original inline block checked (required candidates first).
|
|
309
|
+
SHORTHAND_FAMILY_MAPPINGS[0].shorthand_name_val = str_border;
|
|
310
|
+
SHORTHAND_FAMILY_MAPPINGS[0].candidate_vals[0] = str_border_style;
|
|
311
|
+
SHORTHAND_FAMILY_MAPPINGS[0].candidate_vals[1] = str_border_width;
|
|
312
|
+
SHORTHAND_FAMILY_MAPPINGS[0].candidate_vals[2] = str_border_color;
|
|
313
|
+
|
|
314
|
+
SHORTHAND_FAMILY_MAPPINGS[1].shorthand_name_val = str_list_style;
|
|
315
|
+
SHORTHAND_FAMILY_MAPPINGS[1].candidate_vals[0] = str_list_style_type;
|
|
316
|
+
SHORTHAND_FAMILY_MAPPINGS[1].candidate_vals[1] = str_list_style_position;
|
|
317
|
+
SHORTHAND_FAMILY_MAPPINGS[1].candidate_vals[2] = str_list_style_image;
|
|
318
|
+
|
|
319
|
+
SHORTHAND_FAMILY_MAPPINGS[2].shorthand_name_val = str_font;
|
|
320
|
+
SHORTHAND_FAMILY_MAPPINGS[2].candidate_vals[0] = str_font_size;
|
|
321
|
+
SHORTHAND_FAMILY_MAPPINGS[2].candidate_vals[1] = str_font_family;
|
|
322
|
+
SHORTHAND_FAMILY_MAPPINGS[2].candidate_vals[2] = str_font_style;
|
|
323
|
+
SHORTHAND_FAMILY_MAPPINGS[2].candidate_vals[3] = str_font_variant;
|
|
324
|
+
SHORTHAND_FAMILY_MAPPINGS[2].candidate_vals[4] = str_font_weight;
|
|
325
|
+
SHORTHAND_FAMILY_MAPPINGS[2].candidate_vals[5] = str_line_height;
|
|
326
|
+
|
|
327
|
+
SHORTHAND_FAMILY_MAPPINGS[3].shorthand_name_val = str_background;
|
|
328
|
+
SHORTHAND_FAMILY_MAPPINGS[3].candidate_vals[0] = str_background_color;
|
|
329
|
+
SHORTHAND_FAMILY_MAPPINGS[3].candidate_vals[1] = str_background_image;
|
|
330
|
+
SHORTHAND_FAMILY_MAPPINGS[3].candidate_vals[2] = str_background_repeat;
|
|
331
|
+
SHORTHAND_FAMILY_MAPPINGS[3].candidate_vals[3] = str_background_position;
|
|
332
|
+
SHORTHAND_FAMILY_MAPPINGS[3].candidate_vals[4] = str_background_attachment;
|
|
333
|
+
}
|
|
458
334
|
|
|
459
335
|
// Helper function: Try to recreate a shorthand property from its longhand components
|
|
460
336
|
// Uses cached VALUE objects for property names to avoid repeated string allocations
|
|
@@ -511,6 +387,92 @@ static inline void try_recreate_shorthand(VALUE properties_hash, const struct sh
|
|
|
511
387
|
DEBUG_PRINTF(" -> Recreated %s shorthand\n", mapping->shorthand_name);
|
|
512
388
|
}
|
|
513
389
|
|
|
390
|
+
// Helper function: Try to recreate a "family" shorthand (border, font,
|
|
391
|
+
// list-style, background) from its longhand components. Unlike
|
|
392
|
+
// try_recreate_shorthand above (always exactly 4 required sides), these
|
|
393
|
+
// allow a mix of required and optional candidates - see
|
|
394
|
+
// shorthand_family_mapping for the exact rules.
|
|
395
|
+
static inline void try_recreate_shorthand_family(VALUE properties_hash, const struct shorthand_family_mapping *family) {
|
|
396
|
+
// Zero-initialize all slots (not just the first num_candidates) so every
|
|
397
|
+
// read below is well-defined regardless of a given family's arity.
|
|
398
|
+
VALUE datas[MAX_SHORTHAND_FAMILY_CANDIDATES] = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil };
|
|
399
|
+
int present_count = 0;
|
|
400
|
+
|
|
401
|
+
for (int i = 0; i < family->num_candidates; i++) {
|
|
402
|
+
datas[i] = rb_hash_aref(properties_hash, family->candidate_vals[i]);
|
|
403
|
+
if (!NIL_P(datas[i])) present_count++;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Required candidates (if any) must all be present
|
|
407
|
+
for (int i = 0; i < family->num_required; i++) {
|
|
408
|
+
if (NIL_P(datas[i])) return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// If nothing is individually required, need a minimum count of ANY present
|
|
412
|
+
if (family->num_required == 0 && present_count < family->min_present) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// All present candidates must share the same !important flag
|
|
417
|
+
VALUE reference_imp = Qnil;
|
|
418
|
+
int have_reference = 0;
|
|
419
|
+
for (int i = 0; i < family->num_candidates; i++) {
|
|
420
|
+
if (NIL_P(datas[i])) continue;
|
|
421
|
+
VALUE imp = RARRAY_AREF(datas[i], PROP_IMPORTANT);
|
|
422
|
+
if (!have_reference) {
|
|
423
|
+
reference_imp = imp;
|
|
424
|
+
have_reference = 1;
|
|
425
|
+
} else if (RTEST(reference_imp) != RTEST(imp)) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Build a hash of present property values for the creator function
|
|
431
|
+
VALUE props = rb_hash_new();
|
|
432
|
+
for (int i = 0; i < family->num_candidates; i++) {
|
|
433
|
+
if (!NIL_P(datas[i])) {
|
|
434
|
+
rb_hash_aset(props, family->candidate_vals[i], RARRAY_AREF(datas[i], PROP_VALUE));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
VALUE shorthand_value = family->creator_func(Qnil, props);
|
|
439
|
+
if (NIL_P(shorthand_value)) {
|
|
440
|
+
return; // Creator decided not to create shorthand
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// First present candidate (in declared order) supplies source_order/
|
|
444
|
+
// specificity/importance for the shorthand - always the first required
|
|
445
|
+
// candidate when there is one (border-style for border, font-size for
|
|
446
|
+
// font), otherwise the first present optional candidate, matching what
|
|
447
|
+
// each original inline block hardcoded.
|
|
448
|
+
int primary_idx = -1;
|
|
449
|
+
for (int i = 0; i < family->num_candidates; i++) {
|
|
450
|
+
if (!NIL_P(datas[i])) {
|
|
451
|
+
primary_idx = i;
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (primary_idx < 0) {
|
|
456
|
+
return; // Nothing present to build a shorthand from
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
VALUE shorthand_data = rb_ary_new_capa(4);
|
|
460
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(datas[primary_idx], PROP_SOURCE_ORDER));
|
|
461
|
+
rb_ary_push(shorthand_data, RARRAY_AREF(datas[primary_idx], PROP_SPECIFICITY));
|
|
462
|
+
rb_ary_push(shorthand_data, reference_imp);
|
|
463
|
+
rb_ary_push(shorthand_data, shorthand_value);
|
|
464
|
+
|
|
465
|
+
// Add shorthand and remove longhand properties
|
|
466
|
+
rb_hash_aset(properties_hash, family->shorthand_name_val, shorthand_data);
|
|
467
|
+
for (int i = 0; i < family->num_candidates; i++) {
|
|
468
|
+
if (!NIL_P(datas[i])) {
|
|
469
|
+
rb_hash_delete(properties_hash, family->candidate_vals[i]);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
DEBUG_PRINTF(" -> Recreated family shorthand\n");
|
|
474
|
+
}
|
|
475
|
+
|
|
514
476
|
/*
|
|
515
477
|
* Helper struct: For processing expanded properties during merge
|
|
516
478
|
*/
|
|
@@ -829,44 +791,49 @@ static VALUE flatten_rules_for_selector(VALUE rules_array, VALUE rule_indices, V
|
|
|
829
791
|
if (first_char == 'm' || first_char == 'p' || first_char == 'b' ||
|
|
830
792
|
first_char == 'f' || first_char == 'l') {
|
|
831
793
|
// Potentially a shorthand - check specific property names
|
|
794
|
+
//
|
|
795
|
+
// Importance is always passed as Qfalse here: the expanded
|
|
796
|
+
// declarations' own `important` field is never read below
|
|
797
|
+
// (see expand_data.is_important) - actual importance comes
|
|
798
|
+
// from the original, un-expanded declaration instead.
|
|
832
799
|
if (strcmp(prop_cstr, "margin") == 0) {
|
|
833
|
-
expanded = cataract_expand_margin(Qnil, value);
|
|
800
|
+
expanded = cataract_expand_margin(Qnil, value, Qfalse);
|
|
834
801
|
DEBUG_PRINTF(" -> Expanding margin shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
835
802
|
} else if (strcmp(prop_cstr, "padding") == 0) {
|
|
836
|
-
expanded = cataract_expand_padding(Qnil, value);
|
|
803
|
+
expanded = cataract_expand_padding(Qnil, value, Qfalse);
|
|
837
804
|
DEBUG_PRINTF(" -> Expanding padding shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
838
805
|
} else if (strcmp(prop_cstr, "background") == 0) {
|
|
839
|
-
expanded = cataract_expand_background(Qnil, value);
|
|
806
|
+
expanded = cataract_expand_background(Qnil, value, Qfalse);
|
|
840
807
|
DEBUG_PRINTF(" -> Expanding background shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
841
808
|
} else if (strcmp(prop_cstr, "font") == 0) {
|
|
842
|
-
expanded = cataract_expand_font(Qnil, value);
|
|
809
|
+
expanded = cataract_expand_font(Qnil, value, Qfalse);
|
|
843
810
|
DEBUG_PRINTF(" -> Expanding font shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
844
811
|
} else if (strcmp(prop_cstr, "border") == 0) {
|
|
845
|
-
expanded = cataract_expand_border(Qnil, value);
|
|
812
|
+
expanded = cataract_expand_border(Qnil, value, Qfalse);
|
|
846
813
|
DEBUG_PRINTF(" -> Expanding border shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
847
814
|
} else if (strcmp(prop_cstr, "border-color") == 0) {
|
|
848
|
-
expanded = cataract_expand_border_color(Qnil, value);
|
|
815
|
+
expanded = cataract_expand_border_color(Qnil, value, Qfalse);
|
|
849
816
|
DEBUG_PRINTF(" -> Expanding border-color shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
850
817
|
} else if (strcmp(prop_cstr, "border-style") == 0) {
|
|
851
|
-
expanded = cataract_expand_border_style(Qnil, value);
|
|
818
|
+
expanded = cataract_expand_border_style(Qnil, value, Qfalse);
|
|
852
819
|
DEBUG_PRINTF(" -> Expanding border-style shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
853
820
|
} else if (strcmp(prop_cstr, "border-width") == 0) {
|
|
854
|
-
expanded = cataract_expand_border_width(Qnil, value);
|
|
821
|
+
expanded = cataract_expand_border_width(Qnil, value, Qfalse);
|
|
855
822
|
DEBUG_PRINTF(" -> Expanding border-width shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
856
823
|
} else if (strcmp(prop_cstr, "list-style") == 0) {
|
|
857
|
-
expanded = cataract_expand_list_style(Qnil, value);
|
|
824
|
+
expanded = cataract_expand_list_style(Qnil, value, Qfalse);
|
|
858
825
|
DEBUG_PRINTF(" -> Expanding list-style shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
859
826
|
} else if (strcmp(prop_cstr, "border-top") == 0) {
|
|
860
|
-
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("top"), value);
|
|
827
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("top"), value, Qfalse);
|
|
861
828
|
DEBUG_PRINTF(" -> Expanding border-top shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
862
829
|
} else if (strcmp(prop_cstr, "border-right") == 0) {
|
|
863
|
-
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("right"), value);
|
|
830
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("right"), value, Qfalse);
|
|
864
831
|
DEBUG_PRINTF(" -> Expanding border-right shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
865
832
|
} else if (strcmp(prop_cstr, "border-bottom") == 0) {
|
|
866
|
-
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("bottom"), value);
|
|
833
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("bottom"), value, Qfalse);
|
|
867
834
|
DEBUG_PRINTF(" -> Expanding border-bottom shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
868
835
|
} else if (strcmp(prop_cstr, "border-left") == 0) {
|
|
869
|
-
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("left"), value);
|
|
836
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("left"), value, Qfalse);
|
|
870
837
|
DEBUG_PRINTF(" -> Expanding border-left shorthand (%ld longhands)\n", RARRAY_LEN(expanded));
|
|
871
838
|
}
|
|
872
839
|
}
|
|
@@ -916,204 +883,9 @@ static VALUE flatten_rules_for_selector(VALUE rules_array, VALUE rule_indices, V
|
|
|
916
883
|
try_recreate_shorthand(properties_hash, mapping);
|
|
917
884
|
}
|
|
918
885
|
|
|
919
|
-
// Try to recreate
|
|
920
|
-
{
|
|
921
|
-
|
|
922
|
-
VALUE style = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-style"));
|
|
923
|
-
VALUE color = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-color"));
|
|
924
|
-
|
|
925
|
-
// Need at least style (border shorthand requires style)
|
|
926
|
-
if (!NIL_P(style)) {
|
|
927
|
-
// Check all have same !important flag
|
|
928
|
-
VALUE style_imp = RARRAY_AREF(style, PROP_IMPORTANT);
|
|
929
|
-
int same_importance = 1;
|
|
930
|
-
if (!NIL_P(width)) same_importance = same_importance && (RTEST(style_imp) == RTEST(RARRAY_AREF(width, PROP_IMPORTANT)));
|
|
931
|
-
if (!NIL_P(color)) same_importance = same_importance && (RTEST(style_imp) == RTEST(RARRAY_AREF(color, PROP_IMPORTANT)));
|
|
932
|
-
|
|
933
|
-
if (same_importance) {
|
|
934
|
-
VALUE props = rb_hash_new();
|
|
935
|
-
if (!NIL_P(width)) rb_hash_aset(props, STR_NEW_CSTR("border-width"), RARRAY_AREF(width, PROP_VALUE));
|
|
936
|
-
rb_hash_aset(props, STR_NEW_CSTR("border-style"), RARRAY_AREF(style, PROP_VALUE));
|
|
937
|
-
if (!NIL_P(color)) rb_hash_aset(props, STR_NEW_CSTR("border-color"), RARRAY_AREF(color, PROP_VALUE));
|
|
938
|
-
|
|
939
|
-
VALUE shorthand_value = cataract_create_border_shorthand(Qnil, props);
|
|
940
|
-
if (!NIL_P(shorthand_value)) {
|
|
941
|
-
VALUE shorthand_data = rb_ary_new_capa(4);
|
|
942
|
-
rb_ary_push(shorthand_data, RARRAY_AREF(style, PROP_SOURCE_ORDER));
|
|
943
|
-
rb_ary_push(shorthand_data, RARRAY_AREF(style, PROP_SPECIFICITY));
|
|
944
|
-
rb_ary_push(shorthand_data, style_imp);
|
|
945
|
-
rb_ary_push(shorthand_data, shorthand_value);
|
|
946
|
-
rb_hash_aset(properties_hash, USASCII_STR("border"), shorthand_data);
|
|
947
|
-
|
|
948
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-width"));
|
|
949
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-style"));
|
|
950
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-color"));
|
|
951
|
-
DEBUG_PRINTF(" -> Recreated border shorthand\n");
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// Try to recreate list-style shorthand
|
|
958
|
-
{
|
|
959
|
-
VALUE type = rb_hash_aref(properties_hash, STR_NEW_CSTR("list-style-type"));
|
|
960
|
-
VALUE position = rb_hash_aref(properties_hash, STR_NEW_CSTR("list-style-position"));
|
|
961
|
-
VALUE image = rb_hash_aref(properties_hash, STR_NEW_CSTR("list-style-image"));
|
|
962
|
-
|
|
963
|
-
// Need at least 2 properties to create shorthand
|
|
964
|
-
// Single property should stay as longhand (semantic difference)
|
|
965
|
-
int list_count = 0;
|
|
966
|
-
if (!NIL_P(type)) list_count++;
|
|
967
|
-
if (!NIL_P(position)) list_count++;
|
|
968
|
-
if (!NIL_P(image)) list_count++;
|
|
969
|
-
|
|
970
|
-
if (list_count >= 2) {
|
|
971
|
-
// Check all have same !important flag
|
|
972
|
-
VALUE first_imp = Qnil;
|
|
973
|
-
if (!NIL_P(type)) first_imp = RARRAY_AREF(type, PROP_IMPORTANT);
|
|
974
|
-
else if (!NIL_P(position)) first_imp = RARRAY_AREF(position, PROP_IMPORTANT);
|
|
975
|
-
else if (!NIL_P(image)) first_imp = RARRAY_AREF(image, PROP_IMPORTANT);
|
|
976
|
-
|
|
977
|
-
int same_importance = 1;
|
|
978
|
-
if (!NIL_P(type)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(type, PROP_IMPORTANT)));
|
|
979
|
-
if (!NIL_P(position)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(position, PROP_IMPORTANT)));
|
|
980
|
-
if (!NIL_P(image)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(image, PROP_IMPORTANT)));
|
|
981
|
-
|
|
982
|
-
if (same_importance) {
|
|
983
|
-
VALUE props = rb_hash_new();
|
|
984
|
-
if (!NIL_P(type)) rb_hash_aset(props, STR_NEW_CSTR("list-style-type"), RARRAY_AREF(type, PROP_VALUE));
|
|
985
|
-
if (!NIL_P(position)) rb_hash_aset(props, STR_NEW_CSTR("list-style-position"), RARRAY_AREF(position, PROP_VALUE));
|
|
986
|
-
if (!NIL_P(image)) rb_hash_aset(props, STR_NEW_CSTR("list-style-image"), RARRAY_AREF(image, PROP_VALUE));
|
|
987
|
-
|
|
988
|
-
VALUE shorthand_value = cataract_create_list_style_shorthand(Qnil, props);
|
|
989
|
-
if (!NIL_P(shorthand_value)) {
|
|
990
|
-
VALUE first_prop = !NIL_P(type) ? type : (!NIL_P(position) ? position : image);
|
|
991
|
-
VALUE shorthand_data = rb_ary_new_capa(4);
|
|
992
|
-
rb_ary_push(shorthand_data, RARRAY_AREF(first_prop, PROP_SOURCE_ORDER));
|
|
993
|
-
rb_ary_push(shorthand_data, RARRAY_AREF(first_prop, PROP_SPECIFICITY));
|
|
994
|
-
rb_ary_push(shorthand_data, first_imp);
|
|
995
|
-
rb_ary_push(shorthand_data, shorthand_value);
|
|
996
|
-
rb_hash_aset(properties_hash, USASCII_STR("list-style"), shorthand_data);
|
|
997
|
-
|
|
998
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("list-style-type"));
|
|
999
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("list-style-position"));
|
|
1000
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("list-style-image"));
|
|
1001
|
-
DEBUG_PRINTF(" -> Recreated list-style shorthand\n");
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// Try to recreate font shorthand (requires at least font-size and font-family)
|
|
1008
|
-
{
|
|
1009
|
-
VALUE size = rb_hash_aref(properties_hash, STR_NEW_CSTR("font-size"));
|
|
1010
|
-
VALUE family = rb_hash_aref(properties_hash, STR_NEW_CSTR("font-family"));
|
|
1011
|
-
|
|
1012
|
-
if (!NIL_P(size) && !NIL_P(family)) {
|
|
1013
|
-
VALUE style = rb_hash_aref(properties_hash, STR_NEW_CSTR("font-style"));
|
|
1014
|
-
VALUE variant = rb_hash_aref(properties_hash, STR_NEW_CSTR("font-variant"));
|
|
1015
|
-
VALUE weight = rb_hash_aref(properties_hash, STR_NEW_CSTR("font-weight"));
|
|
1016
|
-
VALUE line_height = rb_hash_aref(properties_hash, STR_NEW_CSTR("line-height"));
|
|
1017
|
-
|
|
1018
|
-
// Check all font properties have same !important flag
|
|
1019
|
-
VALUE size_imp = RARRAY_AREF(size, PROP_IMPORTANT);
|
|
1020
|
-
VALUE family_imp = RARRAY_AREF(family, PROP_IMPORTANT);
|
|
1021
|
-
|
|
1022
|
-
int same_importance = (RTEST(size_imp) == RTEST(family_imp));
|
|
1023
|
-
if (!NIL_P(style)) same_importance = same_importance && (RTEST(size_imp) == RTEST(RARRAY_AREF(style, PROP_IMPORTANT)));
|
|
1024
|
-
if (!NIL_P(variant)) same_importance = same_importance && (RTEST(size_imp) == RTEST(RARRAY_AREF(variant, PROP_IMPORTANT)));
|
|
1025
|
-
if (!NIL_P(weight)) same_importance = same_importance && (RTEST(size_imp) == RTEST(RARRAY_AREF(weight, PROP_IMPORTANT)));
|
|
1026
|
-
if (!NIL_P(line_height)) same_importance = same_importance && (RTEST(size_imp) == RTEST(RARRAY_AREF(line_height, PROP_IMPORTANT)));
|
|
1027
|
-
|
|
1028
|
-
if (same_importance) {
|
|
1029
|
-
VALUE props = rb_hash_new();
|
|
1030
|
-
rb_hash_aset(props, STR_NEW_CSTR("font-size"), RARRAY_AREF(size, PROP_VALUE));
|
|
1031
|
-
rb_hash_aset(props, STR_NEW_CSTR("font-family"), RARRAY_AREF(family, PROP_VALUE));
|
|
1032
|
-
if (!NIL_P(style)) rb_hash_aset(props, STR_NEW_CSTR("font-style"), RARRAY_AREF(style, PROP_VALUE));
|
|
1033
|
-
if (!NIL_P(variant)) rb_hash_aset(props, STR_NEW_CSTR("font-variant"), RARRAY_AREF(variant, PROP_VALUE));
|
|
1034
|
-
if (!NIL_P(weight)) rb_hash_aset(props, STR_NEW_CSTR("font-weight"), RARRAY_AREF(weight, PROP_VALUE));
|
|
1035
|
-
if (!NIL_P(line_height)) rb_hash_aset(props, STR_NEW_CSTR("line-height"), RARRAY_AREF(line_height, PROP_VALUE));
|
|
1036
|
-
|
|
1037
|
-
VALUE shorthand_value = cataract_create_font_shorthand(Qnil, props);
|
|
1038
|
-
if (!NIL_P(shorthand_value)) {
|
|
1039
|
-
VALUE shorthand_data = rb_ary_new_capa(4);
|
|
1040
|
-
rb_ary_push(shorthand_data, RARRAY_AREF(size, PROP_SOURCE_ORDER));
|
|
1041
|
-
rb_ary_push(shorthand_data, RARRAY_AREF(size, PROP_SPECIFICITY));
|
|
1042
|
-
rb_ary_push(shorthand_data, size_imp);
|
|
1043
|
-
rb_ary_push(shorthand_data, shorthand_value);
|
|
1044
|
-
rb_hash_aset(properties_hash, USASCII_STR("font"), shorthand_data);
|
|
1045
|
-
|
|
1046
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-size"));
|
|
1047
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-family"));
|
|
1048
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-style"));
|
|
1049
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-variant"));
|
|
1050
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-weight"));
|
|
1051
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("line-height"));
|
|
1052
|
-
DEBUG_PRINTF(" -> Recreated font shorthand\n");
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
// Try to recreate background shorthand (if 2+ properties present)
|
|
1059
|
-
{
|
|
1060
|
-
VALUE color = rb_hash_aref(properties_hash, STR_NEW_CSTR("background-color"));
|
|
1061
|
-
VALUE image = rb_hash_aref(properties_hash, STR_NEW_CSTR("background-image"));
|
|
1062
|
-
VALUE repeat = rb_hash_aref(properties_hash, STR_NEW_CSTR("background-repeat"));
|
|
1063
|
-
VALUE position = rb_hash_aref(properties_hash, STR_NEW_CSTR("background-position"));
|
|
1064
|
-
VALUE attachment = rb_hash_aref(properties_hash, STR_NEW_CSTR("background-attachment"));
|
|
1065
|
-
|
|
1066
|
-
int bg_count = 0;
|
|
1067
|
-
if (!NIL_P(color)) bg_count++;
|
|
1068
|
-
if (!NIL_P(image)) bg_count++;
|
|
1069
|
-
if (!NIL_P(repeat)) bg_count++;
|
|
1070
|
-
if (!NIL_P(position)) bg_count++;
|
|
1071
|
-
if (!NIL_P(attachment)) bg_count++;
|
|
1072
|
-
|
|
1073
|
-
// Need at least 2 properties to create shorthand
|
|
1074
|
-
if (bg_count >= 2) {
|
|
1075
|
-
// Check all have same !important flag
|
|
1076
|
-
VALUE first_imp = Qnil;
|
|
1077
|
-
if (!NIL_P(color)) first_imp = RARRAY_AREF(color, PROP_IMPORTANT);
|
|
1078
|
-
else if (!NIL_P(image)) first_imp = RARRAY_AREF(image, PROP_IMPORTANT);
|
|
1079
|
-
else if (!NIL_P(repeat)) first_imp = RARRAY_AREF(repeat, PROP_IMPORTANT);
|
|
1080
|
-
else if (!NIL_P(position)) first_imp = RARRAY_AREF(position, PROP_IMPORTANT);
|
|
1081
|
-
else if (!NIL_P(attachment)) first_imp = RARRAY_AREF(attachment, PROP_IMPORTANT);
|
|
1082
|
-
|
|
1083
|
-
int same_importance = 1;
|
|
1084
|
-
if (!NIL_P(color)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(color, PROP_IMPORTANT)));
|
|
1085
|
-
if (!NIL_P(image)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(image, PROP_IMPORTANT)));
|
|
1086
|
-
if (!NIL_P(repeat)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(repeat, PROP_IMPORTANT)));
|
|
1087
|
-
if (!NIL_P(position)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(position, PROP_IMPORTANT)));
|
|
1088
|
-
if (!NIL_P(attachment)) same_importance = same_importance && (RTEST(first_imp) == RTEST(RARRAY_AREF(attachment, PROP_IMPORTANT)));
|
|
1089
|
-
|
|
1090
|
-
if (same_importance) {
|
|
1091
|
-
VALUE props = rb_hash_new();
|
|
1092
|
-
if (!NIL_P(color)) rb_hash_aset(props, STR_NEW_CSTR("background-color"), RARRAY_AREF(color, PROP_VALUE));
|
|
1093
|
-
if (!NIL_P(image)) rb_hash_aset(props, STR_NEW_CSTR("background-image"), RARRAY_AREF(image, PROP_VALUE));
|
|
1094
|
-
if (!NIL_P(repeat)) rb_hash_aset(props, STR_NEW_CSTR("background-repeat"), RARRAY_AREF(repeat, PROP_VALUE));
|
|
1095
|
-
if (!NIL_P(position)) rb_hash_aset(props, STR_NEW_CSTR("background-position"), RARRAY_AREF(position, PROP_VALUE));
|
|
1096
|
-
if (!NIL_P(attachment)) rb_hash_aset(props, STR_NEW_CSTR("background-attachment"), RARRAY_AREF(attachment, PROP_VALUE));
|
|
1097
|
-
|
|
1098
|
-
VALUE shorthand_value = cataract_create_background_shorthand(Qnil, props);
|
|
1099
|
-
if (!NIL_P(shorthand_value)) {
|
|
1100
|
-
VALUE first_prop = !NIL_P(color) ? color : (!NIL_P(image) ? image : (!NIL_P(repeat) ? repeat : (!NIL_P(position) ? position : attachment)));
|
|
1101
|
-
VALUE shorthand_data = rb_ary_new_capa(4);
|
|
1102
|
-
rb_ary_push(shorthand_data, RARRAY_AREF(first_prop, PROP_SOURCE_ORDER));
|
|
1103
|
-
rb_ary_push(shorthand_data, RARRAY_AREF(first_prop, PROP_SPECIFICITY));
|
|
1104
|
-
rb_ary_push(shorthand_data, first_imp);
|
|
1105
|
-
rb_ary_push(shorthand_data, shorthand_value);
|
|
1106
|
-
rb_hash_aset(properties_hash, USASCII_STR("background"), shorthand_data);
|
|
1107
|
-
|
|
1108
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-color"));
|
|
1109
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-image"));
|
|
1110
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-repeat"));
|
|
1111
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-position"));
|
|
1112
|
-
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-attachment"));
|
|
1113
|
-
DEBUG_PRINTF(" -> Recreated background shorthand\n");
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
886
|
+
// Try to recreate family shorthands (border, font, list-style, background) using the mapping table
|
|
887
|
+
for (size_t i = 0; i < NUM_SHORTHAND_FAMILIES; i++) {
|
|
888
|
+
try_recreate_shorthand_family(properties_hash, &SHORTHAND_FAMILY_MAPPINGS[i]);
|
|
1117
889
|
}
|
|
1118
890
|
|
|
1119
891
|
// Build declarations array from properties_hash
|
|
@@ -1311,6 +1083,104 @@ static void update_selector_lists_for_divergence(VALUE merged_rules, VALUE selec
|
|
|
1311
1083
|
DEBUG_PRINTF("\n=== End divergence tracking: %ld selector lists preserved ===\n\n", RHASH_SIZE(selector_lists));
|
|
1312
1084
|
}
|
|
1313
1085
|
|
|
1086
|
+
// Build a map from rule_id -> media_query_id for all Rule (non-AtRule)
|
|
1087
|
+
// entries that have one set. Used to group rules by (selector, media)
|
|
1088
|
+
// instead of just selector, and later to remap the stylesheet's media_index
|
|
1089
|
+
// after flattening.
|
|
1090
|
+
//
|
|
1091
|
+
// @param out_input_media_index Output: the input Stylesheet's @media_index
|
|
1092
|
+
// ivar (Qnil if input isn't a Stylesheet), needed by the caller for
|
|
1093
|
+
// remap_media_index_callback
|
|
1094
|
+
static VALUE build_rule_media_map(VALUE input, VALUE rules_array, long num_rules, VALUE *out_input_media_index) {
|
|
1095
|
+
VALUE rule_media_map = rb_hash_new();
|
|
1096
|
+
*out_input_media_index = Qnil;
|
|
1097
|
+
|
|
1098
|
+
if (!rb_obj_is_kind_of(input, cStylesheet)) {
|
|
1099
|
+
return rule_media_map;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
*out_input_media_index = rb_ivar_get(input, id_ivar_media_index);
|
|
1103
|
+
|
|
1104
|
+
// Only process Rule objects, not AtRules (AtRules don't have media_query_id field at same offset)
|
|
1105
|
+
for (long i = 0; i < num_rules; i++) {
|
|
1106
|
+
VALUE rule = rb_ary_entry(rules_array, i);
|
|
1107
|
+
if (!rb_obj_is_kind_of(rule, cAtRule)) {
|
|
1108
|
+
VALUE media_query_id = rb_struct_aref(rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
1109
|
+
if (!NIL_P(media_query_id)) {
|
|
1110
|
+
VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
1111
|
+
rb_hash_aset(rule_media_map, rule_id, media_query_id);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
return rule_media_map;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// Group rules by (selector, media) - not just selector - since rules with
|
|
1120
|
+
// the same selector but different media contexts must NOT be merged
|
|
1121
|
+
// together. AtRule entries (e.g. @keyframes, @font-face) have no
|
|
1122
|
+
// declarations to merge and can't be grouped this way at all, so they're
|
|
1123
|
+
// collected separately into passthrough_rules and passed through unchanged.
|
|
1124
|
+
//
|
|
1125
|
+
// @param passthrough_rules Output: empty array to populate with AtRule entries
|
|
1126
|
+
// @return Hash of [selector, media_context] => Array of rule indices
|
|
1127
|
+
static VALUE group_rules_by_selector_and_media(VALUE rules_array, long num_rules, VALUE rule_media_map, VALUE passthrough_rules) {
|
|
1128
|
+
VALUE selector_groups = rb_hash_new();
|
|
1129
|
+
|
|
1130
|
+
for (long i = 0; i < num_rules; i++) {
|
|
1131
|
+
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
1132
|
+
|
|
1133
|
+
// Handle AtRule objects (@keyframes, @font-face, etc.) - pass through unchanged
|
|
1134
|
+
// AtRule has 'content' (string) instead of 'declarations' (array)
|
|
1135
|
+
if (rb_obj_is_kind_of(rule, cAtRule)) {
|
|
1136
|
+
DEBUG_PRINTF(" [Rule %ld] PASSTHROUGH: AtRule (e.g., @keyframes, @font-face)\n", i);
|
|
1137
|
+
rb_ary_push(passthrough_rules, rule);
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
VALUE declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
1142
|
+
VALUE selector = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR));
|
|
1143
|
+
VALUE rule_id_val = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
1144
|
+
|
|
1145
|
+
// Skip empty rules (no declarations)
|
|
1146
|
+
// This handles both empty containers and rules with no properties
|
|
1147
|
+
if (RARRAY_LEN(declarations) == 0) {
|
|
1148
|
+
DEBUG_PRINTF(" [Rule %ld] SKIP: selector='%s' (empty declarations)\n",
|
|
1149
|
+
i, RSTRING_PTR(selector));
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// Note: We do NOT skip parent rules that have children!
|
|
1154
|
+
// Per CSS spec, parent can have its own declarations AND nested rules.
|
|
1155
|
+
// Example: .parent { color: red; .child { color: blue; } }
|
|
1156
|
+
// Should output both .parent (color: red) and .parent .child (color: blue)
|
|
1157
|
+
// The nesting is already flattened during parsing, so they have different selectors.
|
|
1158
|
+
|
|
1159
|
+
// Get media context for this rule (nil if not in media query)
|
|
1160
|
+
VALUE media_context = rb_hash_aref(rule_media_map, rule_id_val);
|
|
1161
|
+
|
|
1162
|
+
// Build grouping key as [selector, media_context]
|
|
1163
|
+
VALUE group_key = rb_ary_new3(2, selector, media_context);
|
|
1164
|
+
|
|
1165
|
+
DEBUG_PRINTF(" [Rule %ld] ADD: selector='%s', media=%s, %ld declarations\n",
|
|
1166
|
+
i, RSTRING_PTR(selector),
|
|
1167
|
+
NIL_P(media_context) ? "nil" : RSTRING_PTR(rb_inspect(media_context)),
|
|
1168
|
+
RARRAY_LEN(declarations));
|
|
1169
|
+
|
|
1170
|
+
VALUE group = rb_hash_aref(selector_groups, group_key);
|
|
1171
|
+
if (NIL_P(group)) {
|
|
1172
|
+
group = rb_ary_new();
|
|
1173
|
+
rb_hash_aset(selector_groups, group_key, group);
|
|
1174
|
+
DEBUG_PRINTF(" -> Created new selector+media group for '%s' + %s\n",
|
|
1175
|
+
RSTRING_PTR(selector),
|
|
1176
|
+
NIL_P(media_context) ? "nil" : RSTRING_PTR(rb_inspect(media_context)));
|
|
1177
|
+
}
|
|
1178
|
+
rb_ary_push(group, LONG2FIX(i));
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
return selector_groups;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1314
1184
|
// Flatten CSS rules by applying cascade rules
|
|
1315
1185
|
// Input: Stylesheet object or CSS string
|
|
1316
1186
|
// Output: Stylesheet with flattened declarations (cascade applied)
|
|
@@ -1334,13 +1204,6 @@ VALUE cataract_flatten(VALUE self, VALUE input) {
|
|
|
1334
1204
|
|
|
1335
1205
|
Check_Type(rules_array, T_ARRAY);
|
|
1336
1206
|
|
|
1337
|
-
// Check if stylesheet has nesting (affects selector rollup)
|
|
1338
|
-
int has_nesting = 0;
|
|
1339
|
-
if (rb_obj_is_kind_of(input, cStylesheet)) {
|
|
1340
|
-
VALUE has_nesting_ivar = rb_ivar_get(input, rb_intern("@_has_nesting"));
|
|
1341
|
-
has_nesting = RTEST(has_nesting_ivar);
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
1207
|
long num_rules = RARRAY_LEN(rules_array);
|
|
1345
1208
|
// Empty stylesheets are rare
|
|
1346
1209
|
if (num_rules == 0) {
|
|
@@ -1421,107 +1284,16 @@ VALUE cataract_flatten(VALUE self, VALUE input) {
|
|
|
1421
1284
|
* ============================================================================
|
|
1422
1285
|
*/
|
|
1423
1286
|
|
|
1424
|
-
// For nested CSS: identify parent rules (rules that have children)
|
|
1425
|
-
// These should be skipped during flatten, even if they have declarations
|
|
1426
|
-
// Use Ruby hash as a set: parent_id => true
|
|
1427
|
-
VALUE parent_ids = Qnil;
|
|
1428
|
-
if (has_nesting) {
|
|
1429
|
-
DEBUG_PRINTF("\n=== FLATTEN: has_nesting=true, num_rules=%ld ===\n", num_rules);
|
|
1430
|
-
parent_ids = rb_hash_new();
|
|
1431
|
-
for (long i = 0; i < num_rules; i++) {
|
|
1432
|
-
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
1433
|
-
VALUE parent_rule_id = rb_struct_aref(rule, INT2FIX(RULE_PARENT_RULE_ID));
|
|
1434
|
-
DEBUG_PRINTF(" Rule %ld: selector='%s', rule_id=%d, parent_rule_id=%s\n",
|
|
1435
|
-
i,
|
|
1436
|
-
RSTRING_PTR(rb_struct_aref(rule, INT2FIX(RULE_SELECTOR))),
|
|
1437
|
-
FIX2INT(rb_struct_aref(rule, INT2FIX(RULE_ID))),
|
|
1438
|
-
NIL_P(parent_rule_id) ? "nil" : RSTRING_PTR(rb_inspect(parent_rule_id)));
|
|
1439
|
-
if (!NIL_P(parent_rule_id)) {
|
|
1440
|
-
// This rule has a parent, so mark that parent ID
|
|
1441
|
-
rb_hash_aset(parent_ids, parent_rule_id, Qtrue);
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
1287
|
// Build rule_media_map: rule_id => media_query_id
|
|
1447
1288
|
// This is used to group rules by (selector, media) instead of just selector
|
|
1448
1289
|
VALUE input_media_index = Qnil;
|
|
1449
|
-
VALUE rule_media_map =
|
|
1450
|
-
if (rb_obj_is_kind_of(input, cStylesheet)) {
|
|
1451
|
-
input_media_index = rb_ivar_get(input, id_ivar_media_index);
|
|
1452
|
-
|
|
1453
|
-
// Build map from rules' media_query_id field
|
|
1454
|
-
// Only process Rule objects, not AtRules (AtRules don't have media_query_id field at same offset)
|
|
1455
|
-
for (long i = 0; i < num_rules; i++) {
|
|
1456
|
-
VALUE rule = rb_ary_entry(rules_array, i);
|
|
1457
|
-
if (!rb_obj_is_kind_of(rule, cAtRule)) {
|
|
1458
|
-
VALUE media_query_id = rb_struct_aref(rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
1459
|
-
if (!NIL_P(media_query_id)) {
|
|
1460
|
-
VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
1461
|
-
// Store media_query_id as the grouping key
|
|
1462
|
-
rb_hash_aset(rule_media_map, rule_id, media_query_id);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1290
|
+
VALUE rule_media_map = build_rule_media_map(input, rules_array, num_rules, &input_media_index);
|
|
1467
1291
|
|
|
1468
1292
|
// Group rules by (selector, media) instead of just selector
|
|
1469
1293
|
// Rules with same selector but different media contexts should NOT be merged
|
|
1470
|
-
|
|
1471
|
-
DEBUG_PRINTF("\n=== Building selector+media groups (has_nesting=%d) ===\n", has_nesting);
|
|
1472
|
-
VALUE selector_groups = rb_hash_new();
|
|
1294
|
+
DEBUG_PRINTF("\n=== Building selector+media groups ===\n");
|
|
1473
1295
|
VALUE passthrough_rules = rb_ary_new(); // AtRules to pass through unchanged
|
|
1474
|
-
|
|
1475
|
-
for (long i = 0; i < num_rules; i++) {
|
|
1476
|
-
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
1477
|
-
|
|
1478
|
-
// Handle AtRule objects (@keyframes, @font-face, etc.) - pass through unchanged
|
|
1479
|
-
// AtRule has 'content' (string) instead of 'declarations' (array)
|
|
1480
|
-
if (rb_obj_is_kind_of(rule, cAtRule)) {
|
|
1481
|
-
DEBUG_PRINTF(" [Rule %ld] PASSTHROUGH: AtRule (e.g., @keyframes, @font-face)\n", i);
|
|
1482
|
-
rb_ary_push(passthrough_rules, rule);
|
|
1483
|
-
continue;
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
VALUE declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
1487
|
-
VALUE selector = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR));
|
|
1488
|
-
VALUE rule_id_val = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
1489
|
-
|
|
1490
|
-
// Skip empty rules (no declarations)
|
|
1491
|
-
// This handles both empty containers and rules with no properties
|
|
1492
|
-
if (RARRAY_LEN(declarations) == 0) {
|
|
1493
|
-
DEBUG_PRINTF(" [Rule %ld] SKIP: selector='%s' (empty declarations)\n",
|
|
1494
|
-
i, RSTRING_PTR(selector));
|
|
1495
|
-
continue;
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
// Note: We do NOT skip parent rules that have children!
|
|
1499
|
-
// Per CSS spec, parent can have its own declarations AND nested rules.
|
|
1500
|
-
// Example: .parent { color: red; .child { color: blue; } }
|
|
1501
|
-
// Should output both .parent (color: red) and .parent .child (color: blue)
|
|
1502
|
-
// The nesting is already flattened during parsing, so they have different selectors.
|
|
1503
|
-
|
|
1504
|
-
// Get media context for this rule (nil if not in media query)
|
|
1505
|
-
VALUE media_context = rb_hash_aref(rule_media_map, rule_id_val);
|
|
1506
|
-
|
|
1507
|
-
// Build grouping key as [selector, media_context]
|
|
1508
|
-
VALUE group_key = rb_ary_new3(2, selector, media_context);
|
|
1509
|
-
|
|
1510
|
-
DEBUG_PRINTF(" [Rule %ld] ADD: selector='%s', media=%s, %ld declarations\n",
|
|
1511
|
-
i, RSTRING_PTR(selector),
|
|
1512
|
-
NIL_P(media_context) ? "nil" : RSTRING_PTR(rb_inspect(media_context)),
|
|
1513
|
-
RARRAY_LEN(declarations));
|
|
1514
|
-
|
|
1515
|
-
VALUE group = rb_hash_aref(selector_groups, group_key);
|
|
1516
|
-
if (NIL_P(group)) {
|
|
1517
|
-
group = rb_ary_new();
|
|
1518
|
-
rb_hash_aset(selector_groups, group_key, group);
|
|
1519
|
-
DEBUG_PRINTF(" -> Created new selector+media group for '%s' + %s\n",
|
|
1520
|
-
RSTRING_PTR(selector),
|
|
1521
|
-
NIL_P(media_context) ? "nil" : RSTRING_PTR(rb_inspect(media_context)));
|
|
1522
|
-
}
|
|
1523
|
-
rb_ary_push(group, LONG2FIX(i));
|
|
1524
|
-
}
|
|
1296
|
+
VALUE selector_groups = group_rules_by_selector_and_media(rules_array, num_rules, rule_media_map, passthrough_rules);
|
|
1525
1297
|
DEBUG_PRINTF("=== Total selector+media groups: %ld ===\n\n", RHASH_SIZE(selector_groups));
|
|
1526
1298
|
|
|
1527
1299
|
// ALWAYS group by selector and keep them separate
|
|
@@ -1559,9 +1331,7 @@ VALUE cataract_flatten(VALUE self, VALUE input) {
|
|
|
1559
1331
|
}
|
|
1560
1332
|
|
|
1561
1333
|
return passthrough_sheet;
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
if (RHASH_SIZE(selector_groups) > 0) {
|
|
1334
|
+
} else {
|
|
1565
1335
|
DEBUG_PRINTF(" -> Taking SELECTOR-GROUPED path (%ld unique selectors)\n",
|
|
1566
1336
|
RHASH_SIZE(selector_groups));
|
|
1567
1337
|
VALUE merged_sheet = rb_class_new_instance(0, NULL, cStylesheet);
|
|
@@ -1665,486 +1435,4 @@ VALUE cataract_flatten(VALUE self, VALUE input) {
|
|
|
1665
1435
|
return merged_sheet;
|
|
1666
1436
|
}
|
|
1667
1437
|
|
|
1668
|
-
// Single-merge path: merge all rules into one
|
|
1669
|
-
VALUE properties_hash = rb_hash_new();
|
|
1670
|
-
|
|
1671
|
-
// Track selector for rollup (minimize allocations)
|
|
1672
|
-
// Store pointer + length to first non-parent selector
|
|
1673
|
-
// Also keep the VALUE alive since we extract C pointer before allocations
|
|
1674
|
-
const char *first_selector_ptr = NULL;
|
|
1675
|
-
long first_selector_len = 0;
|
|
1676
|
-
VALUE first_selector_value = Qnil;
|
|
1677
|
-
int all_same_selector = 1;
|
|
1678
|
-
|
|
1679
|
-
// Track source order for cascade rules
|
|
1680
|
-
long source_order = 0;
|
|
1681
|
-
|
|
1682
|
-
// Iterate through each rule
|
|
1683
|
-
for (long i = 0; i < num_rules; i++) {
|
|
1684
|
-
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
1685
|
-
Check_Type(rule, T_STRUCT);
|
|
1686
|
-
|
|
1687
|
-
// Extract rule fields
|
|
1688
|
-
VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
1689
|
-
VALUE selector = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR));
|
|
1690
|
-
VALUE declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
1691
|
-
|
|
1692
|
-
// Skip parent rules when handling nested CSS
|
|
1693
|
-
// Example: .button { color: black; &:hover { color: red; } }
|
|
1694
|
-
// - Rule id=0, selector=".button", declarations=[color: black] (SKIP - has children)
|
|
1695
|
-
// - Rule id=1, selector=".button:hover", declarations=[color: red] (PROCESS)
|
|
1696
|
-
if (has_nesting && !NIL_P(parent_ids)) {
|
|
1697
|
-
VALUE is_parent = rb_hash_aref(parent_ids, rule_id);
|
|
1698
|
-
if (RTEST(is_parent)) {
|
|
1699
|
-
continue;
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
long num_decls = RARRAY_LEN(declarations);
|
|
1704
|
-
// Skip rules with no declarations (empty parent containers)
|
|
1705
|
-
if (num_decls == 0) {
|
|
1706
|
-
continue;
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
// Track selectors for rollup (delay allocation)
|
|
1710
|
-
const char *sel_ptr = RSTRING_PTR(selector);
|
|
1711
|
-
long sel_len = RSTRING_LEN(selector);
|
|
1712
|
-
if (first_selector_ptr == NULL) {
|
|
1713
|
-
first_selector_ptr = sel_ptr;
|
|
1714
|
-
first_selector_len = sel_len;
|
|
1715
|
-
first_selector_value = selector; // Keep VALUE alive for RB_GC_GUARD
|
|
1716
|
-
} else if (all_same_selector) {
|
|
1717
|
-
if (sel_len != first_selector_len || memcmp(sel_ptr, first_selector_ptr, sel_len) != 0) {
|
|
1718
|
-
all_same_selector = 0;
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
VALUE specificity_val = rb_struct_aref(rule, INT2FIX(RULE_SPECIFICITY));
|
|
1723
|
-
|
|
1724
|
-
// Calculate specificity if not provided (lazy)
|
|
1725
|
-
int specificity = 0;
|
|
1726
|
-
if (NIL_P(specificity_val)) {
|
|
1727
|
-
specificity_val = calculate_specificity(Qnil, selector);
|
|
1728
|
-
// Cache the calculated value back to the struct
|
|
1729
|
-
rb_struct_aset(rule, INT2FIX(RULE_SPECIFICITY), specificity_val);
|
|
1730
|
-
}
|
|
1731
|
-
specificity = NUM2INT(specificity_val);
|
|
1732
|
-
|
|
1733
|
-
// Process each declaration in this rule
|
|
1734
|
-
Check_Type(declarations, T_ARRAY);
|
|
1735
|
-
|
|
1736
|
-
for (long j = 0; j < num_decls; j++) {
|
|
1737
|
-
VALUE decl = RARRAY_AREF(declarations, j);
|
|
1738
|
-
|
|
1739
|
-
// Extract property, value, important from Declaration struct
|
|
1740
|
-
VALUE property = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
1741
|
-
VALUE value = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
1742
|
-
VALUE important = rb_struct_aref(decl, INT2FIX(DECL_IMPORTANT));
|
|
1743
|
-
|
|
1744
|
-
// Properties are already lowercased during parsing (see cataract_new.c)
|
|
1745
|
-
// No need to lowercase again
|
|
1746
|
-
int is_important = RTEST(important);
|
|
1747
|
-
|
|
1748
|
-
// Expand shorthand properties if needed
|
|
1749
|
-
// Most properties are NOT shorthands, so hint compiler accordingly
|
|
1750
|
-
const char *prop_str = StringValueCStr(property);
|
|
1751
|
-
VALUE expanded = Qnil;
|
|
1752
|
-
|
|
1753
|
-
// Early exit: shorthand properties only start with m, p, b, f, or l
|
|
1754
|
-
char first_char = prop_str[0];
|
|
1755
|
-
if (first_char == 'm' || first_char == 'p' || first_char == 'b' ||
|
|
1756
|
-
first_char == 'f' || first_char == 'l') {
|
|
1757
|
-
// Potentially a shorthand - check specific property names
|
|
1758
|
-
if (strcmp(prop_str, "margin") == 0) {
|
|
1759
|
-
expanded = cataract_expand_margin(Qnil, value);
|
|
1760
|
-
} else if (strcmp(prop_str, "padding") == 0) {
|
|
1761
|
-
expanded = cataract_expand_padding(Qnil, value);
|
|
1762
|
-
} else if (strcmp(prop_str, "border") == 0) {
|
|
1763
|
-
expanded = cataract_expand_border(Qnil, value);
|
|
1764
|
-
} else if (strcmp(prop_str, "border-color") == 0) {
|
|
1765
|
-
expanded = cataract_expand_border_color(Qnil, value);
|
|
1766
|
-
} else if (strcmp(prop_str, "border-style") == 0) {
|
|
1767
|
-
expanded = cataract_expand_border_style(Qnil, value);
|
|
1768
|
-
} else if (strcmp(prop_str, "border-width") == 0) {
|
|
1769
|
-
expanded = cataract_expand_border_width(Qnil, value);
|
|
1770
|
-
} else if (strcmp(prop_str, "border-top") == 0) {
|
|
1771
|
-
expanded = cataract_expand_border_side(Qnil, USASCII_STR("top"), value);
|
|
1772
|
-
} else if (strcmp(prop_str, "border-right") == 0) {
|
|
1773
|
-
expanded = cataract_expand_border_side(Qnil, USASCII_STR("right"), value);
|
|
1774
|
-
} else if (strcmp(prop_str, "border-bottom") == 0) {
|
|
1775
|
-
expanded = cataract_expand_border_side(Qnil, USASCII_STR("bottom"), value);
|
|
1776
|
-
} else if (strcmp(prop_str, "border-left") == 0) {
|
|
1777
|
-
expanded = cataract_expand_border_side(Qnil, USASCII_STR("left"), value);
|
|
1778
|
-
} else if (strcmp(prop_str, "font") == 0) {
|
|
1779
|
-
expanded = cataract_expand_font(Qnil, value);
|
|
1780
|
-
} else if (strcmp(prop_str, "list-style") == 0) {
|
|
1781
|
-
expanded = cataract_expand_list_style(Qnil, value);
|
|
1782
|
-
} else if (strcmp(prop_str, "background") == 0) {
|
|
1783
|
-
expanded = cataract_expand_background(Qnil, value);
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
// If first_char doesn't match, expanded stays Qnil
|
|
1787
|
-
|
|
1788
|
-
// If property was expanded, iterate array and apply cascade
|
|
1789
|
-
// Expansion is rare (most properties are not shorthands)
|
|
1790
|
-
if (!NIL_P(expanded)) {
|
|
1791
|
-
Check_Type(expanded, T_ARRAY);
|
|
1792
|
-
|
|
1793
|
-
struct expand_context ctx;
|
|
1794
|
-
ctx.properties_hash = properties_hash;
|
|
1795
|
-
ctx.source_order = source_order;
|
|
1796
|
-
ctx.specificity = specificity;
|
|
1797
|
-
ctx.important = important;
|
|
1798
|
-
|
|
1799
|
-
long expanded_len = RARRAY_LEN(expanded);
|
|
1800
|
-
for (long i = 0; i < expanded_len; i++) {
|
|
1801
|
-
VALUE decl = rb_ary_entry(expanded, i);
|
|
1802
|
-
VALUE prop = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
1803
|
-
VALUE val = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
1804
|
-
flatten_expanded_callback(prop, val, (VALUE)&ctx);
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
RB_GC_GUARD(expanded);
|
|
1808
|
-
continue; // Skip processing the original shorthand property
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
// Apply CSS cascade rules
|
|
1812
|
-
VALUE existing = rb_hash_aref(properties_hash, property);
|
|
1813
|
-
|
|
1814
|
-
// In merge scenarios, properties often collide (same property in multiple rules)
|
|
1815
|
-
// so existing property is the common case
|
|
1816
|
-
if (NIL_P(existing)) {
|
|
1817
|
-
// New property - add it as array: [source_order, specificity, important, value]
|
|
1818
|
-
VALUE prop_data = rb_ary_new_capa(4);
|
|
1819
|
-
rb_ary_push(prop_data, LONG2NUM(source_order));
|
|
1820
|
-
rb_ary_push(prop_data, INT2NUM(specificity));
|
|
1821
|
-
rb_ary_push(prop_data, important);
|
|
1822
|
-
rb_ary_push(prop_data, value);
|
|
1823
|
-
rb_hash_aset(properties_hash, property, prop_data);
|
|
1824
|
-
} else {
|
|
1825
|
-
// Property exists - check cascade rules
|
|
1826
|
-
long existing_order = NUM2LONG(RARRAY_AREF(existing, PROP_SOURCE_ORDER));
|
|
1827
|
-
int existing_spec_int = NUM2INT(RARRAY_AREF(existing, PROP_SPECIFICITY));
|
|
1828
|
-
VALUE existing_important = RARRAY_AREF(existing, PROP_IMPORTANT);
|
|
1829
|
-
int existing_is_important = RTEST(existing_important);
|
|
1830
|
-
|
|
1831
|
-
int should_replace = 0;
|
|
1832
|
-
|
|
1833
|
-
// Most declarations are NOT !important
|
|
1834
|
-
if (is_important) {
|
|
1835
|
-
// New is !important - wins if existing is NOT important OR higher specificity OR (equal specificity AND later order)
|
|
1836
|
-
if (!existing_is_important || existing_spec_int < specificity ||
|
|
1837
|
-
(existing_spec_int == specificity && existing_order <= source_order)) {
|
|
1838
|
-
should_replace = 1;
|
|
1839
|
-
}
|
|
1840
|
-
} else {
|
|
1841
|
-
// New is NOT important - only wins if existing is also NOT important AND (higher specificity OR equal specificity with later order)
|
|
1842
|
-
if (!existing_is_important &&
|
|
1843
|
-
(existing_spec_int < specificity ||
|
|
1844
|
-
(existing_spec_int == specificity && existing_order <= source_order))) {
|
|
1845
|
-
should_replace = 1;
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
// Replacement is common in merge scenarios
|
|
1850
|
-
if (should_replace) {
|
|
1851
|
-
RARRAY_ASET(existing, PROP_SOURCE_ORDER, LONG2NUM(source_order));
|
|
1852
|
-
RARRAY_ASET(existing, PROP_SPECIFICITY, INT2NUM(specificity));
|
|
1853
|
-
RARRAY_ASET(existing, PROP_IMPORTANT, important);
|
|
1854
|
-
RARRAY_ASET(existing, PROP_VALUE, value);
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
RB_GC_GUARD(property);
|
|
1859
|
-
RB_GC_GUARD(value);
|
|
1860
|
-
RB_GC_GUARD(decl);
|
|
1861
|
-
source_order++;
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
RB_GC_GUARD(selector);
|
|
1865
|
-
RB_GC_GUARD(declarations);
|
|
1866
|
-
RB_GC_GUARD(rule);
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
// Create shorthand from longhand properties
|
|
1870
|
-
// Uses cached static strings to avoid runtime allocation
|
|
1871
|
-
|
|
1872
|
-
// Try to create margin shorthand
|
|
1873
|
-
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
1874
|
-
str_margin_top, str_margin_right, str_margin_bottom, str_margin_left,
|
|
1875
|
-
str_margin, cataract_create_margin_shorthand);
|
|
1876
|
-
|
|
1877
|
-
// Try to create padding shorthand
|
|
1878
|
-
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
1879
|
-
str_padding_top, str_padding_right, str_padding_bottom, str_padding_left,
|
|
1880
|
-
str_padding, cataract_create_padding_shorthand);
|
|
1881
|
-
|
|
1882
|
-
// Create border-width from individual sides
|
|
1883
|
-
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
1884
|
-
str_border_top_width, str_border_right_width, str_border_bottom_width, str_border_left_width,
|
|
1885
|
-
str_border_width, cataract_create_border_width_shorthand);
|
|
1886
|
-
|
|
1887
|
-
// Create border-style from individual sides
|
|
1888
|
-
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
1889
|
-
str_border_top_style, str_border_right_style, str_border_bottom_style, str_border_left_style,
|
|
1890
|
-
str_border_style, cataract_create_border_style_shorthand);
|
|
1891
|
-
|
|
1892
|
-
// Create border-color from individual sides
|
|
1893
|
-
TRY_CREATE_FOUR_SIDED_SHORTHAND(properties_hash,
|
|
1894
|
-
str_border_top_color, str_border_right_color, str_border_bottom_color, str_border_left_color,
|
|
1895
|
-
str_border_color, cataract_create_border_color_shorthand);
|
|
1896
|
-
|
|
1897
|
-
// Now create border shorthand from border-{width,style,color}
|
|
1898
|
-
VALUE border_width = GET_PROP_VALUE_STR(properties_hash, str_border_width);
|
|
1899
|
-
VALUE border_style = GET_PROP_VALUE_STR(properties_hash, str_border_style);
|
|
1900
|
-
VALUE border_color = GET_PROP_VALUE_STR(properties_hash, str_border_color);
|
|
1901
|
-
|
|
1902
|
-
if (!NIL_P(border_width) || !NIL_P(border_style) || !NIL_P(border_color)) {
|
|
1903
|
-
// Use first available property's metadata as reference
|
|
1904
|
-
VALUE border_data_src = !NIL_P(border_width) ? GET_PROP_DATA_STR(properties_hash, str_border_width) :
|
|
1905
|
-
!NIL_P(border_style) ? GET_PROP_DATA_STR(properties_hash, str_border_style) :
|
|
1906
|
-
GET_PROP_DATA_STR(properties_hash, str_border_color);
|
|
1907
|
-
VALUE border_important = RARRAY_AREF(border_data_src, PROP_IMPORTANT);
|
|
1908
|
-
int border_is_important = RTEST(border_important);
|
|
1909
|
-
|
|
1910
|
-
// Check that all present properties have the same !important flag
|
|
1911
|
-
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_border_width, border_is_important) &&
|
|
1912
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_border_style, border_is_important) &&
|
|
1913
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_border_color, border_is_important);
|
|
1914
|
-
|
|
1915
|
-
if (important_match) {
|
|
1916
|
-
VALUE border_props = rb_hash_new();
|
|
1917
|
-
if (!NIL_P(border_width)) rb_hash_aset(border_props, str_border_width, border_width);
|
|
1918
|
-
if (!NIL_P(border_style)) rb_hash_aset(border_props, str_border_style, border_style);
|
|
1919
|
-
if (!NIL_P(border_color)) rb_hash_aset(border_props, str_border_color, border_color);
|
|
1920
|
-
|
|
1921
|
-
VALUE border_shorthand = cataract_create_border_shorthand(Qnil, border_props);
|
|
1922
|
-
if (!NIL_P(border_shorthand)) {
|
|
1923
|
-
VALUE border_data = rb_ary_new_capa(4);
|
|
1924
|
-
rb_ary_push(border_data, RARRAY_AREF(border_data_src, PROP_SOURCE_ORDER));
|
|
1925
|
-
rb_ary_push(border_data, RARRAY_AREF(border_data_src, PROP_SPECIFICITY));
|
|
1926
|
-
rb_ary_push(border_data, border_important);
|
|
1927
|
-
rb_ary_push(border_data, border_shorthand);
|
|
1928
|
-
rb_hash_aset(properties_hash, str_border, border_data);
|
|
1929
|
-
|
|
1930
|
-
if (!NIL_P(border_width)) rb_hash_delete(properties_hash, str_border_width);
|
|
1931
|
-
if (!NIL_P(border_style)) rb_hash_delete(properties_hash, str_border_style);
|
|
1932
|
-
if (!NIL_P(border_color)) rb_hash_delete(properties_hash, str_border_color);
|
|
1933
|
-
}
|
|
1934
|
-
RB_GC_GUARD(border_props);
|
|
1935
|
-
RB_GC_GUARD(border_shorthand);
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
// Try to create font shorthand
|
|
1940
|
-
VALUE font_size = GET_PROP_VALUE_STR(properties_hash, str_font_size);
|
|
1941
|
-
VALUE font_family = GET_PROP_VALUE_STR(properties_hash, str_font_family);
|
|
1942
|
-
|
|
1943
|
-
// Font shorthand requires at least font-size and font-family
|
|
1944
|
-
if (!NIL_P(font_size) && !NIL_P(font_family)) {
|
|
1945
|
-
VALUE font_style = GET_PROP_VALUE_STR(properties_hash, str_font_style);
|
|
1946
|
-
VALUE font_variant = GET_PROP_VALUE_STR(properties_hash, str_font_variant);
|
|
1947
|
-
VALUE font_weight = GET_PROP_VALUE_STR(properties_hash, str_font_weight);
|
|
1948
|
-
VALUE line_height = GET_PROP_VALUE_STR(properties_hash, str_line_height);
|
|
1949
|
-
|
|
1950
|
-
// Get metadata from font-size as reference
|
|
1951
|
-
VALUE size_data = GET_PROP_DATA_STR(properties_hash, str_font_size);
|
|
1952
|
-
VALUE font_important = RARRAY_AREF(size_data, PROP_IMPORTANT);
|
|
1953
|
-
int font_is_important = RTEST(font_important);
|
|
1954
|
-
|
|
1955
|
-
// Check that all present properties have the same !important flag
|
|
1956
|
-
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_font_style, font_is_important) &&
|
|
1957
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_font_variant, font_is_important) &&
|
|
1958
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_font_weight, font_is_important) &&
|
|
1959
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_line_height, font_is_important) &&
|
|
1960
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_font_family, font_is_important);
|
|
1961
|
-
|
|
1962
|
-
if (important_match) {
|
|
1963
|
-
VALUE font_props = rb_hash_new();
|
|
1964
|
-
if (!NIL_P(font_style)) rb_hash_aset(font_props, str_font_style, font_style);
|
|
1965
|
-
if (!NIL_P(font_variant)) rb_hash_aset(font_props, str_font_variant, font_variant);
|
|
1966
|
-
if (!NIL_P(font_weight)) rb_hash_aset(font_props, str_font_weight, font_weight);
|
|
1967
|
-
rb_hash_aset(font_props, str_font_size, font_size);
|
|
1968
|
-
if (!NIL_P(line_height)) rb_hash_aset(font_props, str_line_height, line_height);
|
|
1969
|
-
rb_hash_aset(font_props, str_font_family, font_family);
|
|
1970
|
-
|
|
1971
|
-
VALUE font_shorthand = cataract_create_font_shorthand(Qnil, font_props);
|
|
1972
|
-
if (!NIL_P(font_shorthand)) {
|
|
1973
|
-
VALUE font_data = rb_ary_new_capa(4);
|
|
1974
|
-
rb_ary_push(font_data, RARRAY_AREF(size_data, PROP_SOURCE_ORDER));
|
|
1975
|
-
rb_ary_push(font_data, RARRAY_AREF(size_data, PROP_SPECIFICITY));
|
|
1976
|
-
rb_ary_push(font_data, font_important);
|
|
1977
|
-
rb_ary_push(font_data, font_shorthand);
|
|
1978
|
-
rb_hash_aset(properties_hash, str_font, font_data);
|
|
1979
|
-
|
|
1980
|
-
// Remove longhand properties
|
|
1981
|
-
if (!NIL_P(font_style)) rb_hash_delete(properties_hash, str_font_style);
|
|
1982
|
-
if (!NIL_P(font_variant)) rb_hash_delete(properties_hash, str_font_variant);
|
|
1983
|
-
if (!NIL_P(font_weight)) rb_hash_delete(properties_hash, str_font_weight);
|
|
1984
|
-
rb_hash_delete(properties_hash, str_font_size);
|
|
1985
|
-
if (!NIL_P(line_height)) rb_hash_delete(properties_hash, str_line_height);
|
|
1986
|
-
rb_hash_delete(properties_hash, str_font_family);
|
|
1987
|
-
}
|
|
1988
|
-
RB_GC_GUARD(font_props);
|
|
1989
|
-
RB_GC_GUARD(font_shorthand);
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
// Try to create list-style shorthand
|
|
1994
|
-
VALUE list_style_type = GET_PROP_VALUE_STR(properties_hash, str_list_style_type);
|
|
1995
|
-
VALUE list_style_position = GET_PROP_VALUE_STR(properties_hash, str_list_style_position);
|
|
1996
|
-
VALUE list_style_image = GET_PROP_VALUE_STR(properties_hash, str_list_style_image);
|
|
1997
|
-
|
|
1998
|
-
// List-style shorthand requires at least 2 properties
|
|
1999
|
-
int list_style_count = (!NIL_P(list_style_type) ? 1 : 0) +
|
|
2000
|
-
(!NIL_P(list_style_position) ? 1 : 0) +
|
|
2001
|
-
(!NIL_P(list_style_image) ? 1 : 0);
|
|
2002
|
-
|
|
2003
|
-
if (list_style_count >= 2) {
|
|
2004
|
-
// Use first available property's metadata as reference
|
|
2005
|
-
VALUE list_style_data_src = !NIL_P(list_style_type) ? GET_PROP_DATA_STR(properties_hash, str_list_style_type) :
|
|
2006
|
-
!NIL_P(list_style_position) ? GET_PROP_DATA_STR(properties_hash, str_list_style_position) :
|
|
2007
|
-
GET_PROP_DATA_STR(properties_hash, str_list_style_image);
|
|
2008
|
-
VALUE list_style_important = RARRAY_AREF(list_style_data_src, PROP_IMPORTANT);
|
|
2009
|
-
int list_style_is_important = RTEST(list_style_important);
|
|
2010
|
-
|
|
2011
|
-
// Check that all present properties have the same !important flag
|
|
2012
|
-
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_list_style_type, list_style_is_important) &&
|
|
2013
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_list_style_position, list_style_is_important) &&
|
|
2014
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_list_style_image, list_style_is_important);
|
|
2015
|
-
|
|
2016
|
-
if (important_match) {
|
|
2017
|
-
VALUE list_style_props = rb_hash_new();
|
|
2018
|
-
if (!NIL_P(list_style_type)) rb_hash_aset(list_style_props, str_list_style_type, list_style_type);
|
|
2019
|
-
if (!NIL_P(list_style_position)) rb_hash_aset(list_style_props, str_list_style_position, list_style_position);
|
|
2020
|
-
if (!NIL_P(list_style_image)) rb_hash_aset(list_style_props, str_list_style_image, list_style_image);
|
|
2021
|
-
|
|
2022
|
-
VALUE list_style_shorthand = cataract_create_list_style_shorthand(Qnil, list_style_props);
|
|
2023
|
-
if (!NIL_P(list_style_shorthand)) {
|
|
2024
|
-
VALUE list_style_data = rb_ary_new_capa(4);
|
|
2025
|
-
rb_ary_push(list_style_data, RARRAY_AREF(list_style_data_src, PROP_SOURCE_ORDER));
|
|
2026
|
-
rb_ary_push(list_style_data, RARRAY_AREF(list_style_data_src, PROP_SPECIFICITY));
|
|
2027
|
-
rb_ary_push(list_style_data, list_style_important);
|
|
2028
|
-
rb_ary_push(list_style_data, list_style_shorthand);
|
|
2029
|
-
rb_hash_aset(properties_hash, str_list_style, list_style_data);
|
|
2030
|
-
|
|
2031
|
-
// Remove longhand properties
|
|
2032
|
-
if (!NIL_P(list_style_type)) rb_hash_delete(properties_hash, str_list_style_type);
|
|
2033
|
-
if (!NIL_P(list_style_position)) rb_hash_delete(properties_hash, str_list_style_position);
|
|
2034
|
-
if (!NIL_P(list_style_image)) rb_hash_delete(properties_hash, str_list_style_image);
|
|
2035
|
-
}
|
|
2036
|
-
RB_GC_GUARD(list_style_props);
|
|
2037
|
-
RB_GC_GUARD(list_style_shorthand);
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
// Try to create background shorthand
|
|
2042
|
-
VALUE background_color = GET_PROP_VALUE_STR(properties_hash, str_background_color);
|
|
2043
|
-
VALUE background_image = GET_PROP_VALUE_STR(properties_hash, str_background_image);
|
|
2044
|
-
VALUE background_repeat = GET_PROP_VALUE_STR(properties_hash, str_background_repeat);
|
|
2045
|
-
VALUE background_attachment = GET_PROP_VALUE_STR(properties_hash, str_background_attachment);
|
|
2046
|
-
VALUE background_position = GET_PROP_VALUE_STR(properties_hash, str_background_position);
|
|
2047
|
-
|
|
2048
|
-
// Background shorthand requires at least 2 properties
|
|
2049
|
-
int background_count = (!NIL_P(background_color) ? 1 : 0) +
|
|
2050
|
-
(!NIL_P(background_image) ? 1 : 0) +
|
|
2051
|
-
(!NIL_P(background_repeat) ? 1 : 0) +
|
|
2052
|
-
(!NIL_P(background_attachment) ? 1 : 0) +
|
|
2053
|
-
(!NIL_P(background_position) ? 1 : 0);
|
|
2054
|
-
|
|
2055
|
-
if (background_count >= 2) {
|
|
2056
|
-
// Use first available property's metadata as reference
|
|
2057
|
-
VALUE background_data_src = !NIL_P(background_color) ? GET_PROP_DATA_STR(properties_hash, str_background_color) :
|
|
2058
|
-
!NIL_P(background_image) ? GET_PROP_DATA_STR(properties_hash, str_background_image) :
|
|
2059
|
-
!NIL_P(background_repeat) ? GET_PROP_DATA_STR(properties_hash, str_background_repeat) :
|
|
2060
|
-
!NIL_P(background_attachment) ? GET_PROP_DATA_STR(properties_hash, str_background_attachment) :
|
|
2061
|
-
GET_PROP_DATA_STR(properties_hash, str_background_position);
|
|
2062
|
-
VALUE background_important = RARRAY_AREF(background_data_src, PROP_IMPORTANT);
|
|
2063
|
-
int background_is_important = RTEST(background_important);
|
|
2064
|
-
|
|
2065
|
-
// Check that all present properties have the same !important flag
|
|
2066
|
-
int important_match = CHECK_IMPORTANT_MATCH(properties_hash, str_background_color, background_is_important) &&
|
|
2067
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_background_image, background_is_important) &&
|
|
2068
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_background_repeat, background_is_important) &&
|
|
2069
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_background_attachment, background_is_important) &&
|
|
2070
|
-
CHECK_IMPORTANT_MATCH(properties_hash, str_background_position, background_is_important);
|
|
2071
|
-
|
|
2072
|
-
if (important_match) {
|
|
2073
|
-
VALUE background_props = rb_hash_new();
|
|
2074
|
-
if (!NIL_P(background_color)) rb_hash_aset(background_props, str_background_color, background_color);
|
|
2075
|
-
if (!NIL_P(background_image)) rb_hash_aset(background_props, str_background_image, background_image);
|
|
2076
|
-
if (!NIL_P(background_repeat)) rb_hash_aset(background_props, str_background_repeat, background_repeat);
|
|
2077
|
-
if (!NIL_P(background_attachment)) rb_hash_aset(background_props, str_background_attachment, background_attachment);
|
|
2078
|
-
if (!NIL_P(background_position)) rb_hash_aset(background_props, str_background_position, background_position);
|
|
2079
|
-
|
|
2080
|
-
VALUE background_shorthand = cataract_create_background_shorthand(Qnil, background_props);
|
|
2081
|
-
if (!NIL_P(background_shorthand)) {
|
|
2082
|
-
VALUE background_data = rb_ary_new_capa(4);
|
|
2083
|
-
rb_ary_push(background_data, RARRAY_AREF(background_data_src, PROP_SOURCE_ORDER));
|
|
2084
|
-
rb_ary_push(background_data, RARRAY_AREF(background_data_src, PROP_SPECIFICITY));
|
|
2085
|
-
rb_ary_push(background_data, background_important);
|
|
2086
|
-
rb_ary_push(background_data, background_shorthand);
|
|
2087
|
-
rb_hash_aset(properties_hash, str_background, background_data);
|
|
2088
|
-
|
|
2089
|
-
// Remove longhand properties
|
|
2090
|
-
if (!NIL_P(background_color)) rb_hash_delete(properties_hash, str_background_color);
|
|
2091
|
-
if (!NIL_P(background_image)) rb_hash_delete(properties_hash, str_background_image);
|
|
2092
|
-
if (!NIL_P(background_repeat)) rb_hash_delete(properties_hash, str_background_repeat);
|
|
2093
|
-
if (!NIL_P(background_attachment)) rb_hash_delete(properties_hash, str_background_attachment);
|
|
2094
|
-
if (!NIL_P(background_position)) rb_hash_delete(properties_hash, str_background_position);
|
|
2095
|
-
}
|
|
2096
|
-
RB_GC_GUARD(background_props);
|
|
2097
|
-
RB_GC_GUARD(background_shorthand);
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
#undef GET_PROP_VALUE
|
|
2102
|
-
#undef GET_PROP_DATA
|
|
2103
|
-
|
|
2104
|
-
// Build merged declarations array
|
|
2105
|
-
VALUE merged_declarations = rb_ary_new();
|
|
2106
|
-
rb_hash_foreach(properties_hash, flatten_build_result_callback, merged_declarations);
|
|
2107
|
-
|
|
2108
|
-
// Determine final selector (allocate only once at the end)
|
|
2109
|
-
VALUE final_selector;
|
|
2110
|
-
if (has_nesting && all_same_selector && first_selector_ptr != NULL) {
|
|
2111
|
-
// All rules have same selector - use it for rollup
|
|
2112
|
-
final_selector = rb_usascii_str_new(first_selector_ptr, first_selector_len);
|
|
2113
|
-
} else {
|
|
2114
|
-
// Mixed selectors or no nesting - use "merged"
|
|
2115
|
-
final_selector = str_merged_selector;
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
// Create a new Stylesheet with a single merged rule
|
|
2119
|
-
// Use rb_class_new_instance instead of rb_funcall for better performance
|
|
2120
|
-
VALUE merged_sheet = rb_class_new_instance(0, NULL, cStylesheet);
|
|
2121
|
-
|
|
2122
|
-
// Create merged rule
|
|
2123
|
-
VALUE merged_rule = rb_struct_new(cRule,
|
|
2124
|
-
INT2FIX(0), // id
|
|
2125
|
-
final_selector, // selector (rolled-up or "merged")
|
|
2126
|
-
merged_declarations, // declarations
|
|
2127
|
-
Qnil, // specificity (not applicable)
|
|
2128
|
-
Qnil, // parent_rule_id (not nested)
|
|
2129
|
-
Qnil, // nesting_style (not nested)
|
|
2130
|
-
Qnil, // selector_list_id
|
|
2131
|
-
Qnil // media_query_id
|
|
2132
|
-
);
|
|
2133
|
-
|
|
2134
|
-
// Set @rules array with single merged rule (use cached ID)
|
|
2135
|
-
VALUE rules_ary = rb_ary_new_from_args(1, merged_rule);
|
|
2136
|
-
rb_ivar_set(merged_sheet, id_ivar_rules, rules_ary);
|
|
2137
|
-
|
|
2138
|
-
// Set @media_index with :all pointing to rule 0 (use cached ID)
|
|
2139
|
-
VALUE media_idx = rb_hash_new();
|
|
2140
|
-
VALUE all_ids = rb_ary_new_from_args(1, INT2FIX(0));
|
|
2141
|
-
rb_hash_aset(media_idx, ID2SYM(id_all), all_ids);
|
|
2142
|
-
rb_ivar_set(merged_sheet, id_ivar_media_index, media_idx);
|
|
2143
|
-
|
|
2144
|
-
// Guard first_selector_value: C pointer extracted via RSTRING_PTR during iteration,
|
|
2145
|
-
// then used after many allocations (hash operations, shorthand expansions) when
|
|
2146
|
-
// creating final_selector with rb_usascii_str_new
|
|
2147
|
-
RB_GC_GUARD(first_selector_value);
|
|
2148
|
-
|
|
2149
|
-
return merged_sheet;
|
|
2150
1438
|
}
|