1054 Commits

Author SHA1 Message Date
alonso.torres
f44e13df70 ✨ Make horizontal Japanese justify and squeeze follow vertical JLREQ
Horizontal Japanese justification spread each line's slack evenly over
all character gaps, while vertical columns widen gaps in JLREQ pair-table
order. Squeezing also differed: horizontal squeezed only under push-in
first, while vertical also squeezes under push-out first when push-out
cannot resolve a kinsoku break.

Move the axis-free part of the vertical allocator into text_allocation:
jlreq_expansion_gap classifies a gap from the pair table and
jlreq_ordered_expansion widens word spaces, then Japanese/Western gaps,
then solid kana and kanji, then spreads any rest. Vertical columns and
horizontal lines both use it. Horizontal collects real characters from
the laid-out lines; inserted joiners and spacing belong to the gap
between their neighbours. The last line, distribution and the warichu,
joiner and Western-word skips stay as they were.

Under push-out first, horizontal now squeezes a line when the break would
move back over more than MAX_KINSOKU_SHIFT kinsoku pairs, the limit
vertical oidashi uses, now shared from jlreq. A cheap scan for such runs
gates the pass, so other text keeps its layout cost. Push-out only never
squeezes.

This changes existing horizontal justified Japanese layouts on purpose:
punctuation aki and gaps beside ruby bases no longer widen, and a line
with no expandable gap stays short, as vertical columns do. No existing
test needed changes beyond the new line adjustment argument.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
cc6f183a90 🐛 Let vertical Chinese and Korean ruby overhang its neighbours
The pairwise vertical path (Chinese, Korean and plain text) grew ruby
bases without first computing overhang rooms, so a long Pinyin reading
always spread its base. Horizontal text, and vertical Japanese, let the
reading hang over an adjacent kana, comma, full stop or middle dot.

Run set_ruby_overhang_rooms before grow_ruby_bases on that path too. It
uses ruby_overhang_room, the rule horizontal applies to every language,
so Han neighbours still take no overhang and ruby-overhang none keeps
the old growth. A comment at the early return notes that warichu never
splits there: only Japanese renders warichu.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
31115ab29a 🐛 Make horizontal emphasis mark bounds match where the mark paints
The mark box used the mark's em box (half the base size, square), while
painting placed the shaped mark by its tight rect: advance wide, ascent
plus descent tall. Text bounds, selection and export therefore disagreed
with the canvas; over marks could paint above the bounds.

Shape each span's mark once in HorizontalEmphasisMark and place that
measured box for paint, text bounds and position data alike. Position
data keeps an em box, now centred on the painted mark, because SVG
export draws the mark at a font size equal to the box width.

Tests that built synthetic placements now pass the mark ink explicitly;
their assertions are unchanged. Vertical marks had no mismatch: paint
and position data centre on the same point.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
619f136587 ♻️ Split the vertical pairwise flow out of the JLREQ passes
text_vertical/flow.rs held both the JLREQ column passes and the planner
the plain, Chinese and Korean policies use. Move the pairwise planner,
spacing and alignment to flow/pairwise.rs; the JLREQ passes stay in
flow/mod.rs with the flow types both share.

Also list the CJK renderer modules and their new names in
docs/cjk-layout.md.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
177929fd1a ♻️ Move shared glyph shaping out of the vertical text module
The horizontal ruby, Zhuyin and emphasis painters and the text content
code shape glyphs through helpers that lived under text_vertical. Move
shaping.rs, font_tables.rs and orientation.rs to a new
shapes/text_shaping module, which both engines import.

The position-data direction values were split between the vertical
positions module and the Zhuyin module. They now sit next to
PositionData in shapes/text.rs, with their values unchanged.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
fc0f7d72a2 ♻️ Share duplicated CJK layout helpers across both axes
Several CJK passes had a copy per writing direction. Merge them without
changing their results:

- allocate_bounded_expansion and bounded_expansion divide justify and
  distribute slack for both axes. Each axis still collects its own gaps;
  the horizontal one checks no-break controls over the source range and
  the vertical one at cell edges, as before.
