/// <summary>Resolves content and counter(s) styles of a node given the passed context.</summary> /// <param name="node">the node</param> /// <param name="context">the CSS context (RootFontSize, etc.)</param> public virtual void ResolveContentAndCountersStyles(INode node, CssContext context) { IDictionary <String, String> elementStyles = ResolveElementsStyles(node); CounterProcessorUtil.ProcessCounters(elementStyles, context); ResolveContentProperty(elementStyles, node, context); }
/// <summary>Check if a pages counter is mentioned.</summary> /// <param name="styleSheet">the stylesheet to analyze</param> /// <param name="cssContext">the CSS context</param> private void CheckIfPagesCounterMentioned(CssStyleSheet styleSheet, CssContext cssContext) { // The presence of counter(pages) means that theoretically relayout may be needed. // We don't know it yet because that selector might not even be used, but // when we know it for sure, it's too late because the Document is created right in the start. if (CssStyleSheetAnalyzer.CheckPagesCounterPresence(styleSheet)) { cssContext.SetPagesCounterPresent(true); } }
/// <summary>Check if a pages counter is mentioned.</summary> /// <param name="cssContents">the CSS contents</param> /// <param name="cssContext">the CSS context</param> private void CheckIfPagesCounterMentioned(String cssContents, CssContext cssContext) { // TODO more efficient (avoid searching in text string) and precise (e.g. skip spaces) check during the parsing. if (cssContents.Contains("counter(pages)") || cssContents.Contains("counters(pages")) { // The presence of counter(pages) means that theoretically relayout may be needed. // We don't know it yet because that selector might not even be used, but // when we know it for sure, it's too late because the Document is created right in the start. cssContext.SetPagesCounterPresent(true); } }
public virtual void ResolveContentTargetCounterNotPageTest() { IDictionary <String, String> styles = new Dictionary <String, String>(); styles.Put(CssConstants.CONTENT, "target-counter(url('#some_target'), some_counter)"); CssContext context = new CssContext(); IList <INode> result = CssContentPropertyResolver.ResolveContent(styles, null, context); NUnit.Framework.Assert.IsNotNull(result); NUnit.Framework.Assert.AreEqual(1, result.Count); NUnit.Framework.Assert.IsTrue(result[0] is ITextNode); }
public virtual void ResolveContentInvalidParamsTest() { IDictionary <String, String> styles = new Dictionary <String, String>(); styles.Put(CssConstants.CONTENT, "target-counter(url('#some_target'))"); CssContext context = new CssContext(); IList <INode> result = CssContentPropertyResolver.ResolveContent(styles, null, context); NUnit.Framework.Assert.IsNull(result); styles.Put(CssConstants.CONTENT, "target-counters(url('#some_target'), some_counter)"); result = CssContentPropertyResolver.ResolveContent(styles, null, context); NUnit.Framework.Assert.IsNull(result); styles.Put(CssConstants.CONTENT, "counter()"); result = CssContentPropertyResolver.ResolveContent(styles, null, context); NUnit.Framework.Assert.IsNull(result); styles.Put(CssConstants.CONTENT, "counters(some_counter)"); result = CssContentPropertyResolver.ResolveContent(styles, null, context); NUnit.Framework.Assert.IsNull(result); }
/// <summary>Resolves content.</summary> /// <param name="styles">the styles map</param> /// <param name="contentContainer">the content container</param> /// <param name="context">the CSS context</param> /// <returns> /// a list of /// <see cref="iText.StyledXmlParser.Node.INode"/> /// instances /// </returns> internal static IList <INode> ResolveContent(IDictionary <String, String> styles, INode contentContainer, CssContext context) { String contentStr = styles.Get(CssConstants.CONTENT); IList <INode> result = new List <INode>(); if (contentStr == null || CssConstants.NONE.Equals(contentStr) || CssConstants.NORMAL.Equals(contentStr)) { return(null); } CssDeclarationValueTokenizer tokenizer = new CssDeclarationValueTokenizer(contentStr); CssDeclarationValueTokenizer.Token token; CssQuotes quotes = null; while ((token = tokenizer.GetNextValidToken()) != null) { if (token.IsString()) { result.Add(new CssContentPropertyResolver.ContentTextNode(contentContainer, token.GetValue())); } else { if (token.GetValue().StartsWith(CssConstants.COUNTERS + "(")) { String paramsStr = token.GetValue().JSubstring(CssConstants.COUNTERS.Length + 1, token.GetValue().Length - 1); String[] @params = iText.IO.Util.StringUtil.Split(paramsStr, ","); if (@params.Length == 0) { return(ErrorFallback(contentStr)); } // Counters are denoted by case-sensitive identifiers String counterName = @params[0].Trim(); String counterSeparationStr = @params[1].Trim(); counterSeparationStr = counterSeparationStr.JSubstring(1, counterSeparationStr.Length - 1); String listStyleType = @params.Length > 2 ? @params[2].Trim() : null; CssCounterManager counterManager = context.GetCounterManager(); INode scope = contentContainer; if (CssConstants.PAGE.Equals(counterName)) { result.Add(new PageCountElementNode(false, contentContainer)); } else { if (CssConstants.PAGES.Equals(counterName)) { result.Add(new PageCountElementNode(true, contentContainer)); } else { String resolvedCounter = counterManager.ResolveCounters(counterName, counterSeparationStr, listStyleType, scope); if (resolvedCounter == null) { logger.Error(MessageFormatUtil.Format(iText.Html2pdf.LogMessageConstant.UNABLE_TO_RESOLVE_COUNTER, counterName )); } else { result.Add(new CssContentPropertyResolver.ContentTextNode(scope, resolvedCounter)); } } } } else { if (token.GetValue().StartsWith(CssConstants.COUNTER + "(")) { String paramsStr = token.GetValue().JSubstring(CssConstants.COUNTER.Length + 1, token.GetValue().Length - 1); String[] @params = iText.IO.Util.StringUtil.Split(paramsStr, ","); if (@params.Length == 0) { return(ErrorFallback(contentStr)); } // Counters are denoted by case-sensitive identifiers String counterName = @params[0].Trim(); String listStyleType = @params.Length > 1 ? @params[1].Trim() : null; CssCounterManager counterManager = context.GetCounterManager(); INode scope = contentContainer; if (CssConstants.PAGE.Equals(counterName)) { result.Add(new PageCountElementNode(false, contentContainer)); } else { if (CssConstants.PAGES.Equals(counterName)) { result.Add(new PageCountElementNode(true, contentContainer)); } else { String resolvedCounter = counterManager.ResolveCounter(counterName, listStyleType, scope); if (resolvedCounter == null) { logger.Error(MessageFormatUtil.Format(iText.Html2pdf.LogMessageConstant.UNABLE_TO_RESOLVE_COUNTER, counterName )); } else { result.Add(new CssContentPropertyResolver.ContentTextNode(scope, resolvedCounter)); } } } } else { if (token.GetValue().StartsWith("url(")) { IDictionary <String, String> attributes = new Dictionary <String, String>(); attributes.Put(AttributeConstants.SRC, CssUtils.ExtractUrl(token.GetValue())); //TODO: probably should add user agent styles on CssContentElementNode creation, not here. attributes.Put(AttributeConstants.STYLE, CssConstants.DISPLAY + ":" + CssConstants.INLINE_BLOCK); result.Add(new CssContentElementNode(contentContainer, TagConstants.IMG, attributes)); } else { if (CssGradientUtil.IsCssLinearGradientValue(token.GetValue())) { IDictionary <String, String> attributes = new Dictionary <String, String>(); attributes.Put(AttributeConstants.STYLE, CssConstants.BACKGROUND_IMAGE + ":" + token.GetValue() + ";" + CssConstants .HEIGHT + ":" + CssConstants.INHERIT + ";" + CssConstants.WIDTH + ":" + CssConstants.INHERIT + ";"); result.Add(new CssContentElementNode(contentContainer, TagConstants.DIV, attributes)); } else { if (token.GetValue().StartsWith("attr(") && contentContainer is CssPseudoElementNode) { int endBracket = token.GetValue().IndexOf(')'); if (endBracket > 5) { String attrName = token.GetValue().JSubstring(5, endBracket); if (attrName.Contains("(") || attrName.Contains(" ") || attrName.Contains("'") || attrName.Contains("\"")) { return(ErrorFallback(contentStr)); } IElementNode element = (IElementNode)contentContainer.ParentNode(); String value = element.GetAttribute(attrName); result.Add(new CssContentPropertyResolver.ContentTextNode(contentContainer, value == null ? "" : value)); } } else { if (token.GetValue().EndsWith("quote") && contentContainer is IStylesContainer) { if (quotes == null) { quotes = CssQuotes.CreateQuotes(styles.Get(CssConstants.QUOTES), true); } String value = quotes.ResolveQuote(token.GetValue(), context); if (value == null) { return(ErrorFallback(contentStr)); } result.Add(new CssContentPropertyResolver.ContentTextNode(contentContainer, value)); } else { if (token.GetValue().StartsWith(CssConstants.ELEMENT + "(") && contentContainer is PageMarginBoxContextNode ) { String paramsStr = token.GetValue().JSubstring(CssConstants.ELEMENT.Length + 1, token.GetValue().Length - 1); String[] @params = iText.IO.Util.StringUtil.Split(paramsStr, ","); if (@params.Length == 0) { return(ErrorFallback(contentStr)); } String name = @params[0].Trim(); String runningElementOccurrence = null; if (@params.Length > 1) { runningElementOccurrence = @params[1].Trim(); } result.Add(new PageMarginRunningElementNode(name, runningElementOccurrence)); } else { return(ErrorFallback(contentStr)); } } } } } } } } } return(result); }
/// <summary>Collects CSS declarationss.</summary> /// <param name="rootNode">the root node</param> /// <param name="resourceResolver">the resource resolver</param> /// <param name="cssContext">the CSS context</param> private void CollectCssDeclarations(INode rootNode, ResourceResolver resourceResolver, CssContext cssContext ) { cssStyleSheet = new CssStyleSheet(); LinkedList <INode> q = new LinkedList <INode>(); q.Add(rootNode); while (!q.IsEmpty()) { INode currentNode = q.JGetFirst(); q.RemoveFirst(); if (currentNode is IElementNode) { IElementNode headChildElement = (IElementNode)currentNode; if (TagConstants.STYLE.Equals(headChildElement.Name())) { if (currentNode.ChildNodes().Count > 0 && currentNode.ChildNodes()[0] is IDataNode) { String styleData = ((IDataNode)currentNode.ChildNodes()[0]).GetWholeData(); CssStyleSheet styleSheet = CssStyleSheetParser.Parse(styleData); styleSheet = WrapStyleSheetInMediaQueryIfNecessary(headChildElement, styleSheet); cssStyleSheet.AppendCssStyleSheet(styleSheet); } } else { if (CssUtils.IsStyleSheetLink(headChildElement)) { String styleSheetUri = headChildElement.GetAttribute(AttributeConstants.HREF); try { using (Stream stream = resourceResolver.RetrieveResourceAsInputStream(styleSheetUri)) { if (stream != null) { CssStyleSheet styleSheet = CssStyleSheetParser.Parse(stream, resourceResolver.ResolveAgainstBaseUri(styleSheetUri ).ToExternalForm()); styleSheet = WrapStyleSheetInMediaQueryIfNecessary(headChildElement, styleSheet); cssStyleSheet.AppendCssStyleSheet(styleSheet); } } } catch (Exception exc) { ILog logger = LogManager.GetLogger(typeof(iText.Html2pdf.Css.Resolve.DefaultCssResolver)); logger.Error(iText.Html2pdf.LogMessageConstant.UNABLE_TO_PROCESS_EXTERNAL_CSS_FILE, exc); } } } } foreach (INode child in currentNode.ChildNodes()) { if (child is IElementNode) { q.Add(child); } } } CheckIfPagesCounterMentioned(cssStyleSheet, cssContext); }
/// <summary>Resolves a content property.</summary> /// <param name="styles">the styles map</param> /// <param name="contentContainer">the content container</param> /// <param name="context">the CSS context</param> private void ResolveContentProperty(IDictionary <String, String> styles, INode contentContainer, CssContext context) { if (contentContainer is CssPseudoElementNode || contentContainer is PageMarginBoxContextNode) { IList <INode> resolvedContent = CssContentPropertyResolver.ResolveContent(styles, contentContainer, context ); if (resolvedContent != null) { foreach (INode child in resolvedContent) { contentContainer.AddChild(child); } } } }
/* (non-Javadoc) * @see com.itextpdf.html2pdf.css.resolve.ICssResolver#resolveStyles(com.itextpdf.html2pdf.html.node.INode, com.itextpdf.html2pdf.css.resolve.CssContext) */ private IDictionary <String, String> ResolveStyles(INode element, CssContext context) { IList <CssRuleSet> ruleSets = new List <CssRuleSet>(); ruleSets.Add(new CssRuleSet(null, UserAgentCss.GetStyles(element))); if (element is IElementNode) { ruleSets.Add(new CssRuleSet(null, HtmlStylesToCssConverter.Convert((IElementNode)element))); } ruleSets.AddAll(cssStyleSheet.GetCssRuleSets(element, deviceDescription)); if (element is IElementNode) { String styleAttribute = ((IElementNode)element).GetAttribute(AttributeConstants.STYLE); if (styleAttribute != null) { ruleSets.Add(new CssRuleSet(null, CssRuleSetParser.ParsePropertyDeclarations(styleAttribute))); } } IDictionary <String, String> elementStyles = CssStyleSheet.ExtractStylesFromRuleSets(ruleSets); if (CssConstants.CURRENTCOLOR.Equals(elementStyles.Get(CssConstants.COLOR))) { // css-color-3/#currentcolor: // If the ‘currentColor’ keyword is set on the ‘color’ property itself, it is treated as ‘color: inherit’. elementStyles.Put(CssConstants.COLOR, CssConstants.INHERIT); } String parentFontSizeStr = null; if (element.ParentNode() is IStylesContainer) { IStylesContainer parentNode = (IStylesContainer)element.ParentNode(); IDictionary <String, String> parentStyles = parentNode.GetStyles(); if (parentStyles == null && !(element.ParentNode() is IDocumentNode)) { ILog logger = LogManager.GetLogger(typeof(iText.Html2pdf.Css.Resolve.DefaultCssResolver)); logger.Error(iText.Html2pdf.LogMessageConstant.ERROR_RESOLVING_PARENT_STYLES); } if (parentStyles != null) { ICollection <IStyleInheritance> inheritanceRules = new HashSet <IStyleInheritance>(); inheritanceRules.Add(cssInheritance); foreach (KeyValuePair <String, String> entry in parentStyles) { elementStyles = StyleUtil.MergeParentStyleDeclaration(elementStyles, entry.Key, entry.Value, parentStyles. Get(CommonCssConstants.FONT_SIZE), inheritanceRules); } parentFontSizeStr = parentStyles.Get(CssConstants.FONT_SIZE); } } String elementFontSize = elementStyles.Get(CssConstants.FONT_SIZE); if (CssUtils.IsRelativeValue(elementFontSize) || CssConstants.LARGER.Equals(elementFontSize) || CssConstants .SMALLER.Equals(elementFontSize)) { float baseFontSize; if (CssUtils.IsRemValue(elementFontSize)) { baseFontSize = context.GetRootFontSize(); } else { if (parentFontSizeStr == null) { baseFontSize = CssUtils.ParseAbsoluteFontSize(CssDefaults.GetDefaultValue(CssConstants.FONT_SIZE)); } else { baseFontSize = CssUtils.ParseAbsoluteLength(parentFontSizeStr); } } float absoluteFontSize = CssUtils.ParseRelativeFontSize(elementFontSize, baseFontSize); // Format to 4 decimal places to prevent differences between Java and C# elementStyles.Put(CssConstants.FONT_SIZE, DecimalFormatUtil.FormatNumber(absoluteFontSize, "0.####") + CssConstants .PT); } else { elementStyles.Put(CssConstants.FONT_SIZE, Convert.ToString(CssUtils.ParseAbsoluteFontSize(elementFontSize) , System.Globalization.CultureInfo.InvariantCulture) + CssConstants.PT); } // Update root font size if (element is IElementNode && TagConstants.HTML.Equals(((IElementNode)element).Name())) { context.SetRootFontSize(elementStyles.Get(CssConstants.FONT_SIZE)); } ICollection <String> keys = new HashSet <String>(); foreach (KeyValuePair <String, String> entry in elementStyles) { if (CssConstants.INITIAL.Equals(entry.Value) || CssConstants.INHERIT.Equals(entry.Value)) { // if "inherit" is not resolved till now, parents don't have it keys.Add(entry.Key); } } foreach (String key in keys) { elementStyles.Put(key, CssDefaults.GetDefaultValue(key)); } // This is needed for correct resolving of content property, so doing it right here CounterProcessorUtil.ProcessCounters(elementStyles, context, element); ResolveContentProperty(elementStyles, element, context); return(elementStyles); }
/// <summary>Check if a non-page(s) target-counter(s) is mentioned and enables it.</summary> /// <param name="styleSheet">the stylesheet to analyze</param> /// <param name="cssContext">the CSS context</param> private static void EnableNonPageTargetCounterIfMentioned(CssStyleSheet styleSheet, CssContext cssContext) { if (CssStyleSheetAnalyzer.CheckNonPagesTargetCounterPresence(styleSheet)) { cssContext.SetNonPagesTargetCounterPresent(true); } }