Merge branch 'main/rendor-staging' into fixes
[ryzomcore.git] / nel / src / gui / css_style.cpp
blobfb7701f7fec7e27c4c30a0b253cd31a10c1db7b3
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2021 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdpch.h"
22 #include <string>
23 #include "nel/misc/types_nl.h"
24 #include "nel/gui/html_element.h"
25 #include "nel/gui/css_style.h"
26 #include "nel/gui/css_parser.h"
27 #include "nel/gui/libwww.h"
29 using namespace NLMISC;
31 #ifdef DEBUG_NEW
32 #define new DEBUG_NEW
33 #endif
35 namespace NLGUI
37 uint CCssStyle::SStyleRule::specificity() const
39 uint count = 0;
40 for(uint i = 0; i < Selector.size(); ++i)
42 count += Selector[i].specificity();
44 // counted as element tag like DIV
45 if (!PseudoElement.empty())
47 count += 0x000001;
50 return count;
53 // ***************************************************************************
54 void CCssStyle::reset()
56 _StyleRules.clear();
57 _StyleStack.clear();
59 Root = CStyleParams();
60 Current = CStyleParams();
63 // ***************************************************************************
64 // Sorting helper
65 struct CCssSpecificityPred
67 bool operator()(CCssStyle::SStyleRule lhs, CCssStyle::SStyleRule rhs) const
69 return lhs.specificity() < rhs.specificity();
73 // ***************************************************************************
74 void CCssStyle::parseStylesheet(const std::string &styleString)
76 CCssParser parser;
77 parser.parseStylesheet(styleString, _StyleRules);
79 // keep the list sorted
80 std::stable_sort(_StyleRules.begin(), _StyleRules.end(), CCssSpecificityPred());
83 void CCssStyle::getStyleFor(CHtmlElement &elm) const
85 std::vector<SStyleRule> mRules;
86 for (std::vector<SStyleRule>::const_iterator it = _StyleRules.begin(); it != _StyleRules.end(); ++it)
88 if (match(it->Selector, elm))
90 mRules.push_back(*it);
94 elm.Style.clear();
95 elm.clearPseudo();
97 if (!mRules.empty())
99 // style is sorted by specificity (lowest first), eg. html, .class, html.class, #id, html#id.class
100 for(std::vector<SStyleRule>::const_iterator i = mRules.begin(); i != mRules.end(); ++i)
102 if (i->PseudoElement.empty())
104 merge(elm.Style, i->Properties);
106 else
108 TStyle props;
109 merge(props, i->Properties);
110 elm.setPseudo(i->PseudoElement, props);
115 // style from "style" attribute overrides <style>
116 if (elm.hasNonEmptyAttribute("style"))
118 TStyleVec styles = CCssParser::parseDecls(elm.getAttribute("style"));
119 merge(elm.Style, styles);
123 void CCssStyle::merge(TStyle &dst, const TStyleVec &src) const
125 // TODO: does not use '!important' flag
126 for(TStyleVec::const_iterator it = src.begin(); it != src.end(); ++it)
128 dst[it->first] = it->second;
129 expandShorthand(it->first, it->second, dst);
133 bool CCssStyle::match(const std::vector<CCssSelector> &selector, const CHtmlElement &elm) const
135 if (selector.empty()) return false;
137 // first selector, '>' immediate parent
138 bool matches = false;
139 bool mustMatchNext = true;
140 bool matchGeneralChild = false;
141 bool matchGeneralSibling = false;
143 const CHtmlElement *child;
144 child = &elm;
145 std::vector<CCssSelector>::const_reverse_iterator ritSelector = selector.rbegin();
146 char matchCombinator = '\0';
147 while(ritSelector != selector.rend())
149 if (!child)
151 return false;
154 matches = ritSelector->match(*child);
155 if (!matches && mustMatchNext)
157 return false;
160 if (!matches)
162 if (matchCombinator == ' ')
164 if (!child->parent)
166 return false;
168 child = child->parent;
169 // walk up the tree until there is match for current selector
170 continue;
173 if (matchCombinator == '~')
175 // any previous sibling must match current selector
176 if (!child->previousSibling)
178 return false;
180 child = child->previousSibling;
182 // check siblings until there is match for current selector
183 continue;
187 mustMatchNext = false;
188 switch(ritSelector->Combinator)
190 case '\0':
191 // default case when single selector
192 break;
193 case ' ':
195 // general child - match child->parent to current/previous selector
196 if (!child->parent)
198 return false;
200 child = child->parent;
201 matchCombinator = ritSelector->Combinator;
203 break;
204 case '~':
206 // any previous sibling must match current selector
207 if (!child->previousSibling)
209 return false;
211 child = child->previousSibling;
212 matchCombinator = ritSelector->Combinator;
214 break;
215 case '+':
217 // adjacent sibling - previous sibling must match previous selector
218 if (!child->previousSibling)
220 return false;
222 child = child->previousSibling;
223 mustMatchNext = true;
225 break;
226 case '>':
228 // child of - immediate parent must match previous selector
229 if (!child->parent)
231 return false;
233 child = child->parent;
234 mustMatchNext = true;
236 break;
237 default:
238 // should not reach
239 return false;
242 ++ritSelector;
245 return matches;
248 // ***************************************************************************
249 void CCssStyle::applyRootStyle(const std::string &styleString)
251 getStyleParams(styleString, Root, Root);
254 // ***************************************************************************
255 void CCssStyle::applyRootStyle(const TStyle &styleRules)
257 getStyleParams(styleRules, Root, Root);
260 // ***************************************************************************
261 void CCssStyle::applyStyle(const std::string &styleString)
263 if (styleString.empty()) return;
265 if (_StyleStack.empty())
267 getStyleParams(styleString, Current, Root);
269 else
271 getStyleParams(styleString, Current, _StyleStack.back());
275 // ***************************************************************************
276 void CCssStyle::applyStyle(const TStyle &styleRules)
278 if (_StyleStack.empty())
280 getStyleParams(styleRules, Current, Root);
282 else
284 getStyleParams(styleRules, Current, _StyleStack.back());
288 // ***************************************************************************
289 bool CCssStyle::scanCssLength(const std::string& str, uint32 &px) const
291 if (fromString(str, px))
292 return true;
294 if (str == "thin")
296 px = 1;
297 return true;
299 if (str == "medium")
301 px = 3;
302 return true;
304 if (str == "thick")
306 px = 5;
307 return true;
310 return false;
313 // ***************************************************************************
314 void CCssStyle::splitParams(const std::string &str, char sep, std::vector<std::string> &result) const
316 // TODO: does not handle utf8
318 uint32 pos = 0;
319 for(uint i = 0; i< str.size(); i++)
321 // split by separator first, then check if string or function
322 if (str[i] == sep)
324 std::string sub = trim(str.substr(pos, i - pos));
325 if (!sub.empty())
326 result.push_back(str.substr(pos, i - pos));
327 // skip sep
328 pos = i + 1;
330 else if (str[i] == '"' || str[i] == '(')
332 // string "this is string", or function rgb(1, 2, 3)
333 char endChar;
334 if (str[i] == '"')
335 endChar = '"';
336 else if (str[i] == '\'')
337 endChar = '\'';
338 else
339 endChar = ')';
341 // skip start
342 i++;
343 while(i < str.size() && str[i] != endChar)
345 if (str[i] == '\\')
346 i++;
347 i++;
351 if (pos < str.size())
352 result.push_back(str.substr(pos).c_str());
355 // ***************************************************************************
356 // CStyleParams style;
357 // style.FontSize; // font-size: 10px;
358 // style.TextColor; // color: #ABCDEF;
359 // style.Underlined; // text-decoration: underline; text-decoration-line: underline;
360 // style.StrikeThrough; // text-decoration: line-through; text-decoration-line: line-through;
361 void CCssStyle::getStyleParams(const std::string &styleString, CStyleParams &style, const CStyleParams &current) const
363 TStyleVec stylevec = CCssParser::parseDecls(styleString);
365 TStyle styles;
366 merge(styles, stylevec);
368 getStyleParams(styles, style, current);
371 void CCssStyle::getStyleParams(const TStyle &styleRules, CStyleParams &style, const CStyleParams &current) const
373 float tmpf;
374 TStyle::const_iterator it;
376 if(styleRules.empty())
378 return;
381 normalize(styleRules, style, current);
382 apply(style, current);
385 // first pass
386 // - get font-size for 'em' sizes
387 // - split shorthand to its parts
388 // - get TextColor value that could be used for 'currentcolor'
389 // - normalize values
390 void CCssStyle::normalize(const TStyle &styleRules, CStyleParams &style, const CStyleParams &current) const
392 std::set<std::string> seenProperties;
394 TStyle::const_iterator it;
395 for (it=styleRules.begin(); it != styleRules.end(); ++it)
397 std::string value = it->second;
399 // replace possible custom properties, ignore property if var() fails
400 if (!cssFuncVar(value, styleRules, seenProperties))
401 continue;
403 // update local copy of applied style
404 style.StyleRules[it->first] = value;
406 if (it->first == "color")
408 if (value == "inherit")
410 style.TextColor = current.TextColor;
412 else
414 scanHTMLColor(value.c_str(), style.TextColor);
417 else
418 if (it->first == "font")
420 if (value == "inherit")
422 style.FontSize = current.FontSize;
423 style.FontFamily = current.FontFamily;
424 style.FontWeight = current.FontWeight;
425 style.FontOblique = current.FontOblique;
428 else
429 if (it->first == "font-size")
431 if (value == "inherit")
433 style.FontSize = current.FontSize;
435 else if (value == "x-small")
437 style.FontSize = 10; // 62.5%
439 else if (value == "small")
441 style.FontSize = 13; // 80%;
443 else if (value == "medium")
445 style.FontSize = 16; // 100%;
447 else if (value == "large")
449 style.FontSize = 18; // 112.5%
451 else if (value == "x-large")
453 style.FontSize = 24; // 150%
455 else if (value == "xx-large")
457 style.FontSize = 32; // 200%;
459 else if (value == "smaller")
461 if (style.FontSize < 5)
462 style.FontSize = 3;
463 else
464 style.FontSize -= 2;
466 else if (value == "larger")
468 style.FontSize += 2;
470 else
472 float tmpf;
473 std::string unit;
474 if (getCssLength(tmpf, unit, value.c_str()))
476 if (unit == "rem")
477 style.FontSize = Root.FontSize * tmpf;
478 else if (unit == "em")
479 style.FontSize = current.FontSize * tmpf;
480 else if (unit == "pt")
481 style.FontSize = tmpf / 0.75f;
482 else if (unit == "%")
483 style.FontSize = current.FontSize * tmpf / 100.f;
484 else
485 style.FontSize = tmpf;
489 else
490 if (it->first == "background-repeat")
492 // old ryzom specific value
493 if (value == "1")
494 style.StyleRules[it->first] = "repeat";
496 else
497 if (it->first == "display")
499 if (value == "inherit")
500 style.DisplayBlock = current.DisplayBlock;
501 else
502 style.DisplayBlock = (value == "block" || value == "table");
507 void CCssStyle::applyBorderWidth(const std::string &value, CSSLength *dest, const CSSLength &currentWidth) const
509 if (!dest) return;
510 if (value == "inherit")
512 *dest = currentWidth;
514 else if (value == "thin")
516 dest->setFloatValue(1, "px");
518 else if (value == "medium")
520 dest->setFloatValue(3, "px");
522 else if (value == "thick")
524 dest->setFloatValue(5, "px");
526 else
528 dest->parseValue(value, false, false);
532 void CCssStyle::applyBorderColor(const std::string &value, CRGBA *dest, const CRGBA &currentColor, const CRGBA &textColor) const
534 if (!dest) return;
536 if (value == "inherit")
537 *dest = currentColor;
538 else if (value == "transparent")
539 *dest = CRGBA::Transparent;
540 else if (value == "currentcolor")
541 *dest = textColor;
542 else
543 scanHTMLColor(value.c_str(), *dest);
546 void CCssStyle::applyLineStyle(const std::string &value, CSSLineStyle *dest, const CSSLineStyle &currentStyle) const
548 if (!dest) return;
550 if (value == "inherit")
551 *dest = currentStyle;
552 else if (value == "none")
553 *dest = CSS_LINE_STYLE_NONE;
554 else if (value == "hidden")
555 *dest = CSS_LINE_STYLE_HIDDEN;
556 else if (value == "dotted")
557 *dest = CSS_LINE_STYLE_DOTTED;
558 else if (value == "dashed")
559 *dest = CSS_LINE_STYLE_DASHED;
560 else if (value == "solid")
561 *dest = CSS_LINE_STYLE_SOLID;
562 else if (value == "double")
563 *dest = CSS_LINE_STYLE_DOUBLE;
564 else if (value == "groove")
565 *dest = CSS_LINE_STYLE_GROOVE;
566 else if (value == "ridge")
567 *dest = CSS_LINE_STYLE_RIDGE;
568 else if (value == "inset")
569 *dest = CSS_LINE_STYLE_INSET;
570 else if (value == "outset")
571 *dest = CSS_LINE_STYLE_OUTSET;
574 void CCssStyle::applyPaddingWidth(const std::string &value, uint32 *dest, const uint32 currentPadding, uint32 fontSize) const
576 if (!dest) return;
578 if (value == "inherit")
580 *dest = currentPadding;
581 return;
584 float tmpf;
585 std::string unit;
586 if (getCssLength(tmpf, unit, value.c_str()))
588 if (unit == "rem")
589 *dest = fontSize * tmpf;
590 else if (unit == "em")
591 *dest = fontSize * tmpf;
592 else if (unit == "pt")
593 *dest = tmpf / 0.75f;
594 else if (unit == "%")
595 *dest = 0; // TODO: requires content width, must remember 'unit' type
596 else
597 *dest = tmpf;
601 void CCssStyle::applyMarginWidth(const std::string &value, uint32 *dest, const uint32 current, uint32 fontSize) const
603 if (!dest) return;
605 if (value == "inherit")
607 *dest = current;
608 return;
610 else if (value == "auto")
612 // TODO: requires content width;
613 *dest = 0;
614 return;
617 float tmpf;
618 std::string unit;
619 if (getCssLength(tmpf, unit, value.c_str()))
621 if (unit == "rem")
622 *dest = fontSize * tmpf;
623 else if (unit == "em")
624 *dest = fontSize * tmpf;
625 else if (unit == "pt")
626 *dest = tmpf / 0.75f;
627 else if (unit == "%")
628 *dest = 0; // TODO: requires content width, must remember 'unit' type
629 else
630 *dest = tmpf;
634 // apply style rules
635 void CCssStyle::apply(CStyleParams &style, const CStyleParams &current) const
637 float tmpf;
638 TStyle::const_iterator it;
639 for (it=style.StyleRules.begin(); it != style.StyleRules.end(); ++it)
641 if (it->first == "border-top-width") applyBorderWidth(it->second, &style.Border.Top.Width, current.Border.Top.Width);
642 else if (it->first == "border-top-color") applyBorderColor(it->second, &style.Border.Top.Color, current.Border.Top.Color, current.TextColor);
643 else if (it->first == "border-top-style") applyLineStyle(it->second, &style.Border.Top.Style, current.Border.Top.Style);
644 else if (it->first == "border-right-width") applyBorderWidth(it->second, &style.Border.Right.Width, current.Border.Right.Width);
645 else if (it->first == "border-right-color") applyBorderColor(it->second, &style.Border.Right.Color, current.Border.Right.Color, current.TextColor);
646 else if (it->first == "border-right-style") applyLineStyle(it->second, &style.Border.Right.Style, current.Border.Right.Style);
647 else if (it->first == "border-bottom-width") applyBorderWidth(it->second, &style.Border.Bottom.Width, current.Border.Bottom.Width);
648 else if (it->first == "border-bottom-color") applyBorderColor(it->second, &style.Border.Bottom.Color, current.Border.Bottom.Color, current.TextColor);
649 else if (it->first == "border-bottom-style") applyLineStyle(it->second, &style.Border.Bottom.Style, current.Border.Bottom.Style);
650 else if (it->first == "border-left-width") applyBorderWidth(it->second, &style.Border.Left.Width, current.Border.Left.Width);
651 else if (it->first == "border-left-color") applyBorderColor(it->second, &style.Border.Left.Color, current.Border.Left.Color, current.TextColor);
652 else if (it->first == "border-left-style") applyLineStyle(it->second, &style.Border.Left.Style, current.Border.Left.Style);
653 else if (it->first == "margin-top") applyMarginWidth(it->second, &style.MarginTop, current.MarginTop, current.FontSize);
654 else if (it->first == "margin-right") applyMarginWidth(it->second, &style.MarginRight, current.MarginRight, current.FontSize);
655 else if (it->first == "margin-bottom") applyMarginWidth(it->second, &style.MarginBottom, current.MarginBottom, current.FontSize);
656 else if (it->first == "margin-left") applyMarginWidth(it->second, &style.MarginLeft, current.MarginLeft, current.FontSize);
657 else if (it->first == "padding-top") applyPaddingWidth(it->second, &style.PaddingTop, current.PaddingTop, current.FontSize);
658 else if (it->first == "padding-right") applyPaddingWidth(it->second, &style.PaddingRight, current.PaddingRight, current.FontSize);
659 else if (it->first == "padding-bottom") applyPaddingWidth(it->second, &style.PaddingBottom, current.PaddingBottom, current.FontSize);
660 else if (it->first == "padding-left") applyPaddingWidth(it->second, &style.PaddingLeft, current.PaddingLeft, current.FontSize);
661 else
662 if (it->first == "font-style")
664 if (it->second == "inherit")
665 style.FontOblique = current.FontOblique;
666 else
667 if (it->second == "italic" || it->second == "oblique")
668 style.FontOblique = true;
670 else
671 if (it->first == "font-family")
673 if (it->second == "inherit")
674 style.FontFamily = current.FontFamily;
675 else
676 style.FontFamily = it->second;
678 else
679 if (it->first == "font-weight")
681 // https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
682 uint weight = 400;
683 if (it->second == "inherit")
684 weight = current.FontWeight;
685 else
686 if (it->second == "normal")
687 weight = 400;
688 else
689 if (it->second == "bold")
690 weight = 700;
691 else
692 if (it->second == "lighter")
694 const uint lighter[] = {100, 100, 100, 100, 100, 400, 400, 700, 700};
695 uint index = current.FontWeight / 100 - 1;
696 clamp(index, 1u, 9u);
697 weight = lighter[index-1];
699 else
700 if (it->second == "bolder")
702 const uint bolder[] = {400, 400, 400, 700, 700, 900, 900, 900, 900};
703 uint index = current.FontWeight / 100 + 1;
704 clamp(index, 1u, 9u);
705 weight = bolder[index-1];
707 else
708 if (fromString(it->second, weight))
710 weight = (weight / 100);
711 clamp(weight, 1u, 9u);
712 weight *= 100;
714 style.FontWeight = weight;
716 else
717 if (it->first == "text-decoration" || it->first == "text-decoration-line")
719 std::string prop(toLowerAscii(it->second));
720 style.Underlined = (prop.find("underline") != std::string::npos);
721 style.StrikeThrough = (prop.find("line-through") != std::string::npos);
723 else
724 if (it->first == "text-stroke" || it->first == "-webkit-text-stroke")
726 // text-stroke: length || color
727 bool success = false;
728 uint px = 0;
729 CRGBA color;
730 std::vector<std::string> parts;
731 splitParams(it->second, ' ', parts);
732 if (parts.size() == 1)
734 success = scanCssLength(parts[0], px);
735 if (!success)
736 success = scanHTMLColor(parts[0].c_str(), color);
738 else if (parts.size() == 2)
740 success = scanCssLength(parts[0], px);
741 if (success)
742 success = scanHTMLColor(parts[1].c_str(), color);
743 else
745 success = scanHTMLColor(parts[0].c_str(), color);
746 success = success && scanCssLength(parts[1], px);
750 // do not disable shadow if one is already set
751 if (success)
753 style.TextShadow.Enabled = (px > 0);
754 style.TextShadow.Color = color;
755 style.TextShadow.X = px;
756 style.TextShadow.Y = px;
757 style.TextShadow.Outline = true;
760 else
761 if (it->first == "text-shadow")
763 if (it->second == "none")
764 style.TextShadow = CStyleParams::STextShadow(false);
765 else
766 if (it->second == "inherit")
767 style.TextShadow = current.TextShadow;
768 else
770 // text-shadow: offset-x offset-y | blur | #color
771 // text-shadow: #color | offset-x offset-y
772 bool success = true;
773 std::string prop(it->second);
774 size_t pos;
775 pos = prop.find_first_of(",\n\r");
776 if (pos != std::string::npos)
777 prop = prop.substr(0, pos);
779 std::vector<std::string> parts;
780 splitParams(prop, ' ', parts);
781 switch(parts.size())
783 case 1:
785 success = scanHTMLColor(it->second.c_str(), style.TextShadow.Color);
786 break;
788 // no case 2:
789 case 3:
791 if (!fromString(parts[0], style.TextShadow.X))
793 success = scanHTMLColor(parts[0].c_str(), style.TextShadow.Color);
794 success = success && fromString(parts[1], style.TextShadow.X);
795 success = success && fromString(parts[2], style.TextShadow.Y);
797 else
799 success = fromString(parts[1], style.TextShadow.Y);
800 success = success && scanHTMLColor(parts[2].c_str(), style.TextShadow.Color);
802 break;
804 case 4:
806 if (!fromString(parts[0], style.TextShadow.X))
808 success = scanHTMLColor(parts[0].c_str(), style.TextShadow.Color);
809 success = success && fromString(parts[1], style.TextShadow.X);
810 success = success && fromString(parts[2], style.TextShadow.Y);
811 // ignore blur [3]
813 else
815 success = fromString(parts[0], style.TextShadow.X);
816 success = success && fromString(parts[1], style.TextShadow.Y);
817 // ignore blur [2]
818 success = success && scanHTMLColor(parts[3].c_str(), style.TextShadow.Color);
820 break;
822 default:
824 // unsupported rule
825 break;
829 style.TextShadow.Enabled = success;
832 else
833 if (it->first == "text-align")
835 if (it->second == "inherit")
836 style.TextAlign = current.TextAlign;
837 else if (it->second == "left" || it->second == "right" || it->second == "center" || it->second == "justify")
838 style.TextAlign = it->second;
840 else
841 if (it->first == "vertical-align")
843 if (it->second == "inherit")
844 style.VerticalAlign = current.VerticalAlign;
845 else if (it->second == "top" || it->second == "middle" || it->second == "bottom")
846 style.VerticalAlign = it->second;
848 else
849 if (it->first == "white-space")
851 if (it->second == "inherit")
852 style.WhiteSpace = current.WhiteSpace;
853 else if (it->second == "normal" || it->second == "nowrap" || it->second == "pre")
854 style.WhiteSpace = it->second;
856 else
857 if (it->first == "width")
859 std::string unit;
860 if (getCssLength(tmpf, unit, it->second.c_str()))
862 if (unit == "rem")
863 style.Width = tmpf * Root.FontSize;
864 else if (unit == "em")
865 style.Width = tmpf * style.FontSize;
866 else if (unit == "pt")
867 style.FontSize = tmpf / 0.75f;
868 else if (unit == "%")
869 style.Width = 0; // TODO: style.WidthRatio
870 else
871 style.Width = tmpf;
874 else
875 if (it->first == "height")
877 std::string unit;
878 if (getCssLength(tmpf, unit, it->second.c_str()))
880 if (unit == "rem")
881 style.Height = tmpf * Root.FontSize;
882 else if (unit == "em")
883 style.Height = tmpf * style.FontSize;
884 else if (unit == "pt")
885 style.FontSize = tmpf / 0.75f;
886 else if (unit == "%")
887 style.Height = 0; // TODO: style.HeightRatio
888 else
889 style.Height = tmpf;
892 else
893 if (it->first == "max-width")
895 std::string unit;
896 if (getCssLength(tmpf, unit, it->second.c_str()))
898 if (unit == "rem")
899 style.MaxWidth = tmpf * Root.FontSize;
900 else if (unit == "em")
901 style.MaxWidth = tmpf * style.FontSize;
902 else if (unit == "pt")
903 style.FontSize = tmpf / 0.75f;
904 else if (unit == "%")
905 style.MaxWidth = 0; // TODO: style.MaxWidthRatio
906 else
907 style.MaxWidth = tmpf;
910 else
911 if (it->first == "max-height")
913 std::string unit;
914 if (getCssLength(tmpf, unit, it->second.c_str()))
916 if (unit == "rem")
917 style.MaxHeight = tmpf * Root.FontSize;
918 else if (unit == "em")
919 style.MaxHeight = tmpf * style.FontSize;
920 else if (unit == "pt")
921 style.FontSize = tmpf / 0.75f;
922 else if (unit == "%")
923 style.MaxHeight = 0; // TODO: style.MaxHeightRatio
924 else
925 style.MaxHeight = tmpf;
928 else
929 if (it->first == "-ryzom-modulate-color")
931 bool b;
932 if (it->second == "inherit")
933 style.GlobalColor = current.GlobalColor;
934 else
935 if (fromString(it->second, b))
936 style.GlobalColor = b;
938 else
939 if (it->first == "-ryzom-modulate-text-color")
941 bool b;
942 if (it->second == "inherit")
943 style.GlobalColorText = current.GlobalColorText;
944 else
945 if (fromString(it->second, b))
946 style.GlobalColorText = b;
948 else
949 if (it->first == "background-color")
951 if (it->second == "inherit")
952 style.Background.color = current.Background.color;
953 else if (it->second == "transparent")
954 style.Background.color = CRGBA(0, 0, 0, 0);
955 else if (it->second == "currentcolor")
956 style.Background.color = style.TextColor;
957 else
958 scanHTMLColor(it->second.c_str(), style.Background.color);
960 else
961 if (it->first == "-ryzom-background-color-over")
963 if (it->second == "inherit")
964 style.BackgroundColorOver = current.BackgroundColorOver;
965 else if (it->second == "transparent")
966 style.BackgroundColorOver = CRGBA(0, 0, 0, 0);
967 else if (it->second == "currentcolor")
968 style.BackgroundColorOver = style.TextColor;
969 else
970 scanHTMLColor(it->second.c_str(), style.BackgroundColorOver);
972 else
973 if (it->first == "background-image")
975 // normalize
976 std::string image = trim(it->second);
977 if (toLowerAscii(image.substr(0, 4)) == "url(")
979 image = image.substr(4, image.size()-5);
981 style.StyleRules[it->first] = trimQuotes(image);
982 style.Background.setImage(style.StyleRules[it->first]);
984 else
985 if (it->first == "background-repeat")
987 style.Background.setRepeat(it->second);
988 // TODO: remove after removing old code that depends on this
989 // normalize
990 std::string val = toLowerAscii(trim(it->second));
991 std::vector<std::string> parts;
992 splitParams(val, ' ', parts);
993 // check for "repeat repeat"
994 if (parts.size() == 2 && parts[0] == parts[1])
995 val = parts[0];
997 style.StyleRules[it->first] = val;
999 else
1000 if (it->first == "background-size")
1002 style.Background.setSize(it->second);
1003 // TODO: remove after removing old code that depends on this
1004 // normalize
1005 std::string val = toLowerAscii(trim(it->second));
1006 std::vector<std::string> parts;
1007 splitParams(val, ' ', parts);
1008 if (parts.size() == 2 && parts[0] == parts[1])
1009 val = parts[0];
1011 style.StyleRules[it->first] = val;
1013 else
1014 if (it->first == "background-position")
1016 // TODO: background-position-x, background-position-y
1017 style.Background.setPosition(it->second);
1019 else
1020 if (it->first == "background-origin")
1022 style.Background.setOrigin(it->second);
1024 else
1025 if (it->first == "background-clip")
1027 style.Background.setClip(it->second);
1029 else
1030 if (it->first == "background-attachment")
1032 style.Background.setAttachment(it->second);
1036 // if outer element has underline set, then inner element cannot remove it
1037 if (current.Underlined)
1038 style.Underlined = current.Underlined;
1040 // if outer element has line-through set, then inner element cannot remove it
1041 if (current.StrikeThrough)
1042 style.StrikeThrough = current.StrikeThrough;
1045 // ***************************************************************************
1046 void CCssStyle::expandBackgroundShorthand(const std::string &value, TStyle &style) const
1048 // background: url(image.jpg) top center / 200px 200px no-repeat fixed padding-box content-box red;
1049 // background-image : url(image.jpg)
1050 // background-position : top center
1051 // background-size : 200px 200px
1052 // background-repeat : no-repeat
1053 // background-attachment : fixed
1054 // background-origin : padding-box
1055 // background-clip : content-box
1056 // background-color : red
1058 const uint nbProps = 8;
1059 std::string props[nbProps] = {"background-image", "background-position", "background-size", "background-repeat",
1060 "background-attachment", "background-origin", "background-clip", "background-color"};
1061 std::string values[nbProps];
1062 bool found[nbProps] = {false};
1063 bool bgClipFound = false;
1064 std::string bgClipValue;
1065 std::string bgPositionX;
1066 std::string bgPositionY;
1068 uint partIndex = 0;
1069 std::vector<std::string> parts;
1070 std::vector<std::string>::iterator it;
1071 splitParams(value, ' ', parts);
1073 bool failed = false;
1074 bool allowSize = false;
1075 uint index = 0;
1076 while(!failed && index < parts.size())
1078 std::string val = toLowerAscii(parts[index]);
1079 bool matches = false;
1080 for(uint i = 0; i < nbProps; i++)
1082 if (found[i]) continue;
1084 if (props[i] == "background-image")
1086 if (val.substr(0, 4) == "url(")
1088 matches = true;
1089 found[i] = true;
1090 // use original value as 'val' is lowercase
1091 values[i] = parts[index];
1094 else if (props[i] == "background-position")
1096 uint next = index;
1097 bool loop = false;
1100 float fval;
1101 std::string unit;
1103 // first loop -> true
1104 // second loop -> false && break
1105 loop = !loop;
1107 if (next >= parts.size())
1108 break;
1110 val = toLower(parts[next]);
1111 if (val == "center")
1113 if (bgPositionX.empty()) bgPositionX = "center";
1114 if (bgPositionY.empty()) bgPositionY = "center";
1115 // consume 'center'
1116 next++;
1118 else if ((bgPositionX.empty() || bgPositionX == "center") && (val == "left" || val == "right"))
1120 bgPositionX = val;
1121 // consume 'left|right'
1122 next++;
1123 if (next < parts.size() && getCssLength(fval, unit, parts[next]))
1125 bgPositionX += " " + parts[next];
1126 // consume css length
1127 next++;
1130 else if ((bgPositionY.empty() || bgPositionY == "center") && (val == "top" || val == "bottom"))
1132 bgPositionY = val;
1133 // consume top|bottom
1134 next++;
1135 if (next < parts.size() && getCssLength(fval, unit, parts[next]))
1137 bgPositionY += " " + parts[next];
1138 // consume css length
1139 next++;
1142 else if (getCssLength(fval, unit, parts[next]))
1144 // override X only on first loop
1145 if (next == index)
1147 bgPositionX = parts[next];
1149 else if (bgPositionY.empty())
1151 bgPositionY = parts[next];
1153 else
1155 // invalid
1156 bgPositionX.clear();
1157 bgPositionY.clear();
1158 break;
1160 next++;
1162 else
1164 // invalid value
1165 bgPositionX.clear();
1166 bgPositionY.clear();
1167 break;
1169 } while (loop);
1172 if (!bgPositionX.empty() && !bgPositionY.empty())
1174 matches = true;
1175 found[i] = true;
1176 // consume position values if there were any
1177 index = next-1;
1179 // look ahead to see if size is next
1180 if (next < parts.size() && parts[next] == "/")
1181 allowSize = true;
1184 else if (props[i] == "background-size")
1186 if (allowSize && val == "/")
1188 uint next = index + 1;
1189 if (next < parts.size())
1191 val = toLowerAscii(parts[next]);
1192 if (val == "cover" || val == "contain")
1194 matches = true;
1195 found[i] = true;
1196 values[i] = val;
1197 index = next;
1199 else
1201 float fval;
1202 std::string unit;
1203 std::string h, v;
1205 if (val == "auto" || getCssLength(fval, unit, val))
1207 if (val == "auto")
1208 h = v = "auto";
1209 else
1210 h = v = val;
1212 next++;
1213 if (next < parts.size())
1215 val = toLowerAscii(parts[next]);
1216 if (val == "auto")
1217 v = "auto";
1218 else if (getCssLength(fval, unit, val))
1219 v = val;
1220 else
1221 next--; // not size token
1223 else
1225 // not size token
1226 next--;
1230 if (!h.empty() && !v.empty())
1232 matches = true;
1233 found[i] = true;
1234 values[i] = h + " " + v;
1235 index = next;
1239 else
1241 // no size, just '/'
1242 failed = true;
1243 break;
1247 else if (props[i] == "background-repeat")
1249 if (val == "repeat-x" || val == "repeat-y" || val == "repeat" || val == "space" || val == "round" || val == "no-repeat")
1251 matches = true;
1252 found[i] = true;
1254 if (val == "repeat-x")
1256 values[i] = "repeat no-repeat";
1258 else if (val == "repeat-y")
1260 values[i] = "no-repeat repeat";
1262 else
1264 std::string horiz = val;
1265 std::string vert = val;
1266 uint next = index + 1;
1267 if (next < parts.size())
1269 val = toLowerAscii(parts[next]);
1270 if (val == "repeat" || val == "space" || val == "round" || val == "no-repeat")
1272 vert = val;
1273 index = next;
1276 if (vert == horiz)
1277 values[i] = vert;
1278 else
1279 values[i] = horiz + " " + vert;
1283 else if (props[i] == "background-attachment")
1285 if (val == "scroll" || val == "fixed" || val == "local")
1287 matches = true;
1288 found[i] = true;
1289 values[i] = val;
1292 else if (props[i] == "background-origin")
1294 if (val == "padding-box" || val == "border-box" || val == "content-box")
1296 matches = true;
1297 found[i] = true;
1298 values[i] = val;
1300 // first time background-origin is set, also set background-clip
1301 if (!bgClipFound)
1303 bgClipValue = val;
1304 bgClipFound = true;
1308 else if (props[i] == "background-clip")
1310 if (val == "text" || val == "padding-box" || val == "border-box" || val == "content-box")
1312 matches = true;
1313 found[i] = true;
1314 bgClipFound = true;
1315 bgClipValue = val;
1318 else if (props[i] == "background-color")
1320 CRGBA color;
1321 if (val == "transparent" || val == "currentcolor" || scanHTMLColor(val.c_str(), color))
1323 matches = true;
1324 found[i] = true;
1325 values[i] = val;
1329 // prop was found and parsed
1330 if (found[i])
1331 break;
1333 failed = !matches;
1335 index++;
1338 // invalidate whole rule
1339 if (failed)
1341 bgClipFound = false;
1342 for(uint i = 0; i < nbProps; i++)
1344 found[i] = false;
1348 // apply found styles or use default
1349 for(uint i = 0; i < nbProps; i++)
1351 if (found[i])
1353 if (props[i] == "background-position")
1355 style["background-position"] = bgPositionX + " " + bgPositionY;
1356 style["background-position-x"] = bgPositionX;
1357 style["background-position-y"] = bgPositionY;
1359 else if (props[i] == "background-clip")
1361 style["background-clip"] = bgClipValue;
1363 else
1365 style[props[i]] = values[i];
1368 else
1370 // fill in default if one is not set
1371 if (props[i] == "background-image")
1373 style[props[i]] = "";
1375 else if (props[i] == "background-position")
1377 style[props[i]] = "0% 0%";
1378 style["background-position-x"] = "left 0%";
1379 style["background-position-y"] = "top 0%";
1381 else if (props[i] == "background-size")
1383 style[props[i]] = "auto auto";
1385 else if (props[i] == "background-repeat")
1387 style[props[i]] = "repeat";
1389 else if(props[i] == "background-attachment")
1391 style[props[i]] = "scroll";
1393 else if(props[i] == "background-origin")
1395 style[props[i]] = "padding-box";
1397 else if (props[i] == "background-clip")
1399 if (bgClipFound)
1400 style[props[i]] = bgClipValue;
1401 else
1402 style[props[i]] = "border-box";
1404 else if (props[i] == "background-color")
1406 style[props[i]] = "transparent";
1412 // ***************************************************************************
1413 bool CCssStyle::getShorthandIndices(const uint32 size, uint8 &t, uint8 &r, uint8 &b, uint8 &l) const
1415 if (size == 0 || size > 4) return false;
1417 if (size == 1)
1419 t = r = b = l = 0;
1421 else if (size == 2)
1423 t = b = 0;
1424 r = l = 1;
1426 else if (size == 3)
1428 t = 0;
1429 r = l = 1;
1430 b = 2;
1432 else // size == 4
1434 t = 0;
1435 r = 1;
1436 b = 2;
1437 l = 3;
1440 return true;
1443 // ***************************************************************************
1444 bool CCssStyle::tryBorderWidthShorthand(const std::string &prop, const std::string &value, TStyle &style) const
1446 std::vector<std::string> parts;
1447 splitParams(toLowerAscii(value), ' ', parts);
1448 float tmpf;
1449 std::string unit;
1451 // verify that parts are valid
1452 uint8 maxSize = (prop == "border" || prop == "border-width") ? 4 : 1;
1453 bool hasTop = (prop == "border" || prop == "border-width" || prop == "border-top" || prop == "border-top-width");
1454 bool hasRight = (prop == "border" || prop == "border-width" || prop == "border-right" || prop == "border-right-width");
1455 bool hasBottom = (prop == "border" || prop == "border-width" || prop == "border-bottom" || prop == "border-bottom-width");
1456 bool hasLeft = (prop == "border" || prop == "border-width" || prop == "border-left" || prop == "border-left-width");
1457 if (parts.size() > maxSize || (!hasTop && !hasRight && !hasBottom && !hasLeft))
1459 return false;
1462 for(uint i = 0; i< parts.size(); ++i)
1464 if (parts[i] != "inherit" && parts[i] != "thin" && parts[i] != "medium" && parts[i] != "thick"
1465 && !getCssLength(tmpf, unit, parts[i].c_str()))
1467 return false;
1471 uint8 t, r, b, l;
1472 if (!getShorthandIndices(parts.size(), t, r, b, l)) return false;
1473 if (hasTop) style["border-top-width"] = parts[t];
1474 if (hasRight) style["border-right-width"] = parts[r];
1475 if (hasBottom) style["border-bottom-width"] = parts[b];
1476 if (hasLeft) style["border-left-width"] = parts[l];
1478 return true;
1480 // ***************************************************************************
1481 bool CCssStyle::tryBorderStyleShorthand(const std::string &prop, const std::string &value, TStyle &style) const
1483 std::vector<std::string> parts;
1484 splitParams(toLowerAscii(value), ' ', parts);
1486 // verify that parts are valid
1487 uint8 maxSize = (prop == "border" || prop == "border-style") ? 4 : 1;
1488 bool hasTop = (prop == "border" || prop == "border-style" || prop == "border-top" || prop == "border-top-style");
1489 bool hasRight = (prop == "border" || prop == "border-style" || prop == "border-right" || prop == "border-right-style");
1490 bool hasBottom = (prop == "border" || prop == "border-style" || prop == "border-bottom" || prop == "border-bottom-style");
1491 bool hasLeft = (prop == "border" || prop == "border-style" || prop == "border-left" || prop == "border-left-style");
1492 if (parts.size() > maxSize || (!hasTop && !hasRight && !hasBottom && !hasLeft))
1494 return false;
1497 const uint nbValues = 10;
1498 std::string values[nbValues] = {"none", "hidden", "dotted", "dashed",
1499 "solid", "double", "groove", "ridge", "inset", "outset"};
1501 for(uint i = 0; i < parts.size(); ++i)
1503 bool found = false;
1504 for(uint iValue = 0; iValue < nbValues; ++iValue)
1506 if (parts[i] == values[iValue])
1508 found = true;
1509 break;
1513 if (!found)
1515 // invalid rule
1516 return false;
1520 uint8 t, r, b, l;
1521 if (!getShorthandIndices(parts.size(), t, r, b, l)) return false;
1522 if (hasTop) style["border-top-style"] = parts[t];
1523 if (hasRight) style["border-right-style"] = parts[r];
1524 if (hasBottom) style["border-bottom-style"] = parts[b];
1525 if (hasLeft) style["border-left-style"] = parts[l];
1527 return true;
1529 // ***************************************************************************
1530 bool CCssStyle::tryBorderColorShorthand(const std::string &prop, const std::string &value, TStyle &style) const
1532 std::vector<std::string> parts;
1533 splitParams(toLowerAscii(value), ' ', parts);
1534 CRGBA color;
1536 // verify that parts are valid
1537 uint8 maxSize = (prop == "border" || prop == "border-color") ? 4 : 1;
1538 bool hasTop = (prop == "border" || prop == "border-color" || prop == "border-top" || prop == "border-top-color");
1539 bool hasRight = (prop == "border" || prop == "border-color" || prop == "border-right" || prop == "border-right-color");
1540 bool hasBottom = (prop == "border" || prop == "border-color" || prop == "border-bottom" || prop == "border-bottom-color");
1541 bool hasLeft = (prop == "border" || prop == "border-color" || prop == "border-left" || prop == "border-left-color");
1542 if (parts.size() > maxSize || (!hasTop && !hasRight && !hasBottom && !hasLeft))
1544 return false;
1547 for(uint i = 0; i< parts.size(); ++i)
1549 if (!scanHTMLColor(parts[i].c_str(), color) && parts[i] != "currentcolor" && parts[i] != "inherit")
1550 return false;
1553 uint8 t, r, b, l;
1554 if (!getShorthandIndices(parts.size(), t, r, b, l)) return false;
1555 if (hasTop) style["border-top-color"] = parts[t];
1556 if (hasRight) style["border-right-color"] = parts[r];
1557 if (hasBottom) style["border-bottom-color"] = parts[b];
1558 if (hasLeft) style["border-left-color"] = parts[l];
1560 return true;
1563 // ***************************************************************************
1564 void CCssStyle::expandBorderShorthand(const std::string &prop, const std::string &value, TStyle &style) const
1566 // border: 1px solid #000;
1567 bool hasTop = (prop == "border" || prop == "border-top");
1568 bool hasRight = (prop == "border" || prop == "border-right");
1569 bool hasBottom = (prop == "border" || prop == "border-bottom");
1570 bool hasLeft = (prop == "border" || prop == "border-left");
1572 bool foundWidth = false;
1573 bool foundStyle = false;
1574 bool foundColor = false;
1576 TStyle borderStyle;
1577 std::vector<std::string> parts;
1578 splitParams(toLowerAscii(value), ' ', parts);
1580 for(uint index = 0; index < parts.size(); ++index)
1582 bool matched = false;
1583 if (!foundWidth)
1585 matched = foundWidth = tryBorderWidthShorthand(prop, parts[index], borderStyle);
1588 if (!matched && !foundStyle)
1590 matched = foundStyle = tryBorderStyleShorthand(prop, parts[index], borderStyle);
1593 if (!matched && !foundColor)
1595 matched = foundColor = tryBorderColorShorthand(prop, parts[index], borderStyle);
1598 // invalid rule if nothing gets matched
1599 if (!matched)
1601 return;
1605 // apply rules that are present
1606 TStyle::const_iterator it = borderStyle.begin();
1607 while(it != borderStyle.end())
1609 style[it->first] = it->second;
1610 ++it;
1613 // reset those not present
1614 if (!foundWidth)
1616 if (hasTop) style["border-top-width"] = "medium";
1617 if (hasRight) style["border-right-width"] = "medium";
1618 if (hasBottom) style["border-bottom-width"] = "medium";
1619 if (hasLeft) style["border-left-width"] = "medium";
1622 if (!foundStyle)
1624 if (hasTop) style["border-top-style"] = "none";
1625 if (hasRight) style["border-right-style"] = "none";
1626 if (hasBottom) style["border-bottom-style"] = "none";
1627 if (hasLeft) style["border-left-style"] = "none";
1630 if (!foundColor)
1632 if (hasTop) style["border-top-color"] = "currentcolor";
1633 if (hasRight) style["border-right-color"] = "currentcolor";
1634 if (hasBottom) style["border-bottom-color"] = "currentcolor";
1635 if (hasLeft) style["border-left-color"] = "currentcolor";
1639 // ***************************************************************************
1640 void CCssStyle::expandPaddingShorthand(const std::string &value, TStyle &style) const
1642 std::vector<std::string> parts;
1643 splitParams(toLowerAscii(value), ' ', parts);
1645 uint8 t, r, b, l;
1646 if (!getShorthandIndices(parts.size(), t, r, b, l))
1647 return;
1649 style["padding-top"] = parts[t];
1650 style["padding-right"] = parts[r];
1651 style["padding-bottom"] = parts[b];
1652 style["padding-left"] = parts[l];
1655 // ***************************************************************************
1656 void CCssStyle::expandMarginShorthand(const std::string &value, TStyle &style) const
1658 std::vector<std::string> parts;
1659 splitParams(toLowerAscii(value), ' ', parts);
1661 uint8 t, r, b, l;
1662 if (!getShorthandIndices(parts.size(), t, r, b, l))
1663 return;
1665 style["margin-top"] = parts[t];
1666 style["margin-right"] = parts[r];
1667 style["margin-bottom"] = parts[b];
1668 style["margin-left"] = parts[l];
1671 // ***************************************************************************
1672 void CCssStyle::expandShorthand(const std::string &prop, const std::string &value, TStyle &style) const
1674 // if shorthand matches, then remove it after expansion
1675 bool keep = false;
1677 if (prop == "background")
1679 expandBackgroundShorthand(value, style);
1681 else if (prop == "background-scale")
1683 // replace old ryzom specific rule with background-size
1684 if (value != "1")
1686 style["background-size"] = "auto";
1688 else
1690 style["background-size"] = "100%";
1693 else if (prop == "border"
1694 || prop == "border-top" || prop == "border-right"
1695 || prop == "border-bottom" || prop == "border-left")
1697 // TODO: use enum or bitmap constant instead of passing a string name (prop)
1698 expandBorderShorthand(prop, value, style);
1700 else if (prop == "border-width")
1702 tryBorderWidthShorthand(prop, value, style);
1704 else if (prop == "border-style")
1706 tryBorderStyleShorthand(prop, value, style);
1708 else if (prop == "border-color")
1710 tryBorderColorShorthand(prop, value, style);
1712 else if (prop == "padding")
1714 expandPaddingShorthand(value, style);
1716 else
1718 keep = true;
1721 if (!keep)
1723 TStyle::iterator pos = style.find(prop);
1724 if (pos != style.end())
1725 style.erase(pos);
1729 // ***************************************************************************
1730 void CCssStyle::applyCssMinMax(sint32 &width, sint32 &height, sint32 minw, sint32 minh, sint32 maxw, sint32 maxh) const
1732 if (maxw <= 0) maxw = width;
1733 if (maxh <= 0) maxh = height;
1735 maxw = std::max(minw, maxw);
1736 maxh = std::max(minh, maxh);
1738 float ratio = (float) width / std::max(1, height);
1739 if (width > maxw)
1741 width = maxw;
1742 height = std::max((sint32)(maxw /ratio), minh);
1744 if (width < minw)
1746 width = minw;
1747 height = std::min((sint32)(minw / ratio), maxh);
1749 if (height > maxh)
1751 width = std::max((sint32)(maxh * ratio), minw);
1752 height = maxh;
1754 if (height < minh)
1756 width = std::min((sint32)(minh * ratio), maxw);
1757 height = minh;
1759 if (width > maxw && height > maxh)
1761 if (maxw/width <= maxh/height)
1763 width = maxw;
1764 height = std::max(minh, (sint32)(maxw / ratio));
1766 else
1768 width = std::max(minw, (sint32)(maxh * ratio));
1769 height = maxh;
1772 if (width < minw && height < minh)
1774 if (minw / width <= minh / height)
1776 width = std::min(maxw, (sint32)(minh * ratio));
1777 height = minh;
1779 else
1781 width = minw;
1782 height = std::min(maxh, (sint32)(minw / ratio));
1785 if (width < minw && height > maxh)
1787 width = minw;
1788 height = maxh;
1790 if (width > maxw && height < minh)
1792 width = maxw;
1793 height = minh;
1797 // ***************************************************************************
1798 static void skipString(const std::string &value, std::string::size_type &pos)
1800 char quote = value[pos];
1801 while(pos < value.size() && value[pos] != quote)
1803 if (value[pos] == '\\')
1804 pos++;
1806 pos++;
1809 static void skipBlock(const std::string &value, std::string::size_type &pos, bool isString)
1811 char openChar = value[pos];
1812 char closeChar = value[pos];
1813 if (openChar == '(') closeChar = ')';
1814 else if (openChar == '[') closeChar = ']';
1815 else if (openChar == '{') closeChar = '}';
1816 pos++;
1818 while(pos < value.size())
1820 char c = value[pos];
1821 if (c == '\\')
1822 pos++;
1823 else if (!isString && (c == '(' || c == '[' || c == '{'))
1824 skipBlock(value, pos, false);
1825 else if (c == closeChar)
1826 break;
1827 else if (c == '"' || c == '\'')
1829 if (isString)
1830 break;
1832 skipBlock(value, pos, true);
1835 pos++;
1839 static void skipWhitespace(const std::string &value, std::string::size_type &pos)
1841 while(pos < value.size() && (value[pos] == ' ' || value[pos] == '\t' || value[pos] == '\r'))
1842 pos++;
1845 // ***************************************************************************
1846 bool CCssStyle::cssFuncVar(std::string &func, const TStyle &styleRules, const std::set<std::string> &seenProperties) const
1848 // TODO: fails if var() is inside string, ie '--text: ".. var(...) .."';
1850 // start of 'var('
1851 std::string::size_type pos = func.find("var(");
1852 if (pos == std::string::npos)
1853 return true;
1855 // simple test to make sure 'var' is not substring
1856 if (pos > 0 && (func[pos-1] != '_') && ((func[pos-1] >= 'a' && func[pos-1] <= 'z') || (func[pos-1] >= 'A' && func[pos-1] <='Z')))
1857 return true;
1859 // find closing ')'
1860 std::string::size_type funcStart = pos;
1861 std::string::size_type funcEnd = funcStart + 3;
1862 skipBlock(func, funcEnd, false);
1864 pos += 4;
1866 // ',' separator
1867 std::string::size_type sep = func.find_first_of(",)", pos);
1868 if (sep > funcEnd)
1870 // unlikely
1871 sep = funcEnd;
1873 else if (sep + 1 == funcEnd)
1875 // no whitespace between ',' and ')', ie 'var(--name,)'
1876 return false;
1879 // extract name
1880 std::string name = func.substr(funcStart + 4, sep - pos);
1881 if (seenProperties.count(name) > 0)
1882 return false;
1884 std::string value;
1885 // if name is not defined or resolves to 'initial', use fallback
1886 bool found = lookupPropertyValue(name, value, styleRules);
1887 if (found) {
1888 // check if substituted value has 'var()'
1889 std::set<std::string> newSeen = seenProperties;
1890 newSeen.insert(name);
1891 found = cssFuncVar(value, styleRules, newSeen);
1892 if (value == "initial")
1893 found = false;
1896 // --name failed and we have fallback
1897 if (!found && func[sep] == ',')
1899 sep++;
1900 skipWhitespace(func, sep);
1902 value = func.substr(sep, funcEnd - sep);
1903 if (value.empty())
1905 found = true;
1907 else
1909 // check if substituted fallback has 'var()'
1910 std::set<std::string> newSeen = seenProperties;
1911 newSeen.insert(name);
1912 found = cssFuncVar(value, styleRules, newSeen);
1913 if (value == "initial")
1914 found = false;
1918 // invalidate property as both name and fallback failed
1919 if (!found)
1920 return false;
1922 // everything before 'var(' and after ')'
1923 std::string result;
1924 if (funcStart > 0)
1925 result = trim(func.substr(0, funcStart)) + " ";
1927 result += trim(value);
1928 if ((funcEnd+1) < func.size())
1929 result += " " + trim(func.substr(funcEnd+1));
1931 // check replaced string for var()
1932 std::set<std::string> newSeen = seenProperties;
1933 newSeen.insert(name);
1934 bool success = cssFuncVar(result, styleRules, newSeen);
1935 if (result == "initial")
1936 success = false;
1938 func = result;
1939 return success;
1942 // ***************************************************************************
1943 bool CCssStyle::lookupPropertyValue(const std::string &name, std::string &value, const TStyle &styleRules) const
1945 bool success = true;
1946 TStyle::const_iterator it = styleRules.find(name);
1947 if (it != styleRules.end())
1948 value = it->second;
1949 else if (Current.hasStyle(name))
1950 value = Current.getStyle(name);
1951 else
1952 success = false;
1954 if (success && value != "inherit")
1955 return true;
1957 std::vector<CStyleParams>::const_reverse_iterator rit = _StyleStack.rbegin();
1958 for(; rit != _StyleStack.rend(); ++rit)
1960 if (rit->hasStyle(name))
1962 value = rit->getStyle(name);
1963 if (value != "inherit")
1965 return true;
1970 return false;
1973 } // namespace