- PolicyBoundaries holds the break and emergency rules at each grapheme
  of a paragraph. The horizontal overlong-word and atomic-overflow passes
  and the vertical pairwise column planner read it. relax_overlong_words
  moves next to retain_atomic_overflow and shares annotation_ranges.
- TextLayoutPolicy::pair_spacing_em gives the compression and script gap
  that both axes apply with their own caps.
- cjk_chars keeps the Han, kana, hangul and punctuation ranges. Ranges
  that differ on purpose stay separate there, with a note on why.
- OverhangClaims tracks the ruby overhang an earlier reading takes.
- horizontal_zhuyin_glyphs places Zhuyin for position data and painting.
- paint_horizontal_annotations paints warichu, emphasis and ruby; the
  emoji overlay keeps its place between the marks and the ruby.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
2b888f712b ♻️ Make the text layout policy one language decision
TextLayoutPolicy had nine flat variants that mixed language, Chinese
region and Korean wrap. It is now Plain, Japanese, Chinese(ChineseRegion)
or Korean(KoreanWrap), and the layout code asks it named questions
(uses_jlreq, plans_breaks, keeps_overlong_units, reopens_overlong_words,
bounded_justify, needs_joiner, normalizes_western_spaces,
uses_clreq_positions, ...) instead of comparing variants.

Paragraph parses its language once, when the language or the Korean wrap
changes, and copies the policy to its spans. Span code no longer builds a
policy from the tag with a dummy Korean wrap, and the emphasis and
underline positions take the policy instead of a Chinese flag.

A LanguageTag parser now serves both the layout policy and the CJK font
choice. It also accepts '_' as a separator, as the font choice did, so
a tag such as "ja_JP" now selects the Japanese policy.

The korean-wrap byte decodes through a new exported RawKoreanWrap enum
(Syllable=0, SpacesOnly=1, KeepWords=2) with the same byte values.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
1b16d995ee ♻️ Rename CJK text modules and helpers in render-wasm
The CJK layout code kept names from when it only handled Japanese.
Chinese regional presets, Pinyin, Zhuyin and Korean now share it, so
rename the modules and helpers to say what they do:

- kinsoku.rs becomes line_breaks.rs: the layout-text rewrite with
  OffsetMap and the inserted break markers. The kinsoku predicates move
  to jlreq.rs (was japanese.rs); JapaneseClass becomes JlreqClass.
- is_japanese_text_char becomes is_cjk_text_char.
- text_japanese.rs splits into text_attrs.rs (span attribute enums),
  text_ruby.rs (ruby pieces and overhang), text_warichu.rs and
  text_allocation.rs (capped_equal_allocations, shared by both axes).
- wasm/text/japanese_layout.rs becomes layout_attrs.rs.
- RubySide becomes AnnotationSide; RawTextSpan.ruby_side becomes
  ruby_flags, since the byte packs the side and the Zhuyin bit.
- horizontal_aki_sheds becomes the horizontal_pair_spacing dispatcher
  with a jlreq_aki_sheds branch; the plan field sheds is pair_spacing.
- The policy_* helpers become pairwise_* and the Japanese ones jlreq_*.
- The oikomi policy parameter is adjustment and minimum_oikomi_extent is
  min_squeezed_extent.

Exported functions, Raw* enums and byte layouts stay unchanged.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
5d647b2cd5 ✨ Replace the CJK layout toggles with a text Language panel
The CJK text options were behind per-language "Enable layout" toggles in
the file menu and the settings, each with its own sidebar section. A text
now has one language, picked in a single Language panel, and the options
of that language show below it.

- Rename the `vertical-text-layout` flag and feature to `cjk-support`.
- Remove the per-file and per-profile layout toggles, the file options
  that stored them and the `:set-text-layout` change.
- Add the Language panel: a grouped dropdown of Japanese, Korean and the
  regional Chinese variants, a row to change the language and a button to
  remove it, "Mixed" when the selected texts differ. Picking a language
  writes it to every paragraph of the texts.
