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: "&#x2219; ";
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