From cc0dccf6319f8d47456951e89be7cb2747274508 Mon Sep 17 00:00:00 2001 From: Justin Haygood Date: Thu, 16 Jul 2026 18:59:43 -0400 Subject: [PATCH 1/4] Port of CSS engine from PeachPDF (ExCSS-based) Port of CSS engine from PeachPDF --- Source/HtmlRenderer/Adapters/RAdapter.cs | 11 +- Source/HtmlRenderer/Core/CssData.cs | 935 +++++++-- Source/HtmlRenderer/Core/CssDefaults.cs | 111 ++ .../CssEngine/BackgroundPositionGrammar.cs | 189 ++ .../Core/CssEngine/BackgroundSizeGrammar.cs | 88 + .../Core/CssEngine/Calc/CalcCategory.cs | 25 + .../Core/CssEngine/Calc/CalcEvaluator.cs | 121 ++ .../Core/CssEngine/Calc/CalcNode.cs | 98 + .../Core/CssEngine/Calc/CalcParser.cs | 238 +++ .../Core/CssEngine/Calc/CalcSerializer.cs | 259 +++ .../Core/CssEngine/Calc/CalcTypeChecker.cs | 150 ++ .../Core/CssEngine/Calc/CalcValue.cs | 33 + .../Core/CssEngine/Calc/CalcValueConverter.cs | 41 + .../Core/CssEngine/Conditions/AndCondition.cs | 31 + .../Conditions/DeclarationCondition.cs | 28 + .../CssEngine/Conditions/EmptyCondition.cs | 16 + .../CssEngine/Conditions/GroupCondition.cs | 34 + .../Core/CssEngine/Conditions/NotCondition.cs | 33 + .../Core/CssEngine/Conditions/OrCondition.cs | 35 + .../CssEngine/Enumerations/AlignContent.cs | 18 + .../Core/CssEngine/Enumerations/AlignItem.cs | 17 + .../Enumerations/AnimationDirection.cs | 10 + .../Enumerations/AnimationFillStyle.cs | 10 + .../Enumerations/BackgroundAttachment.cs | 9 + .../Enumerations/BackgroundRepeat.cs | 10 + .../Core/CssEngine/Enumerations/BlendMode.cs | 22 + .../CssEngine/Enumerations/BorderRepeat.cs | 9 + .../Core/CssEngine/Enumerations/BoxModel.cs | 9 + .../Core/CssEngine/Enumerations/BreakMode.cs | 16 + .../Core/CssEngine/Enumerations/ClearMode.cs | 10 + .../Core/CssEngine/Enumerations/Colors.cs | 205 ++ .../CssEngine/Enumerations/Combinators.cs | 20 + .../CssEngine/Enumerations/ContainerType.cs | 9 + .../CssEngine/Enumerations/DirectionMode.cs | 8 + .../CssEngine/Enumerations/DisplayMode.cs | 25 + .../CssEngine/Enumerations/FeatureNames.cs | 48 + .../Core/CssEngine/Enumerations/FillRule.cs | 8 + .../CssEngine/Enumerations/FlexDirection.cs | 10 + .../Core/CssEngine/Enumerations/FlexWrap.cs | 9 + .../Core/CssEngine/Enumerations/Floating.cs | 9 + .../Core/CssEngine/Enumerations/FontSize.cs | 16 + .../CssEngine/Enumerations/FontStretch.cs | 15 + .../Core/CssEngine/Enumerations/FontStyle.cs | 9 + .../CssEngine/Enumerations/FontVariant.cs | 8 + .../Core/CssEngine/Enumerations/FontWeight.cs | 10 + .../CssEngine/Enumerations/FunctionNames.cs | 57 + .../Enumerations/HorizontalAlignment.cs | 10 + .../CssEngine/Enumerations/HoverAbility.cs | 9 + .../CssEngine/Enumerations/IntrinsicSizing.cs | 10 + .../CssEngine/Enumerations/JustifyContent.cs | 18 + .../Core/CssEngine/Enumerations/Keywords.cs | 358 ++++ .../Core/CssEngine/Enumerations/LineStyle.cs | 16 + .../CssEngine/Enumerations/ListPosition.cs | 8 + .../Core/CssEngine/Enumerations/ListStyle.cs | 19 + .../CssEngine/Enumerations/ObjectFitting.cs | 11 + .../Core/CssEngine/Enumerations/Overflow.cs | 10 + .../CssEngine/Enumerations/OverflowWrap.cs | 8 + .../Core/CssEngine/Enumerations/ParseError.cs | 20 + .../Core/CssEngine/Enumerations/PdfTagType.cs | 53 + .../Core/CssEngine/Enumerations/PlayState.cs | 8 + .../CssEngine/Enumerations/PointerAccuracy.cs | 9 + .../CssEngine/Enumerations/PositionMode.cs | 11 + .../CssEngine/Enumerations/PropertyFlags.cs | 15 + .../CssEngine/Enumerations/PropertyNames.cs | 248 +++ .../Enumerations/PseudoClassNames.cs | 55 + .../Enumerations/PseudoElementNames.cs | 14 + .../Core/CssEngine/Enumerations/QuirksMode.cs | 9 + .../Core/CssEngine/Enumerations/RuleNames.cs | 17 + .../Core/CssEngine/Enumerations/RuleType.cs | 24 + .../CssEngine/Enumerations/ScriptingState.cs | 9 + .../CssEngine/Enumerations/StrokeLinecap.cs | 9 + .../CssEngine/Enumerations/StrokeLinejoin.cs | 9 + .../CssEngine/Enumerations/SystemCursor.cs | 42 + .../Core/CssEngine/Enumerations/SystemFont.cs | 12 + .../CssEngine/Enumerations/TextAlignLast.cs | 13 + .../Core/CssEngine/Enumerations/TextAnchor.cs | 9 + .../Enumerations/TextDecorationLine.cs | 10 + .../Enumerations/TextDecorationStyle.cs | 11 + .../CssEngine/Enumerations/TextJustify.cs | 15 + .../CssEngine/Enumerations/TextTransform.cs | 11 + .../Core/CssEngine/Enumerations/TokenType.cs | 39 + .../CssEngine/Enumerations/UnicodeMode.cs | 12 + .../Core/CssEngine/Enumerations/UnitNames.cs | 32 + .../CssEngine/Enumerations/UpdateFrequency.cs | 9 + .../Enumerations/VerticalAlignment.cs | 14 + .../Core/CssEngine/Enumerations/Visibility.cs | 9 + .../Core/CssEngine/Enumerations/Whitespace.cs | 11 + .../Core/CssEngine/Enumerations/WordBreak.cs | 9 + .../CssEngine/Extensions/CharExtensions.cs | 130 ++ .../Extensions/CollectionExtensions.cs | 36 + .../CssEngine/Extensions/FormatExtensions.cs | 28 + .../Extensions/PortableExtensions.cs | 27 + .../CssEngine/Extensions/StringExtensions.cs | 106 + .../Extensions/ValueConverterExtensions.cs | 190 ++ .../CssEngine/Extensions/ValueExtensions.cs | 514 +++++ .../Factories/AttributeSelectorFactory.cs | 52 + .../Factories/MediaFeatureFactory.cs | 85 + .../CssEngine/Factories/PropertyFactory.cs | 450 +++++ .../Factories/PseudoClassSelectorFactory.cs | 84 + .../Factories/PseudoElementSelectorFactory.cs | 48 + .../Formatting/CompressedStyleFormatter.cs | 123 ++ .../CssEngine/Functions/DocumentFunction.cs | 23 + .../CssEngine/Functions/DomainFunction.cs | 20 + .../CssEngine/Functions/IConditionFunction.cs | 7 + .../CssEngine/Functions/IDocumentFunction.cs | 8 + .../CssEngine/Functions/RegexpFunction.cs | 19 + .../Core/CssEngine/Functions/UrlFunction.cs | 17 + .../CssEngine/Functions/UrlPrefixFunction.cs | 16 + .../MediaFeatures/AspectRatioMediaFeature.cs | 11 + .../MediaFeatures/ColorIndexMediaFeature.cs | 16 + .../MediaFeatures/ColorMediaFeature.cs | 16 + .../DeviceAspectRatioMediaFeature.cs | 11 + .../MediaFeatures/DeviceHeightMediaFeature.cs | 11 + .../MediaFeatures/DevicePixelRatioFeature.cs | 11 + .../MediaFeatures/DeviceWidthMediaFeature.cs | 11 + .../MediaFeatures/GridMediaFeature.cs | 11 + .../MediaFeatures/HeightMediaFeature.cs | 11 + .../MediaFeatures/HoverMediaFeature.cs | 13 + .../CssEngine/MediaFeatures/MediaFeature.cs | 69 + .../MediaFeatures/MonochromeMediaFeature.cs | 14 + .../MediaFeatures/OrientationMediaFeature.cs | 13 + .../MediaFeatures/PointerMediaFeature.cs | 13 + .../MediaFeatures/ResolutionMediaFeature.cs | 11 + .../MediaFeatures/ScanMediaFeature.cs | 14 + .../MediaFeatures/ScriptingMediaFeature.cs | 13 + .../MediaFeatures/SizeMediaFeature.cs | 11 + .../MediaFeatures/UnknownMediaFeature.cs | 11 + .../UpdateFrequencyMediaFeature.cs | 13 + .../MediaFeatures/WidthMediaFeature.cs | 11 + .../Core/CssEngine/Model/Combinator.cs | 80 + .../Core/CssEngine/Model/Comment.cs | 19 + .../Core/CssEngine/Model/Converters.cs | 513 +++++ .../Core/CssEngine/Model/IMediaFeature.cs | 9 + .../Core/CssEngine/Model/ISelector.cs | 8 + .../Core/CssEngine/Model/IStyleFormattable.cs | 9 + .../Core/CssEngine/Model/IStyleFormatter.cs | 18 + .../Core/CssEngine/Model/IStylesheetNode.cs | 10 + .../Core/CssEngine/Model/IValueConverter.cs | 21 + .../HtmlRenderer/Core/CssEngine/Model/Map.cs | 651 +++++++ .../Core/CssEngine/Model/MediaList.cs | 76 + .../Core/CssEngine/Model/Medium.cs | 49 + .../Core/CssEngine/Model/ParseException.cs | 11 + .../Core/CssEngine/Model/ParserExtensions.cs | 162 ++ .../Core/CssEngine/Model/ParserOptions.cs | 13 + .../HtmlRenderer/Core/CssEngine/Model/Pool.cs | 78 + .../Core/CssEngine/Model/Priority.cs | 103 + .../Core/CssEngine/Model/StyleDeclaration.cs | 1706 +++++++++++++++++ .../Core/CssEngine/Model/Stylesheet.cs | 70 + .../Core/CssEngine/Model/StylesheetNode.cs | 92 + .../Core/CssEngine/Model/StylesheetText.cs | 31 + .../Core/CssEngine/Model/Symbols.cs | 90 + .../Core/CssEngine/Model/TaskEx.cs | 26 + .../Core/CssEngine/Model/TextPosition.cs | 97 + .../Core/CssEngine/Model/TextRange.cs | 55 + .../Core/CssEngine/Model/TextSource.cs | 230 +++ .../Core/CssEngine/Model/TokenValue.cs | 58 + .../Core/CssEngine/Model/TransformMatrix.cs | 106 + .../HtmlRenderer/Core/CssEngine/Model/Url.cs | 873 +++++++++ .../Core/CssEngine/Model/ValueBuilder.cs | 136 ++ .../Core/CssEngine/Parser/Lexer.cs | 1225 ++++++++++++ .../Core/CssEngine/Parser/LexerBase.cs | 173 ++ .../CssEngine/Parser/SelectorConstructor.cs | 918 +++++++++ .../CssEngine/Parser/StylesheetComposer.cs | 1313 +++++++++++++ .../Core/CssEngine/Parser/StylesheetParser.cs | 213 ++ .../Core/CssEngine/Parser/TokenizerError.cs | 17 + .../Core/CssEngine/Rules/CharsetRule.cs | 26 + .../Core/CssEngine/Rules/ConditionRule.cs | 10 + .../Core/CssEngine/Rules/ContainerRule.cs | 32 + .../Core/CssEngine/Rules/DeclarationRule.cs | 130 ++ .../Core/CssEngine/Rules/DocumentRule.cs | 41 + .../Core/CssEngine/Rules/FontFaceRule.cs | 63 + .../Core/CssEngine/Rules/GroupingRule.cs | 34 + .../Core/CssEngine/Rules/ICharsetRule.cs | 7 + .../Core/CssEngine/Rules/IConditionRule.cs | 7 + .../Core/CssEngine/Rules/IContainer.cs | 8 + .../Core/CssEngine/Rules/IFontFaceRule.cs | 14 + .../Core/CssEngine/Rules/IGroupingRule.cs | 9 + .../Core/CssEngine/Rules/IImportRule.cs | 8 + .../Core/CssEngine/Rules/IKeyframeRule.cs | 9 + .../Core/CssEngine/Rules/IKeyframesRule.cs | 11 + .../Core/CssEngine/Rules/IMarginRule.cs | 8 + .../Core/CssEngine/Rules/IMediaRule.cs | 7 + .../Core/CssEngine/Rules/INamespaceRule.cs | 8 + .../Core/CssEngine/Rules/IPageRule.cs | 8 + .../Core/CssEngine/Rules/IRule.cs | 10 + .../Core/CssEngine/Rules/IRuleCreator.cs | 7 + .../Core/CssEngine/Rules/IRuleList.cs | 10 + .../Core/CssEngine/Rules/IStyleRule.cs | 9 + .../Core/CssEngine/Rules/ISupportsRule.cs | 7 + .../Core/CssEngine/Rules/ImportRule.cs | 36 + .../Core/CssEngine/Rules/KeyframeRule.cs | 37 + .../Core/CssEngine/Rules/KeyframesRule.cs | 53 + .../Core/CssEngine/Rules/MarginStyleRule.cs | 32 + .../Core/CssEngine/Rules/MediaRule.cs | 27 + .../Core/CssEngine/Rules/NamespaceRule.cs | 66 + .../Core/CssEngine/Rules/PageRule.cs | 47 + .../HtmlRenderer/Core/CssEngine/Rules/Rule.cs | 65 + .../Core/CssEngine/Rules/RuleList.cs | 78 + .../Core/CssEngine/Rules/StyleRule.cs | 33 + .../Core/CssEngine/Rules/SupportsRule.cs | 43 + .../Core/CssEngine/Rules/UnknownRule.cs | 20 + .../Core/CssEngine/Rules/ViewportRule.cs | 15 + .../Core/CssEngine/Selectors/AllSelector.cs | 14 + .../Selectors/AttrAvailableSelector.cs | 10 + .../CssEngine/Selectors/AttrBeginsSelector.cs | 10 + .../Selectors/AttrContainsSelector.cs | 10 + .../CssEngine/Selectors/AttrEndsSelector.cs | 10 + .../CssEngine/Selectors/AttrHyphenSelector.cs | 10 + .../CssEngine/Selectors/AttrListSelector.cs | 10 + .../CssEngine/Selectors/AttrMatchSelector.cs | 10 + .../Selectors/AttrNotMatchSelector.cs | 10 + .../CssEngine/Selectors/AttrSelectorBase.cs | 13 + .../Core/CssEngine/Selectors/ChildSelector.cs | 49 + .../Core/CssEngine/Selectors/ClassSelector.cs | 17 + .../CssEngine/Selectors/CombinatorSelector.cs | 8 + .../CssEngine/Selectors/ComplexSelector.cs | 80 + .../CssEngine/Selectors/CompoundSelector.cs | 12 + .../CssEngine/Selectors/FirstChildSelector.cs | 11 + .../Selectors/FirstColumnSelector.cs | 10 + .../CssEngine/Selectors/FirstTypeSelector.cs | 10 + .../Core/CssEngine/Selectors/HasSelector.cs | 33 + .../Core/CssEngine/Selectors/IAttrSelector.cs | 8 + .../Core/CssEngine/Selectors/IdSelector.cs | 17 + .../CssEngine/Selectors/KeyframeSelector.cs | 75 + .../CssEngine/Selectors/LastChildSelector.cs | 10 + .../CssEngine/Selectors/LastColumnSelector.cs | 10 + .../CssEngine/Selectors/LastTypeSelector.cs | 10 + .../Core/CssEngine/Selectors/ListSelector.cs | 34 + .../CssEngine/Selectors/MatchesSelector.cs | 37 + .../CssEngine/Selectors/NamespaceSelector.cs | 14 + .../Core/CssEngine/Selectors/NotSelector.cs | 30 + .../CssEngine/Selectors/OnlyChildSelector.cs | 10 + .../CssEngine/Selectors/OnlyOfTypeSelector.cs | 10 + .../Selectors/PseudoClassSelector.cs | 17 + .../Selectors/PseudoElementSelector.cs | 17 + .../Core/CssEngine/Selectors/SelectorBase.cs | 21 + .../Core/CssEngine/Selectors/Selectors.cs | 55 + .../Core/CssEngine/Selectors/TypeSelector.cs | 17 + .../CssEngine/Selectors/UnknownSelector.cs | 16 + .../Animation/AnimationDelayProperty.cs | 15 + .../Animation/AnimationDirectionProperty.cs | 15 + .../Animation/AnimationDurationProperty.cs | 14 + .../Animation/AnimationFillModeProperty.cs | 15 + .../AnimationIterationCountProperty.cs | 15 + .../Animation/AnimationNameProperty.cs | 15 + .../Animation/AnimationPlayStateProperty.cs | 15 + .../Animation/AnimationProperty.cs | 26 + .../AnimationTimingFunctionProperty.cs | 15 + .../BackgroundAttachmentProperty.cs | 15 + .../Background/BackgroundClipProperty.cs | 15 + .../Background/BackgroundColorProperty.cs | 14 + .../Background/BackgroundImageProperty.cs | 14 + .../Background/BackgroundOriginProperty.cs | 15 + .../Background/BackgroundPositionProperty.cs | 15 + .../Background/BackgroundProperty.cs | 38 + .../Background/BackgroundRepeatProperty.cs | 15 + .../Background/BackgroundSizeProperty.cs | 15 + .../Border/BorderBottomColorProperty.cs | 15 + .../Border/BorderBottomProperty.cs | 20 + .../Border/BorderBottomStyleProperty.cs | 15 + .../Border/BorderBottomWidthProperty.cs | 14 + .../Border/BorderCollapseProperty.cs | 14 + .../Border/BorderColorProperty.cs | 16 + .../Border/BorderLeftColorProperty.cs | 15 + .../Border/BorderLeftProperty.cs | 20 + .../Border/BorderLeftStyleProperty.cs | 15 + .../Border/BorderLeftWidthProperty.cs | 14 + .../StyleProperties/Border/BorderProperty.cs | 26 + .../Border/BorderRightColorProperty.cs | 15 + .../Border/BorderRightProperty.cs | 20 + .../Border/BorderRightStyleProperty.cs | 15 + .../Border/BorderRightWidthProperty.cs | 14 + .../Border/BorderSpacingProperty.cs | 15 + .../Border/BorderStyleProperty.cs | 16 + .../Border/BorderTopColorProperty.cs | 15 + .../Border/BorderTopProperty.cs | 20 + .../Border/BorderTopStyleProperty.cs | 15 + .../Border/BorderTopWidthProperty.cs | 14 + .../Border/BorderWidthProperty.cs | 16 + .../BorderImage/BorderImageOutsetProperty.cs | 15 + .../BorderImage/BorderImageProperty.cs | 26 + .../BorderImage/BorderImageRepeatProperty.cs | 15 + .../BorderImage/BorderImageSliceProperty.cs | 23 + .../BorderImage/BorderImageSourceProperty.cs | 14 + .../BorderImage/BorderImageWidthProperty.cs | 15 + .../BorderBottomLeftRadiusProperty.cs | 15 + .../BorderBottomRightRadiusProperty.cs | 15 + .../BorderRadius/BorderRadiusProperty.cs | 14 + .../BorderTopLeftRadiusProperty.cs | 15 + .../BorderTopRightRadiusProperty.cs | 15 + .../StyleProperties/Box/BoxDecorationBreak.cs | 14 + .../StyleProperties/Box/BoxShadowProperty.cs | 14 + .../StyleProperties/Box/BoxSizingProperty.cs | 13 + .../Box/MarginBottomProperty.cs | 15 + .../StyleProperties/Box/MarginLeftProperty.cs | 15 + .../StyleProperties/Box/MarginProperty.cs | 17 + .../Box/MarginRightProperty.cs | 15 + .../StyleProperties/Box/MarginTopProperty.cs | 15 + .../Box/PaddingBottomProperty.cs | 15 + .../Box/PaddingLeftProperty.cs | 15 + .../StyleProperties/Box/PaddingProperty.cs | 17 + .../Box/PaddingRightProperty .cs | 15 + .../StyleProperties/Box/PaddingTopProperty.cs | 15 + .../Break/BreakAfterProperty.cs | 15 + .../Break/BreakBeforeProperty.cs | 15 + .../Break/BreakInsideProperty.cs | 15 + .../Break/PageBreakAfterProperty.cs | 15 + .../Break/PageBreakBeforeProperty.cs | 15 + .../Break/PageBreakInsideProperty.cs | 17 + .../StyleProperties/CaptionSideProperty.cs | 13 + .../Columns/ColumnCountProperty.cs | 14 + .../Columns/ColumnFillProperty.cs | 14 + .../Columns/ColumnGapProperty.cs | 17 + .../Columns/ColumnRuleColorProperty.cs | 14 + .../Columns/ColumnRuleProperty.cs | 19 + .../Columns/ColumnRuleStyleProperty.cs | 15 + .../Columns/ColumnRuleWidthProperty.cs | 14 + .../Columns/ColumnSpanProperty.cs | 14 + .../Columns/ColumnWidthProperty.cs | 15 + .../Columns/ColumnsProperty.cs | 18 + .../Container/ContainerNameProperty.cs | 15 + .../Container/ContainerTypeProperty.cs | 15 + .../StyleProperties/ContentProperty.cs | 61 + .../Coordinate/BottomProperty.cs | 15 + .../Coordinate/HeightProperty.cs | 15 + .../Coordinate/LeftProperty.cs | 15 + .../Coordinate/MaxHeightProperty.cs | 15 + .../Coordinate/MaxWidthProperty.cs | 15 + .../Coordinate/MinHeightProperty.cs | 15 + .../Coordinate/MinWidthProperty.cs | 15 + .../Coordinate/RightProperty .cs | 15 + .../StyleProperties/Coordinate/TopProperty.cs | 15 + .../Coordinate/WidthProperty.cs | 15 + .../StyleProperties/CursorProperty.cs | 21 + .../StyleProperties/CustomProperty.cs | 12 + .../StyleProperties/EmptyCellsProperty.cs | 14 + .../StyleProperties/FeatureProperty.cs | 16 + .../Fill/FillOpacityProperty.cs | 14 + .../StyleProperties/Fill/FillProperty.cs | 14 + .../StyleProperties/Fill/FillRuleProperty.cs | 15 + .../Flexbox/AlignContentProperty.cs | 13 + .../Flexbox/AlignItemsProperty.cs | 14 + .../Flexbox/AlignSelfProperty.cs | 13 + .../Flexbox/FlexBasisProperty.cs | 13 + .../Flexbox/FlexDirectionProperty.cs | 13 + .../Flexbox/FlexFlowProperty.cs | 13 + .../Flexbox/FlexGrowProperty.cs | 13 + .../StyleProperties/Flexbox/FlexProperty.cs | 13 + .../Flexbox/FlexShrinkProperty.cs | 13 + .../Flexbox/FlexWrapProperty.cs | 13 + .../Flexbox/JustifyContentProperty.cs | 14 + .../StyleProperties/Flexbox/OrderProperty.cs | 15 + .../StyleProperties/Flow/ClearProperty.cs | 15 + .../StyleProperties/Flow/DisplayProperty.cs | 15 + .../StyleProperties/Flow/FloatProperty.cs | 14 + .../StyleProperties/Flow/OverflowProperty.cs | 15 + .../StyleProperties/Flow/PositionProperty.cs | 15 + .../StyleProperties/Flow/ZIndexProperty.cs | 14 + .../StyleProperties/Font/ColorProperty.cs | 14 + .../Font/FontFamilyProperty.cs | 48 + .../StyleProperties/Font/FontProperty.cs | 27 + .../Font/FontSizeAdjustProperty.cs | 14 + .../StyleProperties/Font/FontSizeProperty.cs | 15 + .../Font/FontStretchProperty.cs | 15 + .../StyleProperties/Font/FontStyleProperty.cs | 15 + .../Font/FontVariantProperty.cs | 15 + .../Font/FontWeightProperty.cs | 17 + .../Font/LetterSpacingProperty.cs | 14 + .../Font/LineHeightProperty.cs | 15 + .../StyleProperties/Font/SrcProperty.cs | 12 + .../Font/UnicodeRangeProperty.cs | 12 + .../Font/WordSpacingProperty.cs | 15 + .../CssEngine/StyleProperties/GapProperty.cs | 15 + .../StyleProperties/HyphensProperty.cs | 18 + .../CssEngine/StyleProperties/IProperties .cs | 14 + .../CssEngine/StyleProperties/IProperty.cs | 10 + .../StyleProperties/IPropertyValue.cs | 9 + .../List/CounterIncrementProperty.cs | 17 + .../List/CounterResetProperty.cs | 17 + .../List/ListStyleImageProperty.cs | 14 + .../List/ListStylePositionProperty.cs | 15 + .../StyleProperties/List/ListStyleProperty.cs | 19 + .../List/ListStyleTypeProperty.cs | 15 + .../StyleProperties/OrphansProperty.cs | 14 + .../Outline/OutlineColorProperty.cs | 15 + .../Outline/OutlineProperty.cs | 19 + .../Outline/OutlineStyleProperty.cs | 15 + .../Outline/OutlineWidthProperty.cs | 14 + .../StyleProperties/PageNameProperty.cs | 13 + .../StyleProperties/PageSizeProperty.cs | 13 + .../StyleProperties/PdfTagTypeProperty.cs | 11 + .../CssEngine/StyleProperties/Property.cs | 65 + .../StyleProperties/QuotesProperty.cs | 15 + .../StyleProperties/RowGapProperty.cs | 15 + .../StyleProperties/ShorthandProperty.cs | 25 + .../Sizing/ObjectFitProperty.cs | 15 + .../Sizing/ObjectPositionProperty.cs | 14 + .../StyleProperties/StringSetProperty.cs | 18 + .../Stroke/StrokeDasharrayProperty.cs | 14 + .../Stroke/StrokeDashoffsetProperty.cs | 14 + .../Stroke/StrokeLinecapProperty.cs | 15 + .../Stroke/StrokeLinejoinProperty.cs | 14 + .../Stroke/StrokeMiterlimitProperty.cs | 14 + .../Stroke/StrokeOpacityProperty.cs | 14 + .../StyleProperties/Stroke/StrokeProperty.cs | 14 + .../Stroke/StrokeWidthProperty.cs | 14 + .../StyleProperties/TableLayoutProperty.cs | 14 + .../StyleProperties/Text/DirectionProperty.cs | 15 + .../Text/OverflowWrapProperty.cs | 14 + .../Text/TextAlignLastProperty.cs | 14 + .../StyleProperties/Text/TextAlignProperty.cs | 15 + .../Text/TextAnchorProperty.cs | 14 + .../Text/TextDecorationColorProperty.cs | 14 + .../Text/TextDecorationLineProperty.cs | 14 + .../Text/TextDecorationProperty.cs | 19 + .../Text/TextDecorationStyleProperty.cs | 15 + .../Text/TextIndentProperty.cs | 15 + .../Text/TextJustifyProperty.cs | 14 + .../Text/TextShadowProperty.cs | 14 + .../Text/TextTransformProperty.cs | 15 + .../Text/VerticalAlignProperty.cs | 17 + .../Text/WhiteSpaceProperty.cs | 15 + .../StyleProperties/Text/WordBreakProperty.cs | 14 + .../Transform/PerspectiveOriginProperty.cs | 31 + .../Transform/PerspectiveProperty.cs | 15 + .../Transform/TransformOriginProperty.cs | 35 + .../Transform/TransformProperty.cs | 15 + .../Transform/TransformStyleProperty.cs | 15 + .../Transition/TransitionDelayProperty.cs | 15 + .../Transition/TransitionDurationProperty.cs | 15 + .../Transition/TransitionProperty.cs | 23 + .../Transition/TransitionPropertyProperty.cs | 15 + .../TransitionTimingFunctionProperty.cs | 15 + .../UnicodeBidirectionalProperty.cs | 15 + .../StyleProperties/UnknownProperty.cs | 12 + .../Visibility/BackfaceVisibilityProperty.cs | 14 + .../Visibility/ClipProperty.cs | 14 + .../Visibility/OpacityProperty.cs | 13 + .../Visibility/VisibilityProperty.cs | 15 + .../StyleProperties/WidowsProperty.cs | 14 + .../Core/CssEngine/TextEncoding.cs | 305 +++ .../Core/CssEngine/Tokens/ColorToken.cs | 17 + .../Core/CssEngine/Tokens/CommentToken.cs | 19 + .../Core/CssEngine/Tokens/FunctionToken.cs | 49 + .../Core/CssEngine/Tokens/KeywordToken.cs | 25 + .../Core/CssEngine/Tokens/NumberToken.cs | 39 + .../Core/CssEngine/Tokens/RangeToken.cs | 51 + .../Core/CssEngine/Tokens/StringToken.cs | 20 + .../Core/CssEngine/Tokens/Token.cs | 24 + .../Core/CssEngine/Tokens/UnitToken.cs | 22 + .../Core/CssEngine/Tokens/UrlToken.cs | 21 + .../ValueConverters/AnyValueConverter.cs | 34 + .../ArgumentsValueConverter.cs | 67 + .../BackgroundPositionValueConverter.cs | 73 + .../BackgroundSizeValueConverter.cs | 54 + .../ValueConverters/BorderRadiusConverter.cs | 127 ++ .../ConditionalStartsWithValueConverter.cs | 92 + .../ConstraintValueConverter.cs | 62 + .../ContentFunctionConverter.cs | 73 + .../ContinuousValueConverter.cs | 79 + .../DictionaryValueConverter.cs | 46 + .../ValueConverters/EndListValueConverter.cs | 104 + .../ValueConverters/FallbackValueConverter.cs | 44 + .../ValueConverters/FunctionValueConverter.cs | 59 + .../ValueConverters/GradientConverter.cs | 249 +++ .../IdentifierValueConverter.cs | 84 + .../ValueConverters/ListValueConverter.cs | 107 ++ .../OneOrMoreValueConverter.cs | 101 + .../ValueConverters/OptionValueConverter.cs | 79 + .../ValueConverters/OrValueConverter.cs | 26 + .../OrderedOptionsConverter.cs | 105 + .../ValueConverters/PeriodicValueConverter.cs | 132 ++ .../ValueConverters/RequiredValueConverter.cs | 25 + .../StartsWithValueConverter.cs | 78 + .../StringFunctionConverter.cs | 88 + .../StringSetValueConverter.cs | 136 ++ .../ValueConverters/StringValueConverter.cs | 38 + .../ValueConverters/StringsValueConverter.cs | 55 + .../ValueConverters/StructValueConverter.cs | 48 + .../TimeBasedShorthandConverter.cs | 209 ++ .../UnorderedOptionsConverter.cs | 151 ++ .../ValueConverters/UrlValueConverter.cs | 38 + .../Core/CssEngine/Values/Angle.cs | 217 +++ .../Core/CssEngine/Values/Color.cs | 384 ++++ .../Core/CssEngine/Values/Counter.cs | 16 + .../Values/CubicBezierTimingFunction.cs | 18 + .../Core/CssEngine/Values/Frequency.cs | 139 ++ .../Core/CssEngine/Values/GradientStop.cs | 14 + .../Core/CssEngine/Values/IGradient.cs | 10 + .../Core/CssEngine/Values/IImageSource.cs | 6 + .../Core/CssEngine/Values/ITimingFunction.cs | 6 + .../Core/CssEngine/Values/ITransform.cs | 7 + .../Core/CssEngine/Values/Length.cs | 364 ++++ .../Core/CssEngine/Values/LinearGradient.cs | 21 + .../Core/CssEngine/Values/MatrixTransform.cs | 17 + .../Core/CssEngine/Values/Number.cs | 102 + .../Core/CssEngine/Values/Percent.cs | 125 ++ .../CssEngine/Values/PerspectiveTransform.cs | 32 + .../Core/CssEngine/Values/Point.cs | 41 + .../Core/CssEngine/Values/PortNumbers.cs | 26 + .../Core/CssEngine/Values/ProtocolNames.cs | 50 + .../Core/CssEngine/Values/RadialGradient.cs | 40 + .../Core/CssEngine/Values/Resolution.cs | 121 ++ .../Core/CssEngine/Values/RotateTransform.cs | 38 + .../Core/CssEngine/Values/ScaleTransform.cs | 21 + .../Core/CssEngine/Values/Shadow.cs | 22 + .../Core/CssEngine/Values/Shape.cs | 18 + .../Core/CssEngine/Values/SkewTransform.cs | 23 + .../CssEngine/Values/StepsTimingFunction.cs | 16 + .../Core/CssEngine/Values/Time.cs | 161 ++ .../CssEngine/Values/TranslateTransform.cs | 24 + .../HtmlRenderer/Core/CssEngine/license.txt | 21 + Source/HtmlRenderer/Core/Dom/CssBox.cs | 18 + .../HtmlRenderer/Core/Dom/CssBoxProperties.cs | 13 + .../HtmlRenderer/Core/Dom/CssContentEngine.cs | 131 ++ Source/HtmlRenderer/Core/Dom/HoverBoxBlock.cs | 16 +- Source/HtmlRenderer/Core/Entities/CssBlock.cs | 235 --- .../Core/Entities/CssBlockSelectorItem.cs | 74 - Source/HtmlRenderer/Core/HtmlContainerInt.cs | 8 +- Source/HtmlRenderer/Core/Parse/CssParser.cs | 942 ++------- .../HtmlRenderer/Core/Parse/CssValueParser.cs | 216 ++- Source/HtmlRenderer/Core/Parse/DomParser.cs | 723 +++++-- .../Core/Parse/RegexParserHelper.cs | 227 --- .../HtmlRenderer/Core/Utils/CssConstants.cs | 6 + Source/HtmlRenderer/Core/Utils/CssUtils.cs | 53 + Source/HtmlRenderer/Core/Utils/DomUtils.cs | 126 +- 526 files changed, 27397 insertions(+), 1649 deletions(-) create mode 100644 Source/HtmlRenderer/Core/CssEngine/BackgroundPositionGrammar.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/BackgroundSizeGrammar.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Calc/CalcCategory.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Calc/CalcEvaluator.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Calc/CalcNode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Calc/CalcParser.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Calc/CalcSerializer.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Calc/CalcTypeChecker.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Calc/CalcValue.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Calc/CalcValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Conditions/AndCondition.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Conditions/DeclarationCondition.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Conditions/EmptyCondition.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Conditions/GroupCondition.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Conditions/NotCondition.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Conditions/OrCondition.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/AlignContent.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/AlignItem.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/AnimationDirection.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/AnimationFillStyle.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/BackgroundAttachment.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/BackgroundRepeat.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/BlendMode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/BorderRepeat.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/BoxModel.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/BreakMode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/ClearMode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/Colors.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/Combinators.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/ContainerType.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/DirectionMode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/DisplayMode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/FeatureNames.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/FillRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/FlexDirection.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/FlexWrap.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/Floating.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/FontSize.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/FontStretch.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/FontStyle.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/FontVariant.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/FontWeight.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/FunctionNames.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/HorizontalAlignment.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/HoverAbility.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/IntrinsicSizing.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/JustifyContent.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/Keywords.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/LineStyle.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/ListPosition.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/ListStyle.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/ObjectFitting.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/Overflow.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/OverflowWrap.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/ParseError.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/PdfTagType.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/PlayState.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/PointerAccuracy.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/PositionMode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/PropertyFlags.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/PropertyNames.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/PseudoClassNames.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/PseudoElementNames.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/QuirksMode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/RuleNames.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/RuleType.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/ScriptingState.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/StrokeLinecap.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/StrokeLinejoin.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/SystemCursor.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/SystemFont.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/TextAlignLast.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/TextAnchor.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/TextDecorationLine.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/TextDecorationStyle.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/TextJustify.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/TextTransform.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/TokenType.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/UnicodeMode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/UnitNames.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/UpdateFrequency.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/VerticalAlignment.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/Visibility.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/Whitespace.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Enumerations/WordBreak.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Extensions/CharExtensions.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Extensions/CollectionExtensions.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Extensions/FormatExtensions.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Extensions/PortableExtensions.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Extensions/StringExtensions.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Extensions/ValueConverterExtensions.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Extensions/ValueExtensions.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Factories/AttributeSelectorFactory.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Factories/MediaFeatureFactory.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Factories/PropertyFactory.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Factories/PseudoClassSelectorFactory.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Factories/PseudoElementSelectorFactory.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Formatting/CompressedStyleFormatter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Functions/DocumentFunction.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Functions/DomainFunction.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Functions/IConditionFunction.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Functions/IDocumentFunction.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Functions/RegexpFunction.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Functions/UrlFunction.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Functions/UrlPrefixFunction.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/AspectRatioMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/ColorIndexMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/ColorMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/DeviceAspectRatioMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/DeviceHeightMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/DevicePixelRatioFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/DeviceWidthMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/GridMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/HeightMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/HoverMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/MediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/MonochromeMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/OrientationMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/PointerMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/ResolutionMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/ScanMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/ScriptingMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/SizeMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/UnknownMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/UpdateFrequencyMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/MediaFeatures/WidthMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/Combinator.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/Comment.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/Converters.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/IMediaFeature.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/ISelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/IStyleFormattable.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/IStyleFormatter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/IStylesheetNode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/IValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/Map.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/MediaList.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/Medium.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/ParseException.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/ParserExtensions.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/ParserOptions.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/Pool.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/Priority.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/StyleDeclaration.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/Stylesheet.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/StylesheetNode.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/StylesheetText.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/Symbols.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/TaskEx.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/TextPosition.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/TextRange.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/TextSource.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/TokenValue.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/TransformMatrix.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/Url.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Model/ValueBuilder.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Parser/Lexer.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Parser/LexerBase.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Parser/SelectorConstructor.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Parser/StylesheetComposer.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Parser/StylesheetParser.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Parser/TokenizerError.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/CharsetRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/ConditionRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/ContainerRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/DeclarationRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/DocumentRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/FontFaceRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/GroupingRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/ICharsetRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IConditionRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IContainer.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IFontFaceRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IGroupingRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IImportRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IKeyframeRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IKeyframesRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IMarginRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IMediaRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/INamespaceRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IPageRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IRuleCreator.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IRuleList.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/IStyleRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/ISupportsRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/ImportRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/KeyframeRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/KeyframesRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/MarginStyleRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/MediaRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/NamespaceRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/PageRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/Rule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/RuleList.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/StyleRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/SupportsRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/UnknownRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Rules/ViewportRule.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/AllSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/AttrAvailableSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/AttrBeginsSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/AttrContainsSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/AttrEndsSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/AttrHyphenSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/AttrListSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/AttrMatchSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/AttrNotMatchSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/AttrSelectorBase.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/ChildSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/ClassSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/CombinatorSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/ComplexSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/CompoundSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/FirstChildSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/FirstColumnSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/FirstTypeSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/HasSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/IAttrSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/IdSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/KeyframeSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/LastChildSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/LastColumnSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/LastTypeSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/ListSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/MatchesSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/NamespaceSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/NotSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/OnlyChildSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/OnlyOfTypeSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/PseudoClassSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/PseudoElementSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/SelectorBase.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/Selectors.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/TypeSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Selectors/UnknownSelector.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Animation/AnimationDelayProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Animation/AnimationDirectionProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Animation/AnimationDurationProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Animation/AnimationFillModeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Animation/AnimationIterationCountProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Animation/AnimationNameProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Animation/AnimationPlayStateProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Animation/AnimationProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Animation/AnimationTimingFunctionProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Background/BackgroundAttachmentProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Background/BackgroundClipProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Background/BackgroundColorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Background/BackgroundImageProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Background/BackgroundOriginProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Background/BackgroundPositionProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Background/BackgroundProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Background/BackgroundRepeatProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Background/BackgroundSizeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderBottomColorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderBottomProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderBottomStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderBottomWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderCollapseProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderColorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderLeftColorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderLeftProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderLeftStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderLeftWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderRightColorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderRightProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderRightStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderRightWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderSpacingProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderTopColorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderTopProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderTopStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderTopWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Border/BorderWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderImage/BorderImageOutsetProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderImage/BorderImageProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderImage/BorderImageRepeatProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderImage/BorderImageSliceProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderImage/BorderImageSourceProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderImage/BorderImageWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderRadius/BorderBottomLeftRadiusProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderRadius/BorderBottomRightRadiusProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderRadius/BorderRadiusProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderRadius/BorderTopLeftRadiusProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/BorderRadius/BorderTopRightRadiusProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/BoxDecorationBreak.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/BoxShadowProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/BoxSizingProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/MarginBottomProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/MarginLeftProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/MarginProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/MarginRightProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/MarginTopProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/PaddingBottomProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/PaddingLeftProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/PaddingProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/PaddingRightProperty .cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Box/PaddingTopProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Break/BreakAfterProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Break/BreakBeforeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Break/BreakInsideProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Break/PageBreakAfterProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Break/PageBreakBeforeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Break/PageBreakInsideProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/CaptionSideProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Columns/ColumnCountProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Columns/ColumnFillProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Columns/ColumnGapProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Columns/ColumnRuleColorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Columns/ColumnRuleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Columns/ColumnRuleStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Columns/ColumnRuleWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Columns/ColumnSpanProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Columns/ColumnWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Columns/ColumnsProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Container/ContainerNameProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Container/ContainerTypeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/ContentProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Coordinate/BottomProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Coordinate/HeightProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Coordinate/LeftProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Coordinate/MaxHeightProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Coordinate/MaxWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Coordinate/MinHeightProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Coordinate/MinWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Coordinate/RightProperty .cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Coordinate/TopProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Coordinate/WidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/CursorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/CustomProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/EmptyCellsProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/FeatureProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Fill/FillOpacityProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Fill/FillProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Fill/FillRuleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/AlignContentProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/AlignItemsProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/AlignSelfProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/FlexBasisProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/FlexDirectionProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/FlexFlowProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/FlexGrowProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/FlexProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/FlexShrinkProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/FlexWrapProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/JustifyContentProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flexbox/OrderProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flow/ClearProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flow/DisplayProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flow/FloatProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flow/OverflowProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flow/PositionProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Flow/ZIndexProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/ColorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/FontFamilyProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/FontProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/FontSizeAdjustProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/FontSizeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/FontStretchProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/FontStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/FontVariantProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/FontWeightProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/LetterSpacingProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/LineHeightProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/SrcProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/UnicodeRangeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Font/WordSpacingProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/GapProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/HyphensProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/IProperties .cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/IProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/IPropertyValue.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/List/CounterIncrementProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/List/CounterResetProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/List/ListStyleImageProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/List/ListStylePositionProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/List/ListStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/List/ListStyleTypeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/OrphansProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Outline/OutlineColorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Outline/OutlineProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Outline/OutlineStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Outline/OutlineWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/PageNameProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/PageSizeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/PdfTagTypeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Property.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/QuotesProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/RowGapProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/ShorthandProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Sizing/ObjectFitProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Sizing/ObjectPositionProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/StringSetProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Stroke/StrokeDasharrayProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Stroke/StrokeDashoffsetProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Stroke/StrokeLinecapProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Stroke/StrokeLinejoinProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Stroke/StrokeMiterlimitProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Stroke/StrokeOpacityProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Stroke/StrokeProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Stroke/StrokeWidthProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/TableLayoutProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/DirectionProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/OverflowWrapProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextAlignLastProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextAlignProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextAnchorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextDecorationColorProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextDecorationLineProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextDecorationProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextDecorationStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextIndentProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextJustifyProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextShadowProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/TextTransformProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/VerticalAlignProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/WhiteSpaceProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Text/WordBreakProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Transform/PerspectiveOriginProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Transform/PerspectiveProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Transform/TransformOriginProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Transform/TransformProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Transform/TransformStyleProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Transition/TransitionDelayProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Transition/TransitionDurationProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Transition/TransitionProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Transition/TransitionPropertyProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Transition/TransitionTimingFunctionProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/UnicodeBidirectionalProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/UnknownProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Visibility/BackfaceVisibilityProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Visibility/ClipProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Visibility/OpacityProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/Visibility/VisibilityProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/StyleProperties/WidowsProperty.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/TextEncoding.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Tokens/ColorToken.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Tokens/CommentToken.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Tokens/FunctionToken.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Tokens/KeywordToken.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Tokens/NumberToken.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Tokens/RangeToken.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Tokens/StringToken.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Tokens/Token.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Tokens/UnitToken.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Tokens/UrlToken.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/AnyValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/ArgumentsValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/BackgroundPositionValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/BackgroundSizeValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/BorderRadiusConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/ConditionalStartsWithValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/ConstraintValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/ContentFunctionConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/ContinuousValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/DictionaryValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/EndListValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/FallbackValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/FunctionValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/GradientConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/IdentifierValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/ListValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/OneOrMoreValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/OptionValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/OrValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/OrderedOptionsConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/PeriodicValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/RequiredValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/StartsWithValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/StringFunctionConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/StringSetValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/StringValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/StringsValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/StructValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/TimeBasedShorthandConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/UnorderedOptionsConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/ValueConverters/UrlValueConverter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Angle.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Color.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Counter.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/CubicBezierTimingFunction.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Frequency.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/GradientStop.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/IGradient.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/IImageSource.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/ITimingFunction.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/ITransform.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Length.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/LinearGradient.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/MatrixTransform.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Number.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Percent.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/PerspectiveTransform.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Point.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/PortNumbers.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/ProtocolNames.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/RadialGradient.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Resolution.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/RotateTransform.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/ScaleTransform.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Shadow.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Shape.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/SkewTransform.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/StepsTimingFunction.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/Time.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/Values/TranslateTransform.cs create mode 100644 Source/HtmlRenderer/Core/CssEngine/license.txt create mode 100644 Source/HtmlRenderer/Core/Dom/CssContentEngine.cs delete mode 100644 Source/HtmlRenderer/Core/Entities/CssBlock.cs delete mode 100644 Source/HtmlRenderer/Core/Entities/CssBlockSelectorItem.cs delete mode 100644 Source/HtmlRenderer/Core/Parse/RegexParserHelper.cs diff --git a/Source/HtmlRenderer/Adapters/RAdapter.cs b/Source/HtmlRenderer/Adapters/RAdapter.cs index 6b13459c1..8a5afc3b9 100644 --- a/Source/HtmlRenderer/Adapters/RAdapter.cs +++ b/Source/HtmlRenderer/Adapters/RAdapter.cs @@ -85,7 +85,16 @@ protected RAdapter() /// public CssData DefaultCssData { - get { return _defaultCssData ?? (_defaultCssData = CssData.Parse(this, CssDefaults.DefaultStyleSheet, false)); } + get + { + if (_defaultCssData == null) + { + _defaultCssData = CssData.Parse(this, CssDefaults.DefaultStyleSheet, false); + foreach (var stylesheet in _defaultCssData.Stylesheets) + stylesheet.IsUserAgent = true; + } + return _defaultCssData; + } } /// diff --git a/Source/HtmlRenderer/Core/CssData.cs b/Source/HtmlRenderer/Core/CssData.cs index f0e4a8106..179956178 100644 --- a/Source/HtmlRenderer/Core/CssData.cs +++ b/Source/HtmlRenderer/Core/CssData.cs @@ -6,54 +6,51 @@ // like the days and months; // they die and are reborn, // like the four seasons." -// +// // - Sun Tsu, // "The Art of War" using System; using System.Collections.Generic; +using System.Linq; using TheArtOfDev.HtmlRenderer.Adapters; -using TheArtOfDev.HtmlRenderer.Core.Entities; +using TheArtOfDev.HtmlRenderer.Core.CssEngine; +using TheArtOfDev.HtmlRenderer.Core.Dom; using TheArtOfDev.HtmlRenderer.Core.Parse; using TheArtOfDev.HtmlRenderer.Core.Utils; namespace TheArtOfDev.HtmlRenderer.Core { /// - /// Holds parsed stylesheet css blocks arranged by media and classes.
- /// + /// Holds parsed stylesheets (UA default + author + inline), and provides origin-aware, + /// specificity-ordered selector matching against the box tree.
///
/// - /// To learn more about CSS blocks visit CSS spec: http://www.w3.org/TR/CSS21/syndata.html#block + /// Ported from PeachPDF's Html/Core/CssData.cs (itself a fork of Tyler Brinks' ExCSS), adapted to + /// HTML-Renderer's CssBox/HtmlTag shape. One deliberate omission from the source: the + /// ::marker pseudo-element box-synthesis path is removed - ::before/::after + /// synthesis is kept, but this codebase has no CssBox.IsMarkerPseudoElement concept and no + /// consumer for it (list markers are painted procedurally, not via a synthesized box). /// public sealed class CssData { - #region Fields and Consts - /// - /// used to return empty array + /// The parsed stylesheets that make up this CssData, in the order they were added (UA default + /// first, then author/`<link>`/`<style>` stylesheets in document order). Each carries its own + /// flag so origin-aware cascade phases can be resolved. /// - private static readonly List _emptyArray = new List(); - - /// - /// dictionary of media type to dictionary of css class name to the cssBlocks collection with all the data. - /// - private readonly Dictionary>> _mediaBlocks = new Dictionary>>(StringComparer.InvariantCultureIgnoreCase); - - #endregion - + internal List Stylesheets { get; } = new List(); /// /// Init. /// internal CssData() { - _mediaBlocks.Add("all", new Dictionary>(StringComparer.InvariantCultureIgnoreCase)); } /// /// Parse the given stylesheet to object.
- /// If is true the parsed css blocks are added to the + /// If is true the parsed css blocks are added to the /// default css data (as defined by W3), merged if class name already exists. If false only the data in the given stylesheet is returned. ///
/// @@ -63,152 +60,872 @@ internal CssData() /// the parsed css data public static CssData Parse(RAdapter adapter, string stylesheet, bool combineWithDefault = true) { - CssParser parser = new CssParser(adapter); + var parser = new CssParser(adapter); return parser.ParseStyleSheet(stylesheet, combineWithDefault); } /// - /// dictionary of media type to dictionary of css class name to the cssBlocks collection with all the data + /// Combine this CSS data's stylesheets with 's. + /// + /// the CSS data to combine with + public void Combine(CssData other) + { + ArgChecker.AssertArgNotNull(other, "other"); + Stylesheets.AddRange(other.Stylesheets); + } + + /// + /// Create a shallow copy of this css data - the returned instance has its own + /// list (so appending a new stylesheet to the clone doesn't affect the original), but the + /// instances themselves are shared (they're never mutated after parsing). /// - internal IDictionary>> MediaBlocks + /// cloned object + public CssData Clone() + { + var clone = new CssData(); + clone.Stylesheets.AddRange(Stylesheets); + return clone; + } + + // --- Rule index ----------------------------------------------------------------------- + // + // Matching every stylesheet rule against every CssBox in the document (the naive approach) + // is O(rules x boxes) and dominates cascade cost on large documents. Real browser engines + // avoid this by bucketing rules by the "subject" simple selector (the one that must match the + // box itself, e.g. the tag/class/id) so a box only needs to test the handful of rules that + // could plausibly match it, instead of the whole stylesheet. DoesSelectorMatch remains the + // source of truth for whether a rule actually matches - the index only narrows the candidates. + // + // Built lazily, once, the first time this CssData's rules are queried. Safe because by the + // time CascadeApplyStyles starts querying rules for the box tree, DomParser has already + // finished building/cloning CssData from "); } From f63ff1cfaa5a8eab300ae9c3f12afd7d9f8cd287 Mon Sep 17 00:00:00 2001 From: Justin Haygood Date: Thu, 16 Jul 2026 19:02:18 -0400 Subject: [PATCH 2/4] Fix CalcSerializer --- Source/HtmlRenderer/Core/CssEngine/Calc/CalcSerializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/HtmlRenderer/Core/CssEngine/Calc/CalcSerializer.cs b/Source/HtmlRenderer/Core/CssEngine/Calc/CalcSerializer.cs index 511bf39d2..39edbb0fa 100644 --- a/Source/HtmlRenderer/Core/CssEngine/Calc/CalcSerializer.cs +++ b/Source/HtmlRenderer/Core/CssEngine/Calc/CalcSerializer.cs @@ -48,7 +48,7 @@ public static string Serialize(CalcNode node, CalcCategory category) case UnaryCalcNode unary: { var value = FoldAngle(unary.Operand); - return value is null ? null : unary.Negative ? -value.Value : value.Value; + return value is null ? null : (double?)(unary.Negative ? -value.Value : value.Value); } case BinaryCalcNode binary when binary.Operator == '+' || binary.Operator == '-': From 0179a9ab1314b35f9c1c33a833f46c1b6e779a15 Mon Sep 17 00:00:00 2001 From: Justin Haygood Date: Thu, 16 Jul 2026 21:11:58 -0400 Subject: [PATCH 3/4] Replace custom CSS with standards The Demo app depending on 2 HTML-Renderer extensions for background gradients and border radius. This ports a minimal version of the CSS spec compliant versions from PeachPDF --- Source/Demo/Common/DemoUtils.cs | 4 +- Source/Demo/Common/Samples/00.Intro.htm | 2 +- .../Common/Samples/07.Additional features.htm | 81 ++-- Source/Demo/Common/Samples/08.Tooltip.htm | 5 +- Source/Demo/Common/TestSamples/12.Text.htm | 16 +- Source/Demo/Common/TestSamples/16.Borders.htm | 12 +- .../Adapters/GradientBrushAdapter.cs | 46 ++ .../Adapters/GraphicsAdapter.cs | 72 ++++ .../Adapters/GraphicsPathAdapter.cs | 8 +- .../Adapters/PdfSharpAdapter.cs | 13 +- .../Adapters/GraphicsPathAdapter.cs | 4 +- .../HtmlRenderer.WPF/Adapters/WpfAdapter.cs | 21 +- .../Adapters/GraphicsPathAdapter.cs | 8 +- .../Adapters/WinFormsAdapter.cs | 28 +- Source/HtmlRenderer/Adapters/RAdapter.cs | 24 +- Source/HtmlRenderer/Adapters/RGraphics.cs | 13 +- Source/HtmlRenderer/Adapters/RGraphicsPath.cs | 15 +- Source/HtmlRenderer/Core/CssDefaults.cs | 16 +- Source/HtmlRenderer/Core/Dom/CssBox.cs | 22 +- .../HtmlRenderer/Core/Dom/CssBoxProperties.cs | 396 +++++++++++------- Source/HtmlRenderer/Core/Entities/CssImage.cs | 32 ++ .../Core/Entities/ParsedLinearGradient.cs | 18 + .../Core/Handlers/BordersDrawHandler.cs | 59 +-- .../Core/Handlers/CssImagePainter.cs | 234 +++++++++++ Source/HtmlRenderer/Core/Parse/CssParser.cs | 10 + .../HtmlRenderer/Core/Parse/CssValueParser.cs | 215 ++++++++++ Source/HtmlRenderer/Core/Utils/CssUtils.cs | 51 +-- Source/HtmlRenderer/Core/Utils/RenderUtils.cs | 56 ++- 28 files changed, 1112 insertions(+), 369 deletions(-) create mode 100644 Source/HtmlRenderer.PdfSharp/Adapters/GradientBrushAdapter.cs create mode 100644 Source/HtmlRenderer/Core/Entities/CssImage.cs create mode 100644 Source/HtmlRenderer/Core/Entities/ParsedLinearGradient.cs create mode 100644 Source/HtmlRenderer/Core/Handlers/CssImagePainter.cs diff --git a/Source/Demo/Common/DemoUtils.cs b/Source/Demo/Common/DemoUtils.cs index 682f325f5..a14b33b3f 100644 --- a/Source/Demo/Common/DemoUtils.cs +++ b/Source/Demo/Common/DemoUtils.cs @@ -74,8 +74,8 @@ public static string GetStylesheet(string src) a:link { text-decoration: none; } a:hover { text-decoration: underline; } .gray { color:gray; } - .example { background-color:#efefef; corner-radius:5px; padding:0.5em; } - .whitehole { background-color:white; corner-radius:10px; padding:15px; } + .example { background-color:#efefef; border-radius:5px; padding:0.5em; } + .whitehole { background-color:white; border-radius:10px; padding:15px; } .caption { font-size: 1.1em } .comment { color: green; margin-bottom: 5px; margin-left: 3px; } .comment2 { color: green; }"; diff --git a/Source/Demo/Common/Samples/00.Intro.htm b/Source/Demo/Common/Samples/00.Intro.htm index d310818b4..7f9f9f565 100644 --- a/Source/Demo/Common/Samples/00.Intro.htm +++ b/Source/Demo/Common/Samples/00.Intro.htm @@ -3,7 +3,7 @@ Intro - +