- Render the CJK attrs only under the language that uses them, so a text
  without language is horizontal and plain. The stored content keeps
  every attr.
- Remember the last picked language for the next new texts.
- Add File > Language: the language of the texts without one of their
  own, applied at render time and laid out again when it changes.
- Add the profile text language in Settings > Options: new files start
  with it as their file language.
- Extract the grouped dropdown of the board size presets into a shared
  component used by the presets and the Language panel.
- Name the Japanese readings furigana, drop the redundant prefixes of
  the furigana and pinyin options, move Distribute below the writing
  mode row and show only the position sides of the writing mode.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
9bc8cae3c9 ⚡ Lay out a window of the paragraph to find squeeze sheds
Finding the line-end squeeze laid out the whole paragraph again for every
line, so the cost grew with the square of the length and typing in a
long Japanese text lagged.

Lines break greedily, so a line depends only on the text from its start.
Lay out a window from the line start that holds the line and the next
one, doubling it when needed, keep it while no squeeze changes the sheds,
and lay out the full paragraph once at the end. A test checks the sheds
match a full relayout.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
7fec1ea69e 🐛 Include horizontal readings in the text export bounds
Readings paint above the line box, outside the glyph metrics. The text
bounds joined the emphasis marks only, so an auto-width text with ruby
or Zhuyin and no emphasis exported with the top of its reading cut off.

Measure the ink of the readings with the code that paints them and join
it to the content and auto-width bounds.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
fc32249a8f 🐛 Keep kinsoku and ruby bases in narrow Japanese text boxes
The narrow-box overflow plan, which lets an indivisible unit overflow a
box narrower than one character, ran for Chinese and Korean only. In a
Japanese box of one character width Skia broke anywhere: a line started
with "、" and a ruby base split over two lines.

Run the plan for Japanese too, and refuse emergency breaks before a
character that may not start a line or after one that may not end it.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
87a21f8a2f 🐛 Combine short Western runs under tate-chu-yoko all
An `all` composite gave up below half width, so "2027" in a font with
proportional digits (about 0.45) stayed sideways, while `digits` combined
the same run down to 1 / length.

A Western run no longer than a `digits` composite now squeezes as far as
one. Upright CJK and longer runs keep the half-width limit, since a
deeper squeeze makes full-width glyphs illegible.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
179dca8b04 🐛 Stop pushing a long forbidden run out of every vertical column
When an item overflowed a column, column_break moved the characters that
may not start a column into the next one, and only checked that they fit
there with the overflowing item. The characters after it may not start a
column either, so a run longer than a column was pushed out again at
every column: "ださい。ー、ャュョッ。" in a five-character box left
"ださ", "い", "。" and "ー" alone in four columns.

Measure the whole unit that moves, up to the next allowed break, and keep
the natural break when that unit is longer than a column.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
f5cdde356d 🐛 Attach horizontal annotations to the ideographic em box
Zhuyin, Pinyin and emphasis marks over horizontal text were placed
against an em box bottom-aligned to Skia's tight glyph rect, which spans
the hhea ascent and descent. In CJK faces the descent reaches past the
em box, so Zhuyin columns sat about 0.17 em below their base, and over
annotations relied on a hand-tuned clearance.

Place them against the ideographic em box (OS/2 typo metrics) of the
font that shaped each base, centred on the line baseline, the same
metrics vertical text uses for upright glyphs.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
aa2dbacd23 🐛 Paint CJK underlines with the color of their own span
Skia keys a line's style runs by UTF-8 byte offset, while line indices
are UTF-16. Decoration segments searched those runs with UTF-16 offsets,
so with CJK text every underline or strike on a line took the paint of
the first run, and a decorated span wrapped onto a new line lost its bar.

Look up each segment's run by the span's UTF-8 style anchor, falling
back to the line's first run for a span continued from an earlier line.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
a5b2b45f9c 🐛 Keep Pinyin readings on one baseline over horizontal text
Each ruby reading attached its lowest ink to the base, which suits kana
but not Latin letters: the descender of "y" lifted "yǒu" above "liáo"
over 友聊, and under ruby moved with ascenders and tone marks.

