1 /*******************************************************
2
3 CoolReader Engine
4
5 lvstsheet.cpp: style sheet implementation
6
7 (c) Vadim Lopatin, 2000-2006
8
9 This source code is distributed under the terms of
10 GNU General Public License.
11
12 See LICENSE file for details.
13
14 *******************************************************/
15
16 #include "../include/lvstsheet.h"
17 #include "../include/lvtinydom.h"
18 #include "../include/fb2def.h"
19 #include "../include/lvstream.h"
20 #include "../include/lvrend.h" // for -cr-only-if:
21
22 // define to dump all tokens
23 //#define DUMP_CSS_PARSING
24
25 #define IMPORTANT_DECL_HIGHER ((lUInt32)0x80000000U) // | to prop_code
26 #define IMPORTANT_DECL_SET ((lUInt32)0x40000000U) // | to prop_code
27 #define IMPORTANT_DECL_REMOVE ((lUInt32)0x3FFFFFFFU) // & to prop_code
28 #define IMPORTANT_DECL_SHIFT 30 // >> from prop_code to get 2 bits (importance, is_important)
29
30 enum css_decl_code {
31 cssd_unknown,
32 cssd_display,
33 cssd_white_space,
34 cssd_text_align,
35 cssd_text_align_last,
36 cssd_text_decoration,
37 cssd_text_transform,
38 cssd_hyphenate, // hyphens (proper css property name)
39 cssd_hyphenate2, // -webkit-hyphens (used by authors as an alternative to adobe-hyphenate)
40 cssd_hyphenate3, // adobe-hyphenate (used by late Adobe RMSDK)
41 cssd_hyphenate4, // adobe-text-layout (used by earlier Adobe RMSDK)
42 cssd_hyphenate5, // hyphenate (fb2? used in obsoleted css files))
43 cssd_color,
44 cssd_border_top_color,
45 cssd_border_right_color,
46 cssd_border_bottom_color,
47 cssd_border_left_color,
48 cssd_background_color,
49 cssd_vertical_align,
50 cssd_font_family, // id families like serif, sans-serif
51 cssd_font_names, // string font name like Arial, Courier
52 cssd_font_size,
53 cssd_font_style,
54 cssd_font_weight,
55 cssd_font_features, // font-feature-settings (not yet parsed)
56 cssd_font_variant, // all these are parsed specifically and mapped into
57 cssd_font_variant_ligatures, // the same style->font_features 31 bits bitmap
58 cssd_font_variant_caps,
59 cssd_font_variant_position,
60 cssd_font_variant_numeric,
61 cssd_font_variant_east_asian,
62 cssd_font_variant_alternates,
63 cssd_text_indent,
64 cssd_line_height,
65 cssd_letter_spacing,
66 cssd_width,
67 cssd_height,
68 cssd_margin_left,
69 cssd_margin_right,
70 cssd_margin_top,
71 cssd_margin_bottom,
72 cssd_margin,
73 cssd_padding_left,
74 cssd_padding_right,
75 cssd_padding_top,
76 cssd_padding_bottom,
77 cssd_padding,
78 cssd_page_break_before, // Historical, but common, page break properties names
79 cssd_page_break_after,
80 cssd_page_break_inside,
81 cssd_break_before, // Newest page break properties names
82 cssd_break_after,
83 cssd_break_inside,
84 cssd_list_style,
85 cssd_list_style_type,
86 cssd_list_style_position,
87 cssd_list_style_image,
88 cssd_border_top_style,
89 cssd_border_top_width,
90 cssd_border_right_style,
91 cssd_border_right_width,
92 cssd_border_bottom_style,
93 cssd_border_bottom_width,
94 cssd_border_left_style,
95 cssd_border_left_width,
96 cssd_border_style,
97 cssd_border_width,
98 cssd_border_color,
99 cssd_border,
100 cssd_border_top,
101 cssd_border_right,
102 cssd_border_bottom,
103 cssd_border_left,
104 cssd_background,
105 cssd_background_image,
106 cssd_background_repeat,
107 cssd_background_position,
108 cssd_background_size,
109 cssd_border_collapse,
110 cssd_border_spacing,
111 cssd_orphans,
112 cssd_widows,
113 cssd_float,
114 cssd_clear,
115 cssd_direction,
116 cssd_content,
117 cssd_cr_ignore_if_dom_version_greater_or_equal,
118 cssd_cr_hint,
119 cssd_cr_only_if,
120 cssd_stop
121 };
122
123 static const char * css_decl_name[] = {
124 "",
125 "display",
126 "white-space",
127 "text-align",
128 "text-align-last",
129 "text-decoration",
130 "text-transform",
131 "hyphens",
132 "-webkit-hyphens",
133 "adobe-hyphenate",
134 "adobe-text-layout",
135 "hyphenate",
136 "color",
137 "border-top-color",
138 "border-right-color",
139 "border-bottom-color",
140 "border-left-color",
141 "background-color",
142 "vertical-align",
143 "font-family",
144 "$dummy-for-font-names$",
145 "font-size",
146 "font-style",
147 "font-weight",
148 "font-feature-settings",
149 "font-variant",
150 "font-variant-ligatures",
151 "font-variant-caps",
152 "font-variant-position",
153 "font-variant-numeric",
154 "font-variant-east-asian",
155 "font-variant-alternates",
156 "text-indent",
157 "line-height",
158 "letter-spacing",
159 "width",
160 "height",
161 "margin-left",
162 "margin-right",
163 "margin-top",
164 "margin-bottom",
165 "margin",
166 "padding-left",
167 "padding-right",
168 "padding-top",
169 "padding-bottom",
170 "padding",
171 "page-break-before",
172 "page-break-after",
173 "page-break-inside",
174 "break-before",
175 "break-after",
176 "break-inside",
177 "list-style",
178 "list-style-type",
179 "list-style-position",
180 "list-style-image",
181 "border-top-style",
182 "border-top-width",
183 "border-right-style",
184 "border-right-width",
185 "border-bottom-style",
186 "border-bottom-width",
187 "border-left-style",
188 "border-left-width",
189 "border-style",
190 "border-width",
191 "border-color",
192 "border",
193 "border-top",
194 "border-right",
195 "border-bottom",
196 "border-left",
197 "background",
198 "background-image",
199 "background-repeat",
200 "background-position",
201 "background-size",
202 "border-collapse",
203 "border-spacing",
204 "orphans",
205 "widows",
206 "float",
207 "clear",
208 "direction",
209 "content",
210 "-cr-ignore-if-dom-version-greater-or-equal",
211 "-cr-hint",
212 "-cr-only-if",
213 NULL
214 };
215
216 // See https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes
217 enum LVCssSelectorPseudoClass
218 {
219 csspc_root, // :root
220 csspc_dir, // :dir(rtl), :dir(ltr)
221 csspc_first_child, // :first-child
222 csspc_first_of_type, // :first-of-type
223 csspc_nth_child, // :nth-child(even), :nth-child(3n+4)
224 csspc_nth_of_type, // :nth-of-type()
225 // Those after this won't be valid when checked in the initial
226 // document loading phase when the XML is being parsed, as at
227 // this point, the checked node is always the last node as we
228 // haven't yet parsed its following siblings. When meeting one,
229 // we'll need to re-render and re-check styles after load with
230 // a fully built DOM.
231 csspc_last_child, // :last-child
232 csspc_last_of_type, // :last-of-type
233 csspc_nth_last_child, // :nth-last-child()
234 csspc_nth_last_of_type, // :nth-last-of-type()
235 csspc_only_child, // :only-child
236 csspc_only_of_type, // :only-of-type
237 csspc_empty, // :empty
238 };
239
240 static const char * css_pseudo_classes[] =
241 {
242 "root",
243 "dir",
244 "first-child",
245 "first-of-type",
246 "nth-child",
247 "nth-of-type",
248 "last-child",
249 "last-of-type",
250 "nth-last-child",
251 "nth-last-of-type",
252 "only-child",
253 "only-of-type",
254 "empty",
255 NULL
256 };
257
258 // https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements
259 enum LVCssSelectorPseudoElement
260 {
261 csspe_before = 1, // ::before
262 csspe_after = 2, // ::after
263 };
264
265 static const char * css_pseudo_elements[] =
266 {
267 "before",
268 "after",
269 NULL
270 };
271
css_is_alpha(char ch)272 inline bool css_is_alpha( char ch )
273 {
274 return ( (ch>='A' && ch<='Z') || ( ch>='a' && ch<='z' ) || (ch=='-') || (ch=='_') );
275 }
276
css_is_alnum(char ch)277 inline bool css_is_alnum( char ch )
278 {
279 return ( css_is_alpha(ch) || ( ch>='0' && ch<='9' ) );
280 }
281
substr_compare(const char * sub,const char * & str)282 static int substr_compare( const char * sub, const char * & str )
283 {
284 int j;
285 for ( j=0; sub[j] == str[j] && sub[j] && str[j]; j++)
286 ;
287 if (!sub[j])
288 {
289 //bool last_alpha = css_is_alpha( sub[j-1] );
290 //bool next_alnum = css_is_alnum( str[j] );
291 if ( !css_is_alpha( sub[j-1] ) || !css_is_alnum( str[j] ) )
292 {
293 str+=j;
294 return j;
295 }
296 }
297 return 0;
298 }
299
toLower(char c)300 inline char toLower( char c )
301 {
302 if ( c>='A' && c<='Z' )
303 return c - 'A' + 'a';
304 return c;
305 }
306
substr_icompare(const char * sub,const char * & str)307 static int substr_icompare( const char * sub, const char * & str )
308 {
309 int j;
310
311 // Small optimisation: don't toLower() 'sub', as all the harcoded values
312 // we compare with are lowercase in this file.
313 // for ( j=0; toLower(sub[j]) == toLower(str[j]) && sub[j] && str[j]; j++)
314 for ( j=0; sub[j] == toLower(str[j]) && sub[j] && str[j]; j++)
315 ;
316 if (!sub[j])
317 {
318 //bool last_alpha = css_is_alpha( sub[j-1] );
319 //bool next_alnum = css_is_alnum( str[j] );
320 if ( !css_is_alpha( sub[j-1] ) || !css_is_alnum( str[j] ) )
321 {
322 str+=j;
323 return j;
324 }
325 }
326 return 0;
327 }
328
skip_spaces(const char * & str)329 static bool skip_spaces( const char * & str )
330 {
331 const char * oldpos = str;
332 for (;;) {
333 while (*str==' ' || *str=='\t' || *str=='\n' || *str == '\r')
334 str++;
335 if ( *str=='/' && str[1]=='*' ) {
336 // comment found
337 while ( *str && str[1] && (str[0]!='*' || str[1]!='/') )
338 str++;
339 if ( *str=='*' && str[1]=='/' )
340 str +=2;
341 }
342 while (*str==' ' || *str=='\t' || *str=='\n' || *str == '\r')
343 str++;
344 if (oldpos == str)
345 break;
346 if (*str == 0)
347 return false;
348 oldpos = str;
349 }
350 return *str != 0;
351 }
352
parse_property_name(const char * & res)353 static css_decl_code parse_property_name( const char * & res )
354 {
355 const char * str = res;
356 for (int i=1; css_decl_name[i]; i++)
357 {
358 if (substr_icompare( css_decl_name[i], str )) // css property case should not matter (eg: "Font-Weight:")
359 {
360 // found!
361 skip_spaces(str);
362 if ( substr_compare( ":", str )) {
363 #ifdef DUMP_CSS_PARSING
364 CRLog::trace("property name: %s", lString8(res, str-res).c_str() );
365 #endif
366 skip_spaces(str);
367 res = str;
368 return (css_decl_code)i;
369 }
370 }
371 }
372 return cssd_unknown;
373 }
374
parse_name(const char * & str,const char ** names,int def_value)375 static int parse_name( const char * & str, const char * * names, int def_value )
376 {
377 for (int i=0; names[i]; i++)
378 {
379 if (substr_icompare( names[i], str )) // css named value case should not matter (eg: "BOLD")
380 {
381 // found!
382 return i;
383 }
384 }
385 return def_value;
386 }
387
parse_important(const char * str)388 static lUInt32 parse_important( const char *str ) // does not advance the original *str
389 {
390 skip_spaces( str );
391 // "! important", with one or more spaces in between, is valid
392 if (*str == '!') {
393 str++;
394 skip_spaces( str );
395 if (substr_icompare( "important", str )) {
396 // returns directly what should be | to prop_code
397 return IMPORTANT_DECL_SET;
398 }
399 }
400 return 0;
401 }
402
next_property(const char * & str)403 static bool next_property( const char * & str )
404 {
405 // todo:
406 // https://www.w3.org/TR/CSS2/syndata.html#parsing-errors
407 // User agents must handle unexpected tokens encountered while
408 // parsing a declaration by reading until the end of the
409 // declaration, while observing the rules for matching pairs
410 // of (), [], {}, "", and '', and correctly handling escapes.
411 while (*str && *str !=';' && *str!='}')
412 str++;
413 if (*str == ';')
414 str++;
415 return skip_spaces( str );
416 }
417
next_token(const char * & str)418 static bool next_token( const char * & str )
419 {
420 // todo: as for next_property()
421 while (*str && *str !=';' && *str!='}' && *str!=' ')
422 str++;
423 if (*str == ' ') {
424 if ( skip_spaces( str ) ) {
425 if (*str && *str !=';' && *str!='}')
426 // Something else before next property or end of declaration
427 return true;
428 }
429 }
430 return false;
431 }
432
parse_integer(const char * & str,int & value)433 static bool parse_integer( const char * & str, int & value)
434 {
435 skip_spaces( str );
436 if (*str<'0' || *str>'9') {
437 return false; // not a number
438 }
439 value = 0;
440 while (*str>='0' && *str<='9') {
441 value = value*10 + (*str - '0');
442 str++;
443 }
444 return true;
445 }
446
parse_number_value(const char * & str,css_length_t & value,bool accept_percent=true,bool accept_negative=false,bool accept_auto=false,bool accept_normal=false,bool accept_contain_cover=false,bool is_font_size=false)447 static bool parse_number_value( const char * & str, css_length_t & value,
448 bool accept_percent=true,
449 bool accept_negative=false,
450 bool accept_auto=false,
451 bool accept_normal=false,
452 bool accept_contain_cover=false,
453 bool is_font_size=false )
454 {
455 const char * orig_pos = str;
456 value.type = css_val_unspecified;
457 skip_spaces( str );
458 // Here and below: named values and unit case should not matter
459 if ( substr_icompare( "inherit", str ) ) {
460 value.type = css_val_inherited;
461 value.value = 0;
462 return true;
463 }
464 if ( accept_auto && substr_icompare( "auto", str ) ) {
465 value.type = css_val_unspecified;
466 value.value = css_generic_auto;
467 return true;
468 }
469 if ( accept_normal && substr_icompare( "normal", str ) ) {
470 value.type = css_val_unspecified;
471 value.value = css_generic_normal;
472 return true;
473 }
474 if ( accept_contain_cover ) {
475 if ( substr_icompare( "contain", str ) ) {
476 value.type = css_val_unspecified;
477 value.value = css_generic_contain;
478 return true;
479 }
480 if ( substr_icompare( "cover", str ) ) {
481 value.type = css_val_unspecified;
482 value.value = css_generic_cover;
483 return true;
484 }
485 }
486 if ( is_font_size ) {
487 // Absolute-size keywords, based on the default font size (which is medium)
488 // Factors as suggested in https://drafts.csswg.org/css-fonts-3/#absolute-size-value
489 if ( substr_icompare( "medium", str ) ) {
490 value.type = css_val_rem;
491 value.value = 256;
492 return true;
493 }
494 else if ( substr_icompare( "small", str ) ) {
495 value.type = css_val_rem;
496 value.value = (int)(256 * 8/9);
497 return true;
498 }
499 else if ( substr_icompare( "x-small", str ) ) {
500 value.type = css_val_rem;
501 value.value = (int)(256 * 3/4);
502 return true;
503 }
504 else if ( substr_icompare( "xx-small", str ) ) {
505 value.type = css_val_rem;
506 value.value = (int)(256 * 3/5);
507 return true;
508 }
509 else if ( substr_icompare( "large", str ) ) {
510 value.type = css_val_rem;
511 value.value = (int)(256 * 6/5);
512 return true;
513 }
514 else if ( substr_icompare( "x-large", str ) ) {
515 value.type = css_val_rem;
516 value.value = (int)(256 * 3/2);
517 return true;
518 }
519 else if ( substr_icompare( "xx-large", str ) ) {
520 value.type = css_val_rem;
521 value.value = (int)(256 * 2);
522 return true;
523 }
524 // Approximate the (usually uneven) gaps between named sizes.
525 else if ( substr_icompare( "smaller", str ) ) {
526 value.type = css_val_percent;
527 value.value = 80 << 8;
528 return true;
529 }
530 else if ( substr_icompare( "larger", str ) ) {
531 value.type = css_val_percent;
532 value.value = 125 << 8;
533 return true;
534 }
535 }
536 bool negative = false;
537 if (accept_negative) {
538 if ( *str == '-' ) {
539 str++;
540 negative = true;
541 }
542 }
543 int n = 0;
544 if (*str != '.') {
545 if (*str<'0' || *str>'9') {
546 str = orig_pos; // revert our possible str++
547 return false; // not a number
548 }
549 while (*str>='0' && *str<='9') {
550 n = n*10 + (*str - '0');
551 str++;
552 }
553 }
554 int frac = 0;
555 int frac_div = 1;
556 if (*str == '.') {
557 str++;
558 while (*str>='0' && *str<='9')
559 {
560 // don't process more than 6 digits after decimal point
561 // to avoid overflow in case of very long values
562 if (frac_div < 1000000) {
563 frac = frac*10 + (*str - '0');
564 frac_div *= 10;
565 }
566 str++;
567 }
568 }
569 if ( substr_icompare( "em", str ) )
570 value.type = css_val_em;
571 else if ( substr_icompare( "pt", str ) )
572 value.type = css_val_pt;
573 else if ( substr_icompare( "ex", str ) )
574 value.type = css_val_ex;
575 else if ( substr_icompare( "rem", str ) )
576 value.type = css_val_rem;
577 else if ( substr_icompare( "px", str ) )
578 value.type = css_val_px;
579 else if ( substr_icompare( "in", str ) )
580 value.type = css_val_in;
581 else if ( substr_icompare( "cm", str ) )
582 value.type = css_val_cm;
583 else if ( substr_icompare( "mm", str ) )
584 value.type = css_val_mm;
585 else if ( substr_icompare( "pc", str ) )
586 value.type = css_val_pc;
587 else if ( substr_icompare( "%", str ) ) {
588 if (accept_percent)
589 value.type = css_val_percent;
590 else {
591 str = orig_pos; // revert our possible str++
592 return false;
593 }
594 }
595 else if (n == 0 && frac == 0)
596 value.type = css_val_px;
597 // allow unspecified unit (for line-height)
598 // else
599 // return false;
600
601 // The largest frac here is 999999, limited above, with a frac_div of
602 // 1000000, and even scaling it by 256 it does not overflow a 32 bit
603 // integer. The frac_div is a power of 10 so always divisible by 2 without
604 // loss when frac is non-zero.
605 value.value = n * 256 + (256 * frac + frac_div / 2 ) / frac_div; // *256
606 if (negative)
607 value.value = -value.value;
608 return true;
609 }
610
parse_nth_value(const lString32 value)611 static lString32 parse_nth_value( const lString32 value )
612 {
613 // https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child
614 // Parse "even", "odd", "5", "5n", "5n+2", "-n"...
615 // Pack 3 numbers, enough to check if match, into another lString32
616 // for quicker checking:
617 // - a tuple of 3 lChar32: (negative, n-step, offset)
618 // - or the empty string when invalid or if it would never match
619 // (Note that we get the input already trimmed and lowercased.)
620 lString32 ret = lString32(); // empty string = never match
621 if ( value == "even" ) { // = "2n"
622 ret << lChar32(0) <<lChar32(2) << lChar32(0);
623 return ret;
624 }
625 if ( value == "odd" ) { // = "2n+1"
626 ret << lChar32(0) <<lChar32(2) << lChar32(1);
627 return ret;
628 }
629 int len = value.length();
630 if (len == 0) // empty value
631 return ret; // invalid
632 bool negative = false;
633 int first = 0;
634 int second = 0;
635 int i = 0;
636 lChar32 c;
637 c = value[i];
638 if ( c == '-' ) {
639 negative = true;
640 i++;
641 }
642 if ( i==len ) // no follow up content
643 return ret; // invalid
644 c = value[i];
645 if ( c == 'n' ) { // 'n' or '-n' without a leading number
646 first = 1;
647 }
648 else {
649 // Parse first number
650 if ( c < '0' || c > '9') // not a digit
651 return ret; // invalid
652 while (true) { // grab digit(s)
653 first = first * 10 + ( c - '0' );
654 i++; // pass by this digit
655 if ( i==len ) { // single number seen: this parsed number is actually the offset
656 if ( negative ) // "-4"
657 return ret; // never match
658 ret << lChar32(0) << lChar32(0) << lChar32(first);
659 return ret;
660 }
661 c = value[i];
662 if ( c < '0' || c > '9') // done grabbing first digits
663 break;
664 }
665 if ( c != 'n' ) // invalid char after first number
666 return ret; // invalid
667 }
668 i++; // pass by that 'n'
669 if ( i==len ) { // ends with that 'n'
670 if ( negative || first == 0) // valid, but would never match anything
671 return ret; // never match
672 ret << lChar32(0) << lChar32(first) << lChar32(0);
673 return ret;
674 }
675 c = value[i];
676 if ( c != '+' ) // follow up content must start with a '+'
677 return ret; // invalid
678 i++; // pass b y that '+'
679 if ( i==len ) // ends with that '+'
680 return ret; // invalid
681 // Parse second number
682 c = value[i];
683 if ( c < '0' || c > '9') // not a digit
684 return ret; // invalid
685 while (true) { // grab digit(s)
686 second = second * 10 + ( c - '0' );
687 i++; // pass by this digit
688 if ( i==len ) // end of string, fully valid
689 break;
690 c = value[i];
691 if ( c < '0' || c > '9') // expected a digit (invalid stuff at end of value)
692 return ret; // invalid
693 }
694 // Valid, and we parsed everything
695 ret << lChar32(negative) << lChar32(first) << lChar32(second);
696 return ret;
697 }
698
match_nth_value(const lString32 value,int n)699 static bool match_nth_value( const lString32 value, int n)
700 {
701 // Apply packed parsed value (parsed by above function) to n
702 if ( value.empty() ) // invalid, or never match
703 return false;
704 bool negative = value[0];
705 int step = value[1];
706 int offset = value[2];
707 if ( step == 0 )
708 return n == offset;
709 if ( negative )
710 n = offset - n;
711 else
712 n = n - offset;
713 if ( n < 0 )
714 return false;
715 return n % step == 0;
716 }
717
718 // For some expensive LVCssSelectorRule::check() checks, that might
719 // be done on a node for multiple rules and would give the same
720 // result, we can cache the result in the node's RenderRectAccessor(),
721 // which is not used at this point and will be reset and cleared after
722 // all styles have been applied, before rendering methods are set.
723 // This is mostly useful for the :zzz-child pseudoclasses checks
724 // that involve using the expensive getUnboxedSibling methods; we
725 // are sure that no boxing is done when applying stylesheets, so
726 // the position among the parent children collection is stable
727 // and can be cached.
728 // Note that we can't cache the value 0: a field with value 0 means
729 // it has not yet been cached (we could tweak it before caching if
730 // storing 0 is needed).
cache_node_checked_property(const ldomNode * node,int property,int value)731 static void cache_node_checked_property( const ldomNode * node, int property, int value )
732 {
733 RenderRectAccessor fmt( (ldomNode*)node );
734 if ( !RENDER_RECT_HAS_FLAG(fmt, TEMP_USED_AS_CSS_CHECK_CACHE) ) {
735 // Clear it from past rendering stuff: we're processing stylesheets,
736 // which means we will soon re-render the whole DOM and have it cleared
737 // and updated. We can use it for caching other stuff until then.
738 fmt.clear();
739 RENDER_RECT_SET_FLAG(fmt, TEMP_USED_AS_CSS_CHECK_CACHE);
740 }
741 switch ( property ) {
742 // Positive integer >= 1: needs a int field
743 case csspc_nth_child:
744 fmt.setY(value);
745 break;
746 case csspc_nth_of_type:
747 fmt.setHeight(value);
748 break;
749 case csspc_nth_last_child:
750 fmt.setTopOverflow(value);
751 break;
752 case csspc_nth_last_of_type:
753 fmt.setBottomOverflow(value);
754 break;
755 // Boolean (1 means false, 2 means true): fine in a short int field
756 case csspc_first_child:
757 fmt.setX(value);
758 break;
759 case csspc_first_of_type:
760 fmt.setWidth(value);
761 break;
762 case csspc_last_child:
763 fmt.setInnerWidth(value);
764 break;
765 case csspc_last_of_type:
766 fmt.setInnerX(value);
767 break;
768 case csspc_only_child:
769 fmt.setInnerY(value);
770 break;
771 case csspc_only_of_type:
772 fmt.setBaseline(value);
773 break;
774 default:
775 break;
776 }
777 }
778
get_cached_node_checked_property(const ldomNode * node,int property,int & value)779 static bool get_cached_node_checked_property( const ldomNode * node, int property, int & value )
780 {
781 RenderRectAccessor fmt( (ldomNode*)node );
782 if ( !RENDER_RECT_HAS_FLAG(fmt, TEMP_USED_AS_CSS_CHECK_CACHE) )
783 return false; // nothing cached yet
784 bool res = false;
785 switch ( property ) {
786 // Positive integer >= 1
787 case csspc_nth_child:
788 value = fmt.getY();
789 res = value != 0;
790 break;
791 case csspc_nth_of_type:
792 value = fmt.getHeight();
793 res = value != 0;
794 break;
795 case csspc_nth_last_child:
796 value = fmt.getTopOverflow();
797 res = value != 0;
798 break;
799 case csspc_nth_last_of_type:
800 value = fmt.getBottomOverflow();
801 res = value != 0;
802 break;
803 // Boolean (1 means false, 2 means true)
804 case csspc_first_child:
805 value = fmt.getX();
806 res = value != 0;
807 break;
808 case csspc_first_of_type:
809 value = fmt.getWidth();
810 res = value != 0;
811 break;
812 case csspc_last_child:
813 value = fmt.getInnerWidth();
814 res = value != 0;
815 break;
816 case csspc_last_of_type:
817 value = fmt.getInnerX();
818 res = value != 0;
819 break;
820 case csspc_only_child:
821 value = fmt.getInnerY();
822 res = value != 0;
823 break;
824 case csspc_only_of_type:
825 value = fmt.getBaseline();
826 res = value != 0;
827 break;
828 default:
829 break;
830 }
831 return res;
832 }
833
834 struct standard_color_t
835 {
836 const char * name;
837 lUInt32 color;
838 };
839
840 standard_color_t standard_color_table[] = {
841 {"aliceblue",0xf0f8ff},
842 {"antiquewhite",0xfaebd7},
843 {"aqua",0x00ffff},
844 {"aquamarine",0x7fffd4},
845 {"azure",0xf0ffff},
846 {"beige",0xf5f5dc},
847 {"bisque",0xffe4c4},
848 {"black",0x000000},
849 {"blanchedalmond",0xffebcd},
850 {"blue",0x0000ff},
851 {"blueviolet",0x8a2be2},
852 {"brown",0xa52a2a},
853 {"burlywood",0xdeb887},
854 {"cadetblue",0x5f9ea0},
855 {"chartreuse",0x7fff00},
856 {"chocolate",0xd2691e},
857 {"coral",0xff7f50},
858 {"cornflowerblue",0x6495ed},
859 {"cornsilk",0xfff8dc},
860 {"crimson",0xdc143c},
861 {"cyan",0x00ffff},
862 {"darkblue",0x00008b},
863 {"darkcyan",0x008b8b},
864 {"darkgoldenrod",0xb8860b},
865 {"darkgray",0xa9a9a9},
866 {"darkgreen",0x006400},
867 {"darkgrey",0xa9a9a9},
868 {"darkkhaki",0xbdb76b},
869 {"darkmagenta",0x8b008b},
870 {"darkolivegreen",0x556b2f},
871 {"darkorange",0xff8c00},
872 {"darkorchid",0x9932cc},
873 {"darkred",0x8b0000},
874 {"darksalmon",0xe9967a},
875 {"darkseagreen",0x8fbc8f},
876 {"darkslateblue",0x483d8b},
877 {"darkslategray",0x2f4f4f},
878 {"darkslategrey",0x2f4f4f},
879 {"darkturquoise",0x00ced1},
880 {"darkviolet",0x9400d3},
881 {"deeppink",0xff1493},
882 {"deepskyblue",0x00bfff},
883 {"dimgray",0x696969},
884 {"dimgrey",0x696969},
885 {"dodgerblue",0x1e90ff},
886 {"firebrick",0xb22222},
887 {"floralwhite",0xfffaf0},
888 {"forestgreen",0x228b22},
889 {"fuchsia",0xff00ff},
890 {"gainsboro",0xdcdcdc},
891 {"ghostwhite",0xf8f8ff},
892 {"gold",0xffd700},
893 {"goldenrod",0xdaa520},
894 {"gray",0x808080},
895 {"green",0x008000},
896 {"grey",0x808080},
897 {"greenyellow",0xadff2f},
898 {"honeydew",0xf0fff0},
899 {"hotpink",0xff69b4},
900 {"indianred",0xcd5c5c},
901 {"indigo",0x4b0082},
902 {"ivory",0xfffff0},
903 {"khaki",0xf0e68c},
904 {"lavender",0xe6e6fa},
905 {"lavenderblush",0xfff0f5},
906 {"lawngreen",0x7cfc00},
907 {"lemonchiffon",0xfffacd},
908 {"lightblue",0xadd8e6},
909 {"lightcoral",0xf08080},
910 {"lightcyan",0xe0ffff},
911 {"lightgoldenrodyellow",0xfafad2},
912 {"lightgray",0xd3d3d3},
913 {"lightgreen",0x90ee90},
914 {"lightgrey",0xd3d3d3},
915 {"lightpink",0xffb6c1},
916 {"lightsalmon",0xffa07a},
917 {"lightseagreen",0x20b2aa},
918 {"lightskyblue",0x87cefa},
919 {"lightslategray",0x778899},
920 {"lightslategrey",0x778899},
921 {"lightsteelblue",0xb0c4de},
922 {"lightyellow",0xffffe0},
923 {"lime",0x00ff00},
924 {"limegreen",0x32cd32},
925 {"linen",0xfaf0e6},
926 {"magenta",0xff00ff},
927 {"maroon",0x800000},
928 {"mediumaquamarine",0x66cdaa},
929 {"mediumblue",0x0000cd},
930 {"mediumorchid",0xba55d3},
931 {"mediumpurple",0x9370db},
932 {"mediumseagreen",0x3cb371},
933 {"mediumslateblue",0x7b68ee},
934 {"mediumspringgreen",0x00fa9a},
935 {"mediumturquoise",0x48d1cc},
936 {"mediumvioletred",0xc71585},
937 {"midnightblue",0x191970},
938 {"mintcream",0xf5fffa},
939 {"mistyrose",0xffe4e1},
940 {"moccasin",0xffe4b5},
941 {"navajowhite",0xffdead},
942 {"navy",0x000080},
943 {"oldlace",0xfdf5e6},
944 {"olive",0x808000},
945 {"olivedrab",0x6b8e23},
946 {"orange",0xffa500},
947 {"orangered",0xff4500},
948 {"orchid",0xda70d6},
949 {"palegoldenrod",0xeee8aa},
950 {"palegreen",0x98fb98},
951 {"paleturquoise",0xafeeee},
952 {"palevioletred",0xdb7093},
953 {"papayawhip",0xffefd5},
954 {"peachpuff",0xffdab9},
955 {"peru",0xcd853f},
956 {"pink",0xffc0cb},
957 {"plum",0xdda0dd},
958 {"powderblue",0xb0e0e6},
959 {"purple",0x800080},
960 {"rebeccapurple",0x663399},
961 {"red",0xff0000},
962 {"rosybrown",0xbc8f8f},
963 {"royalblue",0x4169e1},
964 {"saddlebrown",0x8b4513},
965 {"salmon",0xfa8072},
966 {"sandybrown",0xf4a460},
967 {"seagreen",0x2e8b57},
968 {"seashell",0xfff5ee},
969 {"sienna",0xa0522d},
970 {"silver",0xc0c0c0},
971 {"skyblue",0x87ceeb},
972 {"slateblue",0x6a5acd},
973 {"slategray",0x708090},
974 {"slategrey",0x708090},
975 {"snow",0xfffafa},
976 {"springgreen",0x00ff7f},
977 {"steelblue",0x4682b4},
978 {"tan",0xd2b48c},
979 {"teal",0x008080},
980 {"thistle",0xd8bfd8},
981 {"tomato",0xff6347},
982 {"turquoise",0x40e0d0},
983 {"violet",0xee82ee},
984 {"wheat",0xf5deb3},
985 {"white",0xffffff},
986 {"whitesmoke",0xf5f5f5},
987 {"yellow",0xffff00},
988 {"yellowgreen",0x9acd32},
989 {NULL, 0}
990 };
991
hexDigit(char c)992 static int hexDigit( char c )
993 {
994 if ( c >= '0' && c <= '9' )
995 return c-'0';
996 if ( c >= 'A' && c <= 'F' )
997 return c - 'A' + 10;
998 if ( c >= 'a' && c <= 'f' )
999 return c - 'a' + 10;
1000 return -1;
1001 }
1002
parse_color_value(const char * & str,css_length_t & value)1003 bool parse_color_value( const char * & str, css_length_t & value )
1004 {
1005 // Does not support "rgb(0, 127, 255)" nor "rgba(0,127,255)"
1006 const char * orig_pos = str;
1007 value.type = css_val_unspecified;
1008 skip_spaces( str );
1009 if ( substr_icompare( "transparent", str ) ) {
1010 // Make it an invalid color, but a valid parsing so it
1011 // can be inherited or flagged with !important
1012 value.type = css_val_unspecified;
1013 value.value = css_generic_transparent;
1014 return true;
1015 }
1016 if ( substr_compare( "inherit", str ) )
1017 {
1018 value.type = css_val_inherited;
1019 value.value = 0;
1020 return true;
1021 }
1022 if ( substr_compare( "none", str ) )
1023 {
1024 value.type = css_val_unspecified;
1025 value.value = 0;
1026 return true;
1027 }
1028 if (*str=='#') {
1029 // #rgb or #rrggbb colors
1030 str++;
1031 int nDigits = 0;
1032 for ( ; hexDigit(str[nDigits])>=0; nDigits++ )
1033 ;
1034 if ( nDigits==3 ) {
1035 int r = hexDigit( *str++ );
1036 int g = hexDigit( *str++ );
1037 int b = hexDigit( *str++ );
1038 value.type = css_val_color;
1039 value.value = (((r + r*16) * 256) | (g + g*16)) * 256 | (b + b*16);
1040 return true;
1041 } else if ( nDigits==6 ) {
1042 int r = hexDigit( *str++ ) * 16;
1043 r += hexDigit( *str++ );
1044 int g = hexDigit( *str++ ) * 16;
1045 g += hexDigit( *str++ );
1046 int b = hexDigit( *str++ ) * 16;
1047 b += hexDigit( *str++ );
1048 value.type = css_val_color;
1049 value.value = ((r * 256) | g) * 256 | b;
1050 return true;
1051 } else {
1052 str = orig_pos; // revert our possible str++
1053 return false;
1054 }
1055 }
1056 for ( int i=0; standard_color_table[i].name != NULL; i++ ) {
1057 if ( substr_icompare( standard_color_table[i].name, str ) ) {
1058 value.type = css_val_color;
1059 value.value = standard_color_table[i].color;
1060 return true;
1061 }
1062 }
1063 str = orig_pos; // revert our possible str++
1064 return false;
1065 }
1066
1067 // Parse a CSS "content:" property into an intermediate format single string.
parse_content_property(const char * & str,lString32 & parsed_content)1068 bool parse_content_property( const char * & str, lString32 & parsed_content)
1069 {
1070 // https://developer.mozilla.org/en-US/docs/Web/CSS/content
1071 // The property may have multiple tokens:
1072 // p::before { content: "[" attr(n) "]"; }
1073 // content: "Qq. " attr(qq)
1074 // content: '\201D\ In: ';
1075 // We can meet some bogus values: content: "∙ ";
1076 // or values we don't support: Firefox would drop the whole
1077 // declaration, but, as we don't support all those from the
1078 // specs, we'll just ignore the tokens we don't support.
1079 // We parse the original content into a "parsed content" string,
1080 // consisting of a first letter, indicating its type, and if some
1081 // data: its length (+1 to avoid NULLs in strings) and that data.
1082 // parsed_content may contain multiple values, in the format
1083 // 'X' for 'none' (or 'normal', = none with pseudo elements)
1084 // 's' + <len> + string32 (string content) for ""
1085 // 'a' + <len> + string32 (attribute name) for attr()
1086 // 'Q' for 'open-quote'
1087 // 'q' for 'close-quote'
1088 // 'N' for 'no-open-quote'
1089 // 'n' for 'no-close-quote'
1090 // 'u' for 'url()', that we don't support
1091 // 'z' for unsupported tokens, like gradient()...
1092 // '$' (at start) this content needs post processing before
1093 // being applied to a node's style (needed with quotes,
1094 // to get the correct char for the current nested level).
1095 // Note: this parsing might not be super robust with
1096 // convoluted declarations...
1097 parsed_content.clear();
1098 const char * orig_pos = str;
1099 // The presence of a single 'none' or 'normal' among multiple
1100 // values make the whole thing 'none'.
1101 bool has_none = false;
1102 bool needs_processing_when_applying = false;
1103 while ( skip_spaces( str ) && *str!=';' && *str!='}' && *str!='!' ) {
1104 if ( substr_icompare("none", str) ) {
1105 has_none = true;
1106 continue; // continue parsing
1107 }
1108 else if ( substr_icompare("normal", str) ) {
1109 // Computes to 'none' for pseudo elements
1110 has_none = true;
1111 continue; // continue parsing
1112 }
1113 else if ( substr_icompare("open-quote", str) ) {
1114 parsed_content << U'Q';
1115 needs_processing_when_applying = true;
1116 continue;
1117 }
1118 else if ( substr_icompare("close-quote", str) ) {
1119 parsed_content << U'q';
1120 needs_processing_when_applying = true;
1121 continue;
1122 }
1123 else if ( substr_icompare("no-open-quote", str) ) {
1124 parsed_content << U'N';
1125 needs_processing_when_applying = true;
1126 continue;
1127 }
1128 else if ( substr_icompare("no-close-quote", str) ) {
1129 parsed_content << U'n';
1130 needs_processing_when_applying = true;
1131 continue;
1132 }
1133 else if ( substr_icompare("attr", str) ) {
1134 if ( *str == '(' ) {
1135 str++;
1136 skip_spaces( str );
1137 lString8 attr8;
1138 while ( *str && *str!=')' ) {
1139 attr8 << *str;
1140 str++;
1141 }
1142 if ( *str == ')' ) {
1143 str++;
1144 lString32 attr = Utf8ToUnicode(attr8);
1145 attr.trim();
1146 parsed_content << U'a';
1147 parsed_content << lChar32(attr.length() + 1); // (+1 to avoid storing \x00)
1148 parsed_content << attr;
1149 continue;
1150 }
1151 // No closing ')': invalid
1152 }
1153 }
1154 else if ( substr_icompare("url", str) ) {
1155 // Unsupported for now, but parse it
1156 if ( *str == '(' ) {
1157 str++;
1158 skip_spaces( str );
1159 lString8 url8;
1160 while ( *str && *str!=')' ) {
1161 url8 << *str;
1162 str++;
1163 }
1164 if ( *str == ')' ) {
1165 str++;
1166 parsed_content << U'u';
1167 continue;
1168 }
1169 // No closing ')': invalid
1170 }
1171 }
1172 else if ( *str == '"' || *str == '\'' ) {
1173 // https://developer.mozilla.org/en-US/docs/Web/CSS/string
1174 // https://www.w3.org/TR/CSS2/syndata.html#strings
1175 // https://drafts.csswg.org/css-values-3/#strings
1176 char quote_ch = *str;
1177 str++;
1178 lString8 str8; // quoted string content (as UTF8, like original stylesheet)
1179 while ( *str && *str != quote_ch ) {
1180 if ( *str == '\\' ) {
1181 // https://www.w3.org/TR/CSS2/syndata.html#characters
1182 str++;
1183 if ( hexDigit(*str) >= 0 ) {
1184 lUInt32 codepoint = 0;
1185 int num_digits = 0;
1186 while ( num_digits < 6 ) {
1187 int v = hexDigit(*str);
1188 if ( v >= 0 ) {
1189 codepoint = (codepoint << 4) + v;
1190 num_digits++;
1191 str++;
1192 continue;
1193 }
1194 // Not a hex digit
1195 break;
1196 }
1197 if ( num_digits < 6 && *str == ' ' ) // skip space following a non-6-hex-digits
1198 str++;
1199 if ( codepoint == 0 || codepoint > 0x10FFFF ) {
1200 // zero not allowed, and should be under max valid unicode codepoint
1201 codepoint = 0xFFFD; // replacement character
1202 }
1203 // Serialize it as UTF-8
1204 lString32 c;
1205 c << (lChar32)codepoint;
1206 str8 << UnicodeToUtf8(c);
1207 }
1208 else if ( *str == '\r' && *(str+1) == '\n' ) {
1209 // Ignore \ at end of CRLF line
1210 str += 2;
1211 }
1212 else if ( *str == '\n' ) {
1213 // Ignore \ at end of line
1214 str++;
1215 }
1216 else {
1217 // Accept next char as is
1218 str8 << *str;
1219 str++;
1220 }
1221 }
1222 else {
1223 str8 << *str;
1224 str++;
1225 }
1226 // todo:
1227 // https://www.w3.org/TR/CSS2/syndata.html#parsing-errors
1228 // "User agents must close strings upon reaching the end
1229 // of a line (i.e., before an unescaped line feed, carriage
1230 // return or form feed character), but then drop the construct
1231 // (declaration or rule) in which the string was found."
1232 }
1233 if ( *str == quote_ch ) {
1234 lString32 str32 = Utf8ToUnicode(str8);
1235 parsed_content << U's';
1236 parsed_content << lChar32(str32.length() + 1); // (+1 to avoid storing \x00)
1237 parsed_content << str32;
1238 str++;
1239 continue;
1240 }
1241 }
1242 else {
1243 // Not supported
1244 parsed_content << U'z';
1245 next_token(str);
1246 }
1247 }
1248 if ( has_none ) {
1249 // Forget all other tokens parsed
1250 parsed_content.clear();
1251 parsed_content << U'X';
1252 }
1253 else if ( needs_processing_when_applying ) {
1254 parsed_content.insert(0, 1, U'$');
1255 }
1256 if (*str) // something (;, } or !important) follows
1257 return true;
1258 // Restore original position if we reach end of CSS string,
1259 // as it might just be missing a ')' or closing quote: we'll
1260 // be skipping up to next ; or }, and might manage with
1261 // the rest of the string.
1262 str = orig_pos;
1263 return false;
1264 }
1265
1266 /// Update a style->content, post processed for its node
update_style_content_property(css_style_rec_t * style,ldomNode * node)1267 void update_style_content_property( css_style_rec_t * style, ldomNode * node ) {
1268 // We don't want to update too much: styles are hashed and shared by
1269 // multiple nodes. We don't resolve "attr()" here as attributes are
1270 // stable (and "attr(id)" would make all style->content different
1271 // and prevent styles from being shared, increasing the number
1272 // of styles to cache).
1273 // But we need to resolve quotes, according to their nesting level,
1274 // and transform them into a litteral string 's'.
1275
1276 if ( style->content.empty() || style->content[0] != U'$' ) {
1277 // No update needed
1278 return;
1279 }
1280
1281 // We need to know if this node is visible: if not, quotes nested
1282 // level should not be updated. We might want to still include
1283 // the computed quote (with quote char for level 1) for it to be
1284 // displayed by writeNodeEx() when displaying the HTML, even if
1285 // the node is invisible.
1286 bool visible = style->display != css_d_none;
1287 if ( visible ) {
1288 ldomNode * n = node->getParentNode();
1289 for ( ; !n->isRoot(); n = n->getParentNode() ) {
1290 if ( n->getStyle()->display == css_d_none ) {
1291 visible = false;
1292 break;
1293 }
1294 }
1295 }
1296
1297 // We do not support specifying quote chars to be used via CSS "quotes":
1298 // :root { quotes: '\201c' '\201d' '\2018' '\2019'; }
1299 // We use the ones hardcoded for the node lang tag language (or default
1300 // typography language) provided by TextLangCfg.
1301 // HTML5 default CSS specifies them with:
1302 // :root:lang(af), :not(:lang(af)) > :lang(af) { quotes: '\201c' '\201d' '\2018' '\2019' }
1303 // This might (or not) implies that nested levels are reset when entering
1304 // text with another language, so this new language first level quote is used.
1305 // We can actually get that same behaviour by having each TextLangCfg manage
1306 // its own nesting level (which won't be reset when en>fr>en, though).
1307 // But all this is quite rare, so don't bother about it much.
1308 TextLangCfg * lang_cfg = TextLangMan::getTextLangCfg( node );
1309
1310 // Note: some quote char like (U+201C / U+201D) seem to not be mirrored
1311 // (when using HarfBuzz) when added to some RTL arabic text. But it
1312 // appears that way with Firefox too!
1313 // But if we use another char (U+00AB / U+00BB), it gets mirrored correctly.
1314 // Might be that HarfBuzz first substitute it with arabic quotes (which
1315 // happen to look inverted), and then mirror that?
1316
1317 lString32 res;
1318 lString32 parsed_content = style->content;
1319 lString32 quote;
1320 int i = 1; // skip initial '$'
1321 int parsed_content_len = parsed_content.length();
1322 while ( i < parsed_content_len ) {
1323 lChar32 ctype = parsed_content[i];
1324 if ( ctype == 's' ) { // literal string: copy as-is
1325 lChar32 len = parsed_content[i] - 1; // (remove added +1)
1326 res.append(parsed_content, i, len+2);
1327 i += len+2;
1328 }
1329 else if ( ctype == 'a' ) { // attribute value: copy as-is
1330 lChar32 len = parsed_content[i] - 1; // (remove added +1)
1331 res.append(parsed_content, i, len+2);
1332 i += len+2;
1333 }
1334 else if ( ctype == 'Q' ) { // open-quote
1335 quote = lang_cfg->getOpeningQuote(visible);
1336 // length+1 as expected with 's' by get_applied_content_property()
1337 res << U's' << lChar32(quote.length() + 1) << quote;
1338 i += 1;
1339 }
1340 else if ( ctype == 'q' ) { // close-quote
1341 quote = lang_cfg->getClosingQuote(visible);
1342 // length+1 as expected with 's' by get_applied_content_property()
1343 res << U's' << lChar32(quote.length() + 1) << quote;
1344 i += 1;
1345 }
1346 else if ( ctype == 'N' ) { // no-open-quote
1347 // This should just increment nested quote level and output nothing.
1348 lang_cfg->getOpeningQuote(visible);
1349 i += 1;
1350 }
1351 else if ( ctype == 'n' ) { // no-close-quote
1352 // This should just increment nested quote level and output nothing.
1353 lang_cfg->getClosingQuote(visible);
1354 i += 1;
1355 }
1356 else {
1357 // All other stuff are single char (u, z, X) or unsupported/bogus char.
1358 res.append(parsed_content, i, 1);
1359 i += 1;
1360 }
1361 }
1362 // Replace style->content with what we built
1363 style->content = res;
1364 }
1365
1366 /// Returns the computed value for a node from its parsed CSS "content:" value
get_applied_content_property(ldomNode * node)1367 lString32 get_applied_content_property( ldomNode * node ) {
1368 lString32 res;
1369 css_style_ref_t style = node->getStyle();
1370 lString32 parsed_content = style->content;
1371 if ( parsed_content.empty() )
1372 return res;
1373 int i = 0;
1374 int parsed_content_len = parsed_content.length();
1375 while ( i < parsed_content_len ) {
1376 lChar32 ctype = parsed_content[i++];
1377 if ( ctype == 's' ) { // literal string
1378 lChar32 len = parsed_content[i++] - 1; // (remove added +1)
1379 res << parsed_content.substr(i, len);
1380 i += len;
1381 }
1382 else if ( ctype == 'a' ) { // attribute value
1383 lChar32 len = parsed_content[i++] - 1; // (remove added +1)
1384 lString32 attr_name = parsed_content.substr(i, len);
1385 i += len;
1386 ldomNode * attrNode = node;
1387 if ( node->getNodeId() == el_pseudoElem ) {
1388 // For attributes, we should pick them from the parent of the added pseudo element
1389 attrNode = node->getUnboxedParent();
1390 }
1391 if ( attrNode )
1392 res << attrNode->getAttributeValue(attr_name.c_str());
1393 }
1394 else if ( ctype == 'u' ) { // url
1395 // Url to image: we can't easily support that, as our
1396 // image support needs a reference to a node, and we
1397 // don't have a node here.
1398 // Show a small square so one can see there's something
1399 // that is missing, something different enough from the
1400 // classic tofu char so we can distinguish it.
1401 // res << 0x25FD; // WHITE MEDIUM SMALL SQUARE
1402 res << 0x2B26; // WHITE MEDIUM DIAMOND
1403 }
1404 else if ( ctype == 'X' ) { // 'none'
1405 res.clear(); // should be standalone, but let's be sure
1406 break;
1407 }
1408 else if ( ctype == 'z' ) { // unsupported token
1409 // Just ignore it, don't show anything
1410 }
1411 else if ( ctype == 'Q' ) { // open-quote
1412 // Shouldn't happen: replaced earlier by update_style_content_property()
1413 }
1414 else if ( ctype == 'q' ) { // close-quote
1415 // Shouldn't happen: replaced earlier by update_style_content_property()
1416 }
1417 else if ( ctype == 'N' ) { // no-open-quote
1418 // Shouldn't happen: replaced earlier by update_style_content_property()
1419 }
1420 else if ( ctype == 'n' ) { // no-close-quote
1421 // Shouldn't happen: replaced earlier by update_style_content_property()
1422 }
1423 else { // unexpected
1424 break;
1425 }
1426 }
1427 if ( style->white_space < css_ws_pre_line ) {
1428 // Remove consecutive spaces (although this might be handled well by
1429 // lvtextfm) and '\n' - but we should keep leading and trailing spaces.
1430 res.trimDoubleSpaces(true, true, false);
1431 }
1432 return res;
1433 }
1434
resolve_url_path(lString8 & str,lString32 codeBase)1435 static void resolve_url_path( lString8 & str, lString32 codeBase ) {
1436 // A URL (path to local or container's file) must be resolved
1437 // at parsing time, as it is related to this stylesheet file
1438 // path (and not to the HTML files that are linking to this
1439 // stylesheet) - it wouldn't be possible to resolve it later.
1440 lString32 path = Utf8ToUnicode(str);
1441 path.trim();
1442 if (path.startsWithNoCase(lString32("url"))) path = path.substr(3);
1443 path.trim();
1444 if (path.startsWith(U"(")) path = path.substr(1);
1445 if (path.endsWith(U")")) path = path.substr(0, path.length() - 1);
1446 path.trim();
1447 if (path.startsWith(U"\"") || path.startsWith(U"'")) path = path.substr(1);
1448 if (path.endsWith(U"\"") || path.endsWith(U"'")) path = path.substr(0, path.length() - 1);
1449 path.trim();
1450 if (path.startsWith(lString32("data:image"))) {
1451 // base64 encoded image: leave as-is
1452 }
1453 else {
1454 // We assume it's a path to a local file in the container, so we don't try
1455 // to check if it's a remote url (as we can't fetch its content anyway).
1456 if ( !codeBase.empty() ) {
1457 path = LVCombinePaths( codeBase, path );
1458 }
1459 }
1460 // printf("url: [%s]+%s => %s\n", UnicodeToLocal(codeBase).c_str(), str.c_str(), UnicodeToUtf8(path).c_str());
1461 str = UnicodeToUtf8(path);
1462 }
1463
1464 // The order of items in following tables should match the order in the enums in include/cssdef.h
1465 static const char * css_d_names[] =
1466 {
1467 "inherit",
1468 "ruby",
1469 "run-in",
1470 "inline",
1471 "block",
1472 "-cr-list-item-final", // non-standard, legacy crengine rendering of list items as final: css_d_list_item_legacy
1473 "list-item", // correct rendering of list items as block: css_d_list_item_block
1474 "inline-block",
1475 "inline-table",
1476 "table",
1477 "table-row-group",
1478 "table-header-group",
1479 "table-footer-group",
1480 "table-row",
1481 "table-column-group",
1482 "table-column",
1483 "table-cell",
1484 "table-caption",
1485 "none",
1486 NULL
1487 };
1488
1489 static const char * css_ws_names[] =
1490 {
1491 "inherit",
1492 "normal",
1493 "nowrap",
1494 "pre-line",
1495 "pre",
1496 "pre-wrap",
1497 "break-spaces",
1498 NULL
1499 };
1500
1501 static const char * css_ta_names[] =
1502 {
1503 "inherit",
1504 "left",
1505 "right",
1506 "center",
1507 "justify",
1508 "start",
1509 "end",
1510 "auto",
1511 "-cr-left-if-not-first",
1512 "-cr-right-if-not-first",
1513 "-cr-center-if-not-first",
1514 "-cr-justify-if-not-first",
1515 "-cr-start-if-not-first",
1516 "-cr-end-if-not-first",
1517 NULL
1518 };
1519
1520 static const char * css_td_names[] =
1521 {
1522 "inherit",
1523 "none",
1524 "underline",
1525 "overline",
1526 "line-through",
1527 "blink",
1528 NULL
1529 };
1530
1531 static const char * css_tt_names[] =
1532 {
1533 "inherit",
1534 "none",
1535 "uppercase",
1536 "lowercase",
1537 "capitalize",
1538 "full-width",
1539 NULL
1540 };
1541
1542 // All these css_hyph_names* should map original properties in this order:
1543 // 1st value: inherit
1544 // 2nd value: no hyphenation
1545 // 3rd value: use the hyphenation method set to HyphMan
1546 // See https://github.com/readium/readium-css/blob/master/docs/CSS21-epub_compat.md
1547 // for documentation about the obscure properties.
1548 //
1549 // For "hyphens:", "hyphenate:" (fb2? also used by obsoleted css files)
1550 // No support for "hyphens: manual" (this would involve toggling the hyphenation
1551 // method from what it is set to SoftHyphensHyph locally)
1552 static const char * css_hyph_names[] =
1553 {
1554 "inherit",
1555 "none",
1556 "auto",
1557 NULL
1558 };
1559 // For "adobe-text-layout:" (for documents made for Adobe RMSDK)
1560 static const char * css_hyph_names2[] =
1561 {
1562 "inherit",
1563 "optimizespeed",
1564 "optimizequality",
1565 NULL
1566 };
1567 // For "adobe-hyphenate:"
1568 static const char * css_hyph_names3[] =
1569 {
1570 "inherit",
1571 "none",
1572 "explicit", // this may wrong, as it's supposed to be like "hyphens: manual"
1573 NULL
1574 };
1575
1576 static const char * css_pb_names[] =
1577 {
1578 "inherit",
1579 "auto",
1580 "avoid", // those after this one are not supported by page-break-inside
1581 "always",
1582 "left",
1583 "right",
1584 "page",
1585 "recto",
1586 "verso",
1587 NULL
1588 };
1589
1590 static const char * css_fs_names[] =
1591 {
1592 "inherit",
1593 "normal",
1594 "italic",
1595 "oblique",
1596 NULL
1597 };
1598
1599 static const char * css_fw_names[] =
1600 {
1601 "inherit",
1602 "normal",
1603 "bold",
1604 "bolder",
1605 "lighter",
1606 "100",
1607 "200",
1608 "300",
1609 "400",
1610 "500",
1611 "600",
1612 "700",
1613 "800",
1614 "900",
1615 NULL
1616 };
1617 static const char * css_va_names[] =
1618 {
1619 "inherit",
1620 "baseline",
1621 "sub",
1622 "super",
1623 "top",
1624 "text-top",
1625 "middle",
1626 "bottom",
1627 "text-bottom",
1628 NULL
1629 };
1630
1631 static const char * css_ti_attribute_names[] =
1632 {
1633 "hanging",
1634 NULL
1635 };
1636
1637 static const char * css_ff_names[] =
1638 {
1639 "inherit",
1640 "serif",
1641 "sans-serif",
1642 "cursive",
1643 "fantasy",
1644 "monospace",
1645 NULL
1646 };
1647
1648 static const char * css_lst_names[] =
1649 {
1650 "inherit",
1651 "disc",
1652 "circle",
1653 "square",
1654 "decimal",
1655 "lower-roman",
1656 "upper-roman",
1657 "lower-alpha",
1658 "upper-alpha",
1659 "none",
1660 NULL
1661 };
1662
1663 static const char * css_lsp_names[] =
1664 {
1665 "inherit",
1666 "inside",
1667 "outside",
1668 NULL
1669 };
1670 ///border style names
1671 static const char * css_bst_names[]={
1672 "solid",
1673 "dotted",
1674 "dashed",
1675 "double",
1676 "groove",
1677 "ridge",
1678 "inset",
1679 "outset",
1680 "none",
1681 NULL
1682 };
1683 ///border width value names
1684 static const char * css_bw_names[]={
1685 "thin",
1686 "medium",
1687 "thick",
1688 "initial",
1689 "inherit",
1690 NULL
1691 };
1692
1693 //background repeat names
1694 static const char * css_bg_repeat_names[]={
1695 "repeat",
1696 "repeat-x",
1697 "repeat-y",
1698 "no-repeat",
1699 "initial",
1700 "inherit",
1701 NULL
1702 };
1703 //background attachment names
1704 static const char * css_bg_attachment_names[]={
1705 "scroll",
1706 "fixed",
1707 "local",
1708 "initial",
1709 "inherit",
1710 NULL
1711 };
1712 //background position names
1713 static const char * css_bg_position_names[]={
1714 "left top", // 0
1715 "left center",
1716 "left bottom",
1717 "right top",
1718 "right center",
1719 "right bottom",
1720 "center top",
1721 "center center",
1722 "center bottom", // 8
1723 "top left", // 9
1724 "center left",
1725 "bottom left",
1726 "top right",
1727 "center right",
1728 "bottom right",
1729 "top center",
1730 "center center",
1731 "bottom center", // 17
1732 "center", // 18
1733 "left",
1734 "right",
1735 "top",
1736 "bottom",
1737 "initial", // 23
1738 "inherit", // 24
1739 NULL
1740 };
1741
1742 //border-collpase names
1743 static const char * css_bc_names[]={
1744 "separate",
1745 "collapse",
1746 "initial",
1747 "inherit",
1748 NULL
1749 };
1750
1751 // orphans and widows values (supported only if in range 1-9)
1752 // https://drafts.csswg.org/css-break-3/#widows-orphans
1753 // "Negative values and zero are invalid and must cause the declaration to be ignored."
1754 static const char * css_orphans_widows_names[]={
1755 "inherit",
1756 "1",
1757 "2",
1758 "3",
1759 "4",
1760 "5",
1761 "6",
1762 "7",
1763 "8",
1764 "9",
1765 NULL
1766 };
1767
1768 // float value names
1769 static const char * css_f_names[] =
1770 {
1771 "inherit",
1772 "none",
1773 "left",
1774 "right",
1775 NULL
1776 };
1777
1778 // clear value names
1779 static const char * css_c_names[] =
1780 {
1781 "inherit",
1782 "none",
1783 "left",
1784 "right",
1785 "both",
1786 NULL
1787 };
1788
1789 // direction value names
1790 static const char * css_dir_names[] =
1791 {
1792 "inherit",
1793 "unset",
1794 "ltr",
1795 "rtl",
1796 NULL
1797 };
1798
1799 static const char * css_cr_only_if_names[]={
1800 "any",
1801 "always",
1802 "never",
1803 "legacy",
1804 "enhanced",
1805 "float-floatboxes",
1806 "box-inlineboxes",
1807 "ensure-style-width",
1808 "ensure-style-height",
1809 "allow-style-w-h-absolute-units",
1810 "full-featured",
1811 "epub-document",
1812 "fb2-document",
1813 NULL
1814 };
1815 enum cr_only_if_t {
1816 cr_only_if_any, // always true, don't ignore
1817 cr_only_if_always, // always true, don't ignore
1818 cr_only_if_never, // always false, do ignore
1819 cr_only_if_legacy,
1820 cr_only_if_enhanced,
1821 cr_only_if_float_floatboxes,
1822 cr_only_if_box_inlineboxes,
1823 cr_only_if_ensure_style_width,
1824 cr_only_if_ensure_style_height,
1825 cr_only_if_allow_style_w_h_absolute_units,
1826 cr_only_if_full_featured,
1827 cr_only_if_epub_document,
1828 cr_only_if_fb2_document, // fb2 or fb3
1829 };
1830
parse(const char * & decl,lUInt32 domVersionRequested,bool higher_importance,lxmlDocBase * doc,lString32 codeBase)1831 bool LVCssDeclaration::parse( const char * &decl, lUInt32 domVersionRequested, bool higher_importance, lxmlDocBase * doc, lString32 codeBase )
1832 {
1833 if ( !decl )
1834 return false;
1835 skip_spaces( decl );
1836 if ( *decl != '{' )
1837 return false;
1838 decl++;
1839 SerialBuf buf(512, true);
1840
1841 bool ignoring = false;
1842 while ( *decl && *decl != '}' ) {
1843 skip_spaces( decl );
1844 css_decl_code prop_code = parse_property_name( decl );
1845 if ( ignoring && prop_code != cssd_cr_only_if ) {
1846 // Skip until next -cr-only-if:
1847 next_property( decl );
1848 continue;
1849 }
1850 skip_spaces( decl );
1851 lString8 strValue;
1852 lUInt32 importance = higher_importance ? IMPORTANT_DECL_HIGHER : 0;
1853 lUInt32 parsed_important = 0; // for !important that may be parsed along the way
1854 if (prop_code != cssd_unknown) {
1855 // parsed ok
1856 int n = -1;
1857 switch ( prop_code )
1858 {
1859 // non standard property to ignore declaration depending on gDOMVersionRequested
1860 case cssd_cr_ignore_if_dom_version_greater_or_equal:
1861 {
1862 int dom_version;
1863 if ( parse_integer( decl, dom_version ) ) {
1864 if ( domVersionRequested >= dom_version ) {
1865 return false; // ignore the whole declaration
1866 }
1867 }
1868 else { // ignore the whole declaration too if not an integer
1869 return false;
1870 }
1871 }
1872 break;
1873 // non standard property to only apply next properties if rendering option enabled
1874 case cssd_cr_only_if:
1875 {
1876 // We may have multiple names, and they must all match
1877 ignoring = false;
1878 while ( *decl != ';' ) {
1879 skip_spaces( decl );
1880 bool invert = false;
1881 if ( *decl == '-' ) {
1882 invert = true;
1883 decl++;
1884 }
1885 bool match = false;
1886 int name = parse_name( decl, css_cr_only_if_names, -1 );
1887 if ( name == cr_only_if_any || name == cr_only_if_always ) {
1888 match = !invert;
1889 }
1890 else if ( name == cr_only_if_never ) {
1891 match = invert;
1892 }
1893 else if ( !doc ) {
1894 // Without a doc, we don't have access to any of the following properties
1895 match = false;
1896 }
1897 else if ( name == cr_only_if_legacy ) {
1898 match = BLOCK_RENDERING_D(doc, ENHANCED) == invert;
1899 }
1900 else if ( name == cr_only_if_enhanced ) {
1901 match = BLOCK_RENDERING_D(doc, ENHANCED) != invert;
1902 }
1903 else if ( name == cr_only_if_float_floatboxes ) {
1904 match = BLOCK_RENDERING_D(doc, FLOAT_FLOATBOXES) != invert;
1905 }
1906 else if ( name == cr_only_if_box_inlineboxes ) {
1907 match = BLOCK_RENDERING_D(doc, BOX_INLINE_BLOCKS) != invert;
1908 }
1909 else if ( name == cr_only_if_ensure_style_width ) {
1910 match = BLOCK_RENDERING_D(doc, ENSURE_STYLE_WIDTH) != invert;
1911 }
1912 else if ( name == cr_only_if_ensure_style_height ) {
1913 match = BLOCK_RENDERING_D(doc, ENSURE_STYLE_HEIGHT) != invert;
1914 }
1915 else if ( name == cr_only_if_allow_style_w_h_absolute_units ) {
1916 match = BLOCK_RENDERING_D(doc, ALLOW_STYLE_W_H_ABSOLUTE_UNITS) != invert;
1917 }
1918 else if ( name == cr_only_if_full_featured ) {
1919 match = (doc->getRenderBlockRenderingFlags() == BLOCK_RENDERING_FULL_FEATURED) != invert;
1920 }
1921 else if ( name == cr_only_if_epub_document ) {
1922 match = doc->getProps()->getIntDef(DOC_PROP_FILE_FORMAT_ID, doc_format_none) == doc_format_epub;
1923 if (invert) {
1924 match = !match;
1925 }
1926 }
1927 else if ( name == cr_only_if_fb2_document ) {
1928 int doc_format = doc->getProps()->getIntDef(DOC_PROP_FILE_FORMAT_ID, doc_format_none);
1929 match = (doc_format == doc_format_fb2) || (doc_format == doc_format_fb3);
1930 if (invert) {
1931 match = !match;
1932 }
1933 }
1934 else { // unknown option: ignore
1935 match = false;
1936 }
1937 if ( !match ) {
1938 ignoring = true;
1939 break; // no need to look at others
1940 }
1941 skip_spaces( decl );
1942 }
1943 }
1944 break;
1945 // non standard property for providing hints via style tweaks
1946 case cssd_cr_hint:
1947 {
1948 // All values are mapped into a single style->cr_hint 31 bits bitmap
1949 int hints = 0; // "none" = no hint
1950 int nb_parsed = 0;
1951 int nb_invalid = 0;
1952 while ( *decl && *decl !=';' && *decl!='}') {
1953 // Details in crengine/include/cssdef.h (checks ordered by most likely to be seen)
1954 if ( substr_icompare("none", decl) ) {
1955 // Forget everything parsed previously, and prevent inheritance
1956 hints = CSS_CR_HINT_NONE_NO_INHERIT;
1957 }
1958 else if ( substr_icompare("footnote-inpage", decl) ) hints |= CSS_CR_HINT_FOOTNOTE_INPAGE;
1959 else if ( substr_icompare("strut-confined", decl) ) hints |= CSS_CR_HINT_STRUT_CONFINED;
1960 else if ( substr_icompare("fit-glyphs", decl) ) hints |= CSS_CR_HINT_FIT_GLYPHS;
1961 else if ( substr_icompare("text-selection-skip", decl) ) hints |= CSS_CR_HINT_TEXT_SELECTION_SKIP;
1962 else if ( substr_icompare("text-selection-inline", decl) ) hints |= CSS_CR_HINT_TEXT_SELECTION_INLINE;
1963 else if ( substr_icompare("text-selection-block", decl) ) hints |= CSS_CR_HINT_TEXT_SELECTION_BLOCK;
1964 else if ( substr_icompare("toc-level1", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL1;
1965 else if ( substr_icompare("toc-level2", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL2;
1966 else if ( substr_icompare("toc-level3", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL3;
1967 else if ( substr_icompare("toc-level4", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL4;
1968 else if ( substr_icompare("toc-level5", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL5;
1969 else if ( substr_icompare("toc-level6", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL6;
1970 else if ( substr_icompare("toc-ignore", decl) ) hints |= CSS_CR_HINT_TOC_IGNORE;
1971 else if ( substr_icompare("noteref", decl) ) hints |= CSS_CR_HINT_NOTEREF;
1972 else if ( substr_icompare("noteref-ignore", decl) ) hints |= CSS_CR_HINT_NOTEREF_IGNORE;
1973 else if ( substr_icompare("footnote", decl) ) hints |= CSS_CR_HINT_FOOTNOTE;
1974 else if ( substr_icompare("footnote-ignore", decl) ) hints |= CSS_CR_HINT_FOOTNOTE_IGNORE;
1975 //
1976 else if ( parse_important(decl) ) {
1977 parsed_important = IMPORTANT_DECL_SET;
1978 break; // stop looking for more
1979 }
1980 else { // unsupported or invalid named value
1981 nb_invalid++;
1982 // Walk over unparsed value, and continue checking
1983 while (*decl && *decl !=' ' && *decl !=';' && *decl!='}')
1984 decl++;
1985 }
1986 nb_parsed++;
1987 skip_spaces( decl );
1988 }
1989 if ( nb_parsed - nb_invalid > 0 ) { // at least one valid named value seen
1990 buf<<(lUInt32) (prop_code | importance | parsed_important);
1991 buf<<(lUInt32) css_val_unspecified; // len.type
1992 buf<<(lUInt32) hints; // len.value
1993 // (css_val_unspecified just says this value has no unit)
1994 }
1995 }
1996 break;
1997 case cssd_display:
1998 n = parse_name( decl, css_d_names, -1 );
1999 if (domVersionRequested < 20180524 && n == css_d_list_item_block) {
2000 n = css_d_list_item_legacy; // legacy rendering of list-item
2001 }
2002 break;
2003 case cssd_white_space:
2004 n = parse_name( decl, css_ws_names, -1 );
2005 break;
2006 case cssd_text_align:
2007 n = parse_name( decl, css_ta_names, -1 );
2008 if ( n >= css_ta_auto ) // only accepted with text-align-last
2009 n = -1;
2010 break;
2011 case cssd_text_align_last:
2012 n = parse_name( decl, css_ta_names, -1 );
2013 break;
2014 case cssd_text_decoration:
2015 n = parse_name( decl, css_td_names, -1 );
2016 break;
2017 case cssd_text_transform:
2018 n = parse_name( decl, css_tt_names, -1 );
2019 break;
2020 case cssd_hyphenate:
2021 case cssd_hyphenate2:
2022 case cssd_hyphenate3:
2023 case cssd_hyphenate4:
2024 case cssd_hyphenate5:
2025 prop_code = cssd_hyphenate;
2026 n = parse_name( decl, css_hyph_names, -1 );
2027 if ( n==-1 )
2028 n = parse_name( decl, css_hyph_names2, -1 );
2029 if ( n==-1 )
2030 n = parse_name( decl, css_hyph_names3, -1 );
2031 break;
2032 case cssd_page_break_before:
2033 case cssd_break_before:
2034 prop_code = cssd_page_break_before;
2035 n = parse_name( decl, css_pb_names, -1 );
2036 break;
2037 case cssd_page_break_inside:
2038 case cssd_break_inside:
2039 prop_code = cssd_page_break_inside;
2040 n = parse_name( decl, css_pb_names, -1 );
2041 // Only a subset of css_pb_names are accepted
2042 if (n > css_pb_avoid)
2043 n = -1;
2044 break;
2045 case cssd_page_break_after:
2046 case cssd_break_after:
2047 prop_code = cssd_page_break_after;
2048 n = parse_name( decl, css_pb_names, -1 );
2049 break;
2050 case cssd_list_style_type:
2051 n = parse_name( decl, css_lst_names, -1 );
2052 break;
2053 case cssd_list_style_position:
2054 n = parse_name( decl, css_lsp_names, -1 );
2055 break;
2056 case cssd_list_style:
2057 {
2058 // The list-style property is specified as one, two, or three keywords in any order,
2059 // the keywords being those of list-style-type, list-style-position and list-style-image.
2060 // We don't support (and will fail parsing the declaration) a list-style-image url(...)
2061 // component, but we can parse the declaration when it contains a type (square, decimal) and/or
2062 // a position (inside, outside) in any order.
2063 int ntype=-1;
2064 int nposition=-1;
2065 // check order "type position"
2066 ntype = parse_name( decl, css_lst_names, -1 );
2067 skip_spaces( decl );
2068 nposition = parse_name( decl, css_lsp_names, -1 );
2069 skip_spaces( decl );
2070 if (ntype == -1) { // check again if order was "position type"
2071 ntype = parse_name( decl, css_lst_names, -1 );
2072 skip_spaces( decl );
2073 }
2074 parsed_important = parse_important(decl);
2075 if (ntype != -1) {
2076 buf<<(lUInt32) (cssd_list_style_type | importance | parsed_important);
2077 buf<<(lUInt32) ntype;
2078 }
2079 if (nposition != -1) {
2080 buf<<(lUInt32) (cssd_list_style_position | importance | parsed_important);
2081 buf<<(lUInt32) nposition;
2082 }
2083 }
2084 break;
2085 case cssd_vertical_align:
2086 {
2087 css_length_t len;
2088 int n1 = parse_name( decl, css_va_names, -1 );
2089 if (n1 != -1) {
2090 len.type = css_val_unspecified;
2091 len.value = n1;
2092 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2093 buf<<(lUInt32) len.type;
2094 buf<<(lUInt32) len.value;
2095 }
2096 else {
2097 if ( parse_number_value( decl, len, true, true ) ) { // accepts a negative value
2098 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2099 buf<<(lUInt32) len.type;
2100 buf<<(lUInt32) len.value;
2101 }
2102 }
2103 }
2104 break;
2105 case cssd_font_family:
2106 {
2107 lString8Collection list;
2108 int processed = splitPropertyValueList( decl, list );
2109 decl += processed;
2110 n = -1;
2111 if ( list.length() ) {
2112 for (int i=list.length()-1; i>=0; i--) {
2113 const char * name = list[i].c_str();
2114 int nn = parse_name( name, css_ff_names, -1 );
2115 // Ignore "inherit" (nn=0) in font-family, as its the default
2116 // behaviour, and it may prevent (the way we handle
2117 // it in setNodeStyle()) the use of the font names
2118 // specified alongside.
2119 if (n==-1 && nn!=-1 && nn!=0) {
2120 n = nn;
2121 }
2122 if (nn!=-1) {
2123 // remove family name from font list
2124 list.erase( i, 1 );
2125 }
2126 else if ( substr_icompare( "!important", name ) ) {
2127 // !important may be caught by splitPropertyValueList()
2128 list.erase( i, 1 );
2129 parsed_important = IMPORTANT_DECL_SET;
2130 }
2131 }
2132 strValue = joinPropertyValueList( list );
2133 }
2134 // default to sans-serif generic font-family (the default
2135 // in lvfntman.cpp, as FreeType can't know the family of
2136 // a font)
2137 if (n == -1)
2138 n = css_ff_sans_serif;
2139 }
2140 break;
2141 case cssd_font_style:
2142 n = parse_name( decl, css_fs_names, -1 );
2143 break;
2144 case cssd_font_weight:
2145 n = parse_name( decl, css_fw_names, -1 );
2146 break;
2147 case cssd_font_features: // font-feature-settings
2148 // Not (yet) implemented.
2149 // We map font-variant(|-*) values into the style->font_features bitmap,
2150 // that is associated to cssd_font_features, as "font-feature-settings" looks
2151 // nearer (than font-variant) to how we handle internally OpenType feature tags.
2152 // But font-variant and font-feature-settings, even if they enable the same
2153 // OpenType feature tags, should have each a life (inheritance) of their own,
2154 // which we won't really ensure by mapping all of them into style->font_features.
2155 // Also, font-feature-settings is quite more complicated to parse (optional
2156 // arguments, 0|1|2|3|on|off...), and we would only support up to the 31 tags
2157 // that can be stored in the bitmap, so ignoring all possible others.
2158 // As font-feature-settings is quite new, let's not support it (quite
2159 // often, publishers will include both font-variant and font-feature-settings
2160 // in a same declaration, so we should be fine).
2161 break;
2162 case cssd_font_variant:
2163 case cssd_font_variant_ligatures:
2164 case cssd_font_variant_caps:
2165 case cssd_font_variant_position:
2166 case cssd_font_variant_numeric:
2167 case cssd_font_variant_east_asian:
2168 case cssd_font_variant_alternates:
2169 {
2170 // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant
2171 // https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant
2172 bool parse_ligatures = prop_code == cssd_font_variant || prop_code == cssd_font_variant_ligatures;
2173 bool parse_caps = prop_code == cssd_font_variant || prop_code == cssd_font_variant_caps;
2174 bool parse_position = prop_code == cssd_font_variant || prop_code == cssd_font_variant_position;
2175 bool parse_numeric = prop_code == cssd_font_variant || prop_code == cssd_font_variant_numeric;
2176 bool parse_eastasian = prop_code == cssd_font_variant || prop_code == cssd_font_variant_east_asian;
2177 bool parse_alternates = prop_code == cssd_font_variant || prop_code == cssd_font_variant_alternates;
2178 // All values are mapped into a single style->font_features 31 bits bitmap
2179 prop_code = cssd_font_features;
2180 int features = 0; // "normal" = no extra feature
2181 int nb_parsed = 0;
2182 int nb_invalid = 0;
2183 while ( *decl && *decl !=';' && *decl!='}') {
2184 if ( substr_icompare("normal", decl) ) {
2185 features = 0;
2186 }
2187 else if ( substr_icompare("none", decl) ) {
2188 features = 0;
2189 }
2190 // Details in crengine/include/lvfntman.h
2191 else if ( parse_ligatures && substr_icompare("no-common-ligatures", decl) ) features |= LFNT_OT_FEATURES_M_LIGA;
2192 else if ( parse_ligatures && substr_icompare("no-contextual", decl) ) features |= LFNT_OT_FEATURES_M_CALT;
2193 else if ( parse_ligatures && substr_icompare("discretionary-ligatures", decl) ) features |= LFNT_OT_FEATURES_P_DLIG;
2194 else if ( parse_ligatures && substr_icompare("no-discretionary-ligatures", decl) ) features |= LFNT_OT_FEATURES_M_DLIG;
2195 else if ( parse_ligatures && substr_icompare("historical-ligatures", decl) ) features |= LFNT_OT_FEATURES_P_HLIG;
2196 else if ( parse_ligatures && substr_icompare("no-historical-ligatures", decl) ) features |= LFNT_OT_FEATURES_M_HLIG;
2197 else if ( parse_alternates && substr_icompare("historical-forms", decl) ) features |= LFNT_OT_FEATURES_P_HIST;
2198 else if ( parse_eastasian && substr_icompare("ruby", decl) ) features |= LFNT_OT_FEATURES_P_RUBY;
2199 else if ( parse_caps && substr_icompare("small-caps", decl) ) features |= LFNT_OT_FEATURES_P_SMCP;
2200 else if ( parse_caps && substr_icompare("all-small-caps", decl) ) features |= LFNT_OT_FEATURES_P_C2SC;
2201 else if ( parse_caps && substr_icompare("petite-caps", decl) ) features |= LFNT_OT_FEATURES_P_PCAP;
2202 else if ( parse_caps && substr_icompare("all-petite-caps", decl) ) features |= LFNT_OT_FEATURES_P_C2PC;
2203 else if ( parse_caps && substr_icompare("unicase", decl) ) features |= LFNT_OT_FEATURES_P_UNIC;
2204 else if ( parse_caps && substr_icompare("titling-caps", decl) ) features |= LFNT_OT_FEATURES_P_TITL;
2205 else if ( parse_position && substr_icompare("super", decl) ) features |= LFNT_OT_FEATURES_P_SUPS;
2206 else if ( parse_position && substr_icompare("sub", decl) ) features |= LFNT_OT_FEATURES_P_SUBS;
2207 else if ( parse_numeric && substr_icompare("lining-nums", decl) ) features |= LFNT_OT_FEATURES_P_LNUM;
2208 else if ( parse_numeric && substr_icompare("oldstyle-nums", decl) ) features |= LFNT_OT_FEATURES_P_ONUM;
2209 else if ( parse_numeric && substr_icompare("proportional-nums", decl) ) features |= LFNT_OT_FEATURES_P_PNUM;
2210 else if ( parse_numeric && substr_icompare("tabular-nums", decl) ) features |= LFNT_OT_FEATURES_P_TNUM;
2211 else if ( parse_numeric && substr_icompare("slashed-zero", decl) ) features |= LFNT_OT_FEATURES_P_ZERO;
2212 else if ( parse_numeric && substr_icompare("ordinal", decl) ) features |= LFNT_OT_FEATURES_P_ORDN;
2213 else if ( parse_numeric && substr_icompare("diagonal-fractions", decl) ) features |= LFNT_OT_FEATURES_P_FRAC;
2214 else if ( parse_numeric && substr_icompare("stacked-fractions", decl) ) features |= LFNT_OT_FEATURES_P_AFRC;
2215 else if ( parse_eastasian && substr_icompare("simplified", decl) ) features |= LFNT_OT_FEATURES_P_SMPL;
2216 else if ( parse_eastasian && substr_icompare("traditional", decl) ) features |= LFNT_OT_FEATURES_P_TRAD;
2217 else if ( parse_eastasian && substr_icompare("full-width", decl) ) features |= LFNT_OT_FEATURES_P_FWID;
2218 else if ( parse_eastasian && substr_icompare("proportional-width", decl) ) features |= LFNT_OT_FEATURES_P_PWID;
2219 else if ( parse_eastasian && substr_icompare("jis78", decl) ) features |= LFNT_OT_FEATURES_P_JP78;
2220 else if ( parse_eastasian && substr_icompare("jis83", decl) ) features |= LFNT_OT_FEATURES_P_JP83;
2221 else if ( parse_eastasian && substr_icompare("jis04", decl) ) features |= LFNT_OT_FEATURES_P_JP04;
2222
2223 else if ( parse_important(decl) ) {
2224 parsed_important = IMPORTANT_DECL_SET;
2225 break; // stop looking for more
2226 }
2227 else { // unsupported or invalid named value
2228 nb_invalid++;
2229 // Firefox would ignore the whole declaration if it contains a non-standard named value.
2230 // As we don't parse all valid values (eg. styleset(user-defined-ident)), we just skip
2231 // them without failing the whole.
2232 // Walk over unparsed value
2233 while (*decl && *decl !=' ' && *decl !=';' && *decl!='}')
2234 decl++;
2235 }
2236 nb_parsed++;
2237 skip_spaces( decl );
2238 }
2239 if ( nb_parsed - nb_invalid > 0 ) { // at least one valid named value seen
2240 buf<<(lUInt32) (prop_code | importance | parsed_important);
2241 buf<<(lUInt32) css_val_unspecified; // len.type
2242 buf<<(lUInt32) features; // len.value
2243 // css_val_unspecified just says this value has no unit
2244 // For cssd_font_features, it actually means there is a value specified.
2245 // The default of (css_val_inherited, 0) is what means there was no
2246 // value specified, and that it should be inherited, from possibly
2247 // the root note that has (css_val_unspecified, 0).
2248 }
2249 }
2250 break;
2251 case cssd_text_indent:
2252 {
2253 // read length
2254 css_length_t len;
2255 const char * orig_pos = decl;
2256 if ( parse_number_value( decl, len, true, true ) ) { // accepts % and negative values
2257 // Read optional "hanging" flag
2258 // Note: "1em hanging" is not the same as "-1em"; the former shifts
2259 // all other but first line by 1em to the right, while the latter
2260 // shifts the first by 1em to the left. Visually, lines would
2261 // look the same relative to each other, but the whole block would
2262 // appear shifted to the left with the latter.
2263 // Little hack here: to be able to store the presence of "hanging" as
2264 // a flag in the css_length_t, we reset the lowest bit to 0, which
2265 // shouldn't really have a visual impact on the computed value (as
2266 // the parsed number is stored *256 to allow fractional value, so
2267 // we're losing 0.004em, 0.004px, 0.004%...)
2268 len.value &= 0xFFFFFFFE; // set lowest bit to 0
2269 // printf("3: %x -3: %x => %x %x %d\n", (lInt16)(3), (lInt16)(-3),
2270 // (lInt16)(3&0xFFFFFFFE), (lInt16)((-3)&0xFFFFFFFE), (lInt16)((-3)&0xFFFFFFFE));
2271 // outputs: 3: 3 -3: fffffffd => 2 fffffffc -4
2272 skip_spaces( decl );
2273 int attr = parse_name( decl, css_ti_attribute_names, -1 );
2274 if ( attr == 0 ) { // "hanging" found
2275 len.value |= 0x00000001; // set lowest bit to 1
2276 }
2277 // Note: if needed, we could parse the "each-line" keyword to be able
2278 // to bring back the legacy behaviour (where indent was applied after
2279 // a <br>) with CSS, and put this fact in the 2nd lowest bit.
2280 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2281 buf<<(lUInt32) len.type;
2282 buf<<(lUInt32) len.value;
2283 }
2284 else {
2285 decl = orig_pos; // revert any decl++
2286 }
2287 }
2288 break;
2289
2290 // Next ones accept 1 length value (with possibly named values for borders
2291 // that we map to a length)
2292 case cssd_border_bottom_width:
2293 case cssd_border_top_width:
2294 case cssd_border_left_width:
2295 case cssd_border_right_width:
2296 {
2297 int n1 = parse_name( decl, css_bw_names, -1 );
2298 if (n1 != -1) {
2299 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2300 switch (n1) {
2301 case 0: // thin
2302 buf<<(lUInt32) css_val_px;
2303 buf<<(lUInt32) (1*256);
2304 break;
2305 case 1: // medium
2306 buf<<(lUInt32) css_val_px;
2307 buf<<(lUInt32) (3*256);
2308 break;
2309 case 2: // thick
2310 buf<<(lUInt32) css_val_px;
2311 buf<<(lUInt32) (5*256);
2312 break;
2313 case 3: // initial
2314 buf<<(lUInt32) css_val_px;
2315 buf<<(lUInt32) (3*256);
2316 break;
2317 case 4: // inherit
2318 default:
2319 buf<<(lUInt32) css_val_inherited;
2320 buf<<(lUInt32) 0;
2321 break;
2322 }
2323 break; // We found a named border-width, we're done
2324 }
2325 }
2326 // no named value found, don't break: continue checking if value is a number
2327 case cssd_line_height:
2328 case cssd_letter_spacing:
2329 case cssd_font_size:
2330 case cssd_width:
2331 case cssd_height:
2332 case cssd_margin_left:
2333 case cssd_margin_right:
2334 case cssd_margin_top:
2335 case cssd_margin_bottom:
2336 case cssd_padding_left:
2337 case cssd_padding_right:
2338 case cssd_padding_top:
2339 case cssd_padding_bottom:
2340 {
2341 // borders don't accept length in %
2342 bool accept_percent = true;
2343 if ( prop_code==cssd_border_bottom_width || prop_code==cssd_border_top_width ||
2344 prop_code==cssd_border_left_width || prop_code==cssd_border_right_width )
2345 accept_percent = false;
2346 // only margin accepts negative values
2347 bool accept_negative = false;
2348 if ( prop_code==cssd_margin_bottom || prop_code==cssd_margin_top ||
2349 prop_code==cssd_margin_left || prop_code==cssd_margin_right )
2350 accept_negative = true;
2351 // only margin, width and height accept keyword "auto"
2352 bool accept_auto = false;
2353 if ( prop_code==cssd_margin_bottom || prop_code==cssd_margin_top ||
2354 prop_code==cssd_margin_left || prop_code==cssd_margin_right ||
2355 prop_code==cssd_width || prop_code==cssd_height )
2356 accept_auto = true;
2357 // only line-height and letter-spacing accept keyword "normal"
2358 bool accept_normal = false;
2359 if ( prop_code==cssd_line_height || prop_code==cssd_letter_spacing )
2360 accept_normal = true;
2361 // only font-size is... font-size
2362 bool is_font_size = false;
2363 if ( prop_code==cssd_font_size )
2364 is_font_size = true;
2365 css_length_t len;
2366 if ( parse_number_value( decl, len, accept_percent, accept_negative, accept_auto, accept_normal, false, is_font_size) ) {
2367 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2368 buf<<(lUInt32) len.type;
2369 buf<<(lUInt32) len.value;
2370 }
2371 }
2372 break;
2373 // Done with those that accept 1 length value.
2374
2375 // Next ones accept 1 to 4 length values (with possibly named values for borders
2376 // that we map to a length)
2377 case cssd_border_width:
2378 {
2379 int n1 = parse_name( decl, css_bw_names, -1 );
2380 if (n1!=-1) {
2381 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2382 switch (n1) {
2383 case 0: // thin
2384 for (int i = 0; i < 4; i++) {
2385 buf<<(lUInt32) css_val_px;
2386 buf<<(lUInt32) (1*256);
2387 }
2388 break;
2389 case 1: // medium
2390 for (int i = 0; i < 4; i++) {
2391 buf<<(lUInt32) css_val_px;
2392 buf<<(lUInt32) (3*256);
2393 }
2394 break;
2395 case 2: // thick
2396 for (int i = 0; i < 4; i++) {
2397 buf<<(lUInt32) css_val_px;
2398 buf<<(lUInt32) (5*256);
2399 }
2400 break;
2401 case 3: // initial
2402 for (int i = 0; i < 4; i++) {
2403 buf<<(lUInt32) css_val_px;
2404 buf<<(lUInt32) (3*256);
2405 }
2406 break;
2407 case 4: // inherit
2408 default:
2409 for (int i = 0; i < 4; i++) {
2410 buf<<(lUInt32) css_val_inherited;
2411 buf<<(lUInt32) 0;
2412 }
2413 break;
2414 }
2415 break; // We found a named border-width, we're done
2416 }
2417 }
2418 // no named value found, don't break: continue checking if value is a number
2419 case cssd_margin:
2420 case cssd_padding:
2421 {
2422 bool accept_percent = true;
2423 if ( prop_code==cssd_border_width )
2424 accept_percent = false;
2425 bool accept_auto = false;
2426 bool accept_negative = false;
2427 if ( prop_code==cssd_margin ) {
2428 accept_auto = true;
2429 accept_negative = true;
2430 }
2431 css_length_t len[4];
2432 int i;
2433 for (i = 0; i < 4; i++) {
2434 if (!parse_number_value( decl, len[i], accept_percent, accept_negative, accept_auto ))
2435 break;
2436 }
2437 if (i) {
2438 // If we found 1, it applies to 4 edges
2439 // If we found 2, 1st one apply to top and bottom, 2nd to right and left
2440 // If we found 3, 1st one apply to top 2nd to right and left, 3rd to bottom
2441 switch (i) {
2442 case 1: len[1] = len[0]; /* fall through */
2443 case 2: len[2] = len[0]; /* fall through */
2444 case 3: len[3] = len[1];
2445 }
2446 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2447 for (i = 0; i < 4; i++) {
2448 buf<<(lUInt32) len[i].type;
2449 buf<<(lUInt32) len[i].value;
2450 }
2451 }
2452 }
2453 break;
2454 // Done with those that accept 1 to 4 length values.
2455
2456 case cssd_color:
2457 case cssd_background_color:
2458 case cssd_border_top_color:
2459 case cssd_border_right_color:
2460 case cssd_border_bottom_color:
2461 case cssd_border_left_color:
2462 {
2463 css_length_t len;
2464 if ( parse_color_value( decl, len ) ) {
2465 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2466 buf<<(lUInt32) len.type;
2467 buf<<(lUInt32) len.value;
2468 }
2469 }
2470 break;
2471 case cssd_border_color:
2472 {
2473 // Accepts 1 to 4 color values
2474 css_length_t len[4];
2475 int i;
2476 for (i = 0; i < 4; i++)
2477 if (!parse_color_value( decl, len[i]))
2478 break;
2479 if (i) {
2480 switch (i) {
2481 case 1: len[1] = len[0]; /* fall through */
2482 case 2: len[2] = len[0]; /* fall through */
2483 case 3: len[3] = len[1];
2484 }
2485 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2486 for (i = 0; i < 4; i++) {
2487 buf<<(lUInt32) len[i].type;
2488 buf<<(lUInt32) len[i].value;
2489 }
2490 }
2491 }
2492 break;
2493 case cssd_border_top_style:
2494 case cssd_border_right_style:
2495 case cssd_border_bottom_style:
2496 case cssd_border_left_style:
2497 n = parse_name( decl, css_bst_names, -1 );
2498 break;
2499 case cssd_border_style:
2500 {
2501 // Accepts 1 to 4 named values
2502 int name[4];
2503 int i;
2504 for (i = 0; i < 4; i++) {
2505 int n1 = parse_name( decl, css_bst_names, -1 );
2506 if ( n1 != -1 ) {
2507 name[i] = n1;
2508 skip_spaces(decl);
2509 continue;
2510 }
2511 break;
2512 }
2513 if (i) {
2514 switch (i) {
2515 case 1: name[1] = name[0]; /* fall through */
2516 case 2: name[2] = name[0]; /* fall through */
2517 case 3: name[3] = name[1];
2518 }
2519 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2520 for (i = 0; i < 4; i++) {
2521 buf<<(lUInt32) name[i];
2522 }
2523 }
2524 }
2525 break;
2526
2527 // Next ones accept a triplet (possibly incomplete) like "2px solid blue".
2528 // Borders don't accept length in %, and Firefox ignores the whole
2529 // individual declaration when that happens (with "10% dotted blue", and
2530 // later a style="border-width: 5px", Firefox shows it solid and black).
2531 case cssd_border:
2532 case cssd_border_top:
2533 case cssd_border_right:
2534 case cssd_border_bottom:
2535 case cssd_border_left:
2536 {
2537 bool found_style = false;
2538 bool found_width = false;
2539 bool found_color = false;
2540 int style_val = -1;
2541 css_length_t width;
2542 css_length_t color;
2543 // https://developer.mozilla.org/en-US/docs/Web/CSS/border-right
2544 // We look for 3 values at most, which are allowed to be in any order
2545 // and be missing.
2546 // If they are missing, we should set them to the default value:
2547 // width: medium, style: none, color: currentColor
2548 // Note that the parse_* functions only advance the string when they
2549 // match. When they don't match, we stay at the position we were.
2550 for (int i=0; i<3; i++) {
2551 skip_spaces(decl);
2552 if ( !found_width ) {
2553 if ( parse_number_value( decl, width, false ) ) { // accept_percent=false
2554 found_width = true;
2555 continue;
2556 }
2557 else {
2558 int num = parse_name( decl, css_bw_names, -1 );
2559 if ( num != -1 ) {
2560 width.type = css_val_px;
2561 switch (num){
2562 case 0: // thin
2563 width.value = 1*256;
2564 break;
2565 case 1: // medium
2566 width.value = 3*256;
2567 break;
2568 case 2: // thick
2569 width.value = 5*256;
2570 break;
2571 case 3: // initial
2572 width.value = 3*256;
2573 break;
2574 case 4: // inherit
2575 default:
2576 width.type = css_val_inherited;
2577 width.value = 0;
2578 break;
2579 }
2580 found_width = true;
2581 continue;
2582 }
2583 }
2584 }
2585 if ( !found_style ) {
2586 style_val = parse_name( decl, css_bst_names, -1 );
2587 if ( style_val != -1 ) {
2588 found_style = true;
2589 continue;
2590 }
2591 }
2592 if ( !found_color ) {
2593 if( parse_color_value( decl, color ) ){
2594 found_color = true;
2595 continue;
2596 }
2597 }
2598 // We have not found any usable name/color/width
2599 // in this loop: no need for more
2600 break;
2601 }
2602 parsed_important = parse_important(decl);
2603
2604 // We expect to have at least found one of them
2605 if ( found_style || found_width || found_color ) {
2606 // We must set the not found properties to their default values
2607 if ( !found_style ) {
2608 // Default to "none"
2609 style_val = css_border_none;
2610 }
2611 if ( !found_width ) {
2612 // Default to "medium"
2613 width.type = css_val_px;
2614 width.value = 3*256;
2615 }
2616 if ( !found_color ) {
2617 // We don't support "currentColor": fallback to black
2618 color.type = css_val_color;
2619 color.value = 0x000000;
2620 }
2621 if ( prop_code==cssd_border ) {
2622 buf<<(lUInt32) (cssd_border_style | importance | parsed_important);
2623 for (int i = 0; i < 4; i++) {
2624 buf<<(lUInt32) style_val;
2625 }
2626 buf<<(lUInt32) (cssd_border_width | importance | parsed_important);
2627 for (int i = 0; i < 4; i++) {
2628 buf<<(lUInt32) width.type;
2629 buf<<(lUInt32) width.value;
2630 }
2631 buf<<(lUInt32) (cssd_border_color | importance | parsed_important);
2632 for (int i = 0; i < 4; i++) {
2633 buf<<(lUInt32) color.type;
2634 buf<<(lUInt32) color.value;
2635 }
2636 }
2637 else {
2638 css_decl_code prop_style, prop_width, prop_color;
2639 switch (prop_code) {
2640 case cssd_border_top:
2641 prop_style = cssd_border_top_style;
2642 prop_width = cssd_border_top_width;
2643 prop_color = cssd_border_top_color;
2644 break;
2645 case cssd_border_right:
2646 prop_style = cssd_border_right_style;
2647 prop_width = cssd_border_right_width;
2648 prop_color = cssd_border_right_color;
2649 break;
2650 case cssd_border_bottom:
2651 prop_style = cssd_border_bottom_style;
2652 prop_width = cssd_border_bottom_width;
2653 prop_color = cssd_border_bottom_color;
2654 break;
2655 case cssd_border_left:
2656 default:
2657 prop_style = cssd_border_left_style;
2658 prop_width = cssd_border_left_width;
2659 prop_color = cssd_border_left_color;
2660 break;
2661 }
2662 buf<<(lUInt32) (prop_style | importance | parsed_important);
2663 buf<<(lUInt32) style_val;
2664 buf<<(lUInt32) (prop_width | importance | parsed_important);
2665 buf<<(lUInt32) width.type;
2666 buf<<(lUInt32) width.value;
2667 buf<<(lUInt32) (prop_color | importance | parsed_important);
2668 buf<<(lUInt32) color.type;
2669 buf<<(lUInt32) color.value;
2670 }
2671 }
2672 }
2673 break;
2674 // Done with those that accepts a triplet.
2675
2676 case cssd_background_image:
2677 {
2678 lString8 str;
2679 const char *tmp = decl;
2680 int len=0;
2681 while (*tmp && *tmp!=';' && *tmp!='}' && *tmp!='!') {
2682 if ( *tmp == '(' && *(tmp-3) == 'u' && *(tmp-2) == 'r' && *(tmp-1) == 'l') {
2683 // Accepts everything until ')' after 'url(', including ';'
2684 // needed when parsing: url("data:image/png;base64,abcd...")
2685 tmp++; len++;
2686 while ( *tmp && *tmp!=')' ) {
2687 tmp++; len++;
2688 }
2689 }
2690 else {
2691 tmp++; len++;
2692 }
2693 }
2694 str.append(decl,len);
2695 decl += len;
2696 resolve_url_path(str, codeBase);
2697 len = str.length();
2698 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2699 buf<<(lUInt32) len;
2700 for (int i=0; i<len; i++)
2701 buf<<(lUInt32) str[i];
2702 }
2703 break;
2704 case cssd_background_repeat:
2705 n = parse_name( decl, css_bg_repeat_names, -1 );
2706 break;
2707 case cssd_background_position:
2708 n = parse_name( decl, css_bg_position_names, -1 );
2709 // Only values between 0 and 8 will be checked by the background drawing code
2710 if ( n>8 ) {
2711 if ( n<18 ) n=n-9; // "top left" = "left top"
2712 else if ( n==18 ) n=7; // "center" = "center center"
2713 else if ( n==19 ) n=1; // "left" = "left center"
2714 else if ( n==20 ) n=4; // "right" = "right center"
2715 else if ( n==21 ) n=6; // "top" = "center top"
2716 else if ( n==22 ) n=8; // "bottom" = "center bottom"
2717 else if ( n==23 ) n=0; // "initial" = "left top"
2718 else if ( n==24 ) n=0; // "inherit" = "left top"
2719 }
2720 break;
2721 case cssd_background:
2722 {
2723 // Limited parsing of this possibly complex property
2724 // We only support a single layer in these orders:
2725 // - color
2726 // - url(...) repeat position
2727 // - color url(...) repeat position
2728 // - color url(...) position repeat
2729 // (with repeat and position possibly absent or re-ordered)
2730 css_length_t color;
2731 bool has_color = parse_color_value(decl, color);
2732 skip_spaces(decl);
2733 const char *tmp = decl;
2734 int len = 0;
2735 while (*tmp && *tmp!=';' && *tmp!='}' && *tmp!='!') {
2736 if ( *tmp == '(' && *(tmp-3) == 'u' && *(tmp-2) == 'r' && *(tmp-1) == 'l') {
2737 // Accepts everything until ')' after 'url(', including ';'
2738 // needed when parsing: url("data:image/png;base64,abcd...")
2739 tmp++; len++;
2740 while ( *tmp && *tmp!=')' ) {
2741 tmp++; len++;
2742 }
2743 }
2744 else {
2745 tmp++; len++;
2746 }
2747 }
2748 lString8 str;
2749 str.append(decl,len);
2750 if ( Utf8ToUnicode(str).lowercase().startsWith("url(") ) {
2751 tmp = str.c_str();
2752 len = 0;
2753 while (*tmp && *tmp!=')') {
2754 tmp++; len++;
2755 }
2756 len = len + 1;
2757 str.clear();
2758 str.append(decl, len);
2759 decl += len;
2760 resolve_url_path(str, codeBase);
2761 len = str.length();
2762 // Try parsing following repeat and position
2763 skip_spaces(decl);
2764 int repeat = parse_name( decl, css_bg_repeat_names, -1 );
2765 if( repeat != -1 ) {
2766 skip_spaces(decl);
2767 }
2768 int position = parse_name( decl, css_bg_position_names, -1 );
2769 if ( position != -1 ) {
2770 // Only values between 0 and 8 will be checked by the background drawing code
2771 if ( position>8 ) {
2772 if ( position<18 ) position -= 9; // "top left" = "left top"
2773 else if ( position==18 ) position=7; // "center" = "center center"
2774 else if ( position==19 ) position=1; // "left" = "left center"
2775 else if ( position==20 ) position=4; // "right" = "right center"
2776 else if ( position==21 ) position=6; // "top" = "center top"
2777 else if ( position==22 ) position=8; // "bottom" = "center bottom"
2778 else if ( position==23 ) position=0; // "initial" = "left top"
2779 else if ( position==24 ) position=0; // "inherit" = "left top"
2780 }
2781 }
2782 if( repeat == -1 ) { // Try parsing repeat after position
2783 skip_spaces(decl);
2784 repeat = parse_name( decl, css_bg_repeat_names, -1 );
2785 }
2786 parsed_important = parse_important(decl);
2787 buf<<(lUInt32) (cssd_background_image | importance | parsed_important);
2788 buf<<(lUInt32) len;
2789 for (int i = 0; i < len; i++)
2790 buf<<(lUInt32) str[i];
2791 if(repeat != -1) {
2792 buf<<(lUInt32) (cssd_background_repeat | importance | parsed_important);
2793 buf<<(lUInt32) repeat;
2794 }
2795 if (position != -1) {
2796 buf<<(lUInt32) (cssd_background_position | importance | parsed_important);
2797 buf<<(lUInt32) position;
2798 }
2799 }
2800 else { // no url, only color
2801 decl += len; // skip any unsupported stuff until !
2802 parsed_important = parse_important(decl);
2803 }
2804 if ( has_color ) {
2805 buf<<(lUInt32) (cssd_background_color | importance | parsed_important);
2806 buf<<(lUInt32) color.type;
2807 buf<<(lUInt32) color.value;
2808 }
2809 }
2810 break;
2811 case cssd_background_size:
2812 {
2813 // https://developer.mozilla.org/en-US/docs/Web/CSS/background-size
2814 css_length_t len[2];
2815 int i;
2816 for (i = 0; i < 2; i++) {
2817 if ( !parse_number_value( decl, len[i], true, false, true, false, true ) )
2818 break;
2819 }
2820 if (i) {
2821 if (i == 1) { // Only 1 value parsed
2822 if ( len[0].type == css_val_unspecified ) { // "auto", "contain" or "cover"
2823 len[1].type = css_val_unspecified;
2824 len[1].value = len[0].value;
2825 }
2826 else { // first value is a length: second value should be "auto"
2827 len[1].type = css_val_unspecified;
2828 len[1].value = css_generic_auto;
2829 }
2830 }
2831 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2832 for (i = 0; i < 2; i++) {
2833 buf<<(lUInt32) len[i].type;
2834 buf<<(lUInt32) len[i].value;
2835 }
2836 }
2837 }
2838 break;
2839 case cssd_border_spacing:
2840 {
2841 css_length_t len[2];
2842 int i;
2843 for (i = 0; i < 2; i++) {
2844 // border-spacing doesn't accept values in %
2845 if ( !parse_number_value( decl, len[i], false ) )
2846 break;
2847 }
2848 if (i) {
2849 if (i==1)
2850 len[1] = len[0];
2851 buf<<(lUInt32) (prop_code | importance | parse_important(decl));
2852 for (i = 0; i < 2; i++) {
2853 buf<<(lUInt32) len[i].type;
2854 buf<<(lUInt32) len[i].value;
2855 }
2856 }
2857 }
2858 break;
2859 case cssd_border_collapse:
2860 n = parse_name( decl, css_bc_names, -1 );
2861 break;
2862 case cssd_orphans:
2863 n = parse_name( decl, css_orphans_widows_names, -1 );
2864 break;
2865 case cssd_widows:
2866 n = parse_name( decl, css_orphans_widows_names, -1 );
2867 break;
2868 case cssd_float:
2869 n = parse_name( decl, css_f_names, -1 );
2870 break;
2871 case cssd_clear:
2872 n = parse_name( decl, css_c_names, -1 );
2873 break;
2874 case cssd_direction:
2875 n = parse_name( decl, css_dir_names, -1 );
2876 break;
2877 case cssd_content:
2878 {
2879 lString32 parsed_content;
2880 if ( parse_content_property( decl, parsed_content) ) {
2881 buf<<(lUInt32) (cssd_content | importance | parsed_important | parse_important(decl));
2882 buf<<(lUInt32) parsed_content.length();
2883 for (int i=0; i < parsed_content.length(); i++) {
2884 buf<<(lUInt32) parsed_content[i];
2885 }
2886 }
2887 }
2888 break;
2889 case cssd_stop:
2890 case cssd_unknown:
2891 default:
2892 break;
2893 }
2894 if ( n!= -1) {
2895 // add enum property
2896 buf<<(lUInt32) (prop_code | importance | parsed_important | parse_important(decl));
2897 buf<<(lUInt32) n;
2898 }
2899 if ( !strValue.empty() ) {
2900 // add string property
2901 if ( prop_code==cssd_font_family ) {
2902 // font names
2903 buf<<(lUInt32) (cssd_font_names | importance | parsed_important | parse_important(decl));
2904 buf<<(lUInt32) strValue.length();
2905 for (int i=0; i < strValue.length(); i++)
2906 buf<<(lUInt32) strValue[i];
2907 }
2908 }
2909 }
2910 else {
2911 // skip unknown property
2912 }
2913 next_property( decl );
2914 }
2915
2916 // store parsed result
2917 if (buf.pos()) {
2918 buf<<(lUInt32) cssd_stop; // add end marker
2919 int sz = buf.pos()/4;
2920 _data = new int[sz];
2921 // Could that cause problem with different endianess?
2922 buf.copyTo( (lUInt8*)_data, buf.pos() );
2923 // Alternative:
2924 // buf.setPos(0);
2925 // for (int i=0; i<sz; i++)
2926 // buf >> _data[i];
2927 }
2928
2929 // skip }
2930 skip_spaces( decl );
2931 if (*decl == '}') {
2932 decl++;
2933 return true;
2934 }
2935 return false;
2936 }
2937
read_length(int * & data)2938 static css_length_t read_length( int * &data )
2939 {
2940 css_length_t len;
2941 len.type = (css_value_type_t) (*data++);
2942 len.value = (*data++);
2943 return len;
2944 }
2945
apply(css_style_rec_t * style)2946 void LVCssDeclaration::apply( css_style_rec_t * style )
2947 {
2948 if (!_data)
2949 return;
2950 int * p = _data;
2951 for (;;)
2952 {
2953 lUInt32 prop_code = *p++;
2954 lUInt8 is_important = prop_code >> IMPORTANT_DECL_SHIFT; // 2 bits (importance, is_important)
2955 prop_code = prop_code & IMPORTANT_DECL_REMOVE;
2956 switch (prop_code)
2957 {
2958 case cssd_display:
2959 style->Apply( (css_display_t) *p++, &style->display, imp_bit_display, is_important );
2960 break;
2961 case cssd_white_space:
2962 style->Apply( (css_white_space_t) *p++, &style->white_space, imp_bit_white_space, is_important );
2963 break;
2964 case cssd_text_align:
2965 style->Apply( (css_text_align_t) *p++, &style->text_align, imp_bit_text_align, is_important );
2966 break;
2967 case cssd_text_align_last:
2968 style->Apply( (css_text_align_t) *p++, &style->text_align_last, imp_bit_text_align_last, is_important );
2969 break;
2970 case cssd_text_decoration:
2971 style->Apply( (css_text_decoration_t) *p++, &style->text_decoration, imp_bit_text_decoration, is_important );
2972 break;
2973 case cssd_text_transform:
2974 style->Apply( (css_text_transform_t) *p++, &style->text_transform, imp_bit_text_transform, is_important );
2975 break;
2976 case cssd_hyphenate:
2977 style->Apply( (css_hyphenate_t) *p++, &style->hyphenate, imp_bit_hyphenate, is_important );
2978 break;
2979 case cssd_list_style_type:
2980 style->Apply( (css_list_style_type_t) *p++, &style->list_style_type, imp_bit_list_style_type, is_important );
2981 break;
2982 case cssd_list_style_position:
2983 style->Apply( (css_list_style_position_t) *p++, &style->list_style_position, imp_bit_list_style_position, is_important );
2984 break;
2985 case cssd_page_break_before:
2986 style->Apply( (css_page_break_t) *p++, &style->page_break_before, imp_bit_page_break_before, is_important );
2987 break;
2988 case cssd_page_break_after:
2989 style->Apply( (css_page_break_t) *p++, &style->page_break_after, imp_bit_page_break_after, is_important );
2990 break;
2991 case cssd_page_break_inside:
2992 style->Apply( (css_page_break_t) *p++, &style->page_break_inside, imp_bit_page_break_inside, is_important );
2993 break;
2994 case cssd_vertical_align:
2995 style->Apply( read_length(p), &style->vertical_align, imp_bit_vertical_align, is_important );
2996 break;
2997 case cssd_font_family:
2998 style->Apply( (css_font_family_t) *p++, &style->font_family, imp_bit_font_family, is_important );
2999 break;
3000 case cssd_font_names:
3001 {
3002 lString8 names;
3003 names.reserve(64);
3004 int len = *p++;
3005 for (int i=0; i<len; i++)
3006 names << (lChar8)(*p++);
3007 names.pack();
3008 style->Apply( names, &style->font_name, imp_bit_font_name, is_important );
3009 }
3010 break;
3011 case cssd_font_style:
3012 style->Apply( (css_font_style_t) *p++, &style->font_style, imp_bit_font_style, is_important );
3013 break;
3014 case cssd_font_weight:
3015 style->Apply( (css_font_weight_t) *p++, &style->font_weight, imp_bit_font_weight, is_important );
3016 break;
3017 case cssd_font_size:
3018 style->Apply( read_length(p), &style->font_size, imp_bit_font_size, is_important );
3019 break;
3020 case cssd_font_features:
3021 // We want to 'OR' the bitmap from any declaration that is to be applied to this node
3022 // (while still ensuring !important).
3023 style->ApplyAsBitmapOr( read_length(p), &style->font_features, imp_bit_font_features, is_important );
3024 break;
3025 case cssd_text_indent:
3026 style->Apply( read_length(p), &style->text_indent, imp_bit_text_indent, is_important );
3027 break;
3028 case cssd_line_height:
3029 style->Apply( read_length(p), &style->line_height, imp_bit_line_height, is_important );
3030 break;
3031 case cssd_letter_spacing:
3032 style->Apply( read_length(p), &style->letter_spacing, imp_bit_letter_spacing, is_important );
3033 break;
3034 case cssd_color:
3035 style->Apply( read_length(p), &style->color, imp_bit_color, is_important );
3036 break;
3037 case cssd_background_color:
3038 style->Apply( read_length(p), &style->background_color, imp_bit_background_color, is_important );
3039 break;
3040 case cssd_width:
3041 style->Apply( read_length(p), &style->width, imp_bit_width, is_important );
3042 break;
3043 case cssd_height:
3044 style->Apply( read_length(p), &style->height, imp_bit_height, is_important );
3045 break;
3046 case cssd_margin_left:
3047 style->Apply( read_length(p), &style->margin[0], imp_bit_margin_left, is_important );
3048 break;
3049 case cssd_margin_right:
3050 style->Apply( read_length(p), &style->margin[1], imp_bit_margin_right, is_important );
3051 break;
3052 case cssd_margin_top:
3053 style->Apply( read_length(p), &style->margin[2], imp_bit_margin_top, is_important );
3054 break;
3055 case cssd_margin_bottom:
3056 style->Apply( read_length(p), &style->margin[3], imp_bit_margin_bottom, is_important );
3057 break;
3058 case cssd_margin:
3059 style->Apply( read_length(p), &style->margin[2], imp_bit_margin_top, is_important );
3060 style->Apply( read_length(p), &style->margin[1], imp_bit_margin_right, is_important );
3061 style->Apply( read_length(p), &style->margin[3], imp_bit_margin_bottom, is_important );
3062 style->Apply( read_length(p), &style->margin[0], imp_bit_margin_left, is_important );
3063 break;
3064 case cssd_padding_left:
3065 style->Apply( read_length(p), &style->padding[0], imp_bit_padding_left, is_important );
3066 break;
3067 case cssd_padding_right:
3068 style->Apply( read_length(p), &style->padding[1], imp_bit_padding_right, is_important );
3069 break;
3070 case cssd_padding_top:
3071 style->Apply( read_length(p), &style->padding[2], imp_bit_padding_top, is_important );
3072 break;
3073 case cssd_padding_bottom:
3074 style->Apply( read_length(p), &style->padding[3], imp_bit_padding_bottom, is_important );
3075 break;
3076 case cssd_padding:
3077 style->Apply( read_length(p), &style->padding[2], imp_bit_padding_top, is_important );
3078 style->Apply( read_length(p), &style->padding[1], imp_bit_padding_right, is_important );
3079 style->Apply( read_length(p), &style->padding[3], imp_bit_padding_bottom, is_important );
3080 style->Apply( read_length(p), &style->padding[0], imp_bit_padding_left, is_important );
3081 break;
3082 case cssd_border_top_color:
3083 style->Apply( read_length(p), &style->border_color[0], imp_bit_border_color_top, is_important );
3084 break;
3085 case cssd_border_right_color:
3086 style->Apply( read_length(p), &style->border_color[1], imp_bit_border_color_right, is_important );
3087 break;
3088 case cssd_border_bottom_color:
3089 style->Apply( read_length(p), &style->border_color[2], imp_bit_border_color_bottom, is_important );
3090 break;
3091 case cssd_border_left_color:
3092 style->Apply( read_length(p), &style->border_color[3], imp_bit_border_color_left, is_important );
3093 break;
3094 case cssd_border_top_width:
3095 style->Apply( read_length(p), &style->border_width[0], imp_bit_border_width_top, is_important );
3096 break;
3097 case cssd_border_right_width:
3098 style->Apply( read_length(p), &style->border_width[1], imp_bit_border_width_right, is_important );
3099 break;
3100 case cssd_border_bottom_width:
3101 style->Apply( read_length(p), &style->border_width[2], imp_bit_border_width_bottom, is_important );
3102 break;
3103 case cssd_border_left_width:
3104 style->Apply( read_length(p), &style->border_width[3], imp_bit_border_width_left, is_important );
3105 break;
3106 case cssd_border_top_style:
3107 style->Apply( (css_border_style_type_t) *p++, &style->border_style_top, imp_bit_border_style_top, is_important );
3108 break;
3109 case cssd_border_right_style:
3110 style->Apply( (css_border_style_type_t) *p++, &style->border_style_right, imp_bit_border_style_right, is_important );
3111 break;
3112 case cssd_border_bottom_style:
3113 style->Apply( (css_border_style_type_t) *p++, &style->border_style_bottom, imp_bit_border_style_bottom, is_important );
3114 break;
3115 case cssd_border_left_style:
3116 style->Apply( (css_border_style_type_t) *p++, &style->border_style_left, imp_bit_border_style_left, is_important );
3117 break;
3118 case cssd_border_color:
3119 style->Apply( read_length(p), &style->border_color[0], imp_bit_border_color_top, is_important );
3120 style->Apply( read_length(p), &style->border_color[1], imp_bit_border_color_right, is_important );
3121 style->Apply( read_length(p), &style->border_color[2], imp_bit_border_color_bottom, is_important );
3122 style->Apply( read_length(p), &style->border_color[3], imp_bit_border_color_left, is_important );
3123 break;
3124 case cssd_border_width:
3125 style->Apply( read_length(p), &style->border_width[0], imp_bit_border_width_top, is_important );
3126 style->Apply( read_length(p), &style->border_width[1], imp_bit_border_width_right, is_important );
3127 style->Apply( read_length(p), &style->border_width[2], imp_bit_border_width_bottom, is_important );
3128 style->Apply( read_length(p), &style->border_width[3], imp_bit_border_width_left, is_important );
3129 break;
3130 case cssd_border_style:
3131 style->Apply( (css_border_style_type_t) *p++, &style->border_style_top, imp_bit_border_style_top, is_important );
3132 style->Apply( (css_border_style_type_t) *p++, &style->border_style_right, imp_bit_border_style_right, is_important );
3133 style->Apply( (css_border_style_type_t) *p++, &style->border_style_bottom, imp_bit_border_style_bottom, is_important );
3134 style->Apply( (css_border_style_type_t) *p++, &style->border_style_left, imp_bit_border_style_left, is_important );
3135 break;
3136 case cssd_background_image:
3137 {
3138 lString8 imagefile;
3139 imagefile.reserve(64);
3140 int l = *p++;
3141 for (int i=0; i<l; i++)
3142 imagefile << (lChar8)(*p++);
3143 imagefile.pack();
3144 style->Apply( imagefile, &style->background_image, imp_bit_background_image, is_important );
3145 }
3146 break;
3147 case cssd_background_repeat:
3148 style->Apply( (css_background_repeat_value_t) *p++, &style->background_repeat, imp_bit_background_repeat, is_important );
3149 break;
3150 case cssd_background_position:
3151 style->Apply( (css_background_position_value_t) *p++, &style->background_position, imp_bit_background_position, is_important );
3152 break;
3153 case cssd_background_size:
3154 style->Apply( read_length(p), &style->background_size[0], imp_bit_background_size_h, is_important );
3155 style->Apply( read_length(p), &style->background_size[1], imp_bit_background_size_v, is_important );
3156 break;
3157 case cssd_border_spacing:
3158 style->Apply( read_length(p), &style->border_spacing[0], imp_bit_border_spacing_h, is_important );
3159 style->Apply( read_length(p), &style->border_spacing[1], imp_bit_border_spacing_v, is_important );
3160 break;
3161 case cssd_border_collapse:
3162 style->Apply( (css_border_collapse_value_t) *p++, &style->border_collapse, imp_bit_border_collapse, is_important );
3163 break;
3164 case cssd_orphans:
3165 style->Apply( (css_orphans_widows_value_t) *p++, &style->orphans, imp_bit_orphans, is_important );
3166 break;
3167 case cssd_widows:
3168 style->Apply( (css_orphans_widows_value_t) *p++, &style->widows, imp_bit_widows, is_important );
3169 break;
3170 case cssd_float:
3171 style->Apply( (css_float_t) *p++, &style->float_, imp_bit_float, is_important );
3172 break;
3173 case cssd_clear:
3174 style->Apply( (css_clear_t) *p++, &style->clear, imp_bit_clear, is_important );
3175 break;
3176 case cssd_direction:
3177 style->Apply( (css_direction_t) *p++, &style->direction, imp_bit_direction, is_important );
3178 break;
3179 case cssd_cr_hint:
3180 {
3181 // We want to 'OR' the bitmap from any declaration that is to be applied to this node
3182 // (while still ensuring !important) - unless this declaration had "-cr-hint: none"
3183 // in which case we should reset previously set bits
3184 css_length_t cr_hint = read_length(p);
3185 if ( cr_hint.value & CSS_CR_HINT_NONE_NO_INHERIT ) {
3186 style->Apply( cr_hint, &style->cr_hint, imp_bit_cr_hint, is_important );
3187 }
3188 else {
3189 style->ApplyAsBitmapOr( cr_hint, &style->cr_hint, imp_bit_cr_hint, is_important );
3190 }
3191 }
3192 break;
3193 case cssd_content:
3194 {
3195 int l = *p++;
3196 lString32 content;
3197 if ( l > 0 ) {
3198 content.reserve(l);
3199 for (int i=0; i<l; i++)
3200 content << (lChar32)(*p++);
3201 }
3202 style->Apply( content, &style->content, imp_bit_content, is_important );
3203 }
3204 break;
3205 case cssd_stop:
3206 return;
3207 }
3208 }
3209 }
3210
getHash()3211 lUInt32 LVCssDeclaration::getHash() {
3212 if (!_data)
3213 return 0;
3214 int * p = _data;
3215 lUInt32 hash = 0;
3216 for (;*p != cssd_stop;p++)
3217 hash = hash * 31 + *p;
3218 return hash;
3219 }
3220
parse_ident(const char * & str,char * ident)3221 static bool parse_ident( const char * &str, char * ident )
3222 {
3223 // Note: skipping any space before or after should be ensured by caller if needed
3224 *ident = 0;
3225 if ( !css_is_alpha( *str ) )
3226 return false;
3227 int i;
3228 for (i=0; css_is_alnum(str[i]); i++)
3229 ident[i] = str[i];
3230 ident[i] = 0;
3231 str += i;
3232 return true;
3233 }
3234
3235 // We are storing specificity/weight in a lUInt32.
3236 // We also want to include in it the order in which we have
3237 // seen/parsed the selectors, so we store in the lower bits
3238 // of this lUInt32 some sequence number to ensure selectors
3239 // with the same specificity are applied in the order we've
3240 // seen them when parsing.
3241 // So, apply the real CSS specificity in higher bits, allowing
3242 // for the following number of such rules in a single selector
3243 // (we're not checking for overflow thus...)
3244 #define WEIGHT_SPECIFICITY_ID 1<<29 // allow for 8 #id (b in comment below)
3245 #define WEIGHT_SPECIFICITY_ATTRCLS 1<<24 // allow for 32 .class and [attr...] (c)
3246 #define WEIGHT_SPECIFICITY_ELEMENT 1<<19 // allow for 32 element names div > p span (d)
3247 #define WEIGHT_SELECTOR_ORDER 1 // allow for counting 524288 selectors
3248
getWeight()3249 lUInt32 LVCssSelectorRule::getWeight() {
3250 /* Each LVCssSelectorRule will add its own weight to
3251 its LVCssSelector container specifity.
3252
3253 Following https://www.w3.org/TR/CSS2/cascade.html#specificity
3254
3255 A selector's specificity is calculated as follows:
3256
3257 - count 1 if the declaration is from is a 'style' attribute rather
3258 than a rule with a selector, 0 otherwise (= a) (In HTML, values
3259 of an element's "style" attribute are style sheet rules. These
3260 rules have no selectors, so a=1, b=0, c=0, and d=0.)
3261 - count the number of ID attributes in the selector (= b) => 1 << 16
3262 - count the number of other attributes and pseudo-classes in the
3263 selector (= c) => 1 << 8
3264 - count the number of element names and pseudo-elements in the
3265 selector (= d) => 1
3266
3267 The specificity is based only on the form of the selector. In
3268 particular, a selector of the form "[id=p33]" is counted as an
3269 attribute selector (a=0, b=0, c=1, d=0), even if the id attribute is
3270 defined as an "ID" in the source document's DTD.
3271 */
3272
3273 // declaration from a style="" attribute (a) are always applied last,
3274 // and don't have a selector here.
3275 // LVCssSelector._specificity will be added 1 by LVCssSelector when it
3276 // has itself an elementName // E
3277 //
3278 switch (_type) {
3279 case cssrt_id: // E#id
3280 return WEIGHT_SPECIFICITY_ID;
3281 break;
3282 case cssrt_attrset: // E[foo]
3283 case cssrt_attreq: // E[foo="value"]
3284 case cssrt_attreq_i: // E[foo="value" i]
3285 case cssrt_attrhas: // E[foo~="value"]
3286 case cssrt_attrhas_i: // E[foo~="value" i]
3287 case cssrt_attrstarts_word: // E[foo|="value"]
3288 case cssrt_attrstarts_word_i: // E[foo|="value" i]
3289 case cssrt_attrstarts: // E[foo^="value"]
3290 case cssrt_attrstarts_i: // E[foo^="value" i]
3291 case cssrt_attrends: // E[foo$="value"]
3292 case cssrt_attrends_i: // E[foo$="value" i]
3293 case cssrt_attrcontains: // E[foo*="value"]
3294 case cssrt_attrcontains_i: // E[foo*="value" i]
3295 case cssrt_class: // E.class
3296 case cssrt_pseudoclass: // E:pseudo-class
3297 return WEIGHT_SPECIFICITY_ATTRCLS;
3298 break;
3299 case cssrt_parent: // E > F
3300 case cssrt_ancessor: // E F
3301 case cssrt_predecessor: // E + F
3302 case cssrt_predsibling: // E ~ F
3303 // These don't contribute to specificity. If they
3304 // come with an element name, WEIGHT_SPECIFICITY_ELEMENT
3305 // has already been added in LVCssSelector::parse().
3306 return 0;
3307 break;
3308 case cssrt_universal: // *
3309 return 0;
3310 }
3311 return 0;
3312 }
3313
check(const ldomNode * & node)3314 bool LVCssSelectorRule::check( const ldomNode * & node )
3315 {
3316 if (!node || node->isNull() || node->isRoot())
3317 return false;
3318 // For most checks, while navigating nodes, we must ignore sibling text nodes.
3319 // We also ignore crengine internal boxing elements (inserted for rendering
3320 // purpose) by using the getUnboxedParent/Sibling(true) methods (providing
3321 // 'true' make them skip text nodes).
3322 // Note that if we are returnging 'true', the provided 'node' must stay
3323 // or be updated to the node on which next selectors (on the left in the
3324 // chain) must be checked against. When returning 'false', we can let
3325 // node be in any state, even messy.
3326 switch (_type)
3327 {
3328 case cssrt_parent: // E > F (child combinator)
3329 {
3330 node = node->getUnboxedParent();
3331 if (!node || node->isNull())
3332 return false;
3333 // If _id=0, we are the parent and we match
3334 if (!_id || node->getNodeId() == _id)
3335 return true;
3336 return false;
3337 }
3338 break;
3339 case cssrt_ancessor: // E F (descendant combinator)
3340 {
3341 for (;;) {
3342 node = node->getUnboxedParent();
3343 if (!node || node->isNull())
3344 return false;
3345 // cssrt_ancessor is a non-deterministic rule: next rules
3346 // could fail when checked against this parent that matches
3347 // current rule, but could succeed when checked against
3348 // another parent that matches.
3349 // So, we need to check the full next rules chain on each
3350 // of our parent that matches current rule.
3351 // As we check the whole selector rules chain here,
3352 // LVCssSelector::check() won't have to: so it will trust
3353 // our return value.
3354 // Note: this is quite expensive compared to other combinators.
3355 if ( !_id || node->getNodeId() == _id ) {
3356 // No element name to match against, or this element name
3357 // matches: check next rules starting from there.
3358 const ldomNode * n = node;
3359 if (checkNextRules(n))
3360 // We match all next rules (possibly including other
3361 // cssrt_ancessor)
3362 return true;
3363 // Next rules didn't match: continue with next parent
3364 }
3365 }
3366 }
3367 break;
3368 case cssrt_predecessor: // E + F (adjacent sibling combinator)
3369 {
3370 node = node->getUnboxedPrevSibling(true); // skip text nodes
3371 if (!node || node->isNull())
3372 return false;
3373 if (!_id || node->getNodeId() == _id) {
3374 // No element name to match against, or this element name matches
3375 return true;
3376 }
3377 return false;
3378 }
3379 break;
3380 case cssrt_predsibling: // E ~ F (preceding sibling / general sibling combinator)
3381 {
3382 for (;;) {
3383 node = node->getUnboxedPrevSibling(true); // skip text nodes
3384 if (!node || node->isNull())
3385 return false;
3386 if ( !_id || node->getNodeId() == _id ) {
3387 // No element name to match against, or this element name
3388 // matches: check next rules starting from there.
3389 // Same as what is done in cssrt_ancessor above: we may have
3390 // to check next rules on all preceeding matching siblings.
3391 const ldomNode * n = node;
3392 if (checkNextRules(n))
3393 // We match all next rules (possibly including other
3394 // cssrt_ancessor or cssrt_predsibling)
3395 return true;
3396 // Next rules didn't match: continue with next prev sibling
3397 }
3398 }
3399 }
3400 break;
3401 case cssrt_attrset: // E[foo]
3402 {
3403 if ( !node->hasAttributes() )
3404 return false;
3405 return node->hasAttribute(_attrid);
3406 }
3407 break;
3408 case cssrt_attreq: // E[foo="value"]
3409 case cssrt_attreq_i: // E[foo="value" i]
3410 {
3411 if ( !node->hasAttribute(_attrid) )
3412 return false;
3413 lString32 val = node->getAttributeValue(_attrid);
3414 if (_type == cssrt_attreq_i)
3415 val.lowercase();
3416 return val == _value;
3417 }
3418 break;
3419 case cssrt_attrhas: // E[foo~="value"]
3420 case cssrt_attrhas_i: // E[foo~="value" i]
3421 // one of space separated values
3422 {
3423 if ( !node->hasAttribute(_attrid) )
3424 return false;
3425 lString32 val = node->getAttributeValue(_attrid);
3426 if (_type == cssrt_attrhas_i)
3427 val.lowercase();
3428 int p = val.pos( lString32(_value.c_str()) );
3429 if (p<0)
3430 return false;
3431 if ( (p>0 && val[p-1]!=' ')
3432 || (p+_value.length()<val.length() && val[p+_value.length()]!=' ') )
3433 return false;
3434 return true;
3435 }
3436 break;
3437 case cssrt_attrstarts_word: // E[foo|="value"]
3438 case cssrt_attrstarts_word_i: // E[foo|="value" i]
3439 {
3440 if ( !node->hasAttribute(_attrid) )
3441 return false;
3442 // value can be exactly value or can begin with value
3443 // immediately followed by a hyphen
3444 lString32 val = node->getAttributeValue(_attrid);
3445 int val_len = val.length();
3446 int value_len = _value.length();
3447 if (value_len > val_len)
3448 return false;
3449 if (_type == cssrt_attrstarts_i)
3450 val.lowercase();
3451 if (value_len == val_len) {
3452 return val == _value;
3453 }
3454 if (val[value_len] != '-')
3455 return false;
3456 val = val.substr(0, value_len);
3457 return val == _value;
3458 }
3459 break;
3460 case cssrt_attrstarts: // E[foo^="value"]
3461 case cssrt_attrstarts_i: // E[foo^="value" i]
3462 {
3463 if ( !node->hasAttribute(_attrid) )
3464 return false;
3465 lString32 val = node->getAttributeValue(_attrid);
3466 int val_len = val.length();
3467 int value_len = _value.length();
3468 if (value_len > val_len)
3469 return false;
3470 val = val.substr(0, value_len);
3471 if (_type == cssrt_attrstarts_i)
3472 val.lowercase();
3473 return val == _value;
3474 }
3475 break;
3476 case cssrt_attrends: // E[foo$="value"]
3477 case cssrt_attrends_i: // E[foo$="value" i]
3478 {
3479 if ( !node->hasAttribute(_attrid) )
3480 return false;
3481 lString32 val = node->getAttributeValue(_attrid);
3482 int val_len = val.length();
3483 int value_len = _value.length();
3484 if (value_len > val_len)
3485 return false;
3486 val = val.substr(val_len-value_len, value_len);
3487 if (_type == cssrt_attrends_i)
3488 val.lowercase();
3489 return val == _value;
3490 }
3491 break;
3492 case cssrt_attrcontains: // E[foo*="value"]
3493 case cssrt_attrcontains_i: // E[foo*="value" i]
3494 {
3495 if ( !node->hasAttribute(_attrid) )
3496 return false;
3497 lString32 val = node->getAttributeValue(_attrid);
3498 if (_value.length()>val.length())
3499 return false;
3500 if (_type == cssrt_attrcontains_i)
3501 val.lowercase();
3502 return val.pos(_value, 0) >= 0;
3503 }
3504 break;
3505 case cssrt_id: // E#id
3506 {
3507 lString32 val = node->getAttributeValue(attr_id);
3508 if ( val.empty() )
3509 return false;
3510 /*lString32 ldomDocumentFragmentWriter::convertId( lString32 id ) adds codeBasePrefix to
3511 *original id name, I can not get codeBasePrefix from here so I add a space to identify the
3512 *real id name.*/
3513 int pos = val.pos(" ");
3514 if (pos != -1) {
3515 val = val.substr(pos + 1, val.length() - pos - 1);
3516 }
3517 if (_value.length()>val.length())
3518 return false;
3519 return val == _value;
3520 }
3521 break;
3522 case cssrt_class: // E.class
3523 {
3524 lString32 val = node->getAttributeValue(attr_class);
3525 if ( val.empty() )
3526 return false;
3527 // val.lowercase(); // className should be case sensitive
3528 // if ( val.length() != _value.length() )
3529 // return false;
3530 //CRLog::trace("attr_class: %s %s", LCSTR(val), LCSTR(_value) );
3531 /*As I have eliminated leading and ending spaces in the attribute value, any space in
3532 *val means there are more than one classes */
3533 if (val.pos(" ") != -1) {
3534 lString32 value_w_space_after = _value + " ";
3535 if (val.pos(value_w_space_after) == 0)
3536 return true; // at start
3537 lString32 value_w_space_before = " " + _value;
3538 int pos = val.pos(value_w_space_before);
3539 if (pos != -1 && pos + value_w_space_before.length() == val.length())
3540 return true; // at end
3541 lString32 value_w_spaces_before_after = " " + _value + " ";
3542 if (val.pos(value_w_spaces_before_after) != -1)
3543 return true; // in between
3544 return false;
3545 }
3546 return val == _value;
3547 }
3548 break;
3549 case cssrt_universal: // *
3550 return true; // should it be: return !node->isBoxingNode(); ?
3551 case cssrt_pseudoclass: // E:pseudo-class
3552 {
3553 int nodeId;
3554 switch (_attrid) {
3555 case csspc_root:
3556 {
3557 // We never have any CSS when meeting the crengine root node.
3558 // Only when using :root in our cr3gui/data/*.css we get a chance
3559 // to meet the root node's first child node, which is not always <html>.
3560 // The elements hierarchy may be:
3561 // <html> <body> with plain HTML files.
3562 // <body> <DocFragment> <body> with EPUB documents.
3563 // The embedded stylesheets, being stored as attribute/child of <body>
3564 // or <DocFragment> are not yet there when metting the <html> or the
3565 // first <body> node.
3566 // So, we can only try to match the <body> that is a child of
3567 // <html> or <DocFragment>, and apply this style to it.
3568 // If we were to use :root in our cr3gui/data/*.css, we would meet
3569 // the first <body> or the <html> node, but to avoid applyng the
3570 // style twice (to the 2 <body>s), we want to NOT match the first
3571 // node.
3572 ldomNode * parent = node->getUnboxedParent();
3573 if ( !parent || parent->isRoot() )
3574 return false; // we do not want to return true;
3575 lUInt16 parentNodeId = parent->getNodeId();
3576 return parentNodeId == el_DocFragment || parentNodeId == el_html;
3577 // Note: to override, with style tweaks, styles set with :root,
3578 // it should be enough to use body { ... !important }.
3579 // The '!important' is needed because :root has a higher
3580 // specificity that a simple body {}.
3581 }
3582 break;
3583 case csspc_empty:
3584 return node->getChildCount() == 0;
3585 break;
3586 case csspc_dir:
3587 {
3588 // We're looking at parents, but we don't want to update 'node'
3589 const ldomNode * elem = node;
3590 while (elem) {
3591 if ( !elem->hasAttribute( attr_dir ) ) {
3592 // No need to use getUnboxedParent(), boxes don't have this attribute
3593 elem = elem->getParentNode();
3594 continue;
3595 }
3596 lString32 dir = elem->getAttributeValue( attr_dir );
3597 dir = dir.lowercase(); // (no need for trim(), it's done by the XMLParser)
3598 if ( dir.compare(_value) == 0 )
3599 return true;
3600 // We could ignore invalide values, but for now, just stop looking.
3601 return false;
3602 }
3603 return false;
3604 }
3605 break;
3606 case csspc_first_child:
3607 case csspc_first_of_type:
3608 {
3609 int n; // 1 = false, 2 = true (should not be 0 for caching)
3610 if ( !get_cached_node_checked_property(node, _attrid, n) ) {
3611 n = 2; // true
3612 if ( _attrid == csspc_first_of_type )
3613 nodeId = node->getNodeId();
3614 const ldomNode * elem = node;
3615 for (;;) {
3616 elem = elem->getUnboxedPrevSibling(true); // skip text nodes
3617 if (!elem)
3618 break;
3619 // We have a previous sibling
3620 if (_attrid == csspc_first_child || elem->getNodeId() == nodeId) {
3621 n = 1; // false, we're not the first
3622 break;
3623 }
3624 }
3625 cache_node_checked_property(node, _attrid, n);
3626 }
3627 return n == 2;
3628 }
3629 break;
3630 case csspc_last_child:
3631 case csspc_last_of_type:
3632 {
3633 int n; // 1 = false, 2 = true (should not be 0 for caching)
3634 if ( !get_cached_node_checked_property(node, _attrid, n) ) {
3635 n = 2; // true
3636 if ( _attrid == csspc_last_of_type )
3637 nodeId = node->getNodeId();
3638 const ldomNode * elem = node;
3639 for (;;) {
3640 elem = elem->getUnboxedNextSibling(true); // skip text nodes
3641 if (!elem)
3642 break;
3643 // We have a next sibling
3644 if (_attrid == csspc_last_child || elem->getNodeId() == nodeId) {
3645 n = 1; // false, we're not the last
3646 break;
3647 }
3648 }
3649 cache_node_checked_property(node, _attrid, n);
3650 }
3651 return n == 2;
3652 }
3653 break;
3654 case csspc_nth_child:
3655 case csspc_nth_of_type:
3656 {
3657 int n;
3658 if ( !get_cached_node_checked_property(node, _attrid, n) ) {
3659 if ( _attrid == csspc_nth_of_type )
3660 nodeId = node->getNodeId();
3661 const ldomNode * elem = node;
3662 n = 1;
3663 for (;;) {
3664 elem = elem->getUnboxedPrevSibling(true); // skip text nodes
3665 if (!elem)
3666 break;
3667 if (_attrid == csspc_nth_child || elem->getNodeId() == nodeId)
3668 n++;
3669 }
3670 cache_node_checked_property(node, _attrid, n);
3671 }
3672 return match_nth_value(_value, n);
3673 }
3674 break;
3675 case csspc_nth_last_child:
3676 case csspc_nth_last_of_type:
3677 {
3678 int n;
3679 if ( !get_cached_node_checked_property(node, _attrid, n) ) {
3680 if ( _attrid == csspc_nth_last_of_type )
3681 nodeId = node->getNodeId();
3682 const ldomNode * elem = node;
3683 n = 1;
3684 for (;;) {
3685 elem = elem->getUnboxedNextSibling(true); // skip text nodes
3686 if (!elem)
3687 break;
3688 if (_attrid == csspc_nth_last_child || elem->getNodeId() == nodeId)
3689 n++;
3690 }
3691 cache_node_checked_property(node, _attrid, n);
3692 }
3693 return match_nth_value(_value, n);
3694 }
3695 break;
3696 case csspc_only_child:
3697 case csspc_only_of_type:
3698 {
3699 int n; // 1 = false, 2 = true (should not be 0 for caching)
3700 if ( !get_cached_node_checked_property(node, _attrid, n) ) {
3701 n = 2; // true
3702 if ( _attrid == csspc_only_of_type )
3703 nodeId = node->getNodeId();
3704 const ldomNode * elem = node->getUnboxedParent()->getUnboxedFirstChild(true);
3705 while (elem) {
3706 if (elem != node) {
3707 if (_attrid == csspc_only_child || elem->getNodeId() == nodeId) {
3708 n = 1; // false, we're not alone
3709 break;
3710 }
3711 }
3712 elem = elem->getUnboxedNextSibling(true);
3713 }
3714 cache_node_checked_property(node, _attrid, n);
3715 }
3716 return n == 2;
3717 }
3718 break;
3719 }
3720 }
3721 return false;
3722 }
3723 return true;
3724 }
3725
checkNextRules(const ldomNode * node)3726 bool LVCssSelectorRule::checkNextRules( const ldomNode * node )
3727 {
3728 // Similar to LVCssSelector::check() just below, but
3729 // invoked from a rule
3730 LVCssSelectorRule * rule = getNext();
3731 if (!rule)
3732 return true;
3733 const ldomNode * n = node;
3734 do {
3735 if ( !rule->check(n) )
3736 return false;
3737 if ( rule->isFullChecking() )
3738 return true;
3739 rule = rule->getNext();
3740 } while (rule!=NULL);
3741 return true;
3742 }
3743
check(const ldomNode * node) const3744 bool LVCssSelector::check( const ldomNode * node ) const
3745 {
3746 lUInt16 nodeId = node->getNodeId();
3747 if ( nodeId == el_pseudoElem ) {
3748 if ( !_pseudo_elem ) { // not a ::before/after rule
3749 // Our added pseudoElem element should not match any other rules
3750 // (if we added it as a child of a P element, it should not match P > *)
3751 return false;
3752 }
3753 else {
3754 // We might be the pseudoElem that was created by this selector.
3755 // Start checking the rules starting from the real parent.
3756 node = node->getUnboxedParent();
3757 nodeId = node->getNodeId();
3758 }
3759 }
3760 else if ( _id==0 && node->isBoxingNode() ) {
3761 // Don't apply "... *" or '.classname' selectors to boxing nodes
3762 // (but let those with our internal element names ("... autoBoxing") be applied)
3763 return false;
3764 }
3765 // check main Id
3766 if (_id!=0 && nodeId != _id)
3767 return false;
3768 if (!_rules)
3769 return true;
3770 // check additional rules
3771 const ldomNode * n = node;
3772 LVCssSelectorRule * rule = _rules;
3773 do {
3774 if ( !rule->check(n) )
3775 return false;
3776 // cssrt_ancessor or cssrt_predsibling rules will have checked next
3777 // rules on each parent or sibling. If it didn't return false, it
3778 // found one on which next rules match: no need to check them again
3779 if ( rule->isFullChecking() )
3780 return true;
3781 rule = rule->getNext();
3782 } while (rule!=NULL);
3783 return true;
3784 }
3785
parse_attr_value(const char * & str,char * buf,bool & parse_trailing_i,char stop_char=']')3786 bool parse_attr_value( const char * &str, char * buf, bool &parse_trailing_i, char stop_char=']' )
3787 {
3788 int pos = 0;
3789 skip_spaces( str );
3790 if (*str=='\"')
3791 {
3792 str++;
3793 for ( ; str[pos] && str[pos]!='\"'; pos++)
3794 {
3795 if (pos>=64)
3796 return false;
3797 }
3798 if (str[pos]!='\"')
3799 return false;
3800 for (int i=0; i<pos; i++)
3801 buf[i] = str[i];
3802 buf[pos] = 0;
3803 str += pos+1;
3804 skip_spaces( str );
3805 // The trailing ' i' must be outside the quotes
3806 if (parse_trailing_i) {
3807 parse_trailing_i = false;
3808 if (*str == 'i' || *str == 'I') {
3809 parse_trailing_i = true;
3810 str++;
3811 skip_spaces( str );
3812 }
3813 }
3814 if (*str != stop_char)
3815 return false;
3816 str++;
3817 return true;
3818 }
3819 else
3820 {
3821 for ( ; str[pos] && str[pos]!=' ' && str[pos]!='\t' && str[pos]!=stop_char; pos++)
3822 {
3823 if (pos>=64)
3824 return false;
3825 }
3826 int end_pos = pos;
3827 if (parse_trailing_i) {
3828 parse_trailing_i = false;
3829 if (end_pos == 0) // Empty value, or some leading space: this is invalid
3830 return false;
3831 if (str[pos] && str[pos]==' ' && str[pos+1] && (str[pos+1]=='i' || str[pos+1]=='I')) {
3832 parse_trailing_i = true;
3833 pos+=2;
3834 }
3835 }
3836 if (str[pos]!=stop_char)
3837 return false;
3838 for (int i=0; i<end_pos; i++)
3839 buf[i] = str[i];
3840 buf[end_pos] = 0;
3841 str+=pos;
3842 str++;
3843 return true;
3844 }
3845 }
3846
parse_attr_value(const char * & str,char * buf,char stop_char=']')3847 bool parse_attr_value( const char * &str, char * buf, char stop_char=']' )
3848 {
3849 bool parse_trailing_i = false;
3850 return parse_attr_value( str, buf, parse_trailing_i, stop_char );
3851 }
3852
parse_attr(const char * & str,lxmlDocBase * doc)3853 LVCssSelectorRule * parse_attr( const char * &str, lxmlDocBase * doc )
3854 {
3855 // We should not skip_spaces() here: it's invalid just after one of '.#:'
3856 // and we should keep the one after the parsed value as its presence or not
3857 // has a different meaning (no space: multiple attributes or classnames
3858 // selector - space: descendant combinator)
3859 char attrname[512];
3860 char attrvalue[512];
3861 LVCssSelectorRuleType st = cssrt_universal;
3862 if (*str=='.') {
3863 // E.class
3864 str++;
3865 if (!parse_ident( str, attrvalue ))
3866 return NULL;
3867 LVCssSelectorRule * rule = new LVCssSelectorRule(cssrt_class);
3868 lString32 s( attrvalue );
3869 // s.lowercase(); // className should be case sensitive
3870 rule->setAttr(attr_class, s);
3871 return rule;
3872 } else if ( *str=='#' ) {
3873 // E#id
3874 str++;
3875 if (!parse_ident( str, attrvalue ))
3876 return NULL;
3877 LVCssSelectorRule * rule = new LVCssSelectorRule(cssrt_id);
3878 lString32 s( attrvalue );
3879 rule->setAttr(attr_id, s);
3880 return rule;
3881 } else if ( *str==':' ) {
3882 // E:pseudo-class (eg: E:first-child)
3883 str++;
3884 if (*str==':') {
3885 // pseudo element (double ::, eg: E::first-line) are not supported,
3886 // except ::before/after which are handled in LVCssSelector::parse()
3887 str--;
3888 return NULL;
3889 }
3890 int n = parse_name( str, css_pseudo_classes, -1 );
3891 if (n == -1) { // not one of out supported pseudo classes
3892 str--; // LVCssSelector::parse() will also check for :before/after with a single ':'
3893 return NULL;
3894 }
3895 attrvalue[0] = 0;
3896 if (*str=='(') { // parse () content
3897 str++;
3898 if ( !parse_attr_value( str, attrvalue, ')') )
3899 return NULL;
3900 // We don't parse the value here, it may have specific meaning
3901 // per pseudo-class type
3902 // But for the ones we handle, we only compare strings to a fixed set of target
3903 // values, so trim() and lowercase() below to avoid doing it on each check.
3904 }
3905 LVCssSelectorRule * rule = new LVCssSelectorRule(cssrt_pseudoclass);
3906 lString32 s( attrvalue );
3907 s.trim().lowercase();
3908 if ( n == csspc_nth_child || n == csspc_nth_of_type || n == csspc_nth_last_child || n == csspc_nth_last_of_type ) {
3909 // Parse "even", "odd", "5", "5n", "5n+2", "-n" into a few
3910 // numbers packed into a lString32, for quicker checking.
3911 s = parse_nth_value(s);
3912 }
3913 rule->setAttr(n, s);
3914 // printf("made pseudo class rule %d with %s\n", n, UnicodeToLocal(s).c_str());
3915 if ( n >= csspc_last_child ) {
3916 // Pseudoclasses after csspc_last_child can't be accurately checked
3917 // in the initial loading phase: a re-render will be needed.
3918 doc->setNodeStylesInvalidIfLoading();
3919 // There might still be some issues if CSS would set some display: property
3920 // as, when re-rendering, a cache might be present and prevent modifying
3921 // the DOM for some needed autoBoxing - or the invalid styles set now
3922 // while loading would have created some autoBoxing that we won't be
3923 // able to remove...
3924 }
3925 return rule;
3926 } else if (*str != '[') // We're looking for an attribute selector after here
3927 return NULL;
3928 str++;
3929 // We may find and skip spaces inside [...]
3930 skip_spaces( str );
3931 if (!parse_ident( str, attrname ))
3932 return NULL;
3933 skip_spaces( str );
3934 attrvalue[0] = 0;
3935 bool parse_trailing_i = false;
3936 if (*str==']')
3937 {
3938 st = cssrt_attrset;
3939 str++;
3940 }
3941 else if (*str=='=')
3942 {
3943 str++;
3944 parse_trailing_i = true; // reset to false if value does not end with " i"
3945 if (!parse_attr_value( str, attrvalue, parse_trailing_i))
3946 return NULL;
3947 if (parse_trailing_i)
3948 st = cssrt_attreq_i;
3949 else
3950 st = cssrt_attreq;
3951 }
3952 else if (*str=='~' && str[1]=='=')
3953 {
3954 str+=2;
3955 parse_trailing_i = true;
3956 if (!parse_attr_value( str, attrvalue, parse_trailing_i))
3957 return NULL;
3958 if (parse_trailing_i)
3959 st = cssrt_attrhas_i;
3960 else
3961 st = cssrt_attrhas;
3962 }
3963 else if (*str=='|' && str[1]=='=')
3964 {
3965 str+=2;
3966 parse_trailing_i = true;
3967 if (!parse_attr_value( str, attrvalue, parse_trailing_i))
3968 return NULL;
3969 if (parse_trailing_i)
3970 st = cssrt_attrstarts_word_i;
3971 else
3972 st = cssrt_attrstarts_word;
3973 }
3974 else if (*str=='^' && str[1]=='=')
3975 {
3976 str+=2;
3977 parse_trailing_i = true;
3978 if (!parse_attr_value( str, attrvalue, parse_trailing_i))
3979 return NULL;
3980 if (parse_trailing_i)
3981 st = cssrt_attrstarts_i;
3982 else
3983 st = cssrt_attrstarts;
3984 }
3985 else if (*str=='$' && str[1]=='=')
3986 {
3987 str+=2;
3988 parse_trailing_i = true;
3989 if (!parse_attr_value( str, attrvalue, parse_trailing_i))
3990 return NULL;
3991 if (parse_trailing_i)
3992 st = cssrt_attrends_i;
3993 else
3994 st = cssrt_attrends;
3995 }
3996 else if (*str=='*' && str[1]=='=')
3997 {
3998 str+=2;
3999 parse_trailing_i = true;
4000 if (!parse_attr_value( str, attrvalue, parse_trailing_i))
4001 return NULL;
4002 if (parse_trailing_i)
4003 st = cssrt_attrcontains_i;
4004 else
4005 st = cssrt_attrcontains;
4006 }
4007 else
4008 {
4009 return NULL;
4010 }
4011 LVCssSelectorRule * rule = new LVCssSelectorRule(st);
4012 lString32 s( attrvalue );
4013 if (parse_trailing_i) { // cssrt_attr*_i met
4014 s.lowercase();
4015 }
4016 lUInt16 id = doc->getAttrNameIndex( lString32(attrname).c_str() );
4017 rule->setAttr(id, s);
4018 return rule;
4019 }
4020
insertRuleStart(LVCssSelectorRule * rule)4021 void LVCssSelector::insertRuleStart( LVCssSelectorRule * rule )
4022 {
4023 rule->setNext( _rules );
4024 _rules = rule;
4025 }
4026
insertRuleAfterStart(LVCssSelectorRule * rule)4027 void LVCssSelector::insertRuleAfterStart( LVCssSelectorRule * rule )
4028 {
4029 if ( !_rules ) {
4030 _rules = rule;
4031 return;
4032 }
4033 rule->setNext( _rules->getNext() );
4034 _rules->setNext( rule );
4035 }
4036
parse(const char * & str,lxmlDocBase * doc)4037 bool LVCssSelector::parse( const char * &str, lxmlDocBase * doc )
4038 {
4039 if (!str || !*str)
4040 return false;
4041 for (;;)
4042 {
4043 skip_spaces( str );
4044 // We need to skip spaces in the generic parsing, but we need to
4045 // NOT check for attributes (.class, [attr=val]...) if we did skip
4046 // some spaces (because "DIV .classname" has a different meaning
4047 // (ancestor/descendant combinator) than "DIV.classname" (element
4048 // with classname)).
4049 bool check_attribute_rules = true;
4050 if ( *str == '*' ) // universal selector
4051 {
4052 str++;
4053 if (*str==' ' || *str=='\t' || *str=='\n' || *str == '\r')
4054 check_attribute_rules = false;
4055 skip_spaces( str );
4056 _id = 0;
4057 }
4058 else if ( *str == '.' ) // classname follows
4059 {
4060 _id = 0;
4061 }
4062 else if ( *str == ':' ) // pseudoclass follows
4063 {
4064 _id = 0;
4065 }
4066 else if ( *str == '[' ) // attribute selector follows
4067 {
4068 _id = 0;
4069 }
4070 else if ( *str == '#' ) // node Id follows
4071 {
4072 _id = 0; // (elementName internal id)
4073 }
4074 else if ( css_is_alpha( *str ) ) // element name follows
4075 {
4076 // ident
4077 char ident[64];
4078 if (!parse_ident( str, ident ))
4079 return false;
4080 // All element names have been lowercased by HTMLParser (except
4081 // a few ones that are added explicitely by crengine): we need
4082 // to lowercase them here too to expect a match.
4083 lString32 element(ident);
4084 if ( element.length() < 7 ) {
4085 // Avoid following string comparisons if element name string
4086 // is shorter than the shortest of them (rubyBox)
4087 element = element.lowercase();
4088 }
4089 else if ( element != "DocFragment" && element != "autoBoxing" && element != "tabularBox" &&
4090 element != "rubyBox" && element != "floatBox" && element != "inlineBox" &&
4091 element != "pseudoElem" && element != "FictionBook" ) {
4092 element = element.lowercase();
4093 }
4094 _id = doc->getElementNameIndex( element.c_str() );
4095 // Note: non standard element names (not listed in fb2def.h) in
4096 // selectors (eg: blah {font-style: italic}) may have different values
4097 // returned by getElementNameIndex() across book loadings, and cause:
4098 // "cached rendering is invalid (style hash mismatch): doing full rendering"
4099 _specificity += WEIGHT_SPECIFICITY_ELEMENT; // we have an element: update specificity
4100 if (*str==' ' || *str=='\t' || *str=='\n' || *str == '\r')
4101 check_attribute_rules = false;
4102 skip_spaces( str );
4103 }
4104 else
4105 {
4106 return false;
4107 }
4108 if ( *str == ',' || *str == '{' )
4109 return true;
4110 // one or more attribute rules
4111 bool attr_rule = false;
4112 if (check_attribute_rules) {
4113 while ( *str == '[' || *str=='.' || *str=='#' || *str==':' )
4114 {
4115 LVCssSelectorRule * rule = parse_attr( str, doc );
4116 if (!rule) {
4117 // Might be one of our supported pseudo elements, which should
4118 // start with "::" but might start with a single ":".
4119 // These pseudo element do not add a LVCssSelectorRule.
4120 if ( *str==':' ) {
4121 str++;
4122 if ( *str==':' ) // skip double ::
4123 str++;
4124 int n = parse_name( str, css_pseudo_elements, -1 );
4125 if (n != -1) {
4126 _pseudo_elem = n+1; // starts at 1
4127 _specificity += WEIGHT_SPECIFICITY_ELEMENT;
4128 // Done with this selector: we expect ::before and ::after
4129 // to come always last, and are not followed by other rules.
4130 // ("x::before::before" seems not ensured by Firefox - if we
4131 // stop between them, the 2nd "::before" will make the parsing
4132 // of the declaration invalid, and so this rule.)
4133 return true;
4134 }
4135 }
4136 return false;
4137 }
4138 insertRuleStart( rule ); //insertRuleAfterStart
4139 //insertRuleAfterStart( rule ); //insertRuleAfterStart
4140 _specificity += rule->getWeight();
4141
4142 /*
4143 if ( _id!=0 ) {
4144 LVCssSelectorRule * rule = new LVCssSelectorRule(cssrt_parent);
4145 rule->setId(_id);
4146 insertRuleStart( rule );
4147 _id=0;
4148 }
4149 */
4150
4151 // We should not skip spaces here: combining multiple classnames or
4152 // attributes is to be done only when there is no space in between
4153 // them. Otherwise, it's a descendant combinator (cssrt_ancessor).
4154
4155 attr_rule = true;
4156 //continue;
4157 }
4158 // Skip any space now after all combining attributes or classnames have been parsed
4159 skip_spaces( str );
4160 }
4161 // element relation
4162 if (*str == '>')
4163 {
4164 str++;
4165 LVCssSelectorRule * rule = new LVCssSelectorRule(cssrt_parent);
4166 rule->setId(_id);
4167 insertRuleStart( rule );
4168 _specificity += rule->getWeight();
4169 _id=0;
4170 continue;
4171 }
4172 else if (*str == '+')
4173 {
4174 str++;
4175 LVCssSelectorRule * rule = new LVCssSelectorRule(cssrt_predecessor);
4176 rule->setId(_id);
4177 insertRuleStart( rule );
4178 _specificity += rule->getWeight();
4179 _id=0;
4180 continue;
4181 }
4182 else if (*str == '~')
4183 {
4184 str++;
4185 LVCssSelectorRule * rule = new LVCssSelectorRule(cssrt_predsibling);
4186 rule->setId(_id);
4187 insertRuleStart( rule );
4188 _specificity += rule->getWeight();
4189 _id=0;
4190 continue;
4191 }
4192 else if (css_is_alpha( *str ) || (*str == '.') || (*str == '#') || (*str == '*') )
4193 {
4194 LVCssSelectorRule * rule = new LVCssSelectorRule(cssrt_ancessor);
4195 rule->setId(_id);
4196 insertRuleStart( rule );
4197 _specificity += rule->getWeight();
4198 _id=0;
4199 continue;
4200 }
4201 if ( !attr_rule )
4202 return false;
4203 else if ( *str == ',' || *str == '{' )
4204 return true;
4205 }
4206 }
4207
skip_until_end_of_rule(const char * & str)4208 static bool skip_until_end_of_rule( const char * &str )
4209 {
4210 while ( *str && *str!='}' )
4211 str++;
4212 if ( *str == '}' )
4213 str++;
4214 return *str != 0;
4215 }
4216
applyToPseudoElement(const ldomNode * node,css_style_rec_t * style) const4217 void LVCssSelector::applyToPseudoElement( const ldomNode * node, css_style_rec_t * style ) const
4218 {
4219 // This might be called both on the node that match the selector (we should
4220 // not apply to the style of this node), and on the actual pseudo element
4221 // once it has been created as a child (to which we should apply).
4222 css_style_rec_t * target_style = NULL;
4223 if ( node->getNodeId() == el_pseudoElem ) {
4224 if ( ( _pseudo_elem == csspe_before && node->hasAttribute(attr_Before) )
4225 || ( _pseudo_elem == csspe_after && node->hasAttribute(attr_After) ) ) {
4226 target_style = style;
4227 }
4228 }
4229 else {
4230 // For the matching node, we create two style slots to which we apply
4231 // the declaration. This is just to have all styles applied and see
4232 // at the end if the pseudo element is display:none or not, and if
4233 // it should be skipped or created.
4234 // These css_style_rec_t are just temp slots to gather what's applied,
4235 // they are not the ones that will be associated to the pseudo element.
4236 if ( _pseudo_elem == csspe_before ) {
4237 if ( !style->pseudo_elem_before_style ) {
4238 style->pseudo_elem_before_style = new css_style_rec_t;
4239 }
4240 target_style = style->pseudo_elem_before_style;
4241 }
4242 else if ( _pseudo_elem == csspe_after ) {
4243 if ( !style->pseudo_elem_after_style ) {
4244 style->pseudo_elem_after_style = new css_style_rec_t;
4245 }
4246 target_style = style->pseudo_elem_after_style;
4247 }
4248 }
4249
4250 if ( target_style ) {
4251 if ( !(target_style->flags & STYLE_REC_FLAG_MATCHED ) ) {
4252 // pseudoElem starts with "display: none" (in case they were created and
4253 // inserted in the DOM by a CSS selector that can later disappear).
4254 // Switch them to "display: inline" when we meet such a selector.
4255 // (The coming up _decl->apply() may not update ->display, or it may set
4256 // it explicitely to css_d_none, that we don't want reset to inline.)
4257 target_style->display = css_d_inline;
4258 target_style->flags |= STYLE_REC_FLAG_MATCHED;
4259 }
4260 // And apply this selector styling.
4261 _decl->apply(target_style);
4262 }
4263 return;
4264 }
4265
LVCssSelectorRule(LVCssSelectorRule & v)4266 LVCssSelectorRule::LVCssSelectorRule( LVCssSelectorRule & v )
4267 : _type(v._type), _id(v._id), _attrid(v._attrid)
4268 , _next(NULL)
4269 , _value( v._value )
4270 {
4271 if ( v._next )
4272 _next = new LVCssSelectorRule( *v._next );
4273 }
4274
LVCssSelector(LVCssSelector & v)4275 LVCssSelector::LVCssSelector( LVCssSelector & v )
4276 : _id(v._id), _decl(v._decl), _specificity(v._specificity), _pseudo_elem(v._pseudo_elem), _next(NULL), _rules(NULL)
4277 {
4278 if ( v._next )
4279 _next = new LVCssSelector( *v._next );
4280 if ( v._rules )
4281 _rules = new LVCssSelectorRule( *v._rules );
4282 }
4283
set(LVPtrVector<LVCssSelector> & v)4284 void LVStyleSheet::set(LVPtrVector<LVCssSelector> & v )
4285 {
4286 _selectors.clear();
4287 if ( !v.size() )
4288 return;
4289 _selectors.reserve( v.size() );
4290 for ( int i=0; i<v.size(); i++ ) {
4291 LVCssSelector * selector = v[i];
4292 if ( selector )
4293 _selectors.add( new LVCssSelector( *selector ) );
4294 else
4295 _selectors.add( NULL );
4296 }
4297 }
4298
LVStyleSheet(LVStyleSheet & sheet)4299 LVStyleSheet::LVStyleSheet( LVStyleSheet & sheet )
4300 : _doc( sheet._doc )
4301 {
4302 set( sheet._selectors );
4303 _selector_count = sheet._selector_count;
4304 _charset = sheet._charset;
4305 }
4306
apply(const ldomNode * node,css_style_rec_t * style)4307 void LVStyleSheet::apply( const ldomNode * node, css_style_rec_t * style )
4308 {
4309 if (!_selectors.length())
4310 return; // no rules!
4311
4312 lUInt16 id = node->getNodeId();
4313 if ( id == el_pseudoElem ) { // get the id chain from the parent element
4314 // Note that a "div:before {float:left}" will result in: <div><floatBox><pseudoElem>
4315 id = node->getUnboxedParent()->getNodeId();
4316 }
4317
4318 // _selectors[0] holds the ordered chain of selectors starting (from
4319 // the right of the selector) with a rule with no element name attached
4320 // (eg. "div p .quote1", class name .quote1 should be checked against
4321 // all elements' classnames before continuing checking for ancestors).
4322 // _selectors[element_name_id] holds the ordered chain of selector starting
4323 // with that element name (eg. ".body div.chapter > p" should be
4324 // first checked agains all <p>).
4325 // To see which selectors apply to a <p>, we must iterate thru both chains,
4326 // checking and applying them in the order of specificity/parsed position.
4327 LVCssSelector * selector_0 = _selectors[0];
4328 LVCssSelector * selector_id = id>0 && id<_selectors.length() ? _selectors[id] : NULL;
4329
4330 for (;;)
4331 {
4332 if (selector_0!=NULL)
4333 {
4334 if (selector_id==NULL || selector_0->getSpecificity() < selector_id->getSpecificity() )
4335 {
4336 // step by sel_0
4337 selector_0->apply( node, style );
4338 selector_0 = selector_0->getNext();
4339 }
4340 else
4341 {
4342 // step by sel_id
4343 selector_id->apply( node, style );
4344 selector_id = selector_id->getNext();
4345 }
4346 }
4347 else if (selector_id!=NULL)
4348 {
4349 // step by sel_id
4350 selector_id->apply( node, style );
4351 selector_id = selector_id->getNext();
4352 }
4353 else
4354 {
4355 break; // end of chains
4356 }
4357 }
4358 }
4359
getHash()4360 lUInt32 LVCssSelectorRule::getHash()
4361 {
4362 lUInt32 hash = 0;
4363 hash = ( ( ( (lUInt32)_type * 31
4364 + (lUInt32)_id ) *31 )
4365 + (lUInt32)_attrid * 31 )
4366 + ::getHash(_value);
4367 return hash;
4368 }
4369
getHash()4370 lUInt32 LVCssSelector::getHash()
4371 {
4372 lUInt32 hash = 0;
4373 lUInt32 nextHash = 0;
4374
4375 if (_next)
4376 nextHash = _next->getHash();
4377 for (LVCssSelectorRule * p = _rules; p; p = p->getNext()) {
4378 lUInt32 ruleHash = p->getHash();
4379 hash = hash * 31 + ruleHash;
4380 }
4381 hash = hash * 31 + nextHash;
4382 hash = hash * 31 + _specificity;
4383 hash = hash * 31 + _pseudo_elem;
4384 if (!_decl.isNull())
4385 hash = hash * 31 + _decl->getHash();
4386 //CRLog::trace("selector hash: %8x", hash);
4387 return hash;
4388 }
4389
4390 /// calculate hash
getHash()4391 lUInt32 LVStyleSheet::getHash()
4392 {
4393 lUInt32 hash = 0;
4394 for ( int i=0; i<_selectors.length(); i++ ) {
4395 if ( _selectors[i] )
4396 hash = hash * 31 + _selectors[i]->getHash() + i*15324;
4397 }
4398 //CRLog::trace("LVStyleSheet::getHash() selector count: %d hash: %x", _selectors.length(), hash);
4399 return hash;
4400 }
4401
parse(const char * str,bool higher_importance,lString32 codeBase)4402 bool LVStyleSheet::parse( const char * str, bool higher_importance, lString32 codeBase )
4403 {
4404 if ( !_doc ) {
4405 // We can't parse anything if no _doc to get element name ids from
4406 return false;
4407 }
4408 LVCssSelector * selector = NULL;
4409 LVCssSelector * prev_selector;
4410 int err_count = 0;
4411 int rule_count = 0;
4412 lUInt32 domVersionRequested = _doc->getDOMVersionRequested();
4413 for (;*str;)
4414 {
4415 // new rule
4416 prev_selector = NULL;
4417 bool err = false;
4418 for (;*str;)
4419 {
4420 // parse charset, and... ignored it
4421 // just to avoid generating a parse error
4422 parseCharsetRule(str);
4423 // parse selector(s)
4424 // Have selector count number make the initial value
4425 // of _specificity, so order of selectors is preserved
4426 // when applying selectors with the same CSS specificity.
4427 selector = new LVCssSelector(_selector_count);
4428 _selector_count += 1; // = +WEIGHT_SELECTOR_ORDER
4429 selector->setNext( prev_selector );
4430 if ( !selector->parse(str, _doc) )
4431 {
4432 err = true;
4433 break;
4434 }
4435 else
4436 {
4437 if ( *str == ',' )
4438 {
4439 str++;
4440 prev_selector = selector;
4441 continue; // next selector
4442 }
4443 }
4444 // parse declaration
4445 LVCssDeclRef decl( new LVCssDeclaration );
4446 if ( !decl->parse( str, domVersionRequested, higher_importance, _doc, codeBase ) )
4447 {
4448 err = true;
4449 err_count++;
4450 }
4451 else
4452 {
4453 // set decl to selectors
4454 for (LVCssSelector * p = selector; p; p=p->getNext())
4455 p->setDeclaration( decl );
4456 rule_count++;
4457 }
4458 break;
4459 }
4460 if (err)
4461 {
4462 // error:
4463 // delete chain of selectors
4464 delete selector;
4465 // ignore current rule
4466 skip_until_end_of_rule( str );
4467 }
4468 else
4469 {
4470 // Ok:
4471 // place rules to sheet
4472 for (LVCssSelector * p = selector; p; )
4473 {
4474 LVCssSelector * item = p;
4475 p=p->getNext();
4476 lUInt16 id = item->getElementNameId();
4477 if (_selectors.length()<=id)
4478 _selectors.set(id, NULL);
4479 // insert with specificity sorting
4480 if ( _selectors[id] == NULL
4481 || _selectors[id]->getSpecificity() > item->getSpecificity() )
4482 {
4483 // insert as first item
4484 item->setNext( _selectors[id] );
4485 _selectors[id] = item;
4486 }
4487 else
4488 {
4489 // insert as internal item
4490 for (LVCssSelector * p = _selectors[id]; p; p = p->getNext() )
4491 {
4492 if ( p->getNext() == NULL
4493 || p->getNext()->getSpecificity() > item->getSpecificity() )
4494 {
4495 item->setNext( p->getNext() );
4496 p->setNext( item );
4497 break;
4498 }
4499 }
4500 }
4501 }
4502 }
4503 }
4504 return _selectors.length() > 0;
4505 }
4506
parseCharsetRule(const char * & str)4507 bool LVStyleSheet::parseCharsetRule( const char * &str )
4508 {
4509 // Parse rule '@charset' according https://developer.mozilla.org/en-US/docs/Web/CSS/@charset
4510 if (!str || !*str)
4511 return false;
4512 skip_spaces( str );
4513 if ( *str == '@' ) {
4514 str++;
4515 char word[64];
4516 if ( parse_ident( str, word ) ) {
4517 lString8 keyword(word);
4518 if (keyword == "charset") {
4519 // skip required space(s)
4520 if (*str == ' ' || *str == '\t') {
4521 skip_spaces(str);
4522 if (parse_attr_value(str, word, ';')) {
4523 if (_charset.empty()) {
4524 _charset = word;
4525 return true;
4526 }
4527 }
4528 }
4529 }
4530 }
4531 }
4532 return false;
4533 }
4534
4535 /// extract @import filename from beginning of CSS
LVProcessStyleSheetImport(const char * & str,lString8 & import_file)4536 bool LVProcessStyleSheetImport( const char * &str, lString8 & import_file )
4537 {
4538 const char * p = str;
4539 import_file.clear();
4540 skip_spaces( p );
4541 if ( *p !='@' )
4542 return false;
4543 p++;
4544 if (strncmp(p, "import", 6) != 0)
4545 return false;
4546 p+=6;
4547 skip_spaces( p );
4548 bool in_url = false;
4549 char quote_ch = 0;
4550 if ( !strncmp(p, "url", 3) ) {
4551 p+=3;
4552 skip_spaces( p );
4553 if ( *p != '(' )
4554 return false;
4555 p++;
4556 skip_spaces( p );
4557 in_url = true;
4558 }
4559 if ( *p == '\'' || *p=='\"' )
4560 quote_ch = *p++;
4561 while (*p) {
4562 if ( quote_ch && *p==quote_ch ) {
4563 p++;
4564 break;
4565 }
4566 if ( !quote_ch ) {
4567 if ( in_url && *p==')' ) {
4568 break;
4569 }
4570 if ( *p==' ' || *p=='\t' || *p=='\r' || *p=='\n' )
4571 break;
4572 }
4573 import_file << *p++;
4574 }
4575 skip_spaces( p );
4576 if ( in_url ) {
4577 if ( *p!=')' )
4578 return false;
4579 p++;
4580 }
4581 // Remove trailing ';' at end of "@import url(..);"
4582 skip_spaces( p );
4583 if ( *p==';' )
4584 p++;
4585 if ( import_file.empty() )
4586 return false;
4587 str = p;
4588 return true;
4589 }
4590
4591 /// load stylesheet from file, with processing of import
LVLoadStylesheetFile(lString32 pathName,lString8 & css)4592 bool LVLoadStylesheetFile( lString32 pathName, lString8 & css )
4593 {
4594 LVStreamRef file = LVOpenFileStream( pathName.c_str(), LVOM_READ );
4595 if ( file.isNull() )
4596 return false;
4597 lString8 txt = UnicodeToUtf8( LVReadTextFile( file ) );
4598 lString8 txt2;
4599 const char * s = txt.c_str();
4600 lString8 import_file;
4601 if ( LVProcessStyleSheetImport( s, import_file ) ) {
4602 lString32 importFilename = LVMakeRelativeFilename( pathName, Utf8ToUnicode(import_file) );
4603 //lString8 ifn = UnicodeToLocal(importFilename);
4604 //const char * ifns = ifn.c_str();
4605 if ( !importFilename.empty() ) {
4606 LVStreamRef file2 = LVOpenFileStream( importFilename.c_str(), LVOM_READ );
4607 if ( !file2.isNull() )
4608 txt2 = UnicodeToUtf8( LVReadTextFile( file2 ) );
4609 }
4610 }
4611 if ( !txt2.empty() )
4612 txt2 << "\r\n";
4613 css = txt2 + s;
4614 return !css.empty();
4615 }
4616