Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: resolve css variables #36

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions itext.tests/itext.svg.tests/itext/svg/css/SvgStyleResolverTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,54 @@ public virtual void OverrideDefaultStyleTest() {
NUnit.Framework.Assert.AreEqual("white", resolvedStyles.Get(SvgConstants.Attributes.STROKE));
}

[NUnit.Framework.TestCase("a", "30px", "30px")]
[NUnit.Framework.TestCase("b", "50px", "30px")]
[NUnit.Framework.TestCase("c d", "35px", "35px")]
public virtual void ResolveCssVariablesTest(string divClass, string expectedMargin, string expectedVarValue) {
iText.StyledXmlParser.Jsoup.Nodes.Element styleTag = new iText.StyledXmlParser.Jsoup.Nodes.Element(iText.StyledXmlParser.Jsoup.Parser.Tag
.ValueOf("style"), "");
TextNode styleContents = new TextNode(@"
div {
--test-var: 30px;
}
div.a {
margin: var(--test-var,40px);
}
div.b {
margin: var(--other-var,50px);
}
div.c {
--test-var: 35px;
}
div.c.d {
margin: var(--test-var,40px);
}
");
JsoupElementNode jSoupStyle = new JsoupElementNode(styleTag);
jSoupStyle.AddChild(new JsoupTextNode(styleContents));
SvgProcessorContext context = new SvgProcessorContext(new SvgConverterProperties());

SvgStyleResolver resolver = new SvgStyleResolver(jSoupStyle, context);
AbstractCssContext svgContext = new SvgCssContext();

iText.StyledXmlParser.Jsoup.Nodes.Element div = new iText.StyledXmlParser.Jsoup.Nodes.Element(iText.StyledXmlParser.Jsoup.Parser.Tag
.ValueOf("div"), "");
JsoupElementNode jSoupDiv = new JsoupElementNode(div);
Attributes divAttributes = div.Attributes();
divAttributes.Put(new iText.StyledXmlParser.Jsoup.Nodes.Attribute("class", divClass));

IDictionary<String, String> actual = resolver.ResolveStyles(jSoupDiv, svgContext);
IDictionary<String, String> expected = new Dictionary<String, String>();
expected.Put("class", divClass);
expected.Put("--test-var", expectedVarValue);
expected.Put("margin-top", expectedMargin);
expected.Put("margin-right", expectedMargin);
expected.Put("margin-bottom", expectedMargin);
expected.Put("margin-left", expectedMargin);
expected.Put("font-size", "12pt");
NUnit.Framework.Assert.AreEqual(expected, actual);
}

[NUnit.Framework.Test]
public virtual void SvgCssResolverStyleTagTest() {
iText.StyledXmlParser.Jsoup.Nodes.Element styleTag = new iText.StyledXmlParser.Jsoup.Nodes.Element(iText.StyledXmlParser.Jsoup.Parser.Tag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ public class CssInheritance : IStyleInheritance {
/// <param name="cssProperty">the CSS property</param>
/// <returns>true, if the property is inheritable</returns>
public virtual bool IsInheritable(String cssProperty) {

// Always inherit CSS variables
if (cssProperty.StartsWith("--")) {
return true;
}

return INHERITABLE_PROPERTIES.Contains(cssProperty);
}
}
Expand Down
58 changes: 58 additions & 0 deletions itext/itext.svg/itext/svg/css/impl/SvgStyleResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ You should have received a copy of the GNU Affero General Public License
using iText.Svg.Exceptions;
using iText.Svg.Logs;
using iText.Svg.Processors.Impl;
using System.Text.RegularExpressions;

namespace iText.Svg.Css.Impl {
/// <summary>Default implementation of SVG`s styles and attribute resolver .</summary>
Expand All @@ -60,6 +61,8 @@ public class SvgStyleResolver : ICssResolver {
private static readonly ILogger LOGGER = ITextLogManager.GetLogger(typeof(iText.Svg.Css.Impl.SvgStyleResolver
));

private static readonly Regex CssVarDecl = new Regex(@"^\s*var\(\s*(?<decl>--.*)\)\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

private CssStyleSheet css;

private const String DEFAULT_CSS_PATH = "iText.Svg.default.css";
Expand Down Expand Up @@ -262,6 +265,10 @@ private IDictionary<String, String> ResolveStyles(INode element, SvgCssContext c
// TODO DEVSIX-8792 SVG: implement styles appliers like in html2pdf
styles.Put(CommonCssConstants.FONT_FAMILY, fontFamilies[0]);
}

// Resolve CSS variables
ResolveCssVariables(styles);

// Set root font size
bool isSvgElement = element is IElementNode && SvgConstants.Tags.SVG.Equals(((IElementNode)element).Name()
);
Expand All @@ -275,6 +282,57 @@ private IDictionary<String, String> ResolveStyles(INode element, SvgCssContext c
return styles;
}

private static bool IsCssVarDecl(string decl, out string innerDecl) {
if (decl == null) {
innerDecl = null;
return false;
}

var cssVarDecl = CssVarDecl.Match(decl);
if (cssVarDecl.Success) {
innerDecl = cssVarDecl.Groups["decl"].Value;
return true;
}

innerDecl = null;
return false;
}

private static void ResolveCssVariables(IDictionary<string, string> styles) {
var varOverrides = new Dictionary<string, string>();

foreach (KeyValuePair<String, String> entry in styles) {
if (IsCssVarDecl(entry.Value, out var decl)) {
var value = ResolveVariable(decl);
varOverrides.Add(entry.Key, value);
}
continue;

string ResolveVariable(string substring) {
var hasDefault = substring.IndexOf(',');
var varName = hasDefault == -1 ? substring : substring.Substring(0, hasDefault);
var dfltVal = hasDefault == -1 ? null : substring.Substring(hasDefault + 1).Trim();
if (styles.TryGetValue(varName, out var variable)) {
return variable;
}

if (dfltVal is null) {
return null;
}

if (IsCssVarDecl(dfltVal, out var innerDecl)) {
return ResolveVariable(innerDecl);
}

return dfltVal;
}
}

foreach (var varOverride in varOverrides) {
styles.Put(varOverride.Key, varOverride.Value);
}
}

/// <summary>
/// Resolves the full path of link href attribute,
/// thanks to the resource resolver.
Expand Down