HTML Renderer Project - $$Platform$$
diff --git a/Source/Demo/Common/Samples/07.Additional features.htm b/Source/Demo/Common/Samples/07.Additional features.htm index 764e9511c..bd07880e9 100644 --- a/Source/Demo/Common/Samples/07.Additional features.htm +++ b/Source/Demo/Common/Samples/07.Additional features.htm @@ -5,20 +5,18 @@ @@ -54,7 +54,7 @@

Transparent text

elementum dolor ac mauris. Ut tristique. In varius volutpat metus. Integer leo dolor, tristique a, dignissim ac, iaculis eget, elit. Donec arcu.

-

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ornare mollis elit. Integer_sagittis_Fusce_elementum_commodo_felis_Vivamus_lacinia_eleifend_libero_Donec lacus. diff --git a/Source/Demo/Common/TestSamples/16.Borders.htm b/Source/Demo/Common/TestSamples/16.Borders.htm index 6fae0757c..117dfbcae 100644 --- a/Source/Demo/Common/TestSamples/16.Borders.htm +++ b/Source/Demo/Common/TestSamples/16.Borders.htm @@ -12,13 +12,13 @@

-

- border 1px with corner-radius 5px +
+ border 1px with border-radius 5px

-

- border 2px with corner-radius 10px +
+ border 2px with border-radius 10px

