Repository navigation
Conversation
Marks this fork as an altered version (zlib clause 2), adds CLAUDE.md for cloud sessions and Backdrop/ROADMAP.md: 14 items (SD1-SD14) in three waves, from Backdrop's measured gap catalogue against 0.29.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014secrjJFVQjHWzLji64mzq
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014secrjJFVQjHWzLji64mzq
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BMaJqhjWrmunNiNcMMuJQD
…the document hsl()/hsla(), case-insensitive keywords, rebeccapurple, 4/8-digit hex with alpha, !important, unit-bearing transforms, opacity clamping, SVG 2 plain href on image, gradients and anchors; invalid presentation attributes are ignored. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BX57MfEEMwiafw7LcjGooN
Honour x/y, patternUnits (default objectBoundingBox), patternContentUnits, viewBox (xMidYMid meet) and patternTransform, and inherit attributes and content along the href / xlink:href chain with cycle protection. A pattern without width/height (or an id) no longer fails the document: it paints nothing, per SVG 1.1 §13.3. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013oF1EUUqMrqHSnUp3s5qPW
…e platforms) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BX57MfEEMwiafw7LcjGooN
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VTWP7xSKgyh1SgNiikyvi
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VTWP7xSKgyh1SgNiikyvi
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VTWP7xSKgyh1SgNiikyvi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de budget; assert on layer tree Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014secrjJFVQjHWzLji64mzq
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BMaJqhjWrmunNiNcMMuJQD
- Unknown patternUnits / patternContentUnits values drop the attribute instead of failing the document. - Percentages on x/y/width/height resolve against the viewport (viewBox size, else width/height) under userSpaceOnUse; they stay bbox fractions under objectBoundingBox, including when inherited through href. - A non-finite or non-invertible patternTransform, or a non-finite tile, paints nothing. - The filled shape's bounding box is computed only when objectBoundingBox units need it. - Tests for each, plus a CoreGraphics pixel test for the Y phase. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013oF1EUUqMrqHSnUp3s5qPW
- style tokenizer skips empty/bare declarations instead of failing the document - gradientTransform none/junk is ignored - accept '! important' with whitespace - invalid href falls back to xlink:href - negative stroke-width dropped - more tests (hsl edge cases, #rrggbbaa, LayerTree alpha), fix test licence header Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BX57MfEEMwiafw7LcjGooN
- CGText isolates filter layers with saveGState/restoreGState and warns when a blur is dropped; the optimizer no longer elides state after a filter layer - clamp and check every conversion from stdDeviation and regions; huge values hit the cap - empty clip draws and allocates nothing; vImage errors skip the copy; calloc the temp buffer - over the pixel cap, render the layer at a reduced scale instead of unfiltered - skip no-op passes, pass-through needs no offscreen, clip to the region once - deviations follow rotation; vector contexts are rasterized at 2x - text-only objectBoundingBox blurs draw unfiltered without the unsupported warning - remove the unused inherited filter state; tests for the above Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VTWP7xSKgyh1SgNiikyvi
x/y, patternUnits, patternContentUnits, patternTransform, viewBox, href inheritance of attributes and content; degenerate patterns paint nothing. Reviewed; CI green on 11f2810; Backdrop corpus: 0 regressions, 0 shipping bank changes, 2 patterned eggs fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ef tests Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014secrjJFVQjHWzLji64mzq
<use> cycles (including a use of its own ancestor, dropped at the first use per SVG 1.1 §5.6), mask and pattern href cycles are refused; depth cap 16 and a 20,000-expansion budget bound the work. Reviewed; CI green on 136287e; Backdrop corpus: 0 drawings changed, draw time unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bad attribute value drops the attribute, never the document: hsl/hsla, case-insensitive and CSS named colours, inherit, none for clip-path/mask/ filter, units in transforms, empty values and declarations, !important, opacity clamp, unknown enums, SVG 2 href; 4/8-digit hex fixed. Reviewed; CI green on 086d008; Backdrop corpus 0 regressions, 1 newly parsed file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict with SD4 in parsePresentationAttributes resolved by the integrator: SD1's dash parsing, wrapped in SD4's lenient(). Reviewed; CI green on 1002717; Backdrop corpus 0 regressions, 3 drawings now dashed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # DOM/Sources/Parser.XML.Element.swift
The review fix put the reset in the radial-gradient fill branch, which never sets a dash, and left the radial-gradient stroke without one: a stroke-only shape is the lone push/pop pair the optimizer strips, so the dash leaked into the caller's context in CGText output. Test covers linear and radial strokes (fails without the fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- groups containing text have unknown bounds: objectBoundingBox filters are dropped, never the text - clip the offscreen input to the filter region (rotation/skew); the edge is anti-aliased once by the parent - clamp box sizes per axis to the bitmap dimension - skip the offscreen when every box size is 1 - PDF smoke test, blur-then-clip pixel test, text and kernel tests Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VTWP7xSKgyh1SgNiikyvi
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UtABiZpyZ4snBX6gF7Etfv
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UtABiZpyZ4snBX6gF7Etfv
- A <clipPath> holding only a <use> or <text> child clipped everything away (both are dropped by makeClipShape, so the shape list was empty); the empty-rect clip now applies only when every child is hidden or there are none, and such an element draws unclipped as on main. - testHiddenTextAndImageDrawNothing counted commands, and the LayerTreeProvider draws no path for visible text either; it now checks the layer tree against a visible control. - testForeignObjectWithSVGNamespaceChild asserted >= 1 with a sibling that alone gives 1; it now pins exactly the sibling. - New: display does not apply to gradients, <pattern> or <filter>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selectors (descendant, child, sibling, compound, attribute, :first-child) with specificity and source order, an indexed and memoized matcher, tolerant sheets (one bad declaration drops only itself), and transform, mask and clip-rule from CSS. Head b8aa11d after its third round (SF Symbol layer classes kept in the export, counted matcher tests, CSS-wide keywords dropped, single-pass style= parsing); CI 20/20. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
display="none" removes the element and its subtree before any layer state is built; display is ignored on <mask>, <clipPath>, <pattern>, gradients and <filter>; visibility parsed and inherited (a child may show again); hidden <clipPath> children do not contribute, and one whose children are all hidden clips everything; editor-namespace subtrees are dropped. Head d521b91: the author's fix round (bac0eda) plus an integrator commit keeping a <clipPath> that holds only a <use> or <text> unclipped (bac0eda cleared 76 Noto glyphs that way) and replacing two tests that could not fail. CI 20/20. Integrator resolution with SD10: makeLayer computes the cascaded presentation attributes once, builds the state from them, returns before makeBaseLayer for display:none, and passes the attributes on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clipPathUnits, maskUnits and maskContentUnits with objectBoundingBox resolved in the builder, mask regions, <use> and <text> inside a <clipPath> (text, mixed clip-rules or a clip-path on the <clipPath> become an alpha mask), clip-path on a <clipPath>, clip-rule taken from the clip's children, a bad clipPathUnits dropping only the attribute. Head 0c31a83: the author's round 1 fixes (f6c5c96) plus an integrator commit bounding bbox measurement (document-wide step budget) and clip-path nesting (ReferenceGuard.maxDepth). CI 20/20. Integrator resolutions with SD10 and SD7: - makeBaseLayer resolves the clip from the cascaded attributes (makeClip(id:bounds:)) and passes them to createMaskLayer, which now takes the element too (for its bounding box). - clip members skip hidden visibility as well as display:none (SD7) and read their transform from the cascade (SD10). - the mask state keeps display inline (SD7). - <clipPath style=""> uses SD10's parseStyleDeclarations. - SD10's testClipRuleFromStyleSheet and the .holed sample rule move the clip-rule onto the clip child, as the roadmap asked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and SD14 set aside Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…views Pattern fills leave a stale fill colour in the command optimizer, and a paint with a fallback colour paints black. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…llbacks (url(#id) colour) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMyo4bwzHoq66MiywTcNGG
Lengths on rect, circle, ellipse, line, use, image, text and nested <svg> resolve %, px, pt, pc, mm, cm, in, em and ex (SVG 1.1 §7.10): percentages against the nearest viewport, em/ex against the element's font-size. Root and nested <svg> sizes are stored as Float instead of Int; a root with only width or height takes the other side from the viewBox ratio; a nested <svg> without a size is 100% of its viewport and its children are parsed once. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PCaoqhVNCJtviDcLpUVR7S
DOM tests for every length attribute, LayerTree command-stream tests, CoreGraphics pixel tests (macOS CI) and Samples.bundle/units-percent.svg. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PCaoqhVNCJtviDcLpUVR7S
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PCaoqhVNCJtviDcLpUVR7S
… spaces, href test) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMyo4bwzHoq66MiywTcNGG
…, CGText image warning, inherited pattern fit test) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMyo4bwzHoq66MiywTcNGG
…uote in font-family, matcher counter, empty !important partition) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMyo4bwzHoq66MiywTcNGG
…lipPath visibility/CSS/child clip-path, cascade in bbox, union check, maskIsClip equality, gradientUnits dropped) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMyo4bwzHoq66MiywTcNGG
…is not supported) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMyo4bwzHoq66MiywTcNGG
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feOffset, feFlood, feComposite (over, in, out, atop, xor, lighter, arithmetic), feMerge, feBlend (all Compositing 1 modes) and feColorMatrix (matrix, saturate, hueRotate, luminanceToAlpha), wired by in / in2 / result with SourceGraphic and SourceAlpha. - DOM: DOM.Filter.Primitive records each primitive's inputs, result, subregion (x/y/width/height in primitiveUnits) and the inherited color-interpolation-filters. Unreadable attributes fall back to the spec defaults; nothing new throws. - LayerTree: FilterLayer carries the primary primitive tree resolved to user space: inputs as indices (missing, forward and unknown references take the previous result; Background*/FillPaint/StrokePaint are transparent black), subregions per Filter Effects 1 §9.4 within the filter region, flood colours through the colour converter. - FilterBitmap: platform-independent kernels on premultiplied RGBA8, linearRGB maths decoded per pixel so storage stays sRGB encoded, and the tree evaluator with result liveness. - CoreGraphics: SD2's blur chains keep their in-place path; other filters evaluate the tree on the offscreen. The visible area grows by offsets as well as blurs; the pixel budget is shared by the live bitmaps. - CGText still degrades with a comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtxFp9AizfH7h4sa3PLmEb
- Point-wise kernels compute each run of equal input pixels once (transparent areas, solid fills): about 10x faster per megapixel. - feOffset copies row segments for whole-pixel offsets and uses 8-bit fixed-point weights, skipping uniform neighbourhoods, otherwise. - pow, cos and sin in Double: their Float overloads are missing on Android and Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtxFp9AizfH7h4sa3PLmEb
- FilterBitmap.flood stored its pixel through a pointer into a local array; optimized builds (Swift 6.4, -O) kept only the first byte, so every feFlood was transparent in Release (17 SD12 tests fail under `swift test -c release`; Debug CI never saw it). Quantize directly. - A primary tree over 64 primitives draws unfiltered: each primitive is a full pass over the region, so a hostile chain stalled every frame. - testSamplesResolveEveryFilter skips when Samples.bundle is not on disk (device and emulator runs). - CI: a macOS job builds and tests in release with the newest Xcode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feOffset, feFlood, feComposite, feMerge, feBlend and feColorMatrix render through CoreGraphics, wired by in/in2/result, primary tree only. Integrator commit be9bf84: floods were transparent under -O (release CI job added), a primary tree over 64 primitives draws unfiltered, the samples test skips off the source tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
|
Thanks @sakamura (and claude) I'll go through this, particularly interested in the |
Author
|
Awesome. I'm glad you appreciate it. I needed these to properly support some SVG. These were my priorities. You can find the different branches on my fork for each individual feature. If you need something else, please ask me, I will try to provide it. Cheers! |
pablogb
added a commit
to pablogb/SwiftDraw
that referenced
this pull request
Oct 21, 2026
swhitty#142: dashes and dash offsets, Gaussian blur and the other filter primitives, reference cycles, tolerant parsing, patterns, gradients, display and visibility, clip and mask units, preserveAspectRatio, the CSS cascade, units and percentages, invalid elements skipped. Its sources replace this branch's own <image href>, #RGBA/#RRGGBBAA and stroke-dasharray changes, which it covers; their tests stay where they still apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pablogb
added a commit
to pablogb/SwiftDraw
that referenced
this pull request
Oct 21, 2026
Removes its Backdrop roadmap, CLAUDE.md and CI job, and says in the README that this is an altered version, as the zlib licence asks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pablogb
added a commit
to pablogb/SwiftDraw
that referenced
this pull request
Oct 21, 2026
From the review of swhitty#142's parsing (and two older faults): - Path data stops at its first error; `M0 0Z!` looped forever (also in 0.29.0). - A selector of more than 32 compounds is dropped; matching stops after 2 M evaluations per document, which also bounds its memo. - Defs and stylesheets are read once, by the root: per nested <svg> it was exponential. A <svg> nested more than 16 deep is skipped, and the SAX parser skips elements deeper than 256 levels, so every recursive walker fits a 512 KB thread. - The first 10 parse errors are logged, then a count, each line in one write. - Fast paths in removingImportant and scanUnit: corpus parse time back from 2.42 s to 1.81 s (upstream 1.74 s). - <line> coordinates default to 0; SAX text is appended in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pablogb
added a commit
to pablogb/SwiftDraw
that referenced
this pull request
Oct 21, 2026
From the review of swhitty#142's drawing (and older faults): - Gradient spread periods, filter layers and tile counts clamp before converting to Int. - One depth cap (200, a <use>/mask/pattern hop counting 12) on the builder's stack; <switch> is iterative. Fits a 512 KB thread. - Expansion is bounded by 250,000 elements built inside references, not 20,000 references; patterns, their commands and CGPatterns are cached per id (a 22 s file now takes 0.13 s). - Filter offscreens share one live budget and a per-draw budget; filters are resolved once per id and past 256 primitives or inputs draw unfiltered. - In PDF, masks and clip paths that need one clip with an image mask instead of a blend PDF cannot express (which painted black over unclipped content); a shapes-only nested clip-path is a second vector clip. - Pattern matrices from the full user-to-base transform: patterns were upside down and misplaced (also in 0.29.0). - Filters in PDF at 300 ppi; one cascade per element; glyph paths placed as made (20,000 glyphs: 24 s to 0.35 s). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Good day, awesome developers! I had to use an agent to make a dozen upgrades to the code. Now I strictly do not recommend you merge all the files as-is! These are agent-based, and very little human look got done, it would not be good practice. It also adds up some internal roadmap and reasons, and internal project names (I don't mind them being public), so a healthy housekeeping is really required.
But this is a really good starting point for all the features.
stroke-dasharrayandstroke-dashoffsetfeGaussianBlurrendereddisplayandvisibilitypreserveAspectRatioLayerTree.CommandOptimizer.filterStateCommanddoes not treat.setFillPatternas changing the fillfill="url(#g) #000000"(alsostyle=,url(#g) none; Inkscape writes it), fails to parseI might continue to improve my branch, so you can look it up, but it should be relatively stable from this point on.
Thank you very much for providing your code in a liberal license! Happy continuation!