Western letters in a reading now take the font's descent (over) or cap
height (under) instead of their ink, so Pinyin and romaji readings share
one baseline and descenders stay clear of the base. Upright glyphs keep
the ink edge.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
6605dbc1d0 🐛 Keep distributed and justified lines inside the text box
SkParagraph moves each line right by half the letter-spacing of its first
character. That centres span tracking, but the spacing added to single
characters (distribution and justification gaps, squeezing, compression
and ruby room) must only open the gap after them. A distributed label of
two characters started half a gap in and overflowed the box by as much:
"国家" in a 200px box drew 国 92px from the left edge.

Builders of horizontal text record how far each adjusted character's
letter-spacing departs from its span tracking, and `LayoutParagraph` takes
back half of that on each line, next to its baseline shifts: painting,
glyph and placeholder rects, line metrics, hit testing and text paths.
Span tracking keeps its half spacing before the line.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
ecaa6132e2 🐛 Set digits tate-chu-yoko upright and centred in vertical text
Four fixes to how `digits` tate-chu-yoko composites lay out in vertical
text:

- A lone digit combines too, as CSS `digits <n>` combines runs of n or
  fewer, so it stands upright instead of turning sideways. The renderer
  (`split_digit_runs`) and the export path (`digit-combine-segments`)
  share the rule.
- Composites centre on the OS/2 typographic metrics, like upright glyphs.
  The hhea ascent of CJK faces exceeds the em, which set digits about
  0.07em below the kanji beside them.
- Letter-spacing stays outside the composite, as in CSS. Counting the
  trailing spacing in its width moved the digits left by half of it.
- The regional Chinese profiles no longer add the Han/Western gap around
  a composite: it sets in one ideograph's cell, as JLREQ already does.

AI-assisted-by: claude-opus-5-5
2026-11-05 17:49:00 +01:00
alonso.torres
324e476926 ✨ Add distributed text alignment and preserve fractional line spacing
Add distributed alignment to text styles, sidebar controls, CSS export,
and the Plugin API. Spread characters across horizontal and vertical
lines, including the final line, while preserving graphemes and annotations.

Keep fractional line heights consistent across painting, hit testing,
selection, and text paths to prevent baseline drift between lines.

Add tests for distribution, nonbreaking text, and fractional line geometry.

AI-assisted-by: gpt-6
2026-11-05 17:49:00 +01:00
alonso.torres
51da2812ce 🐛 Restore undo and redo inside the WASM text editor
Keep bounded content and selection history within each editing session.
Group committed compositions and styled insertions, omit IME previews,
and reset history when external content changes the editing baseline.

Route keyboard, workspace menu and browser history input to the editor.
Restore UTF-16 selections through a new WASM binding, preserving styles,
readings and paragraph settings without reaching earlier canvas edits.

Verify 1,587 frontend tests, 671 renderer tests and both WASM builds.
Add four editable showcase examples and verify them after reopening.

AI-assisted-by: GPT-6
2026-11-05 17:49:00 +01:00
alonso.torres
809441762b 🐛 Preserve emphasis across adjacent ruby spans
Recover merged annotation styles on the base character's own line, using
separate UTF-16 line anchors and UTF-8 style keys. Include emphasis marks
in horizontal text bounds so standalone exports keep each mark whole.

Add regression coverage for adjacent readings, wrapped text, emoji line
anchors, raster output and export bounds across all text grow modes.
Verify 670 renderer tests, formatting and both WASM builds. Add four
editable showcase examples and check their layout and exports.

AI-assisted-by: GPT-6
2026-11-05 17:48:59 +01:00
alonso.torres
d06570c010 🐛 Keep indivisible CJK units together in narrow text boxes
Retain legal line starts at the original wrap width before allowing an
indivisible punctuation or annotation unit to overflow together. Share the
layout plan and alignment offset across painting, editing and exports.

Cover mixed styles, nonbreaking controls, graphemes, annotations, alignment,
strokes and resize. All 666 renderer tests and both WASM builds pass.

