Supported SVG Features

PeachPDF renders SVG — inline <svg> elements in HTML, standalone SVG (<img src="x.svg"> / data:image/svg+xml), and SVG used as a CSS background-image/list-style-image url() source — as real vector PDF content. Shapes become native PDF path-construction operators, gradients become native PDF shadings, and clips/masks/patterns become native PDF constructs. SVG stays vector except for one deliberate case: a <filter> that uses a primitive PDF has no operator for (a blur, lighting, morphology, and so on — see Filters) is evaluated over pixels and embedded as a bitmap at the document’s raster resolution. Everything else preserves scalability and keeps the PDF’s file size small regardless of the output resolution. This page documents exactly what is and is not supported. Where a feature is only partially supported, the specific gaps are noted.


SVG Root & Document Structure

Element MDN Reference Notes
svg svg Inline (<svg> inside HTML), standalone (<img src="x.svg">, data:image/svg+xml), and as a CSS background-image/list-style-image url() source are all supported — see HTML & CSS Support for the CSS-side details. viewBox, width, height, and preserveAspectRatio are all honored. A nested <svg> establishes its own viewport, coordinate system, and clip
g g Full support, including presentation-attribute inheritance
defs defs Content is never painted directly, only through a reference (<use>, fill="url(#id)", clip-path, mask, marker properties)
symbol symbol Never painted directly; establishes its own viewport (viewBox/preserveAspectRatio) only when referenced via <use>, sized by the <use> element’s width/height (defaulting to the current viewport’s size, per spec)
use use Resolves href/xlink:href references, including to symbol, nested svg, and ordinary shapes. x/y offset and (for symbol/nested-svg targets) width/height overrides are supported. The <use> element’s own resolved paint becomes the inherited default for an otherwise-unstyled target. A reference cycle is guarded against (max depth 8)
switch switch Renders only the first buildable child. PeachPDF has no requiredFeatures/requiredExtensions/systemLanguage conditional-support axis to evaluate, so this is equivalent to every other candidate always failing its (nonexistent) test — a deliberate v1 simplification
a a Groups children like <g>, and additionally becomes a real PDF link annotation (same-page #id fragments become /GoTo links, other href values become /URI links) covering the transformed content’s bounding box. A rotated/skewed anchor’s link region is approximated by its axis-aligned bounding box, since PDF link annotations are themselves always axis-aligned rectangles

Shapes

Element MDN Reference Notes
path path Full support for the d attribute grammar: move/line/cubic-bezier/quadratic-bezier/elliptical-arc, absolute and relative, including all shorthand/smooth variants
rect rect Full support, including independently-rounded corners via rx/ry (each defaults to the other when only one is specified, per spec)
circle circle Full support
ellipse ellipse Full support
line line Full support. Per spec, fill never applies to a <line> (it has no interior) — no fill operator is emitted regardless of the element’s fill paint
polyline polyline Stroke never closes back to the first point, matching spec, while the fill is painted over the shape closed back to the first point (the spec’s implicit closing segment, for fill purposes only)
polygon polygon Full support

Painting: Fill, Stroke, Color

Feature MDN Reference Notes
fill fill Solid colors (named, hex, rgb()), gradient references (url(#id)), pattern references (url(#id)), none, currentColor, and the SVG 2 context-fill / context-stroke keywords (see Context paint)
visibility visibility visible, hidden and collapse (treated as hidden), inherited. A hidden shape, text or image is not painted but still counts for bounding boxes, clip paths and text layout, and a descendant can set visible again
fill-rule fill-rule nonzero and evenodd, inherited
fill-opacity fill-opacity Full support, inherited, independent of opacity and stroke-opacity
stroke stroke Solid colors and gradient references paint a real stroke (including a real gradient-filled stroke, not an approximation). Pattern-filled strokes are not supported — see Unsupported SVG Features
stroke-width stroke-width Full support, inherited; a bare number (stroke-width: 6, in user units) or a length from a stylesheet as well as an attribute, a negative width is refused
stroke-opacity stroke-opacity Full support, inherited
stroke-linecap stroke-linecap butt, round, square, inherited
stroke-linejoin stroke-linejoin miter, round, bevel, inherited
stroke-miterlimit stroke-miterlimit Full support, inherited
stroke-dasharray stroke-dasharray Full support, inherited. A comma-separated list (2px, 4px) is accepted in a stylesheet or keyframe as well as in the attribute; a bare number is taken as user units
stroke-dashoffset stroke-dashoffset Full support, inherited; a bare number (stroke-dashoffset: 7) or a length from a stylesheet as well as an attribute
opacity opacity Not inherited as a property — it composites instead, per spec. On a leaf shape (no children), applied as a simple alpha multiply on the shape’s own fill/stroke/text color. On a container (<g>, <a>, nested <svg>, or a <use> referencing one (directly, or through further <use> aliases) — including a container whose only content is <text> or <image>) it’s rendered as a genuine, isolated PDF transparency group — the container’s children are painted into an offscreen Form XObject and flattened, then that single flattened result is composited once at the container’s own opacity, so overlapping children never double-blend where they overlap.
currentColor currentColor Resolves to the CSS color property of the inline <svg>’s HTML ancestor; for standalone SVG (no CSS context), resolves to black
color-interpolation-filters color-interpolation-filters Honoured on a <filter> and its primitives when the filter is evaluated over pixels (see Filters); a vector-only filter always computes in sRGB.
color-interpolation color-interpolation Honoured on a <linearGradient>/<radialGradient> (own value, else the nearest ancestor’s; auto is sRGB): linearRGB blends the stops in linear light, by sampling extra stops between each pair, so a black-to-white gradient is visibly lighter at its midpoint than the sRGB default. Honoured on a <mask> too: linearRGB takes the mask’s luminance from its content’s colors converted to linear light, so mid-gray content is more transparent than under the sRGB default. The conversion applies to solid fills/strokes and gradient stops in the mask content; a pattern or raster <image> inside a mask keeps its sRGB values
icc-color() / <color-profile> color A <color> icc-color(name, c1, ...) in fill, stroke, stop-color, flood-color or lighting-color is converted to sRGB through the <color-profile name="..." href="..."> of that name (a data: URI or a fetched resource; rendering-intent honoured). Gray, RGB and CMYK profiles work, with components in 0..1. When the profile is missing, cannot be parsed, or the component count does not fit, the sRGB color written before the icc-color() is used, as the specification prescribes. The CSS @color-profile rule and the color-profile property are not supported

Context paint

context-fill and context-stroke (SVG 2) are values of fill and stroke that stand for the fill and stroke of the context element:

Gradients and patterns pass through <use> and through a marker, each keeping the context element’s own coordinate space and bounding box: a gradient or pattern on the shape a marker is drawn on is mapped through the marker’s placement (its position, rotation and scale) so it lines up with the shape as if the marker were not there, and a gradient or pattern through <use> is measured against the box of what the <use> instantiates, mapped through any transform between that element and the shape actually painting it. This holds whether the <use> targets a shape, a <g>, a <symbol>, or a nested <svg> — for the latter two, the box is that target’s own established viewport content, mapped the same way viewBox/preserveAspectRatio map it when actually painted.

Gradients

Element MDN Reference Notes
linearGradient linearGradient x1/y1/x2/y2, gradientUnits (objectBoundingBox — the spec default — and userSpaceOnUse, both fully resolved against the referencing shape’s bounding box or the user-space coordinate system respectively), gradientTransform (translate/scale; a rotated/skewed gradientTransform on a radial gradient can’t turn the circle into a rotated ellipse — see below), spreadMethod
radialGradient radialGradient cx/cy/r, plus focal point fx/fy (independent of the center, per spec), same gradientUnits/gradientTransform/spreadMethod support as linearGradient. A rotated or skewed gradientTransform renders as a true rotated ellipse
stop stop offset, stop-color, stop-opacity (as presentation attributes, via style=, and from a <style> rule or the document’s stylesheet, which beats the attribute; currentcolor is the colour of the host element)
spreadMethod spreadMethod pad, reflect, repeat. reflect/repeat are implemented by tiling the gradient’s own stop list across an axis (or radius) extended to cover the filled shape’s bounding box — mirroring alternate cycles for reflect so adjacent cycles share a color with no seam — capped at 500 tiled cycles as a safety guard against pathological short-axis/large-shape combinations

Gradients render as native PDF shadings (/ShadingType), not rasterized bitmaps.

Clipping, Masking, Patterns

Element MDN Reference Notes
clipPath clipPath Full support; clip-rule (nonzero/evenodd) controls the clip region’s fill rule. clipPathUnits (userSpaceOnUse — the default — and objectBoundingBox, where the clip’s 0..1 geometry auto-scales to the referencing element’s bounding box) are both supported. Renders as a real PDF clipping path (W n), not a mask. A clip shape’s own transform is applied to its clip geometry — whether set directly on the shape, on a wrapping <g>, or on a <use> (including its x/y), so one shape defined in <defs> can be reused as a clip at several positions (and composes correctly with objectBoundingBox units)
mask mask Renders as a real PDF luminosity soft mask (/SMask via /ExtGState) — mask content is painted (not just used for geometry, unlike clipPath) into an offscreen form, and its luminance becomes the mask’s alpha. maskUnits/maskContentUnits (objectBoundingBox/userSpaceOnUse) are both supported, including the spec’s -10%/-10%/120%/120% default region
pattern pattern patternUnits/patternContentUnits (objectBoundingBox/userSpaceOnUse), patternTransform, viewBox/preserveAspectRatio. Implemented by rendering the pattern’s content once into a PDF Form XObject “tile,” then drawing that same tile repeatedly (clipped to the filled shape’s geometry) across the fill region — every repeat is a reference to the same underlying vector content, never a rasterized bitmap, but this is not a native PDF /PatternType 1 tiling pattern object. Capped at 10,000 tile placements per fill as a safety guard against pathological pattern/shape size ratios

Inheritance: content inside <pattern>, <mask>, <marker> and <clipPath> inherits presentation properties (fill, stroke, stroke-width, fill-opacity, font properties, …) from the definition element’s own ancestors — and from the definition element itself — not from the element that references it. <svg fill="#fff"> around a <mask> whose unstyled content should paint white therefore works as it does in a browser. (A pattern never inherits a paint that references itself, and a marker never inherits a marker-* reference to itself, so neither can draw itself without end.)

Filters

Element MDN Reference Notes
filter filter A real primitive graph. When every primitive can be expressed in PDF (feFlood, feOffset, feMerge, feTile, the Porter-Duff feComposite operators, feBlend, a channel-independent feColorMatrix, and a linear feComponentTransfer), it is evaluated over PDF tiles and the result stays vector: ordinary fills, placements, blend modes, soft masks, and PDF ExtGState /TR transfer functions. If any primitive needs pixels, the whole filter is evaluated over pixels instead: the element’s content is painted into a bitmap covering the filter region, every primitive runs on it, and the result is placed on the page at PdfGenerateConfig.RasterizationDpi. Supports named result/in/in2 references to any earlier primitive (not only the immediately-preceding one), plus the reserved input names SourceGraphic, SourceAlpha, FillPaint, StrokePaint, BackgroundImage and BackgroundAlpha. FillPaint and StrokePaint fill the whole filter region with the referencing element’s fill or stroke paint (a colour, gradient or pattern; none is transparent; fill-opacity/stroke-opacity are not applied). BackgroundImage is what was painted behind the element inside its isolation group: for an inline <svg>, the page behind it over white paper (its ancestors’ backgrounds, the SVG’s own background included) followed by the SVG’s own content painted before the element; BackgroundAlpha is that image’s opacity. An SVG used as an <img>, a CSS image or a standalone document is a separate document, so its backdrop is only its own earlier content over transparency, and a group inside the SVG with opacity, mask or filter starts its backdrop empty. feImage places a stand-alone image (raster, or SVG through a data: or fetched URL) into its primitive subregion using its own preserveAspectRatio, or, for an href="#id" fragment, renders that element in the filtered element’s user space, moved by the subregion’s x/y. A fragment that names nothing leaves the primitive transparent. Any filter using one of these inputs or feImage is evaluated over pixels. Browsers generally treat BackgroundImage/BackgroundAlpha as transparent, so a document that relies on them looks different there. Over white paper an inline SVG’s BackgroundAlpha is fully opaque across the filter region. The filter region (x/y/width/height, filterUnits) defaults to the spec’s -10%/-10%/120%/120% of the referencing element’s bounding box, in both objectBoundingBox (the default) and userSpaceOnUse units; an element with no measurable bounding box (text, for instance) uses its viewport instead. primitiveUnits applies to the lengths of the pixel primitives. A <filter> containing a primitive or type this renderer does not support (an unknown fe* element, or an feComposite operator that does not parse) is rejected as a whole at parse time: the referencing element paints completely unfiltered, the same as a filter attribute naming a nonexistent id — never a partially-applied graph.
feFlood feFlood Full support — fills the filter region with flood-color/flood-opacity
feOffset feOffset Full support — dx/dy, resolved per primitiveUnits
feMerge / feMergeNode feMerge Full support — layers each child feMergeNode’s in via ordinary compositing, in document order
feTile feTile Repeats its input’s own subregion across the whole filter region
feComposite feComposite over, in, out, atop, xor — all via alpha-masked compositing in vector form (SourceAlpha and every alpha-based in/in2 reference a real /Alpha-subtype PDF soft mask, not a luminosity approximation). arithmetic (k1–k4) needs pixels: it makes the filter a bitmap one
feBlend feBlend All standard blend modes, via the same real PDF blend mode (ExtGState /BM) CSS mix-blend-mode uses — see HTML & CSS Support
feColorMatrix feColorMatrix type="matrix" with a channel-independent (diagonal) matrix is applied via a PDF ExtGState /TR transfer function and stays vector; type="luminanceToAlpha" uses the same /Luminosity soft mask mask uses. type="saturate", type="hueRotate" and any type="matrix" with a nonzero off-diagonal term mix colour channels, which PDF cannot express, so they make the filter a bitmap one (the full 4×5 matrix is applied per pixel)
feComponentTransfer feComponentTransfer type="linear" (slope/intercept) on feFuncR/feFuncG/feFuncB is composed into a PDF /TR transfer function and stays vector; a missing channel function defaults to identity, per spec. type="gamma", "table", "discrete" and any non-identity feFuncA make the filter a bitmap one
feGaussianBlur feGaussianBlur stdDeviation (one or two numbers, in user units or fractions of the bounding box per primitiveUnits). A zero or negative deviation disables its axis; both disabled passes the input through. Bitmap evaluation
feDropShadow feDropShadow dx, dy, stdDeviation, flood-color, flood-opacity. Bitmap evaluation
feMorphology feMorphology operator erode/dilate, radius per axis. The area outside the filter region counts as transparent. Bitmap evaluation
feConvolveMatrix feConvolveMatrix order, kernelMatrix, divisor, bias, targetX/targetY, edgeMode (duplicate, wrap, none), preserveAlpha; kernelUnitLength is ignored (the kernel runs at the bitmap’s own pixel pitch). A malformed kernel rejects the filter. Bitmap evaluation
feTurbulence feTurbulence type turbulence/fractalNoise, baseFrequency, numOctaves, seed, stitchTiles; the specification’s reference algorithm, so a given seed draws the same noise as other conforming renderers. Bitmap evaluation
feDisplacementMap feDisplacementMap scale, xChannelSelector, yChannelSelector; nearest-pixel sampling, transparent where a pixel is displaced from outside the region. Bitmap evaluation
feDiffuseLighting / feSpecularLighting feDiffuseLighting surfaceScale, diffuseConstant/specularConstant, specularExponent, lighting-color, and a feDistantLight, fePointLight or feSpotLight child (with limitingConeAngle). The surface normal uses the specification’s Sobel kernels, with its edge and corner variants. Bitmap evaluation. A lighting primitive with no light source rejects the filter

Colour space and subregions (bitmap evaluation). Each primitive computes in the colour space color-interpolation-filters names — linearRGB (the initial value) or sRGB — converting its inputs as needed, and the last result is converted to sRGB for the page. Intermediate results are 8 bits per channel in whichever space, so the darkest few sRGB levels quantize in linear light (an sRGB value below about 8 can round to 0 in a linearRGB pass). A vector-only filter cannot do this (PDF has no working space to interpolate in) and computes in sRGB throughout. Per-primitive x/y/width/height subregions are honoured and make the filter a bitmap one; a primitive without one covers the whole filter region (the specification’s default is the union of its inputs’ subregions, which this simplifies).

Every primitive above builds a fresh PDF tile from its input(s) rather than mutating shared state, so a named result can be referenced by more than one later primitive, and non-adjacent references (a primitive whose in names a result from several steps earlier, not just the previous one) work exactly like an adjacent reference.

Markers

Element MDN Reference Notes
marker marker refX/refY, markerWidth/markerHeight, viewBox/preserveAspectRatio, markerUnits (strokeWidth/userSpaceOnUse), orient (auto, auto-start-reverse, and a fixed angle)
marker-start / marker-mid / marker-end marker-start Full support, plus the marker shorthand (sets all three; an individually-specified property overrides just that one). Inherited. Per spec, markers only attach to path/line/polyline/polygon — not basic shapes like rect/circle/ellipse, which have no defined vertex sequence
Tangent/orientation math — Exact for every segment kind: straight lines and cubic béziers use the segment’s own control-point vectors, and elliptical arcs use the true derivative of the ellipse at the vertex (a degenerate arc, which renders as a straight line, uses its chord)

See a in SVG Root & Document Structure above.

Raster & Nested Content

Element MDN Reference Notes
image image href/xlink:href resolves as a data: URI, a network URL, or a local file path — the same resource-loading pipeline HTML <img> uses (so it obeys <base href>, and a network URL requires a network loader to be configured, exactly like <img>). A raster payload (PNG, JPEG, etc.) embeds as a real PDF image XObject; an image/svg+xml payload (detected by a .svg extension or a Content-Type: image/svg+xml response header) renders as its own real vector scene graph (never rasterized), with its own url(#id) references resolving against its own definitions. A nested SVG payload’s own <image> elements resolve recursively — a network/file <image> inside a referenced SVG is fetched too, with its relative hrefs resolved against that nested SVG’s own location. x/y/width/height/preserveAspectRatio are all honored — including for raster images, which are fit into their box per preserveAspectRatio’s alignment/meet/slice rules, the same math used for viewports elsewhere in this renderer. An unresolvable href (no configured loader, missing resource, malformed payload) leaves the element blank rather than erroring. A relative href inside a standalone (<img src="x.svg"> / fetched) SVG resolves against the SVG’s own location
Nested svg see svg above Establishes its own viewport, coordinate system, and clip
foreignObject foreignObject Embeds HTML in SVG user space, in an inline <svg> in an HTML document: the element’s children are laid out by the ordinary HTML engine as an isolated block width user units wide (one user unit being one CSS pixel), and painted at (x, y) clipped to the width×height viewport. Document and inline styles apply to the HTML, selectors inside match as HTML (case-insensitively), and transform, opacity, clip-path, mask and filter on the foreignObject apply to it like any other element. The width/height must be positive lengths (percentages are not resolved; a missing one paints nothing). Limits: the content is never split across pages, a <a href> inside is not a link annotation, and it adds nothing to the tagged-PDF structure. In a standalone SVG (<img src="x.svg">, a data: URI, a CSS image) the element is not built, so a surrounding switch falls through to its next child

Network and file-path <image> references are fetched through PeachPDF’s general asynchronous resource-loading pipeline ahead of the (synchronous) SVG scene-graph build; a data: URI still resolves inline with no I/O. As with any external asset, a network URL only loads when a network loader is configured — otherwise the element simply renders blank.

Text

Element MDN Reference Notes
text text x/y/dx/dy/rotate as both single values and per-character lists (x="10 20 30", rotate="0 15 30", SVG 1.1 §10.4) — each glyph is positioned/rotated by its own list value, a new absolute x/y starts a text chunk, and text-anchor (start/middle/end) is applied per chunk. font-family/font-size/font-weight/font-style/font-stretch (as both presentation attributes and style=) inherit from any ancestor (<g>/<svg>/<use>), and relative font-size (em/%, and the measured ex/ch/cap/ic/lh, taken from the parent’s font) resolves against the parent’s used size (rem and the r* forms against the root’s); letter-spacing/word-spacing accept the same units against the element’s own font. Bridges directly into PeachPDF’s existing font/text-measurement pipeline — the same font resolution and OpenType shaping ordinary HTML text uses, including letter-spacing, word-spacing, text-transform (uppercase/lowercase/capitalize/full-width), font-variant-ligatures/-caps/-numeric/-east-asian, font-feature-settings, font-kerning, and text-decoration-line/-style/-color (underline/overline/line-through, own value only — not inherited, per CSS Text Decoration 3 — but a descendant with no decoration of its own still shows an ancestor’s line flowing across it, matching real browser behavior; see the known-simplifications list for this feature’s own v1 scope). fill and stroke: a solid fill is painted as a selectable text run; a gradient/pattern fill (fill="url(#…)") or any stroke instead outlines each glyph to a vector path and fills/strokes it through the same paint machinery shapes use (see the known-simplifications list below for the outlined-text trade-offs)
tspan tspan A tspan without its own x/y flows immediately after the preceding text in the <text> subtree; a tspan with its own x and/or y starts a new absolute position (text chunk). Loose text and child tspans are laid out in document order (text authored after a child element keeps its place), and its own per-character x/y/dx/dy/rotate lists apply to its characters. Font properties and paint (fill/stroke/etc.) are inherited from the enclosing text/tspan when not overridden
tref tref (deprecated in SVG2, still supported here) href/xlink:href resolves to another element’s own text content, reused as this run’s text
textPath textPath href/xlink:href resolves to a <path> or a basic shape (rect/circle/ellipse/line/polyline/polygon), whose geometry the run’s glyphs — including any nested <tspan>s — are laid along: each glyph is placed at its own midpoint distance along the path and rotated to the path tangent, plus any per-character rotate/dx/dy. method (align, the default, turns each glyph rigidly to the tangent; stretch bends each glyph’s outline along the curve point by point) and spacing (auto is laid out as exact, which the specification allows). startOffset (a length or a % of the total path length), text-anchor, and side (left/right) shift/flip where the run runs along the path; a glyph whose midpoint falls before the start or past the end of the path is not rendered. Uses the same glyph-outline paint as gradient/stroked text, so a <textPath> also supports gradient/pattern fill and stroke. The outlines are painted in the text’s user space: a userSpaceOnUse gradient/pattern stays fixed there, and an objectBoundingBox one measures against the whole text (the union of its glyph cells), not each glyph. Always flows horizontally along the path regardless of the <text> root’s own writing-mode — there is no vertical variant of path-following text
writing-mode / text-orientation writing-mode / text-orientation writing-mode (resolved once from the <text> root — see below) advances the pen down the column instead of along a line for vertical-rl/vertical-lr; sideways-rl/sideways-lr and SVG 1.1’s legacy tb/tb-rl/etc. keywords fall back to horizontal-tb. text-orientation (mixed, the default; upright; sideways) is genuinely per-glyph and can be overridden on a nested <tspan>: mixed classifies each glyph by Unicode’s Vertical_Orientation property (the same table the HTML pipeline shares), painting CJK/kana upright and Latin/digits rotated 90°; upright/sideways force one answer for every glyph on that run. An explicit per-character rotate="" always overrides the orientation-driven default rotation for that glyph. An upright glyph’s down-the-column advance consults the font’s own real OpenType vertical metrics (vhea/vmtx) when it carries them (mainly professional CJK vertical fonts), clipped to its own reserved cell so a glyph never bleeds into its neighbor’s; a font without real vertical metrics — the common case — falls back to approximating the advance as the font’s own line height. An upright glyph’s anchor position additionally consults the font’s real VORG vertical-origin table when it has one — CFF/CFF2 fonts only, per the OpenType spec’s own restriction; a TrueType-outline font’s VORG table (rare in practice) is never consulted, and such a font falls back to the plain top-of-cell anchor exactly as a font with no VORG at all does. A rotated glyph’s down-the-column footprint is always its own natural pre-rotation width, matching real browser behavior for sideways-oriented text
direction / unicode-bidi direction / unicode-bidi A full Unicode Bidirectional Algorithm (UAX #9) implementation, matching how CSS text integrates bidi (CSS Writing Modes 3 §5.2) and shared with HTML’s own bidi resolver: direction (ltr/rtl, inherited, resolved from any ancestor) sets the <text> root’s paragraph base direction; unicode-bidi (normal/embed/isolate/bidi-override/isolate-override/plaintext, not inherited — resolved fresh per element, per spec) on a <tspan> contributes an explicit embedding/isolate/override push over exactly the characters that <tspan> (including its own descendants) contributes. Per-character bidi class (which characters actually reorder) is always auto-detected from each character’s real Unicode Bidi_Class, independent of direction/unicode-bidi — a strong-right-to-left run (e.g. Arabic or Hebrew text) reorders and a mirrorable character pair (parentheses, brackets) swaps identity even with no direction/unicode-bidi set anywhere, matching real browser behavior

Known text simplifications:

Coordinate Systems & Units

Feature Notes
viewBox Full support, including all 9 preserveAspectRatio alignment keywords (xMinYMin, xMidYMid, xMaxYMax, etc.) and both meet and slice, plus none (independent-axis stretch)
Length units Unitless numbers, % (resolved against the viewport width/height/diagonal per the axis, per spec), px, pt, pc, in, cm, mm, and the font-relative units em/ex/ch/cap/ic/lh and rem/rex/rch/rcap/ric/rlh, in the length attributes of shapes, text, <use>, nested <svg> and <image>, in stroke-width/stroke-dasharray/stroke-dashoffset, and inside calc(). They resolve against the element’s own font-family/font-size — and the root <svg>’s for the r* forms — measured from the real font. An SVG that never declares a font-size anywhere keeps CSS’s 16px initial size for em/rem. ic measures the ideograph in the element’s primary font only (1em when that font lacks it); lh is the font’s normal line height (not the line-height that auto-wrapped text uses). Content inside <pattern>, <marker>, <mask> and <clipPath>, and the coordinates of a gradient, resolve them against the definition element’s own font — inherited from its ancestors like any other element’s
transform matrix(), translate(), scale(), rotate() (including the 3-argument rotate(angle, cx, cy) form), skewX(), skewY(). Multiple functions may be chained; they compose per spec. The CSS syntax of the property is read too (from a stylesheet, a keyframe or style=, and, being one parser, from the attribute as well, which a browser’s attribute grammar does not take): angles with a unit (deg, grad, rad, turn), lengths in px, pt, pc, in, cm, mm and Q, a percentage in scale() (the same number), translateX(), translateY(), scaleX(), scaleY(), skew() and rotateZ(). em, rem and percentage lengths resolve in translate(), translateX() and translateY() (a percentage is of the transform-box, which for an SVG element is the view box by default, or the element’s fill or stroke bounding box with fill-box or stroke-box), and so does a calc() of them in a stylesheet, a keyframe or style= (not in the transform attribute, whose grammar is numbers only). The 3D functions (translate3d(), translateZ(), scale3d(), scaleZ(), rotate3d(), rotateX(), rotateY(), matrix3d() and perspective()) are read too; see 3D transforms. A function with an argument that cannot be resolved here (a unit it does not know, a calc() in the attribute) does nothing, and the functions after it still apply. transform-origin (also as an attribute) turns the transform about that point, measured in transform-box (view-box, the default; fill-box/content-box; stroke-box/border-box); without a transform-origin an SVG element turns about the user-space origin whatever the box. In style= a declaration that is not valid CSS (rotate(10) or translate(10, 20) with no unit) is dropped, as in a browser, and the rule or attribute answers; gradientTransform and patternTransform take bare numbers only

3D transforms

The 3D transform functions apply to every SVG element that has a transform: shapes, text, images, <g>, <a>, <use>, nested <svg> and <foreignObject>. They are the same machinery as CSS 3D transforms on HTML boxes, pointed at an SVG element: the element, with its clip, filter, mask and opacity, is painted as it would be without its transform and the result is carried through the 4×4 matrix.

Feature Notes
3D functions in transform Composed as one 4×4 matrix about transform-origin, whose optional third value (a length) is the origin’s z offset, measured in transform-box like the other two. Without perspective the matrix projects onto the element’s plane as a plain 2D matrix, so rotateX(), rotateY(), translateZ() and scale3d() stay vector artwork
perspective none (default) or a length in user units (a distance under one unit is one unit; zero is none). A container’s property: it is how far the viewer is from the plane for the container’s direct children, which the container itself is not moved by. Applies to <g>, <a>, <use>, nested <svg> and the root <svg>. Not inherited. The perspective() function in a transform is the same view for the one element it is in
perspective-origin The vanishing point, a <position> measured in transform-box (the view box by default); 50% 50% initially
backface-visibility visible (default) or hidden: a hidden element is not painted while its back faces the viewer (rotateY(180deg) shows the back, scaleX(-1) does not)
transform-style flat (default) or preserve-3d: on a container, its children share its 3D space, depth-sorted and, where they intersect, resolved per pixel. opacity below 1, filter, clip-path and mask on the container force flat, as does a nested <svg>, which clips
Where 3D is ignored A transform list that holds a 3D function is ignored as a whole in gradientTransform and patternTransform, and on a <clipPath> the 3D list of a child is the projection of its matrix onto the plane (a clip is a flat shape). <clipPath>, <mask> and <pattern> content is always flat
How it is written A projective result (a real perspective, or a perspective() function) is drawn into a bitmap at the rasterization resolution and warped; one that stays within a quarter of a device pixel of an affine matrix is written as vector artwork instead. A preserve-3d context whose planes are only tilted (no perspective) and do not pierce one another is also vector, drawn back to front; otherwise its planes are depth-tested in one bitmap. A plane turned edge-on, wholly behind the viewer, or turned away with backface-visibility: hidden paints nothing, and neither does an element whose matrix cannot be inverted
Limits Text drawn into a warped bitmap is not selectable; a link inside a warped element covers the bounding box of its orthographic projection (without the perspective); a nested <svg> or <use> whose children are in a 3D context is not itself a context root; vector-effect is not supported at all

CSS Integration

Feature Notes  
Presentation attributes Every property above is readable as a plain XML attribute (e.g. fill="red")  
Geometry properties r, cx, cy, rx, ry, x, y, width, height and d are CSS properties as well as attributes (SVG 2): a <style> rule, the document’s stylesheet, style= or a @keyframes block can set them on circle, ellipse, rect, image, foreignObject, nested svg and use (and d: path("...") / d: none on path), inline style= beating a rule beating the attribute. Percentages resolve against the viewport. x/y/width/height are not read from CSS on mask, pattern, filter or text  
SVG-only properties in a stylesheet flood-color, flood-opacity, lighting-color, paint-order, mask, marker, marker-start/-mid/-end, clip-rule, color-interpolation-filters, transform-box, alignment-baseline, baseline-shift, dominant-baseline, inline-size, shape-inside and shape-subtract can be written in a <style> rule, the document’s stylesheet or a @keyframes block, as well as in style= or as an attribute, and @supports recognises them (transform-box is read but not yet applied to transform). Colour, opacity, marker-* and clip-rule values are checked against their grammar; for the rest the value is checked when the element is built  
style="..." Inline style= is supported on every element, taking precedence over presentation attributes and <style> rules, per CSS cascade rules  
<style> element Fully supported for both inline <svg> and standalone SVG, matched through PeachPDF’s full CSS selector engine — type (rect), class (.foo), ID (#foo), compound (rect.foo), comma-separated lists, descendant/child/sibling combinators (g rect, .wrap > circle), attribute selectors ([gradientUnits="userSpaceOnUse"], [data-x^="a"], …), and structural pseudo-classes (:first-child, :nth-of-type(), :only-of-type, :not(), :empty, …). :empty matches an element with no children other than white-space-only text; comments and processing instructions do not count as children, while any child element, or any text or CDATA section that is not pure white space, does. Standard specificity/cascade order applies, including !important: an !important declaration overrides normal declarations regardless of specificity (CSS Cascade §Importance). var() custom properties and calc()/min()/max()/clamp() length expressions are resolved too. Precedence is inline style= > <style> rule (by specificity) > presentation attribute. A guaranteed-invalid var() (missing custom property with no fallback, or a cyclic reference) is invalid at computed-value time (CSS Custom Properties): the declaration still wins the cascade and the property computes to its inherited (or initial) value — it does not fall back to a lower-priority declaration such as the presentation attribute. The CSS-wide keywords are supported on the SVG paint/geometry properties in a <style> rule: initial computes to the property’s SVG initial value (fill→black, stroke→none, stroke-width→1, …), inherit takes the parent’s value, and unset/revert/revert-layer roll the author cascade back to a lower origin — since an SVG presentation attribute is itself author-origin (specificity 0), the property computes to its inherited/initial value rather than the presentation attribute (with no @layer support, revert-layer behaves as revert; every SVG paint property is inherited, so unset behaves as inherit)  
@property registrations Honored in SVG styling, standalone and inline. A custom property registered with an @property rule contributes its initial-value (the value a var() reference resolves to when the property is otherwise unset), its inherits flag (a descendant that doesn’t set an inherits: false property resolves it to the initial-value rather than the ancestor’s value), and syntax validation (a set value that doesn’t match falls back to the initial-value) — the same behavior as for HTML. A standalone SVG uses @property rules from its own <style>; an inline <svg> uses the host document’s registry  
Document CSS cascades into inline SVG For an inline <svg>, rules in the HTML document’s own <style>/<link> stylesheets that match SVG elements (e.g. circle { fill: red } in <head>) apply to the SVG shapes — inline SVG participates in the document cascade (SVG 2 §6 Styling). A standalone SVG (<img src="x.svg"> / data:image/svg+xml) is an independent document and is styled only by its own <style> — host-document CSS does not reach it  
Selector case-sensitivity SVG is XML, so selectors match SVG element types, attribute names/values, classes, and IDs case-sensitively (Selectors 4 §6) — e.g. RECT {} does not match <rect>. This holds even for an attribute name that would otherwise be on the HTML Standard’s fixed case-insensitive legacy-attribute list (type, dir, align, …) — that exception is scoped to elements in the HTML namespace only, so it never applies inside <svg>. (HTML matching stays ASCII case-insensitive by default for that same fixed list, and case-sensitive otherwise.)  
class / id Read for <style> selector matching, same as HTML (case-sensitively for SVG)  
display display From a presentation attribute, style=, a <style> rule or the document cascade. none removes the element and everything under it from rendering, wherever it is reached from: a <use> instance, a pattern or marker’s content, a <tspan> (whose text drops out of the run). A <switch> whose first candidate is display: none still picks it, so it renders nothing, as in a browser. contents strips g, a, switch, use, tspan and textPath from the formatting tree and keeps their content (CSS Display 3, SVG elements): the element’s own opacity, transform, clip-path, mask and filter no longer apply, a use loses its x/y offset, a tspan or textPath loses its position and rotation lists and a textPath its path, while inherited properties such as fill still reach the content. On every other SVG element, contents computes to none.

CSS animations

With PdfGenerateConfig.AnimationProgress set (see Animations), the elements of an inline <svg> take the value their CSS animation has at the sampled point, as an HTML box does: rect { animation: pulse 2s linear } with a @keyframes pulse { from { fill: red } to { fill: blue } } in the document’s stylesheet.

Unsupported SVG Features

The following SVG 1.0/1.1 features are not supported, each for the reason given: