403 Commits

Author SHA1 Message Date
Andrey Antukh
472ba8dde5 Merge remote-tracking branch 'origin/staging' into develop 2026-10-16 12:49:04 +02:00
Elena Torró
ccdfaffbef
🐛 Fix stroke to path for strokes with different widths per side (#12356) 2026-10-16 12:36:23 +02:00
Andrey Antukh
473a59b134 Merge remote-tracking branch 'origin/staging' into develop 2026-10-09 09:01:12 +02:00
Elena Torró
b3a62eeb4b
🐛 Paint layout children in canvas z-order in vector exports (#12171) 2026-10-08 09:07:33 +02:00
Alejandro Alonso
cc5f2f57ad
⬆️ Upgrade skia-safe to 0.153.3 with wasm SIMD (#12148)
Point SKIA_BINARIES_URL at the 0.153.3 simd prebuild and adapt
fonts, path stroke expansion, and gradients to the new API.
2026-10-07 12:08:33 +02:00
Andrey Antukh
746184caf9 Merge remote-tracking branch 'origin/staging' into develop 2026-10-06 15:58:01 +02:00
Elena Torró
70dfe90571
🐛 Clear doc atlas when canvas background color changes (#12112) 2026-10-06 10:34:47 +02:00
Elena Torró
c148ba5267
⚡ Rebuild drag tiles per moved subtree (#12030)
WASM receives only the transforms that differ from the parent (bool
operands and masks are kept), rebuilds tiles once per moved subtree
and invalidates only the previous frame coverage.
2026-10-02 11:39:24 +02:00
Elena Torró
09ffcb1fbc
⚡ Reduce render-wasm GPU memory and move cost on large pages (#11980) 2026-10-02 10:24:05 +02:00
Elena Torró
c44484a1e5
🐛 Fix SVG filter shadows and inherited group fills in render-wasm: (#11925)
* 🐛 Fix shadows derived from imported SVG filters

* 🐛 Fix inherited group fills in exports and group drop shadows

* 🔧 Run exporter tests on render-wasm changes
2026-09-28 07:29:25 +02:00
Elena Torró
63baf86152
🐛 Fix groups, masks and booleans drop shadow cases (#11911)
* 🐛 Fix blocky and black drop shadows on masked groups

* 🐛 Fix drop shadow spread on paths, bools and circles
2026-09-25 11:50:19 +02:00
Alejandro Alonso
e3e75725d4
✨ Export WASM SVG background blur via foreignObject (#11765)
Emit an XHTML foreignObject with CSS backdrop-filter for visible
background blur. The inner div uses clip-path:path(...) for the GPU
fill+stroke silhouette (glyph outlines for text). The FO sits outside
opacity and layer-blur filters. Stacked on the masks SVG export work.
2026-09-24 15:29:25 +02:00
Belén Albeza
34427c8a28
🐛 Fix borders not fully rendered next to masked groups (#11807)
A masked group now closes its focus scope on the mask pass, pops the
nested stacks on the content pass only, and draws its exit strokes on
the content pass. The focus depth also resets when each tile's walk
starts.

Closes #11805

AI-assisted-by: claude-opus-5
2026-09-23 12:45:02 +02:00
Miguel de Benito Delgado
f0880cf3f9
🐛 Crop cached drop shadows to their filter bounds (#11801)
Snapshot only the scaled shadow bounds instead of the full reusable
filter surface. This avoids retaining viewport-sized textures for each
small shadow while keeping cached pixels valid when the surface is reused.

Use the bounded snapshot for both shape and frame shadow caches. Add
regression tests for rounding, scaling, surface limits, and snapshot reuse.

AI-assisted-by: gpt-6
2026-09-22 11:09:45 +02:00
Belén Albeza
0581c35452
🐛 Fix drop shadows in masks (#11754)
* 🐛 Fix shadows on a masked group in the WASM renderer

A masked group renders in two passes: its content, then the mask shape
composited with DstIn so everything outside the mask silhouette is
erased. Both happen inside one save_layer, and the group's drop shadow
was composited into that same layer before the mask pass — so the mask
erased it. A drop shadow lives mostly outside the silhouette, so it
disappeared entirely.

Inner shadows never drew at all: render_fill_inner_shadows needs fill
geometry to paint into, and a group has none.

Both now ride on an image filter set on the masked-group layer, which
Skia evaluates after the mask is composited, so the shadow comes from
the real masked pixels rather than the group's own, empty geometry.
The effects compose in the order the SVG renderer uses for a group:
drop shadows, then the source, then inner shadows, with the layer blur
over all of it.

That layer is opened on a canvas carrying no transform, so the filter
is built in device units. Shadow::scale_to_device does that rather
than scale_content, because radius_to_sigma is affine: scaling the
radius applies its constant term once at device scale, while a filter
built in document space has the term scaled by the canvas matrix. The
two would blur differently by 0.5 · (scale - 1) sigma, visible as a
masked group's own shadow being narrower than the same shadow on its
parent. The masked-group layer blur had the same flaw.

Three paths are suppressed for masked groups so nothing is drawn twice:
the silhouette composite, the nested_shadows inheritance that would
reach text descendants, and the fill inner-shadow pass.

Every save and restore around that layer is keyed on the shape alone.
Enter and exit run on different walker passes, and a pan or zoom in
between changes fast mode, so deriving them from render state could
leave the canvas clip stack unbalanced.

Refs #11697

AI-assisted-by: claude-opus-5

* 🐛 Fix a container's drop shadow over a masked group in WASM

A container builds its drop shadow by drawing each descendant as a
black silhouette and blurring the result. The walk descends only
through children that can be flattened, and a masked group never can,
so it stopped there and asked the group to draw its own geometry. A
group has none, so nothing was drawn and the shadow layer stayed
blank: no shadow at all for a group, board or frame holding a masked
group. This is what the file attached to the issue reproduces.

render_drop_black_shadow now draws the masked silhouette for such a
group — content children flat black, DstIn the mask, and only then the
offset, blur and spread. Masking after the blur would trim the shadow
along the wrong edge.

The walk recurses, so it narrows the clip the way the main walker
does: content a clipping container hides must not widen the shadow.
The clip rule now lives in one place, shared with the walker, and a
test pins the two against each other. The shadow layer is sized to the
silhouette plus the shadow's reach rather than falling back to the
clip, so a wide blur is not cut at the tile edge.

Spread keeps the renderer's existing behaviour: the silhouette goes
through the same get_drop_shadow_filter every other shadow uses, so a
masked group gains no ordering of its own.

Closes #11697

AI-assisted-by: claude-opus-5
2026-09-18 13:04:11 +02:00
Elena Torró
8124ecf148
⚡ Improve canvas snapshot and nested drop shadows calculation (#11766)
* ⚡ Capture canvas snapshot only when the renderer is idle

* ⚡ Skip descendant silhouettes for frames with nested drop shadows

* 🔧 Add a profiling build mode for render-wasm
2026-09-18 09:21:52 +02:00
Elena Torró
4c940296f1
🐛 Fix fixed-scroll on the viewer (#11752) 2026-09-17 09:43:13 +02:00
Elena Torró
32ed9b5a08
🐛 Fix board clip on drag (#11620) 2026-09-11 08:27:11 +02:00
Andrey Antukh
f695553469 Merge remote-tracking branch 'origin/staging' into develop 2026-09-08 21:15:58 +02:00
Elena Torró
7b135b80b2
🐛 Fix background blur clipping (#11532) 2026-09-07 15:51:27 +02:00
Elena Torró
0533be100d
🐛 Fix text selection render overlay and double/triple click flash (#11512) 2026-09-04 12:38:31 +02:00
Alejandro Alonso
c5897bc50a
⚡ Paint plain text directly onto Current (#11355)
* ♻️ Share text layout paragraphs across modifier clones

Store Skia paragraphs in Rc so TextContentLayout::clone keeps the
cached layout for rotate/pan modifiers. Add layout.clear() and treat
needs_update as paragraphs-empty only.

* ⚡ Reuse cached Skia paragraphs when painting text

Add try_paint_from_layout_cache to paint from TextContent.layout when
versions match, skipping ParagraphBuilder rebuild and layout on each
frame. Wire into the layered text path for plain fills without strokes
or effects.

* ⚡ Paint plain text directly onto Current

Extend can_render_directly for stroke-free text and skip the empty
save_layer in draw_text when no stroke-group opacity is set. Plain text
paints into Current without the Fills/Strokes blit.
2026-09-02 16:17:06 +02:00
Elena Torró
03e6f119e5
♻️ Clean unnecessary methods (#11472)
* ♻️ Remove duplicated/unused set-children code

* ♻️ Remove unused methods
2026-09-02 12:58:52 +02:00
Alejandro Alonso
89a17df403
✨ Add basic WASM SVG export path (#10891)
Introduce a minimal vector SVG exporter via render-wasm: solid fills,
native opacity/blend wrappers, frame clip-content, FFI/frontend wiring,
insta snapshots, and a visual preview gallery. Defer save_layer
workarounds (shadows, blur, masks, special strokes) to follow-up PRs.
2026-08-31 16:06:38 +02:00
Elena Torró
736b99076f
🐛 Fix shapes cut when overflowing a non-clipping board (#11423) 2026-08-31 15:50:19 +02:00
Alejandro Alonso
7419bc7007
🐛 Evict multi-scale tile cache on shape edits (#11337)
those textures across zoom for progressive previews, and invalidate
by old∪new document coverage so rotate/move edits do not leave
stale fragments on zoom-out.
2026-08-26 08:25:23 +02:00
Alejandro Alonso
57c0e81616
⚡ Present viewport before interest and clamp paint to atlas (#11313)
Present visible tiles via ViewportReady so zoom settle turns sharp
without waiting on the interest ring, and paint at atlas slot size so
DPR 2 does not rasterize 1024 only to downscale into 512 slots.
2026-08-25 12:56:07 +02:00
Alejandro Alonso
4cb9f951d2 🐛 Skip atlas writes during pan/zoom fast mode
During view gestures, fast mode renders tiles without shadows or
blur. Writing those tiles into the doc/tile atlas left shadowless
patches when render_from_cache overlayed them on the scaled
preview. Keep the last HQ atlas tiles until the post-gesture
full-quality render completes.
2026-08-21 11:17:16 +02:00
Alejandro Alonso
689d506788 ⚡ Render eligible frame drop shadows via direct geometry path
Add a direct container-geometry path for eligible frames: inline blur
when the kernel fits the tile margin, otherwise a cached filter-surface
pass reused across tiles via DropShadowFilterCache on both the direct
and slow render_shape paths.

Move frame shadow logic into shadows.rs. Fix nested/clipped frame
shadows by deferring parent clip to composite time, apply negative
spread via inset, and allow rotated/transformed frames on the direct
path. Skip descendant extrect walks for clipped frames when only
nested drop shadows matter, and skip child silhouettes when the
container fill already covers shadow descendants.
2026-08-21 11:17:16 +02:00
Alejandro Alonso
43b12bc4b9
⚡ Soft-drain GPU mid-walk on progressive Partials (#11127)
Release packs far more cheap Current draws (e.g. fills_none paths)
into one Partial than debug; a single end-of-Partial
flush_and_submit then stalls the browser. Soft-flush every N walker
nodes (and on Partial yield) keeps ops buffers bounded while Full
still submits via present_frame.
2026-08-07 09:45:39 +02:00
Alejandro Alonso
a76401596e
⚡ Skip imperceptible shadows and simplify low-scale strokes (#11102)
* ⚡ Skip drop shadows that are imperceptible at current scale

Filter drop shadows by on-screen footprint (stricter for recursive
shapes) so overview HQ avoids expensive blur passes that barely show.

* ⚡ Simplify Path and Bool strokes at low scale

At overview zooms, Inner/Outer strokes fall back to Center and
dash/dotted styles become solid when the pattern is subpixel.
Strokes are never skipped so stroke-only icons stay visible.

* ⚡ Drain GPU work on partial render frames

Partial frames only flushed the Backbuffer, so tile GPU commands
queued until present_frame's flush_and_submit and stalled the
browser on large files. Submit the context each partial frame
without presenting Target or re-composing the tile atlas.

* ⚡ Prefer direct painting when effects are imperceptible

Skip the Fills/Strokes layered path when drop/inner shadows would
not paint at the current scale, and allow stroke-only shapes
(fills_none) on the direct path. Apply the same footprint LOD to
inner-shadow painting.
2026-08-06 15:58:11 +02:00
Alejandro Alonso
11fc090bc4
⚡ Expand direct shape painting and skip empty drop-shadow blits (#11100)
* ♻️ Extract apply_clip_stack_to_surfaces helper

Share the layered-path clip loop so the Current-surface direct
path can reuse the same hard-clip stack without duplication.

* ⚡ Expand direct shape painting onto Current

Allow clip stacks, frames, non-identity transforms, and SrcOver
opacity on the Current-surface fast path; skip empty non-masked
groups. Avoids Fills/Strokes blits for common shapes.

* ⚡ Skip empty drop-shadow blits; warm DropShadows once

Early-out drop-shadow composite when a shape has no visible
shadows, and touch DropShadows→Current once per tile instead
of per shape to keep flush_and_submit cheap.
2026-08-06 12:31:49 +02:00
Alejandro Alonso
8b64b0f84f
⚡ Fix progressive render budget when timestamp is stale (#11094)
Pass performance.now from finalize/debounce and re-anchor the WASM
budget if the stamp is 0 or already past max_blocking_time, so HQ
tiles are not yielded after a few nodes with almost no real work.
2026-08-06 09:00:45 +02:00
Elena Torró
d4d22ff1a4
🔧 Prepare wasm render for headless exporter (#10795)
* 🔧 Support background blur on PDF render for strokes and text shapes

* ✨ Add LRU eviction to the wasm image store

* ✨ Add RasterFormat to encode png, jpeg and webp from wasm

* 🐛 Fix client-side wasm export encoding jpeg and webp as png
2026-07-28 11:54:22 +02:00
Belén Albeza
4f46700c94
🐛 Fix text editor v3 blinks (#10654) 2026-07-24 09:14:59 +02:00
Elena Torró
4383cf183a
✨ Add background blur to strokes (#10716) 2026-07-22 17:07:16 +02:00
Elena Torró
08e42687d1
♻️ Extract GPU-free RenderResources; add headless wasm exports (#10653) 2026-07-22 09:56:21 +02:00
Elena Torro
001dba1bef ✨ Add background blur to text shapes 2026-07-16 08:33:36 +02:00
Andrey Antukh
0e25f7d8ad Merge remote-tracking branch 'origin/staging' into develop 2026-07-08 10:25:25 +02:00
Belén Albeza
0a2570c9eb
🐛 Fix text editor not repainting text if there was not a geometry change (#10553) 2026-07-07 11:18:44 +02:00
Belén Albeza
2e32a8743e
♻️ Refactor rulers into ui modules (#10461)
* ♻️ Move rulers rendering to ui submodule

* ♻️ Refactor RulerState into UIState
2026-07-03 08:37:22 +02:00
Elena Torró
28b5371901
🔧 Improve text strokes in texts with emoji (#10519) 2026-07-01 18:10:46 +02:00
Aitor Moreno
851fde01c1
🐛 Fix SVG Raw not being saved (#10492) 2026-07-01 14:08:54 +02:00
Elena Torró
c8f586a197
🐛 Fix async text rendering on tiles (#10504) 2026-07-01 11:09:57 +02:00
Alejandro Alonso
345affc687
🐛 Fix premature WASM view-interaction end during pan (#10425) 2026-06-25 15:07:37 +02:00
Alejandro Alonso
9b22d96553 🐛 Fix render from cache 2026-06-24 14:00:57 +02:00
Elena Torró
07de0e92d5
⚡ Fix slow zoom/edit on Firefox+NVIDIA WebGL renderer (#10371) 2026-06-23 10:58:14 +02:00
Elena Torró
b91eece5bf
🐛 Fix rulers shown on thumbnails 2026-06-19 13:01:26 +02:00
Alejandro Alonso
b9ebe9f4ee
🐛 Fix viewer tile artifacts 2026-06-19 12:51:17 +02:00
Alejandro Alonso
c6ecfb7794
Merge pull request #10320 from penpot/superalex-fix-performance-regression-3
🐛 Fix performance regression
2026-06-18 18:37:40 +02:00