AI-assisted-by: GPT-6
2026-11-05 17:48:59 +01:00
alonso.torres
5d63967cf8 ✨ Add Mandarin Zhuyin reading layout
Add typed one-character Zhuyin readings with upright right-side symbols
and separate neutral and non-neutral tones in both writing modes.
Persist the mode through the editor, WASM bridge and Plugin API. Preserve
annotation clearance and canvas, PNG, PDF and SVG geometry.

Validate syntax and selection boundaries, and cover schema, bridge,
rendering, UI and API behavior. Native IME and reader review remain open.

AI-assisted-by: gpt-6
2026-11-05 17:48:59 +01:00
alonso.torres
ff18d2bd34 ✨ Add Chinese emphasis and underline positions
Carry optional span positions through the schema, WASM header, editor,
Plugin API and CSS output. Expose Chinese controls with language defaults
and independent overrides for horizontal and vertical text.

Reserve automatic clearance per side and stack underlines, ruby and
emphasis. Preserve reading groups during decoration edits and retain
canonical position values in DOM editor style data.

Add protocol, geometry, editing and API tests, including the prior Japanese
mixed-font gutter behavior. Verify both WASM targets and live exports.

AI-assisted-by: GPT-6
2026-11-05 17:48:59 +01:00
alonso.torres
30fe8abf63 ✨ Add Chinese Pinyin reading controls
Support typed character and group readings through the shared ruby model.
Expose Chinese labels, presentation controls and selection guards without
changing Japanese controls or generating pronunciation.

Preserve complete source ranges for glyphs sharing a combining cluster.
Add pinned horizontal and vertical tone-mark fixtures and editor tests.

AI-assisted-by: GPT-6
2026-11-05 17:48:59 +01:00
alonso.torres
937be55c77 ✨ Add CJK layout validation across box sizes
Cover Chinese regional profiles and Korean wrap modes across fitting
widths, style boundaries, punctuation, numeric units and decomposed Jamo.
Check stable offsets and finite geometry in narrower horizontal boxes,
and keep indivisible punctuation units together in short vertical boxes.

AI-assisted-by: GPT-6
2026-11-05 17:48:59 +01:00
alonso.torres
b5ff9ce59f ✨ Add Chinese regional and Korean word layout
Add named Chinese punctuation presets and a Korean keep-words mode for
horizontal and vertical layout. Preserve the saved spaces-only mode and
keep source offsets stable through safe emergency wrapping.

Apply bounded CJK spacing and justification, expose the new choices in the
sidebar, and document preset bounds and remaining release checks.

Verify renderer, frontend, common and exporter tests, both WASM builds,
and sample save/reload plus PNG, PDF and SVG exports. Existing CI failures
remain documented in the layout notes.

AI-assisted-by: GPT-6
2026-11-05 17:48:59 +01:00
alonso.torres
b60d095beb ✨ Add a CJK text layout proof of concept
Choose paragraph layout rules from an optional language tag. Pass language
to horizontal and vertical shaping, add basic Chinese punctuation rules,
and add experimental Korean spaces-only wrapping.

Keep text boundaries grapheme-safe and preserve paragraph profiles across
editor splits and merges. Add reproducible font fixtures for localized
glyphs and regression tests for the shared WASM protocol and layout paths.

Validation: renderer, common JVM/JS, frontend and exporter tests pass.
Both WASM targets build. Full CI still reports baseline lint and format
errors and one backend multipart-upload test failure.

AI-assisted-by: GPT-6
2026-11-05 17:48:59 +01:00
alonso.torres
d5fe85b776 🐛 Fix issues with auto-width/height 2026-11-05 17:48:59 +01:00
alonso.torres
390a0f7672 ✨ Render Japanese text layouts in WASM
Lay out horizontal and vertical Japanese text with kinsoku, ruby,
emphasis, warichu, tate-chu-yoko and proportional font features.
Carry the layout through glyph paths, SVG export and text selection.

