From 6f529c939391b0924f75b4d0cd5d3268089997a3 Mon Sep 17 00:00:00 2001 From: Aviad Pineles Date: Sat, 14 Dec 2024 16:26:21 +0200 Subject: [PATCH 1/3] feat: resolve css variables --- .../css/resolve/DefaultCssResolver.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/itext/itext.html2pdf/itext/html2pdf/css/resolve/DefaultCssResolver.cs b/itext/itext.html2pdf/itext/html2pdf/css/resolve/DefaultCssResolver.cs index b392ca755..5a28b6cef 100644 --- a/itext/itext.html2pdf/itext/html2pdf/css/resolve/DefaultCssResolver.cs +++ b/itext/itext.html2pdf/itext/html2pdf/css/resolve/DefaultCssResolver.cs @@ -199,6 +199,10 @@ private IDictionary ResolveStyles(INode element, CssContext cont keys.Add(entry.Key); } } + + // Resolve CSS variables + ResolveCssVariables(elementStyles); + foreach (String key in keys) { elementStyles.Put(key, CssDefaults.GetDefaultValue(key)); } @@ -208,6 +212,41 @@ private IDictionary ResolveStyles(INode element, CssContext cont return elementStyles; } + private static void ResolveCssVariables(IDictionary elementStyles) { + var varOverrides = new Dictionary(); + + foreach (KeyValuePair entry in elementStyles) { + if (entry.Value?.StartsWith("var(--") ?? false) { + var value = ResolveVariable(entry.Value.Substring(4, entry.Value.Length - 5)); + 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 (elementStyles.TryGetValue(varName, out var variable)) { + return variable; + } + + if (dfltVal is null) { + return null; + } + + if (dfltVal.StartsWith("var(--")) { + return ResolveVariable(dfltVal.Substring(4, dfltVal.Length - 5)); + } + + return dfltVal; + } + } + + foreach (var varOverride in varOverrides) { + elementStyles.Put(varOverride.Key, varOverride.Value); + } + } + private IDictionary ResolveElementsStyles(INode element) { IList ruleSets = new List(); ruleSets.Add(new CssRuleSet(null, UserAgentCss.GetStyles(element))); From 1de26b9041cdbadaebdb8d2675a7b35eee79ecba Mon Sep 17 00:00:00 2001 From: Aviad Pineles Date: Sun, 15 Dec 2024 14:29:13 +0200 Subject: [PATCH 2/3] feat: use regex to account for whitespace --- .../css/resolve/DefaultCssResolver.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/itext/itext.html2pdf/itext/html2pdf/css/resolve/DefaultCssResolver.cs b/itext/itext.html2pdf/itext/html2pdf/css/resolve/DefaultCssResolver.cs index 5a28b6cef..d130e6410 100644 --- a/itext/itext.html2pdf/itext/html2pdf/css/resolve/DefaultCssResolver.cs +++ b/itext/itext.html2pdf/itext/html2pdf/css/resolve/DefaultCssResolver.cs @@ -23,6 +23,7 @@ You should have received a copy of the GNU Affero General Public License using System; using System.Collections.Generic; using System.IO; +using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using iText.Commons; using iText.Html2pdf.Attach; @@ -51,6 +52,8 @@ namespace iText.Html2pdf.Css.Resolve { /// interface. /// public class DefaultCssResolver : ICssResolver { + private static readonly Regex CssVarDecl = new Regex(@"^\s*var\(\s*(?--.*)\)\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + /// The CSS style sheet. private CssStyleSheet cssStyleSheet; @@ -212,12 +215,28 @@ private IDictionary ResolveStyles(INode element, CssContext cont return elementStyles; } + 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 elementStyles) { var varOverrides = new Dictionary(); foreach (KeyValuePair entry in elementStyles) { - if (entry.Value?.StartsWith("var(--") ?? false) { - var value = ResolveVariable(entry.Value.Substring(4, entry.Value.Length - 5)); + if (IsCssVarDecl(entry.Value, out var decl)) { + var value = ResolveVariable(decl); varOverrides.Add(entry.Key, value); } continue; @@ -234,8 +253,8 @@ string ResolveVariable(string substring) { return null; } - if (dfltVal.StartsWith("var(--")) { - return ResolveVariable(dfltVal.Substring(4, dfltVal.Length - 5)); + if (IsCssVarDecl(dfltVal, out var innerDecl)) { + return ResolveVariable(innerDecl); } return dfltVal; From 2f3f190f6aa84010461d308d8e6ba0559f89cbd9 Mon Sep 17 00:00:00 2001 From: Aviad Pineles Date: Sun, 15 Dec 2024 15:37:56 +0200 Subject: [PATCH 3/3] test: added tests for resolve css variables --- .../html2pdf/css/CssStylesResolvingTest.cs | 16 ++++++++++ .../cssVariablesTest01.html | 30 +++++++++++++++++++ .../cssVariablesTest02.html | 30 +++++++++++++++++++ .../cssVariablesTest03.html | 30 +++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest01.html create mode 100644 itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest02.html create mode 100644 itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest03.html diff --git a/itext.tests/itext.html2pdf.tests/itext/html2pdf/css/CssStylesResolvingTest.cs b/itext.tests/itext.html2pdf.tests/itext/html2pdf/css/CssStylesResolvingTest.cs index 68f25d613..5efbf762c 100644 --- a/itext.tests/itext.html2pdf.tests/itext/html2pdf/css/CssStylesResolvingTest.cs +++ b/itext.tests/itext.html2pdf.tests/itext/html2pdf/css/CssStylesResolvingTest.cs @@ -191,6 +191,22 @@ public virtual void HtmlStylesConvertingTest11() { , "font-family: times"); } + [NUnit.Framework.TestCase("cssVariablesTest01.html", "30px", "30px")] + [NUnit.Framework.TestCase("cssVariablesTest02.html", "50px", "30px")] + [NUnit.Framework.TestCase("cssVariablesTest03.html", "35px", "35px")] + public virtual void CssVariablesTest1(string fileName, string expectedMargin, string expectedVarValue) { + Test(fileName, "html body div", + "display: block", + $"--test-var: {expectedVarValue}", + $"margin-top: {expectedMargin}", + $"margin-right: {expectedMargin}", + $"margin-bottom: {expectedMargin}", + $"margin-left: {expectedMargin}", + "font-family: times", + "font-size: 12pt" + ); + } + private void ResolveStylesForTree(INode node, ICssResolver cssResolver, CssContext context) { if (node is IElementNode) { IElementNode element = (IElementNode)node; diff --git a/itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest01.html b/itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest01.html new file mode 100644 index 000000000..dbbdc57f2 --- /dev/null +++ b/itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest01.html @@ -0,0 +1,30 @@ + + + + + +
+ This is a div text with margin 30px +
+ + diff --git a/itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest02.html b/itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest02.html new file mode 100644 index 000000000..fa65bd76a --- /dev/null +++ b/itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest02.html @@ -0,0 +1,30 @@ + + + + + +
+ This is a div text with margin 50px +
+ + diff --git a/itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest03.html b/itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest03.html new file mode 100644 index 000000000..5c36fc646 --- /dev/null +++ b/itext.tests/itext.html2pdf.tests/resources/itext/html2pdf/css/CssElementStylesResolvingTest/cssVariablesTest03.html @@ -0,0 +1,30 @@ + + + + + +
+ This is a div text with margin 35px +
+ +