@@ -37,8 +37,8 @@

-

- dashed border 2px with corner-radius 10px +
+ dashed border 2px with border-radius 10px

diff --git a/Source/HtmlRenderer.PdfSharp/Adapters/GradientBrushAdapter.cs b/Source/HtmlRenderer.PdfSharp/Adapters/GradientBrushAdapter.cs new file mode 100644 index 000000000..b92bf73d3 --- /dev/null +++ b/Source/HtmlRenderer.PdfSharp/Adapters/GradientBrushAdapter.cs @@ -0,0 +1,46 @@ +// "Therefore those skilled at the unorthodox +// are infinite as heaven and earth, +// inexhaustible as the great rivers. +// When they come to an end, +// they begin again, +// like the days and months; +// they die and are reborn, +// like the four seasons." +// +// - Sun Tsu, +// "The Art of War" + +using TheArtOfDev.HtmlRenderer.Adapters; +using TheArtOfDev.HtmlRenderer.Adapters.Entities; + +namespace TheArtOfDev.HtmlRenderer.PdfSharp.Adapters +{ + /// + /// A multi-stop linear gradient "brush" for the PdfSharp backend. Unlike , + /// this does not wrap a real PdfSharp.Drawing.XBrush - XLinearGradientBrush in the + /// PDFsharp 1.50 package this project depends on only supports 2 colors, no stop list. Instead this + /// just carries the gradient line and stops; 's DrawPath/ + /// DrawRectangle special-case this type and paint it as a series of adjacent 2-color + /// XLinearGradientBrush bands, one per consecutive stop pair - each band is a real 2-color + /// linear gradient by definition, so the composite is an exact piecewise-linear rendering, not an + /// approximation. + /// + internal sealed class GradientBrushAdapter : RBrush + { + public GradientBrushAdapter(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops) + { + P1 = p1; + P2 = p2; + Stops = stops; + } + + public RPoint P1 { get; } + + public RPoint P2 { get; } + + public (RColor Color, double Position)[] Stops { get; } + + public override void Dispose() + { } + } +} diff --git a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs index c6d406c58..76ede5ea1 100644 --- a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs +++ b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs @@ -155,6 +155,14 @@ public override void DrawRectangle(RPen pen, double x, double y, double width, d public override void DrawRectangle(RBrush brush, double x, double y, double width, double height) { + if (brush is GradientBrushAdapter gradient) + { + var rectPath = new XGraphicsPath(); + rectPath.AddRectangle(x, y, width, height); + FillGradient(gradient, rectPath); + return; + } + var xBrush = ((BrushAdapter)brush).Brush; var xTextureBrush = xBrush as XTextureBrush; if (xTextureBrush != null) @@ -188,6 +196,12 @@ public override void DrawPath(RPen pen, RGraphicsPath path) public override void DrawPath(RBrush brush, RGraphicsPath path) { + if (brush is GradientBrushAdapter gradient) + { + FillGradient(gradient, ((GraphicsPathAdapter)path).GraphicsPath); + return; + } + _g.DrawPath((XBrush)((BrushAdapter)brush).Brush, ((GraphicsPathAdapter)path).GraphicsPath); } @@ -199,6 +213,64 @@ public override void DrawPolygon(RBrush brush, RPoint[] points) } } + /// + /// Paints a multi-stop linear gradient by clipping to and drawing + /// one real 2-color band per consecutive stop pair, each + /// spanning the full perpendicular extent needed to cover the target - see + /// for why this backend needs banding instead of a single brush. + /// + private void FillGradient(GradientBrushAdapter gradient, XGraphicsPath targetPath) + { + var stops = gradient.Stops; + if (stops.Length == 0) + return; + + _g.Save(); + _g.IntersectClip(targetPath); + + double dx = gradient.P2.X - gradient.P1.X; + double dy = gradient.P2.Y - gradient.P1.Y; + double len = Math.Sqrt(dx * dx + dy * dy); + + if (stops.Length == 1 || len < 1e-6) + { + // Degenerate gradient line (single stop, or a zero-size box) - just flat-fill with the + // last color, matching what a real linear gradient converges to in that case. + var flatBrush = new XSolidBrush(Utils.Convert(stops[stops.Length - 1].Color)); + _g.DrawRectangle(flatBrush, -1e5, -1e5, 2e5, 2e5); + _g.Restore(); + _g.DrawRectangle(XBrushes.White, 0, 0, 0.1, 0.1); + return; + } + + double ux = dx / len, uy = dy / len; + double perpX = -uy, perpY = ux; + double perpHalf = Math.Max(len, 1.0) * 4.0; + + for (int i = 0; i < stops.Length - 1; i++) + { + double t1 = stops[i].Position, t2 = stops[i + 1].Position; + var bp1 = new XPoint(gradient.P1.X + ux * len * t1, gradient.P1.Y + uy * len * t1); + var bp2 = new XPoint(gradient.P1.X + ux * len * t2, gradient.P1.Y + uy * len * t2); + + var band = new[] + { + new XPoint(bp1.X - perpX * perpHalf, bp1.Y - perpY * perpHalf), + new XPoint(bp1.X + perpX * perpHalf, bp1.Y + perpY * perpHalf), + new XPoint(bp2.X + perpX * perpHalf, bp2.Y + perpY * perpHalf), + new XPoint(bp2.X - perpX * perpHalf, bp2.Y - perpY * perpHalf), + }; + + var bandBrush = new XLinearGradientBrush(bp1, bp2, Utils.Convert(stops[i].Color), Utils.Convert(stops[i + 1].Color)); + _g.DrawPolygon(bandBrush, band, XFillMode.Winding); + } + + _g.Restore(); + + // handle bug in PdfSharp that keeps the brush color for next string draw + _g.DrawRectangle(XBrushes.White, 0, 0, 0.1, 0.1); + } + #endregion } } \ No newline at end of file diff --git a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsPathAdapter.cs b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsPathAdapter.cs index 48290543c..9e057d8e9 100644 --- a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsPathAdapter.cs +++ b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsPathAdapter.cs @@ -51,11 +51,11 @@ public override void LineTo(double x, double y) _lastPoint = new RPoint(x, y); } - public override void ArcTo(double x, double y, double size, Corner corner) + public override void ArcTo(double x, double y, double radiusX, double radiusY, Corner corner) { - float left = (float)(Math.Min(x, _lastPoint.X) - (corner == Corner.TopRight || corner == Corner.BottomRight ? size : 0)); - float top = (float)(Math.Min(y, _lastPoint.Y) - (corner == Corner.BottomLeft || corner == Corner.BottomRight ? size : 0)); - _graphicsPath.AddArc(left, top, (float)size * 2, (float)size * 2, GetStartAngle(corner), 90); + float left = (float)(Math.Min(x, _lastPoint.X) - (corner == Corner.TopRight || corner == Corner.BottomRight ? radiusX : 0)); + float top = (float)(Math.Min(y, _lastPoint.Y) - (corner == Corner.BottomLeft || corner == Corner.BottomRight ? radiusY : 0)); + _graphicsPath.AddArc(left, top, (float)radiusX * 2, (float)radiusY * 2, GetStartAngle(corner), 90); _lastPoint = new RPoint(x, y); } diff --git a/Source/HtmlRenderer.PdfSharp/Adapters/PdfSharpAdapter.cs b/Source/HtmlRenderer.PdfSharp/Adapters/PdfSharpAdapter.cs index e5fa309c0..f286da15b 100644 --- a/Source/HtmlRenderer.PdfSharp/Adapters/PdfSharpAdapter.cs +++ b/Source/HtmlRenderer.PdfSharp/Adapters/PdfSharpAdapter.cs @@ -93,18 +93,9 @@ protected override RBrush CreateSolidBrush(RColor color) return new BrushAdapter(solidBrush); } - protected override RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle) + protected override RBrush CreateLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops) { - XLinearGradientMode mode; - if (angle < 45) - mode = XLinearGradientMode.ForwardDiagonal; - else if (angle < 90) - mode = XLinearGradientMode.Vertical; - else if (angle < 135) - mode = XLinearGradientMode.BackwardDiagonal; - else - mode = XLinearGradientMode.Horizontal; - return new BrushAdapter(new XLinearGradientBrush(Utils.Convert(rect), Utils.Convert(color1), Utils.Convert(color2), mode)); + return new GradientBrushAdapter(p1, p2, stops); } protected override RImage ConvertImageInt(object image) diff --git a/Source/HtmlRenderer.WPF/Adapters/GraphicsPathAdapter.cs b/Source/HtmlRenderer.WPF/Adapters/GraphicsPathAdapter.cs index a6ffb66bb..af19357b4 100644 --- a/Source/HtmlRenderer.WPF/Adapters/GraphicsPathAdapter.cs +++ b/Source/HtmlRenderer.WPF/Adapters/GraphicsPathAdapter.cs @@ -46,9 +46,9 @@ public override void LineTo(double x, double y) _geometryContext.LineTo(new Point(x, y), true, true); } - public override void ArcTo(double x, double y, double size, Corner corner) + public override void ArcTo(double x, double y, double radiusX, double radiusY, Corner corner) { - _geometryContext.ArcTo(new Point(x, y), new Size(size, size), 0, false, SweepDirection.Clockwise, true, true); + _geometryContext.ArcTo(new Point(x, y), new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise, true, true); } /// diff --git a/Source/HtmlRenderer.WPF/Adapters/WpfAdapter.cs b/Source/HtmlRenderer.WPF/Adapters/WpfAdapter.cs index 3ea88d9c4..4de4c8777 100644 --- a/Source/HtmlRenderer.WPF/Adapters/WpfAdapter.cs +++ b/Source/HtmlRenderer.WPF/Adapters/WpfAdapter.cs @@ -102,14 +102,19 @@ protected override RBrush CreateSolidBrush(RColor color) return new BrushAdapter(solidBrush); } - protected override RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle) - { - var startColor = angle <= 180 ? Utils.Convert(color1) : Utils.Convert(color2); - var endColor = angle <= 180 ? Utils.Convert(color2) : Utils.Convert(color1); - angle = angle <= 180 ? angle : angle - 180; - double x = angle < 135 ? Math.Max((angle - 45) / 90, 0) : 1; - double y = angle <= 45 ? Math.Max(0.5 - angle / 90, 0) : angle > 135 ? Math.Abs(1.5 - angle / 90) : 0; - return new BrushAdapter(new LinearGradientBrush(startColor, endColor, new Point(x, y), new Point(1 - x, 1 - y))); + protected override RBrush CreateLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops) + { + var gradientStops = new GradientStopCollection(stops.Length); + foreach (var stop in stops) + gradientStops.Add(new GradientStop(Utils.Convert(stop.Color), stop.Position)); + + var brush = new LinearGradientBrush(gradientStops, 0) + { + MappingMode = BrushMappingMode.Absolute, + StartPoint = Utils.Convert(p1), + EndPoint = Utils.Convert(p2) + }; + return new BrushAdapter(brush); } protected override RImage ConvertImageInt(object image) diff --git a/Source/HtmlRenderer.WinForms/Adapters/GraphicsPathAdapter.cs b/Source/HtmlRenderer.WinForms/Adapters/GraphicsPathAdapter.cs index 6c4bbd061..d3a7b3f2c 100644 --- a/Source/HtmlRenderer.WinForms/Adapters/GraphicsPathAdapter.cs +++ b/Source/HtmlRenderer.WinForms/Adapters/GraphicsPathAdapter.cs @@ -51,11 +51,11 @@ public override void LineTo(double x, double y) _lastPoint = new RPoint(x, y); } - public override void ArcTo(double x, double y, double size, Corner corner) + public override void ArcTo(double x, double y, double radiusX, double radiusY, Corner corner) { - float left = (float)(Math.Min(x, _lastPoint.X) - (corner == Corner.TopRight || corner == Corner.BottomRight ? size : 0)); - float top = (float)(Math.Min(y, _lastPoint.Y) - (corner == Corner.BottomLeft || corner == Corner.BottomRight ? size : 0)); - _graphicsPath.AddArc(left, top, (float)size * 2, (float)size * 2, GetStartAngle(corner), 90); + float left = (float)(Math.Min(x, _lastPoint.X) - (corner == Corner.TopRight || corner == Corner.BottomRight ? radiusX : 0)); + float top = (float)(Math.Min(y, _lastPoint.Y) - (corner == Corner.BottomLeft || corner == Corner.BottomRight ? radiusY : 0)); + _graphicsPath.AddArc(left, top, (float)radiusX * 2, (float)radiusY * 2, GetStartAngle(corner), 90); _lastPoint = new RPoint(x, y); } diff --git a/Source/HtmlRenderer.WinForms/Adapters/WinFormsAdapter.cs b/Source/HtmlRenderer.WinForms/Adapters/WinFormsAdapter.cs index 16bc83db8..10da9891a 100644 --- a/Source/HtmlRenderer.WinForms/Adapters/WinFormsAdapter.cs +++ b/Source/HtmlRenderer.WinForms/Adapters/WinFormsAdapter.cs @@ -10,6 +10,7 @@ // - Sun Tsu, // "The Art of War" +using System; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; @@ -83,9 +84,32 @@ protected override RBrush CreateSolidBrush(RColor color) return new BrushAdapter(solidBrush, false); } - protected override RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle) + protected override RBrush CreateLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops) { - return new BrushAdapter(new LinearGradientBrush(Utils.Convert(rect), Utils.Convert(color1), Utils.Convert(color2), (float)angle), true); + var brush = new LinearGradientBrush(Utils.Convert(p1), Utils.Convert(p2), Color.Black, Color.Black); + + var colors = new Color[stops.Length]; + var positions = new float[stops.Length]; + for (int i = 0; i < stops.Length; i++) + { + colors[i] = Utils.Convert(stops[i].Color); + var pos = (float)Math.Min(Math.Max(stops[i].Position, 0.0), 1.0); + // GDI+ requires strictly increasing positions - nudge any duplicate up by an epsilon. + positions[i] = i > 0 && pos <= positions[i - 1] ? positions[i - 1] + 0.0001f : pos; + } + // GDI+ requires the first/last position to be exactly 0/1 - forcing them here (rather than + // requiring the caller to pre-normalize) also matches spec behavior for a gradient whose + // outermost stops aren't at the very ends: the outermost color simply extends flat to the edge. + positions[0] = 0f; + positions[positions.Length - 1] = 1f; + + brush.InterpolationColors = new ColorBlend + { + Colors = colors, + Positions = positions + }; + + return new BrushAdapter(brush, true); } protected override RImage ConvertImageInt(object image) diff --git a/Source/HtmlRenderer/Adapters/RAdapter.cs b/Source/HtmlRenderer/Adapters/RAdapter.cs index 8a5afc3b9..1a19f7a57 100644 --- a/Source/HtmlRenderer/Adapters/RAdapter.cs +++ b/Source/HtmlRenderer/Adapters/RAdapter.cs @@ -139,16 +139,15 @@ public RBrush GetSolidBrush(RColor color) } /// - /// Get linear gradient color brush from to . + /// Get a multi-stop linear gradient brush along the line from to . /// - /// the rectangle to get the brush for - /// the start color of the gradient - /// the end color of the gradient - /// the angle to move the gradient from start color to end color in the rectangle + /// the gradient line's start point + /// the gradient line's end point + /// color stops, each with a position in [0,1] along the gradient line /// linear gradient color brush instance - public RBrush GetLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle) + public RBrush GetLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops) { - return CreateLinearGradientBrush(rect, color1, color2, angle); + return CreateLinearGradientBrush(p1, p2, stops); } /// @@ -360,14 +359,13 @@ internal RFont CreateFont(RFontFamily family, double size, RFontStyle style) protected abstract RBrush CreateSolidBrush(RColor color); /// - /// Get linear gradient color brush from to . + /// Get a multi-stop linear gradient brush along the line from to . /// - /// the rectangle to get the brush for - /// the start color of the gradient - /// the end color of the gradient - /// the angle to move the gradient from start color to end color in the rectangle + /// the gradient line's start point + /// the gradient line's end point + /// color stops, each with a position in [0,1] along the gradient line /// linear gradient color brush instance - protected abstract RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle); + protected abstract RBrush CreateLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops); /// /// Convert image object returned from to . diff --git a/Source/HtmlRenderer/Adapters/RGraphics.cs b/Source/HtmlRenderer/Adapters/RGraphics.cs index af54f2ae4..22ba0df77 100644 --- a/Source/HtmlRenderer/Adapters/RGraphics.cs +++ b/Source/HtmlRenderer/Adapters/RGraphics.cs @@ -76,16 +76,15 @@ public RBrush GetSolidBrush(RColor color) } /// - /// Get linear gradient color brush from to . + /// Get a multi-stop linear gradient brush along the line from to . /// - /// the rectangle to get the brush for - /// the start color of the gradient - /// the end color of the gradient - /// the angle to move the gradient from start color to end color in the rectangle + /// the gradient line's start point + /// the gradient line's end point + /// color stops, each with a position in [0,1] along the gradient line /// linear gradient color brush instance - public RBrush GetLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle) + public RBrush GetLinearGradientBrush(RPoint p1, RPoint p2, (RColor Color, double Position)[] stops) { - return _adapter.GetLinearGradientBrush(rect, color1, color2, angle); + return _adapter.GetLinearGradientBrush(p1, p2, stops); } /// diff --git a/Source/HtmlRenderer/Adapters/RGraphicsPath.cs b/Source/HtmlRenderer/Adapters/RGraphicsPath.cs index 21c86bc1a..474781a5e 100644 --- a/Source/HtmlRenderer/Adapters/RGraphicsPath.cs +++ b/Source/HtmlRenderer/Adapters/RGraphicsPath.cs @@ -30,10 +30,19 @@ public abstract class RGraphicsPath : IDisposable public abstract void LineTo(double x, double y); /// - /// Add circular arc of the given size to the given point from the last point. + /// Add an elliptical arc with independent horizontal (X) and vertical (Y) radii to the given + /// point from the last point - supports elliptical border-radius corners. /// - public abstract void ArcTo(double x, double y, double size, Corner corner); - + public abstract void ArcTo(double x, double y, double radiusX, double radiusY, Corner corner); + + /// + /// Add a circular arc of the given size to the given point from the last point. + /// + public void ArcTo(double x, double y, double size, Corner corner) + { + ArcTo(x, y, size, size, corner); + } + /// /// Release path resources. /// diff --git a/Source/HtmlRenderer/Core/CssDefaults.cs b/Source/HtmlRenderer/Core/CssDefaults.cs index 01e35959a..ec9fce4d9 100644 --- a/Source/HtmlRenderer/Core/CssDefaults.cs +++ b/Source/HtmlRenderer/Core/CssDefaults.cs @@ -119,9 +119,8 @@ @media print { /*This is the background of the HtmlToolTip*/ .htmltooltip { border:solid 1px #767676; - background-color:white; - background-gradient:#E4E5F0; - padding: 8px; + background-color:#E4E5F0; + padding: 8px; Font: 9pt Tahoma; }"; @@ -139,8 +138,6 @@ @media print { new System.Collections.Generic.Dictionary(System.StringComparer.OrdinalIgnoreCase) { { "background-color", "transparent" }, - { "background-gradient", "none" }, - { "background-gradient-angle", "90" }, { "background-image", "none" }, { "background-position", "0% 0%" }, { "background-repeat", "repeat" }, @@ -160,11 +157,10 @@ @media print { { "border-collapse", "separate" }, { "color", "black" }, { "content", "normal" }, - { "corner-nw-radius", "0" }, - { "corner-ne-radius", "0" }, - { "corner-se-radius", "0" }, - { "corner-sw-radius", "0" }, - { "corner-radius", "0" }, + { "border-top-left-radius", "0 0" }, + { "border-top-right-radius", "0 0" }, + { "border-bottom-right-radius", "0 0" }, + { "border-bottom-left-radius", "0 0" }, { "empty-cells", "show" }, { "direction", "ltr" }, { "display", "inline" }, diff --git a/Source/HtmlRenderer/Core/Dom/CssBox.cs b/Source/HtmlRenderer/Core/Dom/CssBox.cs index 6a7140a0d..b01a818ff 100644 --- a/Source/HtmlRenderer/Core/Dom/CssBox.cs +++ b/Source/HtmlRenderer/Core/Dom/CssBox.cs @@ -729,10 +729,10 @@ internal virtual void MeasureWordsSize(RGraphics g) { if (!_wordsSizeMeasured) { - if (BackgroundImage != CssConstants.None && _imageLoadHandler == null) + if (ActualBackgroundImage is CssImage.Url urlImage && _imageLoadHandler == null) { _imageLoadHandler = new ImageLoadHandler(HtmlContainer, OnImageLoadComplete); - _imageLoadHandler.LoadImage(BackgroundImage, HtmlTag != null ? HtmlTag.Attributes : null); + _imageLoadHandler.LoadImage(urlImage.Href, HtmlTag != null ? HtmlTag.Attributes : null); } MeasureWordSpacing(g); @@ -1320,15 +1320,18 @@ protected void PaintBackground(RGraphics g, RRect rect, bool isFirst, bool isLas { RBrush brush = null; - if (BackgroundGradient != CssConstants.None) + var backgroundImage = ActualBackgroundImage; + if (backgroundImage is CssImage.LinearGradient linearGradient) { - brush = g.GetLinearGradientBrush(rect, ActualBackgroundColor, ActualBackgroundGradient, ActualBackgroundGradientAngle); + brush = CssImagePainter.GetLinearGradientBrush(g, rect, linearGradient.Gradient); } else if (RenderUtils.IsColorVisible(ActualBackgroundColor)) { brush = g.GetSolidBrush(ActualBackgroundColor); } + var rad = ComputeRadii(rect); + if (brush != null) { // TODO:a handle it correctly (tables background) @@ -1336,13 +1339,13 @@ protected void PaintBackground(RGraphics g, RRect rect, bool isFirst, bool isLas // rectangle.Width -= ActualWordSpacing + CssUtils.GetWordEndWhitespace(ActualFont); RGraphicsPath roundrect = null; - if (IsRounded) + if (rad.IsRounded) { - roundrect = RenderUtils.GetRoundRect(g, rect, ActualCornerNw, ActualCornerNe, ActualCornerSe, ActualCornerSw); + roundrect = RenderUtils.GetRoundRect(g, rect, rad.TLX, rad.TLY, rad.TRX, rad.TRY, rad.BRX, rad.BRY, rad.BLX, rad.BLY); } Object prevMode = null; - if (HtmlContainer != null && !HtmlContainer.AvoidGeometryAntialias && IsRounded) + if (HtmlContainer != null && !HtmlContainer.AvoidGeometryAntialias && rad.IsRounded) { prevMode = g.SetAntiAliasSmoothingMode(); } @@ -1543,6 +1546,11 @@ protected override RColor GetActualColor(string colorStr) return HtmlContainer.CssParser.ParseColor(colorStr); } + protected override CssImage GetActualBackgroundImageValue(string value) + { + return HtmlContainer.CssParser.ParseBackgroundImage(value); + } + protected override RPoint GetActualLocation(string X, string Y) { var left = CssValueParser.ParseLength(X, this.HtmlContainer.PageSize.Width, this, null); diff --git a/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs b/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs index 6aefc5f2e..d5c9b59d8 100644 --- a/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs +++ b/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs @@ -16,6 +16,7 @@ using System.Text.RegularExpressions; using TheArtOfDev.HtmlRenderer.Adapters; using TheArtOfDev.HtmlRenderer.Adapters.Entities; +using TheArtOfDev.HtmlRenderer.Core.Entities; using TheArtOfDev.HtmlRenderer.Core.Parse; using TheArtOfDev.HtmlRenderer.Core.Utils; @@ -33,8 +34,6 @@ internal abstract class CssBoxProperties #region CSS Fields private string _backgroundColor = "transparent"; - private string _backgroundGradient = "none"; - private string _backgroundGradientAngle = "90"; private string _backgroundImage = "none"; private string _backgroundPosition = "0% 0%"; private string _backgroundRepeat = "repeat"; @@ -55,11 +54,10 @@ internal abstract class CssBoxProperties private string _bottom; private string _color = "black"; private string _content = "normal"; - private string _cornerNwRadius = "0"; - private string _cornerNeRadius = "0"; - private string _cornerSeRadius = "0"; - private string _cornerSwRadius = "0"; - private string _cornerRadius = "0"; + private string _borderTopLeftRadius = "0 0"; + private string _borderTopRightRadius = "0 0"; + private string _borderBottomRightRadius = "0 0"; + private string _borderBottomLeftRadius = "0 0"; private string _emptyCells = "show"; private string _direction = "ltr"; private string _display = "inline"; @@ -121,12 +119,15 @@ internal abstract class CssBoxProperties /// private RSize _size; - private double _actualCornerNw = double.NaN; - private double _actualCornerNe = double.NaN; - private double _actualCornerSw = double.NaN; - private double _actualCornerSe = double.NaN; + private double _actualBorderTopLeftRadiusX = double.NaN; + private double _actualBorderTopLeftRadiusY = double.NaN; + private double _actualBorderTopRightRadiusX = double.NaN; + private double _actualBorderTopRightRadiusY = double.NaN; + private double _actualBorderBottomRightRadiusX = double.NaN; + private double _actualBorderBottomRightRadiusY = double.NaN; + private double _actualBorderBottomLeftRadiusX = double.NaN; + private double _actualBorderBottomLeftRadiusY = double.NaN; private RColor _actualColor = RColor.Empty; - private double _actualBackgroundGradientAngle = double.NaN; private double _actualHeight = double.NaN; private double _actualWidth = double.NaN; private double _actualPaddingTop = double.NaN; @@ -152,7 +153,8 @@ internal abstract class CssBoxProperties private double _actualTextIndent = double.NaN; private double _actualBorderSpacingHorizontal = double.NaN; private double _actualBorderSpacingVertical = double.NaN; - private RColor _actualBackgroundGradient = RColor.Empty; + private bool _actualBackgroundImageComputed; + private CssImage _actualBackgroundImage; private RColor _actualBorderTopColor = RColor.Empty; private RColor _actualBorderLeftColor = RColor.Empty; private RColor _actualBorderBottomColor = RColor.Empty; @@ -281,66 +283,96 @@ public string BorderCollapse set { _borderCollapse = value; } } - public string CornerRadius + /// + /// The "border-radius" shorthand: sets all four corners at once, with an optional "/" separating + /// horizontal and vertical radii for elliptical corners (e.g. "10px 5px / 20px 10px"). Each side + /// of the "/" independently follows the standard 1/2/3/4-value CSS expansion rule + /// (1=all, 2=TL+BR/TR+BL, 3=TL/TR+BL/BR, 4=TL/TR/BR/BL). Write-only, like the other border-* + /// shorthands in this class - the four longhand properties below hold the actual state. + /// + public string BorderRadius { - get { return _cornerRadius; } set { - MatchCollection r = RegexParserUtils.Match(RegexParserUtils.CssLength, value); + int slash = value.IndexOf('/'); + string hGroup = (slash >= 0 ? value.Substring(0, slash) : value).Trim(); + string vGroup = (slash >= 0 ? value.Substring(slash + 1) : hGroup).Trim(); - switch (r.Count) - { - case 1: - CornerNeRadius = r[0].Value; - CornerNwRadius = r[0].Value; - CornerSeRadius = r[0].Value; - CornerSwRadius = r[0].Value; - break; - case 2: - CornerNeRadius = r[0].Value; - CornerNwRadius = r[0].Value; - CornerSeRadius = r[1].Value; - CornerSwRadius = r[1].Value; - break; - case 3: - CornerNeRadius = r[0].Value; - CornerNwRadius = r[1].Value; - CornerSeRadius = r[2].Value; - break; - case 4: - CornerNeRadius = r[0].Value; - CornerNwRadius = r[1].Value; - CornerSeRadius = r[2].Value; - CornerSwRadius = r[3].Value; - break; - } + string[] h = ExpandRadiusShorthand(hGroup); + string[] v = ExpandRadiusShorthand(vGroup); - _cornerRadius = value; + BorderTopLeftRadius = h[0] + " " + v[0]; + BorderTopRightRadius = h[1] + " " + v[1]; + BorderBottomRightRadius = h[2] + " " + v[2]; + BorderBottomLeftRadius = h[3] + " " + v[3]; } } - public string CornerNwRadius + /// + /// Expands a 1-4 value length group into [TopLeft, TopRight, BottomRight, BottomLeft] per the + /// standard CSS shorthand rule. + /// + private static string[] ExpandRadiusShorthand(string group) { - get { return _cornerNwRadius; } - set { _cornerNwRadius = value; } + MatchCollection r = RegexParserUtils.Match(RegexParserUtils.CssLength, group); + + switch (r.Count) + { + case 1: + return new[] { r[0].Value, r[0].Value, r[0].Value, r[0].Value }; + case 2: + return new[] { r[0].Value, r[1].Value, r[0].Value, r[1].Value }; + case 3: + return new[] { r[0].Value, r[1].Value, r[2].Value, r[1].Value }; + case 4: + return new[] { r[0].Value, r[1].Value, r[2].Value, r[3].Value }; + default: + return new[] { "0", "0", "0", "0" }; + } } - public string CornerNeRadius + public string BorderTopLeftRadius { - get { return _cornerNeRadius; } - set { _cornerNeRadius = value; } + get { return _borderTopLeftRadius; } + set + { + _borderTopLeftRadius = value; + _actualBorderTopLeftRadiusX = double.NaN; + _actualBorderTopLeftRadiusY = double.NaN; + } } - public string CornerSeRadius + public string BorderTopRightRadius { - get { return _cornerSeRadius; } - set { _cornerSeRadius = value; } + get { return _borderTopRightRadius; } + set + { + _borderTopRightRadius = value; + _actualBorderTopRightRadiusX = double.NaN; + _actualBorderTopRightRadiusY = double.NaN; + } } - public string CornerSwRadius + public string BorderBottomRightRadius { - get { return _cornerSwRadius; } - set { _cornerSwRadius = value; } + get { return _borderBottomRightRadius; } + set + { + _borderBottomRightRadius = value; + _actualBorderBottomRightRadiusX = double.NaN; + _actualBorderBottomRightRadiusY = double.NaN; + } + } + + public string BorderBottomLeftRadius + { + get { return _borderBottomLeftRadius; } + set + { + _borderBottomLeftRadius = value; + _actualBorderBottomLeftRadiusX = double.NaN; + _actualBorderBottomLeftRadiusY = double.NaN; + } } public string MarginBottom @@ -471,7 +503,11 @@ public string BackgroundColor public string BackgroundImage { get { return _backgroundImage; } - set { _backgroundImage = value; } + set + { + _backgroundImage = value; + _actualBackgroundImageComputed = false; + } } public string BackgroundPosition @@ -486,18 +522,6 @@ public string BackgroundRepeat set { _backgroundRepeat = value; } } - public string BackgroundGradient - { - get { return _backgroundGradient; } - set { _backgroundGradient = value; } - } - - public string BackgroundGradientAngle - { - get { return _backgroundGradientAngle; } - set { _backgroundGradientAngle = value; } - } - public string Color { get { return _color; } @@ -1066,6 +1090,26 @@ public RColor ActualBorderTopColor protected abstract RColor GetActualColor(string colorStr); + protected abstract CssImage GetActualBackgroundImageValue(string value); + + /// + /// Gets the parsed "background-image" value - either a url() reference or a linear-gradient(), + /// lazily resolved (needs adapter-dependent named-color lookup for gradient stops, see + /// ) and cached until is set again. + /// + public CssImage ActualBackgroundImage + { + get + { + if (!_actualBackgroundImageComputed) + { + _actualBackgroundImage = GetActualBackgroundImageValue(BackgroundImage); + _actualBackgroundImageComputed = true; + } + return _actualBackgroundImage; + } + } + /// /// Gets the actual Left border Color /// @@ -1111,72 +1155,147 @@ public RColor ActualBorderRightColor } } - /// - /// Gets the actual length of the north west corner - /// - public double ActualCornerNw + public double ActualBorderTopLeftRadiusX { get { - if (double.IsNaN(_actualCornerNw)) - { - _actualCornerNw = CssValueParser.ParseLength(CornerNwRadius, 0, this); - } - return _actualCornerNw; + if (double.IsNaN(_actualBorderTopLeftRadiusX)) + _actualBorderTopLeftRadiusX = CssValueParser.ParseLength(FirstCssValue(BorderTopLeftRadius), Size.Width, this); + return _actualBorderTopLeftRadiusX; } } - /// - /// Gets the actual length of the north east corner - /// - public double ActualCornerNe + public double ActualBorderTopLeftRadiusY { get { - if (double.IsNaN(_actualCornerNe)) - { - _actualCornerNe = CssValueParser.ParseLength(CornerNeRadius, 0, this); - } - return _actualCornerNe; + if (double.IsNaN(_actualBorderTopLeftRadiusY)) + _actualBorderTopLeftRadiusY = CssValueParser.ParseLength(SecondCssValue(BorderTopLeftRadius), Size.Height, this); + return _actualBorderTopLeftRadiusY; } } - /// - /// Gets the actual length of the south east corner - /// - public double ActualCornerSe + public double ActualBorderTopRightRadiusX { get { - if (double.IsNaN(_actualCornerSe)) - { - _actualCornerSe = CssValueParser.ParseLength(CornerSeRadius, 0, this); - } - return _actualCornerSe; + if (double.IsNaN(_actualBorderTopRightRadiusX)) + _actualBorderTopRightRadiusX = CssValueParser.ParseLength(FirstCssValue(BorderTopRightRadius), Size.Width, this); + return _actualBorderTopRightRadiusX; } } - /// - /// Gets the actual length of the south west corner - /// - public double ActualCornerSw + public double ActualBorderTopRightRadiusY { get { - if (double.IsNaN(_actualCornerSw)) - { - _actualCornerSw = CssValueParser.ParseLength(CornerSwRadius, 0, this); - } - return _actualCornerSw; + if (double.IsNaN(_actualBorderTopRightRadiusY)) + _actualBorderTopRightRadiusY = CssValueParser.ParseLength(SecondCssValue(BorderTopRightRadius), Size.Height, this); + return _actualBorderTopRightRadiusY; + } + } + + public double ActualBorderBottomRightRadiusX + { + get + { + if (double.IsNaN(_actualBorderBottomRightRadiusX)) + _actualBorderBottomRightRadiusX = CssValueParser.ParseLength(FirstCssValue(BorderBottomRightRadius), Size.Width, this); + return _actualBorderBottomRightRadiusX; + } + } + + public double ActualBorderBottomRightRadiusY + { + get + { + if (double.IsNaN(_actualBorderBottomRightRadiusY)) + _actualBorderBottomRightRadiusY = CssValueParser.ParseLength(SecondCssValue(BorderBottomRightRadius), Size.Height, this); + return _actualBorderBottomRightRadiusY; + } + } + + public double ActualBorderBottomLeftRadiusX + { + get + { + if (double.IsNaN(_actualBorderBottomLeftRadiusX)) + _actualBorderBottomLeftRadiusX = CssValueParser.ParseLength(FirstCssValue(BorderBottomLeftRadius), Size.Width, this); + return _actualBorderBottomLeftRadiusX; + } + } + + public double ActualBorderBottomLeftRadiusY + { + get + { + if (double.IsNaN(_actualBorderBottomLeftRadiusY)) + _actualBorderBottomLeftRadiusY = CssValueParser.ParseLength(SecondCssValue(BorderBottomLeftRadius), Size.Height, this); + return _actualBorderBottomLeftRadiusY; } } /// - /// Gets a value indicating if at least one of the corners of the box is rounded + /// Returns the first top-level-whitespace-delimited token in a "border-*-radius" longhand value + /// (the horizontal radius). + /// + private static string FirstCssValue(string value) + { + foreach (var token in CssValueParser.SplitTopLevelWhitespace(value)) + return token; + return value; + } + + /// + /// Returns the second top-level-whitespace-delimited token in a "border-*-radius" longhand value + /// (the vertical radius), or the first if there is no second (spec: omitted v-radius = h-radius). + /// + private static string SecondCssValue(string value) + { + var tokens = new List(CssValueParser.SplitTopLevelWhitespace(value)); + return tokens.Count > 1 ? tokens[1] : value; + } + + /// + /// Computes overlap-reduced corner radii for the given rendering rectangle, per CSS Backgrounds + /// §4. Horizontal and vertical axes are reduced independently so that, e.g., two large radii on + /// the same edge never sum to more than that edge's length. + /// + public BorderRadii ComputeRadii(RRect rect) + { + double tlX = ActualBorderTopLeftRadiusX, tlY = ActualBorderTopLeftRadiusY; + double trX = ActualBorderTopRightRadiusX, trY = ActualBorderTopRightRadiusY; + double brX = ActualBorderBottomRightRadiusX, brY = ActualBorderBottomRightRadiusY; + double blX = ActualBorderBottomLeftRadiusX, blY = ActualBorderBottomLeftRadiusY; + + // Horizontal reduction: check top side and bottom side independently. + double fTop = tlX + trX > 0 && rect.Width > 0 ? rect.Width / (tlX + trX) : 1.0; + double fBot = blX + brX > 0 && rect.Width > 0 ? rect.Width / (blX + brX) : 1.0; + double fX = Math.Min(1.0, Math.Min(fTop, fBot)); + + // Vertical reduction: check left side and right side independently. + double fLeft = tlY + blY > 0 && rect.Height > 0 ? rect.Height / (tlY + blY) : 1.0; + double fRight = trY + brY > 0 && rect.Height > 0 ? rect.Height / (trY + brY) : 1.0; + double fY = Math.Min(1.0, Math.Min(fLeft, fRight)); + + return new BorderRadii(tlX * fX, tlY * fY, trX * fX, trY * fY, + brX * fX, brY * fY, blX * fX, blY * fY); + } + + /// + /// Gets a value indicating if at least one of the corners of the box is rounded. A fast, + /// rect-independent pre-check using the raw (not overlap-reduced) actual radii - use + /// when the actual per-corner values are needed for painting. /// public bool IsRounded { - get { return ActualCornerNe > 0f || ActualCornerNw > 0f || ActualCornerSe > 0f || ActualCornerSw > 0f; } + get + { + return ActualBorderTopLeftRadiusX > 0 || ActualBorderTopLeftRadiusY > 0 || + ActualBorderTopRightRadiusX > 0 || ActualBorderTopRightRadiusY > 0 || + ActualBorderBottomRightRadiusX > 0 || ActualBorderBottomRightRadiusY > 0 || + ActualBorderBottomLeftRadiusX > 0 || ActualBorderBottomLeftRadiusY > 0; + } } /// @@ -1220,37 +1339,6 @@ public RColor ActualBackgroundColor } } - /// - /// Gets the second color that creates a gradient for the background - /// - public RColor ActualBackgroundGradient - { - get - { - if (_actualBackgroundGradient.IsEmpty) - { - _actualBackgroundGradient = GetActualColor(BackgroundGradient); - } - return _actualBackgroundGradient; - } - } - - /// - /// Gets the actual angle specified for the background gradient - /// - public double ActualBackgroundGradientAngle - { - get - { - if (double.IsNaN(_actualBackgroundGradientAngle)) - { - _actualBackgroundGradientAngle = CssValueParser.ParseNumber(BackgroundGradientAngle, 360f); - } - - return _actualBackgroundGradientAngle; - } - } - /// /// Gets the actual font of the parent /// @@ -1528,8 +1616,6 @@ protected void InheritStyle(CssBox p, bool everything) if (everything) { _backgroundColor = p._backgroundColor; - _backgroundGradient = p._backgroundGradient; - _backgroundGradientAngle = p._backgroundGradientAngle; _backgroundImage = p._backgroundImage; _backgroundPosition = p._backgroundPosition; _backgroundRepeat = p._backgroundRepeat; @@ -1546,11 +1632,10 @@ protected void InheritStyle(CssBox p, bool everything) _borderBottomStyle = p._borderBottomStyle; _borderLeftStyle = p._borderLeftStyle; _bottom = p._bottom; - _cornerNwRadius = p._cornerNwRadius; - _cornerNeRadius = p._cornerNeRadius; - _cornerSeRadius = p._cornerSeRadius; - _cornerSwRadius = p._cornerSwRadius; - _cornerRadius = p._cornerRadius; + _borderTopLeftRadius = p._borderTopLeftRadius; + _borderTopRightRadius = p._borderTopRightRadius; + _borderBottomRightRadius = p._borderBottomRightRadius; + _borderBottomLeftRadius = p._borderBottomLeftRadius; _display = p._display; _float = p._float; _height = p._height; @@ -1577,4 +1662,27 @@ protected void InheritStyle(CssBox p, bool everything) } } } + + /// + /// Holds the eight computed (overlap-reduced) corner radii for a box rectangle - see + /// . + /// + internal struct BorderRadii + { + public readonly double TLX, TLY, TRX, TRY, BRX, BRY, BLX, BLY; + + public BorderRadii(double tlX, double tlY, double trX, double trY, + double brX, double brY, double blX, double blY) + { + TLX = tlX; TLY = tlY; + TRX = trX; TRY = trY; + BRX = brX; BRY = brY; + BLX = blX; BLY = blY; + } + + public bool IsRounded + { + get { return TLX > 0 || TLY > 0 || TRX > 0 || TRY > 0 || BRX > 0 || BRY > 0 || BLX > 0 || BLY > 0; } + } + } } \ No newline at end of file diff --git a/Source/HtmlRenderer/Core/Entities/CssImage.cs b/Source/HtmlRenderer/Core/Entities/CssImage.cs new file mode 100644 index 000000000..570f0d275 --- /dev/null +++ b/Source/HtmlRenderer/Core/Entities/CssImage.cs @@ -0,0 +1,32 @@ +namespace TheArtOfDev.HtmlRenderer.Core.Entities +{ + /// + /// The parsed form of a single background-image value: either a url() reference or a + /// gradient function. Unlike PeachPDF's CssImage (which this is a trimmed-down port of, same + /// name for an easy diff), background-image here is a single value, not a comma-separated + /// layered list - this project only backports enough to replace the old non-standard + /// "background-gradient" property. + /// + internal abstract class CssImage + { + internal sealed class Url : CssImage + { + public Url(string href) + { + Href = href; + } + + public string Href { get; } + } + + internal sealed class LinearGradient : CssImage + { + public LinearGradient(ParsedLinearGradient gradient) + { + Gradient = gradient; + } + + public ParsedLinearGradient Gradient { get; } + } + } +} diff --git a/Source/HtmlRenderer/Core/Entities/ParsedLinearGradient.cs b/Source/HtmlRenderer/Core/Entities/ParsedLinearGradient.cs new file mode 100644 index 000000000..cb43e701f --- /dev/null +++ b/Source/HtmlRenderer/Core/Entities/ParsedLinearGradient.cs @@ -0,0 +1,18 @@ +using TheArtOfDev.HtmlRenderer.Adapters.Entities; +using TheArtOfDev.HtmlRenderer.Core.CssEngine; + +namespace TheArtOfDev.HtmlRenderer.Core.Entities +{ + /// + /// A fully parsed linear-gradient()/repeating-linear-gradient() value - the angle and + /// raw (not yet position-resolved) color stop list. Ported from PeachPDF's + /// Html/Core/Entities/ParsedLinearGradient.cs, minus CSS Color 4 interpolation-color-space support + /// (ColorSpace/HueMethod) - gradients always interpolate in sRGB here. + /// + internal sealed class ParsedLinearGradient + { + public double AngleRad { get; set; } + public (RColor? Color, Length? Position, bool IsHint)[] Stops { get; set; } + public bool IsRepeating { get; set; } + } +} diff --git a/Source/HtmlRenderer/Core/Handlers/BordersDrawHandler.cs b/Source/HtmlRenderer/Core/Handlers/BordersDrawHandler.cs index b20c331cb..2cd90b33f 100644 --- a/Source/HtmlRenderer/Core/Handlers/BordersDrawHandler.cs +++ b/Source/HtmlRenderer/Core/Handlers/BordersDrawHandler.cs @@ -202,73 +202,76 @@ private static void SetInOutsetRectanglePoints(Border border, CssBox b, RRect r, /// Beveled border path, null if there is no rounded corners private static RGraphicsPath GetRoundedBorderPath(RGraphics g, Border border, CssBox b, RRect r) { + var rad = b.ComputeRadii(r); + if (!rad.IsRounded) return null; + RGraphicsPath path = null; switch (border) { case Border.Top: - if (b.ActualCornerNw > 0 || b.ActualCornerNe > 0) + if (rad.TLX > 0 || rad.TLY > 0 || rad.TRX > 0 || rad.TRY > 0) { path = g.GetGraphicsPath(); - path.Start(r.Left + b.ActualBorderLeftWidth / 2, r.Top + b.ActualBorderTopWidth / 2 + b.ActualCornerNw); + path.Start(r.Left + b.ActualBorderLeftWidth / 2, r.Top + b.ActualBorderTopWidth / 2 + rad.TLY); - if (b.ActualCornerNw > 0) - path.ArcTo(r.Left + b.ActualBorderLeftWidth / 2 + b.ActualCornerNw, r.Top + b.ActualBorderTopWidth / 2, b.ActualCornerNw, RGraphicsPath.Corner.TopLeft); + if (rad.TLX > 0 || rad.TLY > 0) + path.ArcTo(r.Left + b.ActualBorderLeftWidth / 2 + rad.TLX, r.Top + b.ActualBorderTopWidth / 2, rad.TLX, rad.TLY, RGraphicsPath.Corner.TopLeft); - path.LineTo(r.Right - b.ActualBorderRightWidth / 2 - b.ActualCornerNe, r.Top + b.ActualBorderTopWidth / 2); + path.LineTo(r.Right - b.ActualBorderRightWidth / 2 - rad.TRX, r.Top + b.ActualBorderTopWidth / 2); - if (b.ActualCornerNe > 0) - path.ArcTo(r.Right - b.ActualBorderRightWidth / 2, r.Top + b.ActualBorderTopWidth / 2 + b.ActualCornerNe, b.ActualCornerNe, RGraphicsPath.Corner.TopRight); + if (rad.TRX > 0 || rad.TRY > 0) + path.ArcTo(r.Right - b.ActualBorderRightWidth / 2, r.Top + b.ActualBorderTopWidth / 2 + rad.TRY, rad.TRX, rad.TRY, RGraphicsPath.Corner.TopRight); } break; case Border.Bottom: - if (b.ActualCornerSw > 0 || b.ActualCornerSe > 0) + if (rad.BLX > 0 || rad.BLY > 0 || rad.BRX > 0 || rad.BRY > 0) { path = g.GetGraphicsPath(); - path.Start(r.Right - b.ActualBorderRightWidth / 2, r.Bottom - b.ActualBorderBottomWidth / 2 - b.ActualCornerSe); + path.Start(r.Right - b.ActualBorderRightWidth / 2, r.Bottom - b.ActualBorderBottomWidth / 2 - rad.BRY); - if (b.ActualCornerSe > 0) - path.ArcTo(r.Right - b.ActualBorderRightWidth / 2 - b.ActualCornerSe, r.Bottom - b.ActualBorderBottomWidth / 2, b.ActualCornerSe, RGraphicsPath.Corner.BottomRight); + if (rad.BRX > 0 || rad.BRY > 0) + path.ArcTo(r.Right - b.ActualBorderRightWidth / 2 - rad.BRX, r.Bottom - b.ActualBorderBottomWidth / 2, rad.BRX, rad.BRY, RGraphicsPath.Corner.BottomRight); - path.LineTo(r.Left + b.ActualBorderLeftWidth / 2 + b.ActualCornerSw, r.Bottom - b.ActualBorderBottomWidth / 2); + path.LineTo(r.Left + b.ActualBorderLeftWidth / 2 + rad.BLX, r.Bottom - b.ActualBorderBottomWidth / 2); - if (b.ActualCornerSw > 0) - path.ArcTo(r.Left + b.ActualBorderLeftWidth / 2, r.Bottom - b.ActualBorderBottomWidth / 2 - b.ActualCornerSw, b.ActualCornerSw, RGraphicsPath.Corner.BottomLeft); + if (rad.BLX > 0 || rad.BLY > 0) + path.ArcTo(r.Left + b.ActualBorderLeftWidth / 2, r.Bottom - b.ActualBorderBottomWidth / 2 - rad.BLY, rad.BLX, rad.BLY, RGraphicsPath.Corner.BottomLeft); } break; case Border.Right: - if (b.ActualCornerNe > 0 || b.ActualCornerSe > 0) + if (rad.TRX > 0 || rad.TRY > 0 || rad.BRX > 0 || rad.BRY > 0) { path = g.GetGraphicsPath(); bool noTop = b.BorderTopStyle == CssConstants.None || b.BorderTopStyle == CssConstants.Hidden; bool noBottom = b.BorderBottomStyle == CssConstants.None || b.BorderBottomStyle == CssConstants.Hidden; - path.Start(r.Right - b.ActualBorderRightWidth / 2 - (noTop ? b.ActualCornerNe : 0), r.Top + b.ActualBorderTopWidth / 2 + (noTop ? 0 : b.ActualCornerNe)); + path.Start(r.Right - b.ActualBorderRightWidth / 2 - (noTop ? rad.TRX : 0), r.Top + b.ActualBorderTopWidth / 2 + (noTop ? 0 : rad.TRY)); - if (b.ActualCornerNe > 0 && noTop) - path.ArcTo(r.Right - b.ActualBorderLeftWidth / 2, r.Top + b.ActualBorderTopWidth / 2 + b.ActualCornerNe, b.ActualCornerNe, RGraphicsPath.Corner.TopRight); + if ((rad.TRX > 0 || rad.TRY > 0) && noTop) + path.ArcTo(r.Right - b.ActualBorderLeftWidth / 2, r.Top + b.ActualBorderTopWidth / 2 + rad.TRY, rad.TRX, rad.TRY, RGraphicsPath.Corner.TopRight); - path.LineTo(r.Right - b.ActualBorderRightWidth / 2, r.Bottom - b.ActualBorderBottomWidth / 2 - b.ActualCornerSe); + path.LineTo(r.Right - b.ActualBorderRightWidth / 2, r.Bottom - b.ActualBorderBottomWidth / 2 - rad.BRY); - if (b.ActualCornerSe > 0 && noBottom) - path.ArcTo(r.Right - b.ActualBorderRightWidth / 2 - b.ActualCornerSe, r.Bottom - b.ActualBorderBottomWidth / 2, b.ActualCornerSe, RGraphicsPath.Corner.BottomRight); + if ((rad.BRX > 0 || rad.BRY > 0) && noBottom) + path.ArcTo(r.Right - b.ActualBorderRightWidth / 2 - rad.BRX, r.Bottom - b.ActualBorderBottomWidth / 2, rad.BRX, rad.BRY, RGraphicsPath.Corner.BottomRight); } break; case Border.Left: - if (b.ActualCornerNw > 0 || b.ActualCornerSw > 0) + if (rad.TLX > 0 || rad.TLY > 0 || rad.BLX > 0 || rad.BLY > 0) { path = g.GetGraphicsPath(); bool noTop = b.BorderTopStyle == CssConstants.None || b.BorderTopStyle == CssConstants.Hidden; bool noBottom = b.BorderBottomStyle == CssConstants.None || b.BorderBottomStyle == CssConstants.Hidden; - path.Start(r.Left + b.ActualBorderLeftWidth / 2 + (noBottom ? b.ActualCornerSw : 0), r.Bottom - b.ActualBorderBottomWidth / 2 - (noBottom ? 0 : b.ActualCornerSw)); + path.Start(r.Left + b.ActualBorderLeftWidth / 2 + (noBottom ? rad.BLX : 0), r.Bottom - b.ActualBorderBottomWidth / 2 - (noBottom ? 0 : rad.BLY)); - if (b.ActualCornerSw > 0 && noBottom) - path.ArcTo(r.Left + b.ActualBorderLeftWidth / 2, r.Bottom - b.ActualBorderBottomWidth / 2 - b.ActualCornerSw, b.ActualCornerSw, RGraphicsPath.Corner.BottomLeft); + if ((rad.BLX > 0 || rad.BLY > 0) && noBottom) + path.ArcTo(r.Left + b.ActualBorderLeftWidth / 2, r.Bottom - b.ActualBorderBottomWidth / 2 - rad.BLY, rad.BLX, rad.BLY, RGraphicsPath.Corner.BottomLeft); - path.LineTo(r.Left + b.ActualBorderLeftWidth / 2, r.Top + b.ActualBorderTopWidth / 2 + b.ActualCornerNw); + path.LineTo(r.Left + b.ActualBorderLeftWidth / 2, r.Top + b.ActualBorderTopWidth / 2 + rad.TLY); - if (b.ActualCornerNw > 0 && noTop) - path.ArcTo(r.Left + b.ActualBorderLeftWidth / 2 + b.ActualCornerNw, r.Top + b.ActualBorderTopWidth / 2, b.ActualCornerNw, RGraphicsPath.Corner.TopLeft); + if ((rad.TLX > 0 || rad.TLY > 0) && noTop) + path.ArcTo(r.Left + b.ActualBorderLeftWidth / 2 + rad.TLX, r.Top + b.ActualBorderTopWidth / 2, rad.TLX, rad.TLY, RGraphicsPath.Corner.TopLeft); } break; } diff --git a/Source/HtmlRenderer/Core/Handlers/CssImagePainter.cs b/Source/HtmlRenderer/Core/Handlers/CssImagePainter.cs new file mode 100644 index 000000000..e8fe16d7d --- /dev/null +++ b/Source/HtmlRenderer/Core/Handlers/CssImagePainter.cs @@ -0,0 +1,234 @@ +// "Therefore those skilled at the unorthodox +// are infinite as heaven and earth, +// inexhaustible as the great rivers. +// When they come to an end, +// they begin again, +// like the days and months; +// they die and are reborn, +// like the four seasons." +// +// - Sun Tsu, +// "The Art of War" + +using System; +using System.Collections.Generic; +using System.Linq; +using TheArtOfDev.HtmlRenderer.Adapters; +using TheArtOfDev.HtmlRenderer.Adapters.Entities; +using TheArtOfDev.HtmlRenderer.Core.CssEngine; +using TheArtOfDev.HtmlRenderer.Core.Entities; + +namespace TheArtOfDev.HtmlRenderer.Core.Handlers +{ + /// + /// Turns a parsed linear-gradient() value into a brush ready to paint. Ported from PeachPDF's + /// Html/Core/Handlers/CssImagePainter.cs, trimmed to the linear-gradient-only, single (non-layered) + /// background-image case this engine supports - see . + /// + internal static class CssImagePainter + { + /// + /// Builds the brush for a linear-gradient() background, filling the given rectangle. + /// + public static RBrush GetLinearGradientBrush(RGraphics g, RRect rect, ParsedLinearGradient gradient) + { + var (p1, p2) = ComputeGradientLine(rect, gradient.AngleRad); + double gdx = p2.X - p1.X, gdy = p2.Y - p1.Y; + double gradientLength = Math.Sqrt(gdx * gdx + gdy * gdy); + var stops = NormalizeGradientStops(gradient.Stops, gradientLength); + if (gradient.IsRepeating) + stops = ExpandRepeatingStops(stops); + return g.GetLinearGradientBrush(p1, p2, stops); + } + + /// + /// Computes the CSS gradient line endpoints for the given box rect and angle (standard CSS + /// "corner-to-corner projection" algorithm - the line is centered on the box and long enough that + /// the box's bounding rectangle is fully spanned along the gradient direction). + /// + private static (RPoint p1, RPoint p2) ComputeGradientLine(RRect rect, double angleRad) + { + double dx = Math.Sin(angleRad); + double dy = -Math.Cos(angleRad); + double cx = rect.X + rect.Width / 2; + double cy = rect.Y + rect.Height / 2; + double halfLen = Math.Abs(dx) * rect.Width / 2 + Math.Abs(dy) * rect.Height / 2; + var p1 = new RPoint(cx - dx * halfLen, cy - dy * halfLen); + var p2 = new RPoint(cx + dx * halfLen, cy + dy * halfLen); + return (p1, p2); + } + + private static double? ConvertLength(Length? length, double gradientLength, double emPx = 16.0) + { + if (!length.HasValue) return null; + var len = length.Value; + if (len.Type == Length.Unit.Percent) + return len.Value / 100.0; + if (len.IsAbsolute) + return gradientLength > 0 ? len.ToPixel() / gradientLength : 0.0; + if (len.Type == Length.Unit.Em) + return gradientLength > 0 ? len.Value * emPx / gradientLength : 0.0; + return null; + } + + private static RColor LerpColor(RColor a, RColor b, double t) + { + t = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t); + return RColor.FromArgb( + (int)Math.Round(a.A + t * (b.A - a.A)), + (int)Math.Round(a.R + t * (b.R - a.R)), + (int)Math.Round(a.G + t * (b.G - a.G)), + (int)Math.Round(a.B + t * (b.B - a.B))); + } + + /// + /// Resolves each stop's position to a [0,1] fraction of the gradient line (per CSS Images §3.5.5: + /// the first/last stop default to 0%/100%, middle stops without an explicit position are spaced + /// evenly between their neighbors, and bare-position "hints" between two color stops bias the + /// interpolation curve rather than being a stop themselves). + /// + private static (RColor Color, double Position)[] NormalizeGradientStops( + (RColor? Color, Length? Position, bool IsHint)[] stops, + double gradientLength, + double emPx = 16.0) + { + var colorStops = stops.Where(s => !s.IsHint).ToArray(); + int n = colorStops.Length; + if (n == 0) return new (RColor, double)[0]; + + var rawPos = new double?[n]; + for (int i = 0; i < n; i++) + rawPos[i] = ConvertLength(colorStops[i].Position, gradientLength, emPx); + + var resolved = new (RColor Color, double Position)[n]; + double first = rawPos[0] ?? 0.0; + double last = rawPos[n - 1] ?? 1.0; + resolved[0] = (colorStops[0].Color.Value, first); + resolved[n - 1] = (colorStops[n - 1].Color.Value, last); + + int runStart = -1; + for (int i = 1; i < n - 1; i++) + { + if (rawPos[i].HasValue) + { + resolved[i] = (colorStops[i].Color.Value, rawPos[i].Value); + if (runStart >= 0) + { + double posA = resolved[runStart - 1].Position; + double posB = resolved[i].Position; + int count = i - runStart + 1; + for (int j = runStart; j < i; j++) + { + double t = (double)(j - runStart + 1) / count; + resolved[j] = (colorStops[j].Color.Value, posA + t * (posB - posA)); + } + runStart = -1; + } + } + else + { + if (runStart < 0) runStart = i; + resolved[i] = (colorStops[i].Color.Value, 0); + } + } + if (runStart >= 0) + { + double posA = resolved[runStart - 1].Position; + double posB = resolved[n - 1].Position; + int count = n - 1 - runStart + 1; + for (int j = runStart; j < n - 1; j++) + { + double t = (double)(j - runStart + 1) / count; + resolved[j] = (colorStops[j].Color.Value, posA + t * (posB - posA)); + } + } + + if (!stops.Any(s => s.IsHint)) + return resolved; + + var result = new List<(RColor Color, double Position)>(); + int colorIdx = 0; + for (int i = 0; i < stops.Length; i++) + { + if (!stops[i].IsHint) + { + result.Add(resolved[colorIdx++]); + } + else + { + if (colorIdx == 0 || colorIdx >= n) continue; + var s1 = resolved[colorIdx - 1]; + var s2 = resolved[colorIdx]; + double range = s2.Position - s1.Position; + double hintPos = ConvertLength(stops[i].Position, gradientLength, emPx) ?? (s1.Position + range * 0.5); + double h = range > 1e-9 + ? Math.Min(Math.Max((hintPos - s1.Position) / range, 1e-9), 1.0 - 1e-9) + : 0.5; + const int kSteps = 7; + double logHalf = Math.Log(0.5); + double logH = Math.Log(h); + for (int k = 1; k <= kSteps; k++) + { + double t = (double)k / (kSteps + 1); + double curved = Math.Pow(t, logHalf / logH); + result.Add((LerpColor(s1.Color, s2.Color, curved), s1.Position + t * range)); + } + } + } + return result.ToArray(); + } + + /// + /// Flattens a repeating gradient's stop tile into a single non-repeating stop list spanning the + /// full [0,1] gradient line, by replicating the tile as many times as needed in both directions. + /// + private static (RColor Color, double Position)[] ExpandRepeatingStops((RColor Color, double Position)[] stops) + { + if (stops.Length < 2) return stops; + double tileStart = stops[0].Position; + double tileEnd = stops[stops.Length - 1].Position; + double tileLen = tileEnd - tileStart; + if (tileLen < 1e-6 || (tileStart <= 0.0 && tileEnd >= 1.0)) return stops; + const double eps = 0.0001; + var result = new List<(RColor Color, double Position)>(); + int kMin = (int)Math.Floor(-tileEnd / tileLen); + int kMax = (int)Math.Ceiling((1.0 - tileStart) / tileLen); + for (int k = kMin; k <= kMax; k++) + { + double kOffset = k * tileLen; + for (int i = 0; i < stops.Length; i++) + { + double rawPos = stops[i].Position + kOffset; + bool isLastStop = i == stops.Length - 1; + double adjPos = isLastStop && k < kMax ? rawPos - eps : rawPos; + if (adjPos >= -eps && adjPos <= 1.0 + eps) + result.Add((stops[i].Color, Math.Min(Math.Max(adjPos, 0.0), 1.0))); + } + } + result.Sort((a, b) => a.Position.CompareTo(b.Position)); + if (result.Count == 0) return stops; + if (result[0].Position > eps) + result.Insert(0, (SampleRepeatingColor(stops, tileStart, tileLen, 0.0), 0.0)); + if (result[result.Count - 1].Position < 1.0 - eps) + result.Add((SampleRepeatingColor(stops, tileStart, tileLen, 1.0), 1.0)); + return result.ToArray(); + } + + private static RColor SampleRepeatingColor((RColor Color, double Position)[] stops, double tileStart, double tileLen, double pos) + { + double relPos = (pos - tileStart) % tileLen; + if (relPos < 0) relPos += tileLen; + double absWithinTile = tileStart + relPos; + for (int i = 0; i < stops.Length - 1; i++) + { + if (absWithinTile >= stops[i].Position && absWithinTile <= stops[i + 1].Position) + { + double range = stops[i + 1].Position - stops[i].Position; + double t = range > 1e-12 ? (absWithinTile - stops[i].Position) / range : 0.0; + return LerpColor(stops[i].Color, stops[i + 1].Color, t); + } + } + return absWithinTile <= stops[0].Position ? stops[0].Color : stops[stops.Length - 1].Color; + } + } +} diff --git a/Source/HtmlRenderer/Core/Parse/CssParser.cs b/Source/HtmlRenderer/Core/Parse/CssParser.cs index b492f6e68..4c3d7c289 100644 --- a/Source/HtmlRenderer/Core/Parse/CssParser.cs +++ b/Source/HtmlRenderer/Core/Parse/CssParser.cs @@ -16,6 +16,7 @@ using TheArtOfDev.HtmlRenderer.Adapters; using TheArtOfDev.HtmlRenderer.Adapters.Entities; using TheArtOfDev.HtmlRenderer.Core.CssEngine; +using TheArtOfDev.HtmlRenderer.Core.Entities; using TheArtOfDev.HtmlRenderer.Core.Handlers; using TheArtOfDev.HtmlRenderer.Core.Utils; @@ -155,6 +156,15 @@ public bool IsColorValid(string colorValue) return _valueParser.IsColorValid(colorValue); } + /// + /// Parses a "background-image" value into a url() reference or a linear-gradient() - see + /// . + /// + public CssImage ParseBackgroundImage(string value) + { + return _valueParser.ParseImage(value); + } + /// /// Parse a complex font family css property to check if it contains multiple fonts and if the font exists.
/// returns the font family name to use or 'inherit' if failed. diff --git a/Source/HtmlRenderer/Core/Parse/CssValueParser.cs b/Source/HtmlRenderer/Core/Parse/CssValueParser.cs index e1f6d738e..5efeb06c3 100644 --- a/Source/HtmlRenderer/Core/Parse/CssValueParser.cs +++ b/Source/HtmlRenderer/Core/Parse/CssValueParser.cs @@ -13,10 +13,13 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Text; using TheArtOfDev.HtmlRenderer.Adapters; using TheArtOfDev.HtmlRenderer.Adapters.Entities; using TheArtOfDev.HtmlRenderer.Core.CssEngine; using TheArtOfDev.HtmlRenderer.Core.Dom; +using TheArtOfDev.HtmlRenderer.Core.Entities; using TheArtOfDev.HtmlRenderer.Core.Utils; namespace TheArtOfDev.HtmlRenderer.Core.Parse @@ -753,5 +756,217 @@ private static int ParseHexInt(string str, int idx, int length) } #endregion + + + #region Background image / gradient parsing + + /// + /// Parses a single background-image value: either a url() reference or a + /// linear-gradient()/repeating-linear-gradient() function. Returns null for "none", + /// an empty value, or anything else this engine doesn't recognize (matching this engine's existing + /// silent-failure behavior for unsupported values).
+ /// Ported from PeachPDF's Html/Core/Parse/CssValueParser.ParseImage, trimmed to the single-value, + /// linear-gradient-only case this engine supports (no layered background-image, no radial/conic). + ///
+ public CssImage ParseImage(string value) + { + if (string.IsNullOrWhiteSpace(value) || string.Equals(value, CssConstants.None, StringComparison.OrdinalIgnoreCase)) + return null; + + var tokens = GetCssTokens(value); + + var urlToken = tokens.OfType().FirstOrDefault(); + if (urlToken != null) + return new CssImage.Url(urlToken.Data); + + var funcToken = tokens.OfType().FirstOrDefault(); + if (funcToken == null) + return null; + + if (string.Equals(funcToken.Data, FunctionNames.LinearGradient, StringComparison.OrdinalIgnoreCase) || + string.Equals(funcToken.Data, FunctionNames.RepeatingLinearGradient, StringComparison.OrdinalIgnoreCase)) + { + var gradient = ParseLinearGradient(value); + return gradient != null ? new CssImage.LinearGradient(gradient) : null; + } + + return null; + } + + /// + /// Parses a linear-gradient()/repeating-linear-gradient() function value: an + /// optional angle or "to <side>" direction (default 180deg, top to bottom), followed by 2+ + /// comma-separated color stops (each optionally followed by 1-2 length/percent positions), and + /// bare-position color hints between stops. Returns null if the value isn't a recognized gradient + /// function or doesn't have at least 2 real color stops.
+ /// Ported from PeachPDF's Html/Core/Parse/CssValueParser.ParseLinearGradient, minus CSS Color 4 + /// interpolation-color-space ("in oklab" etc.) support - see ParsedLinearGradient. + ///
+ private ParsedLinearGradient ParseLinearGradient(string value) + { + var tokens = GetCssTokens(value); + + var funcToken = tokens.OfType().FirstOrDefault(t => + string.Equals(t.Data, FunctionNames.LinearGradient, StringComparison.OrdinalIgnoreCase) || + string.Equals(t.Data, FunctionNames.RepeatingLinearGradient, StringComparison.OrdinalIgnoreCase)); + + if (funcToken == null) + return null; + + bool isRepeating = string.Equals(funcToken.Data, FunctionNames.RepeatingLinearGradient, StringComparison.OrdinalIgnoreCase); + + var args = funcToken.ArgumentTokens.ToList(); + if (args.Count == 0) + return null; + + double angleRad = Math.PI; // default: 180deg = top to bottom + int stopOffset = 0; + + var firstGroup = args[0]; + var firstIdents = firstGroup.Where(t => t.Type == TokenType.Ident).Select(t => t.Data.ToLowerInvariant()).ToList(); + + if (firstIdents.Count > 0 && firstIdents[0] == "to") + { + // keyword direction: "to right", "to bottom left", etc. + angleRad = SideKeywordsToAngleRad(firstIdents.Skip(1).ToList()); + stopOffset = 1; + } + else + { + var angle = firstGroup.ToAngle(); + if (angle.HasValue) + { + angleRad = angle.Value.ToRadian(); + stopOffset = 1; + } + // else no angle token, stopOffset stays 0 - first group is already the first color stop + } + + var stopGroups = args.Skip(stopOffset).ToList(); + if (stopGroups.Count < 2) + return null; + + var stops = new List<(RColor? Color, Length? Position, bool IsHint)>(); + + foreach (var group in stopGroups) + { + var items = group.ToItems(); + if (items.Count == 0) + continue; + + Length? position1 = null; + Length? position2 = null; + int colorItemCount = items.Count; + + // Last item may be a length/percent position. + var lastItem = items[items.Count - 1]; + var pv = lastItem.ToDistance(); + if (pv.HasValue) + { + position1 = pv.Value; + colorItemCount--; + + // Two-position shorthand (e.g. "red 0 50%"). + if (colorItemCount > 0) + { + var pv2 = items[colorItemCount - 1].ToDistance(); + if (pv2.HasValue) + { + position2 = position1; + position1 = pv2.Value; + colorItemCount--; + } + } + } + + if (colorItemCount == 0) + { + // Bare position with no color - a color hint. + if (position1.HasValue && !position2.HasValue) + stops.Add((null, position1, true)); + continue; + } + + var colorText = BuildColorText(items.Take(colorItemCount)); + if (string.IsNullOrWhiteSpace(colorText)) + continue; + + var color = GetActualColor(colorText); + stops.Add((color, position1, false)); + if (position2.HasValue) + stops.Add((color, position2, false)); + } + + if (stops.Count(s => !s.IsHint) < 2) + return null; + + return new ParsedLinearGradient + { + AngleRad = angleRad, + Stops = stops.ToArray(), + IsRepeating = isRepeating, + }; + } + + /// + /// Converts "to <side> [<side>]" direction keywords (e.g. "right", "bottom left") to + /// the equivalent gradient-line angle in radians, per the CSS Images spec's side/corner table. + /// + private static double SideKeywordsToAngleRad(List sides) + { + bool hasTop = sides.Contains("top"); + bool hasBottom = sides.Contains("bottom"); + bool hasLeft = sides.Contains("left"); + bool hasRight = sides.Contains("right"); + + if (hasTop && hasRight) return Math.PI / 4; // 45deg + if (hasBottom && hasRight) return 3 * Math.PI / 4; // 135deg + if (hasBottom && hasLeft) return 5 * Math.PI / 4; // 225deg + if (hasTop && hasLeft) return 7 * Math.PI / 4; // 315deg + if (hasTop) return 0; // 0deg + if (hasRight) return Math.PI / 2; // 90deg + if (hasBottom) return Math.PI; // 180deg + if (hasLeft) return 3 * Math.PI / 2; // 270deg + + return Math.PI; // default + } + + /// + /// Re-serializes a color stop's token groups (everything before its trailing position token(s)) + /// back to CSS text so it can be handed to as a normal color string. + /// + private static string BuildColorText(IEnumerable> itemGroups) + { + var sb = new StringBuilder(); + foreach (var group in itemGroups) + { + sb.Append(group.ToText()); + } + return sb.ToString().Trim(); + } + + /// + /// Splits a CSS value string on top-level whitespace (paren-depth-aware, so a calc()/gradient() + /// value's internal spaces aren't mistaken for a delimiter). Used to split a "border-radius" + /// corner value like "10px 5%" into its horizontal/vertical components. + /// + internal static IEnumerable SplitTopLevelWhitespace(string value) + { + int depth = 0, start = 0; + for (int i = 0; i < value.Length; i++) + { + char c = value[i]; + if (c == '(') depth++; + else if (c == ')') depth--; + else if (char.IsWhiteSpace(c) && depth == 0) + { + if (i > start) yield return value.Substring(start, i - start); + start = i + 1; + } + } + if (start < value.Length) yield return value.Substring(start); + } + + #endregion } } \ No newline at end of file diff --git a/Source/HtmlRenderer/Core/Utils/CssUtils.cs b/Source/HtmlRenderer/Core/Utils/CssUtils.cs index a6b5deebc..79f8402ca 100644 --- a/Source/HtmlRenderer/Core/Utils/CssUtils.cs +++ b/Source/HtmlRenderer/Core/Utils/CssUtils.cs @@ -44,12 +44,11 @@ internal static class CssUtils "border-bottom-style", "border-left-style", "border-right-style", "border-top-style", "border-bottom-color", "border-left-color", "border-right-color", "border-top-color", "border-spacing", "border-collapse", - "corner-radius", "corner-nw-radius", "corner-ne-radius", "corner-se-radius", "corner-sw-radius", + "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius", "margin-bottom", "margin-left", "margin-right", "margin-top", "padding-bottom", "padding-left", "padding-right", "padding-top", "page-break-inside", "left", "top", "width", "max-width", "height", "background-color", "background-image", "background-position", "background-repeat", - "background-gradient", "background-gradient-angle", "content", "color", "display", "direction", "empty-cells", "float", "position", "line-height", "vertical-align", "text-indent", "text-align", "text-decoration", "white-space", "word-break", "visibility", "word-spacing", @@ -124,16 +123,14 @@ public static string GetPropertyValue(CssBox cssBox, string propName) return cssBox.BorderSpacing; case "border-collapse": return cssBox.BorderCollapse; - case "corner-radius": - return cssBox.CornerRadius; - case "corner-nw-radius": - return cssBox.CornerNwRadius; - case "corner-ne-radius": - return cssBox.CornerNeRadius; - case "corner-se-radius": - return cssBox.CornerSeRadius; - case "corner-sw-radius": - return cssBox.CornerSwRadius; + case "border-top-left-radius": + return cssBox.BorderTopLeftRadius; + case "border-top-right-radius": + return cssBox.BorderTopRightRadius; + case "border-bottom-right-radius": + return cssBox.BorderBottomRightRadius; + case "border-bottom-left-radius": + return cssBox.BorderBottomLeftRadius; case "margin-bottom": return cssBox.MarginBottom; case "margin-left": @@ -170,10 +167,6 @@ public static string GetPropertyValue(CssBox cssBox, string propName) return cssBox.BackgroundPosition; case "background-repeat": return cssBox.BackgroundRepeat; - case "background-gradient": - return cssBox.BackgroundGradient; - case "background-gradient-angle": - return cssBox.BackgroundGradientAngle; case "content": return cssBox.Content; case "color": @@ -283,20 +276,20 @@ public static void SetPropertyValue(CssBox cssBox, string propName, string value case "border-collapse": cssBox.BorderCollapse = value; break; - case "corner-radius": - cssBox.CornerRadius = value; + case "border-radius": + cssBox.BorderRadius = value; break; - case "corner-nw-radius": - cssBox.CornerNwRadius = value; + case "border-top-left-radius": + cssBox.BorderTopLeftRadius = value; break; - case "corner-ne-radius": - cssBox.CornerNeRadius = value; + case "border-top-right-radius": + cssBox.BorderTopRightRadius = value; break; - case "corner-se-radius": - cssBox.CornerSeRadius = value; + case "border-bottom-right-radius": + cssBox.BorderBottomRightRadius = value; break; - case "corner-sw-radius": - cssBox.CornerSwRadius = value; + case "border-bottom-left-radius": + cssBox.BorderBottomLeftRadius = value; break; case "margin-bottom": cssBox.MarginBottom = value; @@ -352,12 +345,6 @@ public static void SetPropertyValue(CssBox cssBox, string propName, string value case "background-repeat": cssBox.BackgroundRepeat = value; break; - case "background-gradient": - cssBox.BackgroundGradient = value; - break; - case "background-gradient-angle": - cssBox.BackgroundGradientAngle = value; - break; case "color": cssBox.Color = value; break; diff --git a/Source/HtmlRenderer/Core/Utils/RenderUtils.cs b/Source/HtmlRenderer/Core/Utils/RenderUtils.cs index 2726b5fb2..091c75191 100644 --- a/Source/HtmlRenderer/Core/Utils/RenderUtils.cs +++ b/Source/HtmlRenderer/Core/Utils/RenderUtils.cs @@ -95,44 +95,42 @@ public static void DrawImageErrorIcon(RGraphics g, HtmlContainerInt htmlContaine } /// - /// Creates a rounded rectangle using the specified corner radius
- /// NW-----NE + /// Creates a rounded rectangle path. Each corner has independent horizontal (X) and vertical (Y) + /// radii, supporting elliptical corners per the CSS border-radius spec.
+ /// TL-----TR /// | | /// | | - /// SW-----SE + /// BL-----BR ///
/// the device to draw into /// Rectangle to round - /// Radius of the north east corner - /// Radius of the north west corner - /// Radius of the south east corner - /// Radius of the south west corner /// GraphicsPath with the lines of the rounded rectangle ready to be painted - public static RGraphicsPath GetRoundRect(RGraphics g, RRect rect, double nwRadius, double neRadius, double seRadius, double swRadius) + public static RGraphicsPath GetRoundRect(RGraphics g, RRect rect, + double tlX, double tlY, double trX, double trY, + double brX, double brY, double blX, double blY) { var path = g.GetGraphicsPath(); - path.Start(rect.Left + nwRadius, rect.Top); - - path.LineTo(rect.Right - neRadius, rect.Y); - - if (neRadius > 0f) - path.ArcTo(rect.Right, rect.Top + neRadius, neRadius, RGraphicsPath.Corner.TopRight); - - path.LineTo(rect.Right, rect.Bottom - seRadius); - - if (seRadius > 0f) - path.ArcTo(rect.Right - seRadius, rect.Bottom, seRadius, RGraphicsPath.Corner.BottomRight); - - path.LineTo(rect.Left + swRadius, rect.Bottom); - - if (swRadius > 0f) - path.ArcTo(rect.Left, rect.Bottom - swRadius, swRadius, RGraphicsPath.Corner.BottomLeft); - - path.LineTo(rect.Left, rect.Top + nwRadius); - - if (nwRadius > 0f) - path.ArcTo(rect.Left + nwRadius, rect.Top, nwRadius, RGraphicsPath.Corner.TopLeft); + // Top edge: start after TL corner, end before TR corner. + path.Start(rect.Left + tlX, rect.Top); + path.LineTo(rect.Right - trX, rect.Top); + if (trX > 0 || trY > 0) + path.ArcTo(rect.Right, rect.Top + trY, trX, trY, RGraphicsPath.Corner.TopRight); + + // Right edge. + path.LineTo(rect.Right, rect.Bottom - brY); + if (brX > 0 || brY > 0) + path.ArcTo(rect.Right - brX, rect.Bottom, brX, brY, RGraphicsPath.Corner.BottomRight); + + // Bottom edge. + path.LineTo(rect.Left + blX, rect.Bottom); + if (blX > 0 || blY > 0) + path.ArcTo(rect.Left, rect.Bottom - blY, blX, blY, RGraphicsPath.Corner.BottomLeft); + + // Left edge. + path.LineTo(rect.Left, rect.Top + tlY); + if (tlX > 0 || tlY > 0) + path.ArcTo(rect.Left + tlX, rect.Top, tlX, tlY, RGraphicsPath.Corner.TopLeft); return path; } From bc3884e42511400b52226b05e124c19b34ad3dae Mon Sep 17 00:00:00 2001 From: Justin Haygood Date: Thu, 16 Jul 2026 21:39:17 -0400 Subject: [PATCH 4/4] Fixes
Fixes
--- .../HtmlRenderer/Core/Dom/CssContentEngine.cs | 96 +++++++------------ .../HtmlRenderer/Core/Parse/CssValueParser.cs | 2 +- 2 files changed, 33 insertions(+), 65 deletions(-) diff --git a/Source/HtmlRenderer/Core/Dom/CssContentEngine.cs b/Source/HtmlRenderer/Core/Dom/CssContentEngine.cs index 9ddb80fe0..e3d4b55e3 100644 --- a/Source/HtmlRenderer/Core/Dom/CssContentEngine.cs +++ b/Source/HtmlRenderer/Core/Dom/CssContentEngine.cs @@ -10,7 +10,11 @@ // - Sun Tsu, // "The Art of War" +using System; +using System.Linq; using System.Text; +using TheArtOfDev.HtmlRenderer.Core.CssEngine; +using TheArtOfDev.HtmlRenderer.Core.Parse; using TheArtOfDev.HtmlRenderer.Core.Utils; namespace TheArtOfDev.HtmlRenderer.Core.Dom @@ -23,7 +27,12 @@ namespace TheArtOfDev.HtmlRenderer.Core.Dom /// attr(), and the four quote keywords are supported; counter()/counters()/ /// string()/content() are not (CSS counters and named strings remain out of scope /// for this backport - an unrecognized component is simply skipped, the same graceful-ignore - /// behavior this engine already applies to unsupported CSS elsewhere). + /// behavior this engine already applies to unsupported CSS elsewhere).
+ /// Tokenizes via the shared CSS lexer () rather than + /// hand-parsing the CSS text, so string literals (e.g. "\A", the standard UA-stylesheet + /// line-break trick used by br:before) get the same correct escape decoding the lexer + /// already applies when the stylesheet is first parsed - see CalcParser/BackgroundPositionGrammar + /// for the same "don't write two independent parsers for the same grammar" precedent. ///
internal static class CssContentEngine { @@ -50,82 +59,41 @@ public static void ApplyContent(CssBox box) private static string Resolve(CssBox box, string content) { var sb = new StringBuilder(); - var i = 0; - while (i < content.Length) + + foreach (var token in CssValueParser.GetCssTokens(content)) { - if (char.IsWhiteSpace(content[i])) + if (token is StringToken stringToken) { - i++; - continue; + sb.Append(stringToken.Data); } - - if (content[i] == '"' || content[i] == '\'') + else if (token is FunctionToken functionToken && + functionToken.Data.Equals(FunctionNames.Attr, StringComparison.OrdinalIgnoreCase)) { - var quote = content[i]; - var start = i + 1; - var j = start; - while (j < content.Length && content[j] != quote) + if (functionToken.ArgumentTokens.FirstOrDefault() is KeywordToken attrNameToken) { - if (content[j] == '\\' && j + 1 < content.Length) - j++; - j++; + var sourceBox = box.IsPseudoElement && box.ParentBox != null ? box.ParentBox : box; + var attrValue = sourceBox.GetAttribute(attrNameToken.Data, string.Empty); + sb.Append(attrValue); } - sb.Append(Unescape(content.Substring(start, j - start))); - i = j + 1; - continue; } - - var tokenEnd = i; - while (tokenEnd < content.Length && !char.IsWhiteSpace(content[tokenEnd])) - tokenEnd++; - var token = content.Substring(i, tokenEnd - i); - i = tokenEnd; - - if (token.StartsWith("attr(", System.StringComparison.OrdinalIgnoreCase) && token.EndsWith(")")) + else if (token is KeywordToken keywordToken) { - var attrName = token.Substring(5, token.Length - 6).Trim(); - var sourceBox = box.IsPseudoElement && box.ParentBox != null ? box.ParentBox : box; - var attrValue = sourceBox.GetAttribute(attrName, string.Empty); - sb.Append(attrValue); - } - else if (token.Equals("open-quote", System.StringComparison.OrdinalIgnoreCase)) - { - sb.Append('“'); - } - else if (token.Equals("close-quote", System.StringComparison.OrdinalIgnoreCase)) - { - sb.Append('”'); - } - else if (token.Equals("no-open-quote", System.StringComparison.OrdinalIgnoreCase) || - token.Equals("no-close-quote", System.StringComparison.OrdinalIgnoreCase)) - { - // produces no text, but still counts as a quoting depth change per spec - depth - // tracking isn't implemented here (no "quotes" property support), so this is a no-op. + if (keywordToken.Data.Equals(Keywords.OpenQuote, StringComparison.OrdinalIgnoreCase)) + { + sb.Append('“'); + } + else if (keywordToken.Data.Equals(Keywords.CloseQuote, StringComparison.OrdinalIgnoreCase)) + { + sb.Append('”'); + } + // no-open-quote/no-close-quote produce no text, but still count as a quoting depth + // change per spec - depth tracking isn't implemented here (no "quotes" property + // support), so they (like any other unrecognized keyword) are a no-op. } // counter()/counters()/string()/content() and anything else unrecognized: skip. } return sb.ToString(); } - - private static string Unescape(string s) - { - if (s.IndexOf('\\') < 0) return s; - - var sb = new StringBuilder(s.Length); - for (var i = 0; i < s.Length; i++) - { - if (s[i] == '\\' && i + 1 < s.Length) - { - i++; - sb.Append(s[i]); - } - else - { - sb.Append(s[i]); - } - } - return sb.ToString(); - } } } diff --git a/Source/HtmlRenderer/Core/Parse/CssValueParser.cs b/Source/HtmlRenderer/Core/Parse/CssValueParser.cs index 5efeb06c3..1a7205f5e 100644 --- a/Source/HtmlRenderer/Core/Parse/CssValueParser.cs +++ b/Source/HtmlRenderer/Core/Parse/CssValueParser.cs @@ -126,7 +126,7 @@ public static bool IsValidLength(string value) /// skipping whitespace/end-of-file tokens - the same "just enough tokenization to recognize a /// single top-level function call" approach used to detect calc()-family expressions. ///
- private static List GetCssTokens(string value) + internal static List GetCssTokens(string value) { var lexer = new Lexer(value); var tokens = new List();