Commit Graph

317 Commits

Author SHA1 Message Date
bflatastic aa5e8f46e9 Part of #579: vectorcraft-cli lists the fonts font services load too
The DirectWrite font lister (#594) was installed in the desktop app only:
`vectorcraft-cli` (convert, run, MCP) didn't see fonts that font services
such as Adobe Fonts load on Windows, so its exports set such type in a
fallback font.

- The CLI shares the app's `system_fonts.rs` and installs it first thing
  on Windows (not the win7 build). The text crate stays free of unsafe
  code; the CLI denies unsafe code instead of forbidding it, so the
  shared module's own allowance applies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 05:43:13 +07:00
bflatastic 564a3ae662 Merge #616: Fix #597: an image copied in a browser pastes as the picture, not its address
# Conflicts:
#	ROADMAP.md
2026-10-09 05:26:43 +07:00
bflatastic 54eed6ec4c Fix #597: an image copied in a browser pastes as the picture, not its address
A browser's Copy Image puts the picture on the clipboard with the image's
address as text (and HTML). Paste takes text before bitmaps, as word
processors offer a picture of copied text, so it pasted the address.

- `clipboard::is_address`: text that is only an address (http, https,
  file, ftp, data or blob).
- Desktop paste (Windows, macOS, Linux): such text gives way to the
  clipboard's bitmap when it has one. An address alone pastes as text, as
  before; nothing is fetched.
- Web: a paste whose text is only an address takes its picture file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 05:22:09 +07:00
bflatastic 8d5b52965c Merge #611: M7: per-paragraph paragraph attributes 2026-10-09 05:13:59 +07:00
bflatastic 33f38cef08 Merge #610: Fix Shaper recognition and add editable Shaper groups
# Conflicts:
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
2026-10-09 05:13:59 +07:00
bflatastic 8a18a74daf Merge #612: Fix #593: anchors and handle ends are easier to grab than the segments through them
# Conflicts:
#	ROADMAP.md
2026-10-09 05:13:43 +07:00
bflatastic a0d5d72d90 Fix #593: anchors and handle ends are easier to grab than the segments through them
An anchor was picked within the selection tolerance (3 px), the same reach
as its segments: a press a little off the anchor's 5 px square took the
curve instead.

- `ToolContext::point_tol`: anchors and direction handle ends are picked
  within the selection tolerance, but at least 2 px past the drawn point
  (4.5 px at the default Size 3, more with a larger Size). Anchors are
  tested before segments, so where both are within reach the anchor wins.
- Direct Selection (presses, Highlight anchors on mouse over, blend spine
  points and handles), the Anchor Point, Delete Anchor Point and Scissors
  tools, and the Pen (its Alt conversions; its close/continue reach stays
  5 px unless the points are drawn larger) use it. Segments, objects and
  frame edges keep the selection tolerance.
- Of the topmost path with anchors in reach, the nearest anchor is picked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 05:09:31 +07:00
haven f7761bf5bc M7: per-paragraph paragraph attributes
Each paragraph has its own alignment, indents, spacing, hyphenation, tab
stops and paragraph style (TextObject.paras; para stays paragraph 0's, so
old files and readers work). Layout, editing (split/merge), threads, the
Paragraph/Tabs/Paragraph Styles panels, ranged text.setStyle/setFormat/
tabs/paraStyle.apply, SVG export/import and the fuzzers follow.
2026-10-08 15:07:13 -07:00
bflatastic d9a2cfe7f9 Merge #608: Fix #592: file dialogs on Linux no longer stop the window answering
# Conflicts:
#	ROADMAP.md
2026-10-09 05:03:38 +07:00
bflatastic 37d4477362 Fix #592: file dialogs on Linux no longer stop the window answering
VectorCraft asks for a path where it needs one and goes on with it at
once, so its file dialogs ran on the UI thread. Windows and macOS dialogs
run the window's events meanwhile; on Linux nothing answered the Wayland
compositor's pings while a dialog was open, and Hyprland offered to kill
the window as not responding.

- ui-egui `picks`: every file dialog (open, save, folder, place) goes
  through one place. With the host's `start_pick` (desktop Linux) the
  dialog shows on a thread of its own; what asked (the command, the
  dialog's OK, a panel's or dialog's button) gives up for now and runs
  again with the picked path when the dialog answers. Another dialog
  waits for the open one.
- The desktop app passes its window as the dialogs' parent on Linux (the
  portal can then associate its window, "Unhandled parent window type"
  before) and Windows; macOS shows them as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:59:05 +07:00
David Whiting 7e82dbc05b M2.9: add editable Shaper groups and shape orientation steps 2026-10-08 22:52:23 +01:00
David Whiting cfe8eaafaf M2.9: recognize uneven Shaper squares independently of stroke start 2026-10-08 22:52:23 +01:00
bflatastic 611deeda46 Merge #606: M7: area type auto size and shrink text to fit 2026-10-09 04:52:14 +07:00
haven 674aef95b0 M7: area type auto size and shrink text to fit
Area Type Options gain a fit: none, autoHeight (Illustrator's Auto Size:
the frame's height follows the text after every edit, resolved in
refresh_bounds inside the edit so it shares the edit's undo step) and
shrinkText {minPercent} (beyond Illustrator: overflowing text scales its
sizes, leading and baseline shifts down at layout time by the largest
factor, in 0.1 pt steps of the first run, that fits). The Auto Size New
Area Type preference now applies. text.areaOptions and document.inspect
report overflow and the shrink factor; the Area Type Options dialog
offers fit and first baseline as dropdowns.
2026-10-08 14:48:25 -07:00
bflatastic ef99ab8d1c Merge #603: Part of #372: .ai files write layers and sublayers as PDF layers, hidden ones kept
# Conflicts:
#	ROADMAP.md
2026-10-09 04:46:09 +07:00
bflatastic 50bb36ebc2 Part of #372: .ai files write layers and sublayers as PDF layers, hidden ones kept
A `.ai` file's PDF part is what apps that don't read VectorCraft's native
document open. It had no PDF layers: hidden layers and sublayers were left
out of it and everything else was flat page content.

- `.ai` files (Save As, exports, the CLI) write PDF layers unless
  `createLayers` is false (a PDF standard still decides for itself).
- createLayers writes sublayers too, as optional content groups listed
  under their layer's in `/Order`, hidden ones off with their art.
- A layer's form gets a box round the whole page instead of its art's
  bounds, which readers made a clipping group of, gathering a layer's art
  and its sublayers' in one group.
- PDF import: art inside a group, clip or form that dissolves into its
  parent keeps the layer it was drawn in, so sublayers nested in their
  layer's content import as sublayers.
- Save PDF: "Create PDF layers from layers and sublayers" (ja, es, it,
  pt-br, ru, zh-hans, zh-hant).

Hidden objects (not layers) still aren't in the PDF part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:40:30 +07:00
haven d2bb95daab M7: area type vertical alignment
Area Type Options > Align: Top / Center / Bottom / Justify (AreaOptions.verticalAlign,
LayoutOptions.vertical_align). Each row/column cell is aligned on its own; rectangular
cells shift their lines, other frames and text wrap reflow until settled without
losing text. Justify shares the leftover space equally between line gaps. Area Type Options
shows Align and First Baseline as dropdowns (the parameter dialog's choices table).
2026-10-08 14:28:02 -07:00
bflatastic 030d407f61 Merge #594: Fix #579: fonts activated by font services such as Adobe Fonts are listed on Windows
# Conflicts:
#	ROADMAP.md
2026-10-09 04:18:54 +07:00
bflatastic 5854346053 Fix #579: fonts activated by font services such as Adobe Fonts are listed on Windows
Font services load their fonts with GDI at run time, from files in their
own folders, without installing them: neither the font folders nor the
registry list them, so the font scan never saw them. DirectWrite's system
font collection holds them since Windows 10 1607.

- vectorcraft-text: `set_platform_font_files` lets the app name font files
  the platform knows; each scan and each check for installed fonts asks
  again and reads those outside the font folders. A file named by itself
  is read whatever its extension (font services keep fonts in files
  without one).
- The desktop app lists the local files of DirectWrite's system font
  collection on Windows (`system_fonts.rs`, through the windows crate
  already in the build), read in place.
- The check for fonts installed while the app was in the background runs
  on a thread: DirectWrite's check for updates takes about 50 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:16:14 +07:00
bflatastic f4b4312cc9 Merge #591: Fix #578: a drag on the Color panel's spectrum no longer flickers near white
# Conflicts:
#	ROADMAP.md
2026-10-09 04:13:07 +07:00
BFlat 0869f51360
Merge pull request #587 from Haven-King/feat/text-engine/text-composer-ragged
M7: Every-line Composer for ragged text
2026-10-09 04:12:08 +07:00
haven d2aad22dac M7: Every-line Composer for ragged text
Illustrator's Every-line Composer balances the breaks of the whole
paragraph for left/centre/right aligned text too, not only justified
text. VectorCraft only composed justified area text; ragged text was
always broken greedily.

- Model: ParaStyle.composer (vectorcraft_doc::Composer, re-exported by
  vectorcraft-text), Every-line by default like Illustrator, saved only
  when Single-line. layout() reads it from the object;
  LayoutOptions.composer is now an Option override for tests.
- Composer: the justified gate is gone. Ragged lines never shrink (a
  line that doesn't fit ends the node), badness is 100*r^3 with r = the
  line's leftover space over a rag zone of 1/6 of the width (finite for
  lines without spaces); the last line is free unless shorter than 1/3
  of the width (measured in zones of 1/3), so a lone short word on the
  last line pulls a word down. Hyphens cost 25 in ragged text (50 in
  justified). Knuth-Plass fitness classes with adjacency demerits
  (justified 10000, ragged 1000). Lines past the last width change
  share one node key, active nodes are capped (4096) and paragraphs
  over 200k glyphs break single-line, so work stays bounded; ragged
  tries a tolerance of one rag zone, then any fitting breaks, then
  falls back to greedy.
- text.setFormat {composer: "singleLine"|"everyLine"}; the Paragraph
  panel menu's composer items work with a check mark (Reset Panel goes
  back to Every-line).
- Tests: ragged Every-line moves "or" down in "Destroy all creatures
  with toughness X or less." where greedy leaves "less." alone; the rag
  of long paragraphs is no worse than greedy (sum of squared leftover
  and of neighbour differences); Single-line is greedy; the 10k-char
  layout budget covers ragged text; command test with undo; native
  format round trip (rich_session's text is now Single-line).
  No existing test needed changing: the justified tests and
  hyphenated_layout_adds_hyphens (ragged, now Every-line) still pass.

Auto alignment (start of the paragraph direction) is ragged; a wrapped
line's Mojikumi flush opening bracket gives its half em back to the
composer, as in the line loop; tests cover Auto in both directions.

Layout of 10k chars, release: justified Every-line 5.6 ms, ragged
Every-line 5.8 ms, ragged Single-line 4.8 ms.
2026-10-08 14:03:33 -07:00
bflatastic 2e64786e8c Fix #578: a drag on the Color panel's spectrum no longer flickers near white
The Color panel shows the out-of-gamut and out-of-web warnings under its
proxy only when the colour needs them, so the panel grew and shrank with
the colour. Dragging on the spectrum's pale top edge, a colour out of
gamut added a warning, the spectrum moved down under the pointer, the
pointer then picked white (above the spectrum), the warning went away,
the spectrum moved back up: the colour flipped every frame.

The panel now keeps room for the warnings its mode can show, so its
height no longer depends on the colour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:02:44 +07:00
bflatastic 5a09d1aef8 Type: ligatures drop past -20 or +55 tracking, as Adobe's composers do
#583 kept ligatures within +-50/1000 em. Adobe's composers are described
as dropping them past about -8% and +22% of the word space (John Hudson,
www-style, 2012-07): about -20 and +55 for a typical 250/1000 em space.
Condensed type shows a fixed ligature sooner than expanded type does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:56:10 +07:00
bflatastic e1d560a727 Merge #584: Fix #577: the gradient stop popover stays open after the click that opens it
# Conflicts:
#	ROADMAP.md
2026-10-09 03:55:34 +07:00
bflatastic ba93f62bcb Fix #577: the gradient stop popover stays open after the click that opens it
The Gradient panel's colour chip opens the stop popover on a click. The
popover closes on a click elsewhere, and the opening click itself, which
is released in the frame the popover first draws and lies outside it,
counted as one: the popover closed at once (a double-click got through,
as the popover lets the double-click that opens it from the canvas pass).

The popover now records the frame it first drew in and only a click in a
later frame closes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:48:27 +07:00
bflatastic 04827da657 Merge #581: Add Russian (ru) interface translation 2026-10-09 03:48:19 +07:00
MaksPV 89b10cce52 Add Russian (ru) interface translation
Add ru.tsv with the full interface coverage: every menu string, every
tl! literal, the plurals (one/few/many) and the status/error messages
(@msg). Registered as complete_menus with the Russian plural rule;
every ru-* locale resolves to it. Keeps the product, workspace and
perspective preset names in English like the other complete catalogs.

Tests: russian_reads_as_russian, russian_plurals_have_three_forms,
russian_glyphs_are_available_without_system_fonts; ru added to
KEEPS_MENU_NAMES and COMPLETE_MESSAGES.

Clean-room: written from the meaning of the English labels in the
vocabulary Russian-speaking users of vector illustration software know
(Слой, Образцы, Обработка контуров, Монтажная область, Обводка,
Заливка), matching the Russian catalog of the sibling app PhotoCraft.
No other application's localisation was copied.
2026-10-08 23:44:45 +03:00
bflatastic c7094320b3 Merge #580: Fix #522: SVG round trips keep hidden objects and data-* attributes
# Conflicts:
#	ROADMAP.md
2026-10-09 03:41:22 +07:00
bflatastic 2a35c4a82f Fix #522: SVG round trips keep hidden objects and data-* attributes
An SVG used as an animation rig lost its hidden alternates and their
rotation pivots: the exporter left hidden objects out (only hidden layers
could be kept), and the importer dropped data-* attributes.

- SVG export: hiddenLayers (on by default for document.save, as before)
  now keeps hidden objects too, each written display="none" on its own
  element (path, image, text or group). Exports still leave them out
  unless asked; the CLI's convert asks, as a conversion keeps the
  document.
- An object's own data: ObjectAttributes.data, the data-* attributes in
  order (ours, data-name and data-vc-*, aside). The importer keeps them on
  the objects and layers they belong to (at most 64 per element), the
  exporter writes them back, the native format saves them (missing in
  older files), and object.setProps {data: {key: value|null}} sets or
  removes them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:36:08 +07:00
bflatastic 4051a895d7 Merge #574: Fix #526: the exports and the CLI write .ai as Save As does
# Conflicts:
#	ROADMAP.md
2026-10-09 03:26:21 +07:00
bflatastic 1e2cb082ba Fix #526: the exports and the CLI write .ai as Save As does
file.saveAs and document.save wrote a PDF-compatible .ai, but
document.export (and with it the CLI's run --export x.ai and convert
in.svg out.ai, and MCP's export) refused it as a format that can only be
opened, and --help didn't list it as writable.

The .ai encoder is now shared: save.rs's ai_params and encode_ai (a PDF
of every artboard with the PDF options, carrying the native document,
blank pages with pdfCompatible: false) serve Save As and the exports
alike. The .ai format is writable, so document.formats, --help and the
MCP export tool list it; an export leaves the document's own path alone.
Export As and Export for Screens keep leaving .ai to Save As.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:18:07 +07:00
BFlat 207bd09635
Merge pull request #572 from storytold/fix/issue-569
Fix #569: Shape Builder merges leave no stray edges in their outlines
2026-10-09 03:12:05 +07:00
bflatastic 2023a47188 Fix #569: Shape Builder merges leave no stray edges in their outlines
Merging regions united the faces' outlines with a boolean union. Each
face had been refit on its own, so two faces' shared edge could come out
a hair apart: the union then kept the edge between them, or a spur of
it, and a stroked merged shape (unfilled art) showed a stray line inside.
Subtracting the merged shape from the others left slivers along it too.

pathops::FaceMerger merges faces from the planar arrangement itself,
made once per merge: their contours share their edges exactly, so the
edges between merged faces vanish. The Shape Builder builds the merged
shape that way, and what is left of each shape from its faces not merged
(rather than subtracting the merged shape from it). Selections with open
paths keep the old union.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:04:48 +07:00
David Whiting cb61bdf61f M14.8: dismiss macOS native menus after selection or outside input 2026-10-08 20:50:54 +01:00
haven 5e7af3f83c Placed documents: Place's Link tooltip, Break Link in the Links panel 2026-10-08 12:20:59 -07:00
bflatastic 48f07958a9 Merge #565: Fix #541: a click on a selected object makes it the key object
# Conflicts:
#	ROADMAP.md
2026-10-09 02:11:01 +07:00
bflatastic f31d6874aa Fix #541: a click on a selected object makes it the key object
With the Selection tool, a click (no drag, no modifier) on one object of a
selection of several makes it the key object (select.key); a click on the
key again lets it go. The key object is drawn with a thicker outline,
Align aligns to it (the Align panel shows Align to Key Object while there
is a key, and choosing Selection or Artboard lets it go), and
object.align without `to` aligns to the key when there is one, so the
Control bar's and the Properties panel's Align buttons do too. With a
spacing, Distribute Spacing keeps the key object still and spaces the
others from it, and it reports too few objects instead of indexing an
empty list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:08:56 +07:00
bflatastic c44572d272 Merge #562: Fix #555: the font menu scrolls only its list
# Conflicts:
#	ROADMAP.md
2026-10-09 02:05:49 +07:00
bflatastic 1b43d8b8f9 Fix #555: the font menu scrolls only its list
egui's combo box puts its own scroll area around a combo's list, so the
font menu, which scrolls its families itself, had two scrollbars: once
the families reached their end the wheel scrolled the outer one, taking
the search field and the filters out of view.

The font menu (widgets::combo with searchable) now opens its list in a
popup of its own, drawn like the combo box, without that outer scroll
area: below the button, or above it when there is more room there. The
families list fills the popup's room under the search field and filters,
which stay at the top while the list scrolls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:54:11 +07:00
bflatastic 2139c4d661 Merge #558: Fix #550: an SVG of chosen artboards holds only their art
# Conflicts:
#	ROADMAP.md
2026-10-09 01:45:04 +07:00
bflatastic b832599a47 Fix #550: an SVG of chosen artboards holds only their art
Exporting one artboard of many to SVG wrote the whole document's art into
that artboard's file, the other artboards' art outside its viewBox: an
editor opening it showed every artboard's art. A file for an artboard
named in the export (Use Artboards with a range or All, Export for
Screens) now holds the objects over that artboard only, at any depth of
layers, as DXF's crop leaves them; objects reaching into it stay whole
and a clipping layer keeps all of its objects. An SVG with no artboard
named (Save As) still holds the whole document.

Part of #546: Export As with a range writes the chosen artboard alone in
SVG, DXF, PDF and PNG, each file with that artboard's art (tested from
the dialogs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:38:38 +07:00
bflatastic d7b4d85d3c Merge #557: Fix #525: Image Trace of a large photo no longer crashes the app
# Conflicts:
#	ROADMAP.md
2026-10-09 01:35:31 +07:00
bflatastic 06242cd3ab Fix #525: Image Trace of a large photo no longer crashes the app
Tracing a 2048 x 2048 photo with High Fidelity Photo made 733,000 paths
and 7.8 million anchors. The selected result's outlines and anchor
squares then made an egui mesh of 1.8 GB, over the GPU's largest buffer:
wgpu panicked while painting and the app quit (or ran out of memory
first, at 6.4 GB).

- Canvas: a selection with more than 100,000 anchors shows its bounds
  instead of every outline and anchor, and the hover highlight skips
  such an object. The Contextual Task Bar and that outline read the
  selection's bounds from a per-revision cache (VectorcraftApp::
  selection_bounds): a selected trace drew a frame in 20 ms, not 170.
- Image Trace: the colour layers are traced in parallel (26 s to 1.9 s
  for that photo; the web traces them in turn). trace_within stops once
  the paths pass an anchor budget; imageTrace.make allows two million
  and otherwise reports the trace as too complex, with what to change.
  Images over 64 megapixels are refused from their header, before they
  are decoded.
- The trace crate's messages are translated (es, it) and scanned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:18:03 +07:00
bflatastic 086d6287be Merge #552: Fix #543: Object › Image Trace › Make traces with the Default preset
# Conflicts:
#	ROADMAP.md
2026-10-09 01:03:27 +07:00
bflatastic f8004250fb Fix #543: Object › Image Trace › Make traces with the Default preset
Make… and Make and Expand… opened the generic parameter form, whose
preset was a text box holding a preset's name (Black and White Logo,
6 Colors), so the other presets looked missing. As in Illustrator, Make
and Make and Expand now trace at once with the Default preset, as the
Control bar's Image Trace button does; the button's arrow and the Image
Trace panel's Preset list choose among the 12 presets. The catalogs drop
the two "…" labels; Czech gets Make and Expand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:01:02 +07:00
bflatastic 03288f34dc Merge #549: Fix #545: Windows renders through DX12 before Vulkan
# Conflicts:
#	ROADMAP.md
2026-10-09 00:58:47 +07:00
bflatastic aa4fa48751 Merge #548: Part of #516: the in-window menus' Select All, Cut, Copy and Paste act on a focused field
# Conflicts:
#	ROADMAP.md
2026-10-09 00:58:46 +07:00
bflatastic 165db95dd4 Merge #533: Control bar: Transform link opens the Transform panel; reference point locator redrawn 2026-10-09 00:58:40 +07:00
bflatastic ac043129c8 Fix #545: Windows renders through DX12 before Vulkan
Windows lists each GPU under both backends, and Vulkan came first. On an
Intel GPU its driver made the whole window flicker black while the
pointer moved; the same machine was fine with WGPU_BACKEND=dx12 or gl.
adapter_order now ranks a GPU's DX12 adapter before its Vulkan one, after
the power preference, so the GPU chosen doesn't change, only how it is
driven. Vulkan stays the fallback (a failed DX12 start restarts on it)
and WGPU_BACKEND=vulkan still picks it. Elsewhere there is no DX12
adapter, so nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:56:49 +07:00