Update the Rust and ClojureScript binary formats together, including
the editor bridge and headless exporter. Resolve CJK fallback fonts
and add renderer tests and font fixtures.
2026-11-05 17:48:59 +01:00
Elena Torró
02223a67fa
🐛 Fix unmoved layout child stretched during wasm gap drag (#12831)
Follow-up to 419513011c: only pure moves pass down to children in wasm.
Remove unused set-wasm-rotation-modifiers.
2026-11-05 17:33:07 +01:00
Alejandro Alonso
94836cb8c8 Merge remote-tracking branch 'origin/staging' into develop 2026-10-30 13:22:09 +01:00
Elena Torró
b48d163f5f
⚡ Fix slow drop shadow spread in CPU exports (#12718)
* ⚡ Fix slow drop shadow spread in CPU exports

* 🐛 Fix text opacity in board drop shadows on CPU exports
2026-10-29 18:27:19 +01:00
Andrey Antukh
8491cdaf63 Merge branch 'main-2.18.x' 2026-10-29 15:43:06 +01:00
penpot-renovate[bot]
936371d9a6
⬆️ Update dependency pnpm to v12.16.1 (#12714)
- pnpm 12.16.1 (docker/images/Dockerfile.media-processor)
- pnpm 12.16.1 (docker/images/Dockerfile.mcp)
- pnpm 12.16.1 (docker/images/Dockerfile.exporter)
- pnpm 12.16.1 (docker/devenv/Dockerfile)
- pnpm 12.16.1 (render-wasm/package.json)
- pnpm 12.16.1 (plugins/package.json)
- pnpm 12.16.1 (plugins/libs/plugins-styles/package.json)
- pnpm 12.16.1 (plugins/libs/plugins-runtime/package.json)
- pnpm 12.16.1 (plugins/libs/plugin-types/package.json)
- pnpm 12.16.1 (plugins/apps/table-plugin/package.json)
- pnpm 12.16.1 (plugins/apps/rename-layers-plugin/package.json)
- pnpm 12.16.1 (plugins/apps/poc-tokens-plugin/package.json)
- pnpm 12.16.1 (plugins/apps/poc-state-plugin/package.json)
- pnpm 12.16.1 (plugins/apps/plugin-api-test-suite/package.json)
- pnpm 12.16.1 (plugins/apps/lorem-ipsum-plugin/package.json)
- pnpm 12.16.1 (plugins/apps/icons-plugin/package.json)
- pnpm 12.16.1 (plugins/apps/example-styles/package.json)
- pnpm 12.16.1 (plugins/apps/e2e/package.json)
- pnpm 12.16.1 (plugins/apps/create-palette-plugin/package.json)
- pnpm 12.16.1 (plugins/apps/contrast-plugin/package.json)
- pnpm 12.16.1 (plugins/apps/composable-test-suite/package.json)
- pnpm 12.16.1 (plugins/apps/colors-to-tokens-plugin/package.json)
- pnpm 12.16.1 (package.json)
- pnpm 12.16.1 (media-processor/package.json)
- pnpm 12.16.1 (mcp/packages/server/package.json)
- pnpm 12.16.1 (mcp/packages/plugin/package.json)
- pnpm 12.16.1 (mcp/packages/common/package.json)
- pnpm 12.16.1 (mcp/package.json)
- pnpm 12.16.1 (library/package.json)
- pnpm 12.16.1 (frontend/text-editor/package.json)
- pnpm 12.16.1 (frontend/packages/ui/package.json)
- pnpm 12.16.1 (frontend/packages/mousetrap/package.json)
- pnpm 12.16.1 (frontend/packages/draft-js/package.json)
- pnpm 12.16.1 (frontend/package.json)
- pnpm 12.16.1 (exporter/package.json)
- pnpm 12.16.1 (docs/package.json)
- pnpm 12.16.1 (common/package.json)
- pnpm 12.16.1 (backend/package.json)

Signed-off-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-10-29 15:26:20 +01:00
Andrey Antukh
857c666022 ⬆️ Update pnpm to 12.16.1 and node to 24.22.2
pnpm 12.5.1 -> 12.16.1: the tarball pin of docker/devenv and of the
media-processor, exporter and mcp images, the packageManager field of
every workspace manifest, and the lockfile metadata of the 11
workspaces. The new pnpm also normalizes one engine range per lockfile
(>=18.* to >=18).

node 24.21.0 -> 24.22.2: devenv NODE_VERSION and both NODE_SHA256,
.nvmrc, and the runtime pin of the two plugins-deploy workflows. The
dhi.io image tags stay as they are until their digests are available.

AI-assisted-by: deepseek-v4.1-flash
2026-10-29 14:09:54 +00:00
Alejandro Alonso
deb5771248
✨ Use WASM for SVG markup in code inspect and copy (#12567) (#12608)
When render-wasm is active, generate SVG for the Code inspector and
Copy as SVG from the WASM export path instead of classic SVG codegen.
Add multi-root render_shapes_svg so multi-select stays one document.

Closes #12567
2026-10-27 12:05:30 +01:00
Andrey Antukh
74add05226 Merge remote-tracking branch 'origin/staging' into develop 2026-10-23 13:40:44 +02:00
Andrey Antukh
c04af5ddc5 Merge remote-tracking branch 'origin/main' into staging 2026-10-23 13:11:52 +02:00
Belén Albeza
2045fc4900
🐛 Keep text image fills in place when pasting (#12552)
A text image fill was always laid over the whole text box, so a pasted
image span showed another part of the picture in a box of another size.

Text image fills now honour the image transform, as shape fills do: the
image covers the transform rect instead of the whole box. Paste gives
each pasted image span a transform that keeps the image at the same
pixel offset from the span, like the gradients.

The SVG export emits one linked image per span transform, rather than
one per image id, and the style panel keeps the transform it reads back.

AI-assisted-by: claude-opus-5-5
2026-10-23 13:00:53 +02:00
Andrey Antukh
76a83387d9
⬆️ Update JVM, Node, pnpm and devenv dependencies (#12600)
Move the backend JDK to its latest maintenance build within Java 26 and
refresh the dependency versions pinned by hand in the development and
production images.

- Backend: Zulu JDK 26.0.2 to 26.0.2.1. The jlink module list keeps
  jdk.management, so the prometheus process metrics stay exported.
- Node.js 24.22.1 in .nvmrc, the development image tarball and the two
  plugins deploy workflows that pin it.
- pnpm 12.15.0 in the four Dockerfiles, the 34 packageManager fields and
  the eleven lockfiles.
- Development tools: clj-kondo 2026.10.20, Babashka 1.13.226, Pixi
  0.83.0, GitHub CLI 2.103.0, AWS CLI 2.38.5 and uv 0.13.5.
- Infra services: RustFS 1.0.3, Mailpit v1.32.0, and Valkey pinned to
  8.1.10 instead of the floating 8.1 tag.

Fix SERENA_UPDATE_VERSION in the compose file. A value there makes the
start script reinstall that Serena release on every start, overriding
the one baked into the image; it was replacing 1.7.0 with 1.6.1.

The hardened base image updates are left to Renovate.

Closes #12599

AI-assisted-by: deepseek-v4.1-flash

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-10-23 12:36:11 +02:00
Alejandro Alonso
e27c65c77a
✨ Run WASM asset exports locally in the browser (#12515)
When render-wasm is active, shape exports (single file, ZIP, and Export
boards multi-page PDF) run on the shared worker WASM module so the UI
stays responsive. A progressive PDF session (begin/add-page/finish)
updates progress per page; cancel settles on the client. The server
exporter path remains for the SVG renderer.
2026-10-23 11:02:23 +02:00
Alejandro Alonso
6508449fa0
🐛 Rebuild text layout when image fills finish loading (#12564)
Text image shaders are baked into Skia paragraphs at layout time.
touch_shapes_with_image only scanned shape.fills/strokes, so text
spans were never invalidated after store_image. Detect text content
image fills and force a layout rebuild before touching tiles.

Closes #12563
2026-10-23 09:55:23 +02:00
Belén Albeza
0505eb3b1d
🐛 Fix pasted gradient fills in text editor v3 (#12516)
* 🐛 Keep text gradients in place when pasting at the caret

Text gradients are stored as fractions of the whole text box. Paste
copied those fractions unchanged, so in a box of another size the
handles and colours moved away from the pasted text.

Copy from the v3 editor now records where each span with a gradient or
image fill starts in its text box, and the box size, in the clipboard
payload. A new WASM export, text_editor_get_range_origin, gives the
start of any text range.

Paste at the caret finds where those spans start in the target and
rewrites their gradients so the handles keep the same pixel offset from
the span. Auto-sized texts use the box size they get when the editor
closes.

AI-assisted-by: claude-opus-5-5

* 🐛 Rebuild text gradient shaders when the text box resizes

Text gradient and image shaders are built into the cached Skia
paragraphs against the text box size. A resize with no content change
reused those paragraphs, so the colours stayed matched to the old box
until a reload.

The text now remembers the box size its shaders were built for and lays
itself out again when that size changes and a span has a gradient or
image fill. Text with only solid fills keeps its cache.

AI-assisted-by: claude-opus-5-5

* 🐛 Keep text gradients in place when pasting on the canvas

A text selection pasted on the canvas becomes a new text whose box
differs from the source, so its gradients moved like they did for caret
paste.

After adding the new text, lay it out, find where its gradient spans
start and move their gradients to keep the same pixel offset from the
span, in the same undo step. The range origin export now takes a shape
id, so it works on texts that are not being edited.

AI-assisted-by: claude-opus-5-5

* 🐛 Keep text gradients in place when pasting copied texts at the caret

Text shapes copied whole, rather than as an editor selection, reached the
caret paste without any geometry, so their gradients still moved away from
the pasted text.

A shape copy now records where the gradient and image spans of each
selected text start, and the box size, by shape id. Paste joins them in
the order of the joined paragraphs, with no origin for texts copied
without one.

Copied texts each have their own box, so every span origin now carries
its own box size instead of one box for the whole payload.

AI-assisted-by: claude-opus-5-5
2026-10-22 13:39:26 +02:00
Andrey Antukh
fcf9c27635 Merge remote-tracking branch 'origin/staging' into develop 2026-10-21 10:14:55 +02:00
Elena Torró
a1606c42ff
🐛 Export bitmaps over the GPU surface limit through the exporter (#12455) 2026-10-20 15:37:07 +02:00
Belén Albeza
ef68304cb8
🐛 Draw masked group inner shadows in PDF and multi-element export (#12468)
* 🐛 Fix render_to_pdf call in the ToUnicode test

The test passed a scale argument that render_to_pdf does not take, so
the render-wasm test build failed to compile.


* 🐛 Draw masked group inner shadows in PDF and multi-element export

The CPU renderer behind PDF and multi-element raster export drew a
masked group's drop shadow but never its inner shadow, so the masked
content came out flat. Wrap the masked content in a layer that draws
the inner shadows over it, as the GPU single export does.


* 🐛 Export every visible inner shadow of a masked group

The workspace skips shadows too small to see at the current zoom, to
render faster. Export has no such need, so draw every visible inner
shadow instead of filtering by scale.
2026-10-20 15:04:54 +02:00
Alejandro Alonso
c0450a77c3
🐛 Limit board guide hit testing to the board frame (#12460)
WASM find_guide_at only checked distance on the guide axis, so board-bound
guides stayed interactive outside their board. Respect frame_range in
GuidePool hit testing so interaction matches the drawn solid segment and
the SVG hit area.

Closes #12322
2026-10-20 09:49:05 +02:00
Belén Albeza
60a94c65cc
🐛 Fix missing letters when copying text from exported PDFs (#12441)
Skia maps ligatures and other substituted glyphs to U+0000 in the PDF
ToUnicode maps. Rebuild those maps from the laid-out paragraphs and
append them as an incremental update.

Closes #12393
2026-10-20 09:32:53 +02:00