Commit Graph
247 Commits
Author SHA1 Message Date
Triwarr d1e95f57a8 PDF: share embedded fonts and tighten text checks
- Register imported fonts in photocraft_text::shared() so the app can
  re-lay text after edits or DPI changes. Prefer an installed full
  family, and register an embedded face under its PostScript name
  (key-suffixed when two faces share it) so a subset cannot shadow it.
- Warn that text using PDF-embedded fonts may change font when edited or
  reopened; the document format does not carry the bytes yet.
- Reject mirrored text and b == -c matrices; scale the tolerance by the
  em size so 90-degree text stays editable. Tests for rotated and
  mirrored text.
- Use pdfcer's 0.30 em line tolerance.
2026-11-08 05:02:23 +01:00
Triwarr a18268d0d3 PDF: editable text pass and review fixes
- Text: build one TextLayer per line/style run from the Device's glyph
  draws. Derive the family from the PostScript name (subset tag, style
  suffix, MT/PS, camel spacing); italic from the font flags/attributes
  or name; bold from the weight or name; pair fill+stroke glyphs as fake
  bold (Tr 2/6) instead of a second run.
- Require an embedded font with a PostScript name; standard-14/Type 1
  substitutes and Type 3 text fall back to the raster page.
- Register embedded font bytes in the text engine, use the family the
  font reports, and render text caches on import. Tests compare the
  text composite with the raster page.
- Review fixes: accept a page-sized no-op clip; draw zero-width strokes
  as 1 px hairlines and use 1 px as the dash base; reject non-uniform
  stroke transforms; repeat odd dash arrays and keep zero dashes;
  ExtGState ca/CA already reach the colour alpha (tested); note that
  catch_unwind only guards unwinding profiles.
2026-11-08 05:02:23 +01:00
David Mann 1e82972ee1 Merge pull request #9667 from davidmann4/fix/system-tools-full-path
Windows: run explorer.exe and reg.exe by full path (#9666)
2026-11-06 17:26:45 +01:00
Khawar Zamman Wani 0a9717fa64 PDF export: keep the top text and shape layers as vectors, with selectable text (#10355)
* Skip compositing for geometric measurements

* Preserve simple PDF shapes and glyph source ranges

* Preserve text and shapes in PDF export (#10269)

- Export topmost simple text layers as vector glyph outlines with selectable text
- Embed ActualText unicode span and invisible text operators (mode 3 Tr) for 100% extraction
- Support stroked shapes in vector PDF export (centered solid strokes, dashes, line caps/joins)
- Retain glyph style indices in text rendering outlines

* Drop a product name from the glyph outline doc comment

* PDF export: keep path text and synthetic bold raster

* Fix PDF Unicode selection and respect font embedding permissions

* Render the text cache in PDF raster fallback fixtures

* Fix PDF vector selection eligibility and finite text matrices

* Preserve upper PDF vectors and reject overflowing curve controls

* Fix text shape import in PDF regression test

* Cover PDF Unicode map blocks and leading selection whitespace
2026-11-05 03:33:35 +01:00
Tareq HalabyandFredApps 2472b4a46b PSD: keep stylistic sets in Txt2 for PhotoCraft-native documents (#10313)
* PSD: keep stylistic sets in Txt2 for PhotoCraft-native documents.

needs_txt2 now covers Txt2-only OpenType flags (ss01–ss20 and non-default TXT2_FEATURES). Fresh documents get a framed point-text object with full style runs so Set 1/5 round-trip on save/reopen (#10308).

* Txt2: seed fill colour and font list on PhotoCraft-native blocks.

Fresh style runs get a /53 paint dict, and an empty font list gets a CoolTypeFont entry so index 0 resolves (CodeRabbit on #10313).

* Txt2: tag SimplePaint and ensure numbered /0 on legacy blocks.

CodeRabbit: write /99 /SimplePaint on fresh /53 dicts, and create the numbered resource dictionary before pad_frames when a legacy block lacks it.

* Txt2: treat a missing stylistic-set mask as 0 when comparing styles

A style run without key 85 used to agree with any ssNN mask, so enabling
ss01 could leave the file's runs in place on save. Align with import's
default of 0. Also restore the G8 gaps note after rebasing onto main.

* style(io): rustfmt psd_kerning stylistic-sets test

* Txt2: keep Photoshop /0 stable when seeding fonts and frames

Only seed a CoolTypeFont list and inject /0/8 when the block is
PhotoCraft-native or already had those keys / needs new frames, so
unedited Photoshop Txt2 saves stay byte-identical (#2374).

* style(text): rustfmt oversized-template assert

* PSD: keep the Txt2 scaffolding to PhotoCraft-native blocks

A file's own Txt2 block (any Photoshop era) is written as before: no
numbered /0 or /0/8 frame list is injected, a layer without a template
object gets its stub, a run without /53 isn't given one, and no
minimal style base is used. Only a block PhotoCraft creates itself
(prev.is_none()) gets fresh point-text objects, minimal runs, a seeded
font list and a frame list.

Fresh objects now write one /5 paragraph run per paragraph (UTF-16
lengths, checked conversion), as edited objects are refitted.

Tests: Photoshop blocks with an empty or missing /0 get no native
scaffolding; the oversized-template test is back to main's assertion;
fresh paragraph runs; the PSD round trip checks ss01 and ss05 on every
run.

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-04 13:28:06 +01:00
Dylan M. Taylor d5bd171810 text: merge a run of union components once when fitting area type (#11065)
AreaShape::spans folded each Combine/Join component into the growing
interval list with a full merge, so a shape of many disjoint components
cost components x intervals per row, repeated for every row the layout
tries. Gather a run of union components' intervals and merge them once
(sort and sweep) when the run ends; other operators fold as before.
2026-11-03 20:23:51 -05:00
Dylan M. Taylor 282be0927e text: group glyphs by colour once instead of rescanning per colour (#11069)
The rasterizer found each glyph's colour with a linear search of the
colours seen so far, then walked every glyph and decoration twice per
colour (bounds, then coverage) to draw that colour's items. A text layer
with a colour per letter cost colours x glyphs before drawing anything.

Build each colour's glyph and decoration lists in one pass, keyed by the
colour's bits, and draw each pass from its own lists.
2026-11-03 20:23:29 -05:00
Dylan M. Taylor 3fd5304dfe text: find paragraphs and trailing blanks without rescanning (#11045)
Layout looked up each paragraph's style with a reverse scan of every
paragraph start, tab placement re-checked the whole rest of the line for
every cluster, and PSD import found each character run's paragraph by
summing paragraph lengths from the first one. Each was quadratic in
counts a file controls.

Use the existing binary search over run starts for paragraphs, one
reverse scan for the line's last inked slot, and precomputed paragraph
ends with a binary search on import.
2026-11-03 19:35:50 -05:00
d21c403e5f Type: add OpenType Numerals submenu (figure styles) (#11051)
* Type: add OpenType Numerals submenu (figure styles)

Type › OpenType › Numerals sets lnum/onum/pnum/tnum as a radio style
(Default through Tabular Oldstyle). Checkmarks follow the selection
start; TYPE-218-5 scorecard/gaps updated; parity floor 640.

* refactor(engine): one style-at-character lookup for the OpenType menus

feature_on_at and numerals_style_at each walked the runs the same way;
they now share style_at. NumeralsStyle::from_id and numerals_style_of
read the existing tables instead of repeating them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(text): persist OpenType numeral tags through PSD EngineData

Map lnum/pnum/tnum as EngineData bools and the width×height combos as
FigureStyle (Krita/Illustrator), so Numerals choices survive PSD round-trip.

* docs(gaps): note Numerals PSD figure-tag round-trip

* revert(text): keep PSD EngineData to the keys Photoshop writes

Backs out the PSD half of 18610324 and its gaps note (58813de3); the
engine reformat in that commit stays.

- `LiningFigures`, `ProportionalFigures` and `TabularFigures` are not
  Photoshop EngineData keys (the commit says Photoshop only has
  `OldStyle`), so writing them puts keys of our own into users' PSDs.
- The `/FigureStyle` code table (1-4) has no oracle: no Photoshop-authored
  file in photocraft-corpus, and neither ag-psd nor psd-tools reads it.

AGENTS.md rule 3 asks for public specs or observation. PSD persistence
of the numeral styles can come back with a Photoshop-authored oracle.
The Numerals menu still round-trips through .pcraft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(text): lock PSD EngineData to Photoshop OldStyle for numerals

Document AGENTS.md clean-room: only onum survives TySh round-trip; lnum/pnum/tnum stay .pcraft-only until a Photoshop-authored corpus oracle.

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:55:35 +01:00
David Mann 9ec0f51dc1 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	crates/ui-egui/src/i18n/nb.tsv
2026-10-31 13:55:06 +01:00
aiongg 6d737c219e docs: tidy agent, contributor and release docs 2026-10-31 04:17:45 +00:00
David Mann b2db24400e Merge main at b1cfff9d3 into integration batch 10
Conflicts resolved as unions of both sides: codecs re-exports (PngRows + TiffBands), smart_cmds source imports
(main's source_surface_from_buffer and cache API with #8571's svg_source_image_in/convert_svg_source; main's
duplicate rendering import dropped), io flat.rs test modules (#8536's banded TIFF tests and main's PNG ones),
menus/invoke.rs (#8571's smart SVG intercept and main's layer.selectUnderCursor), state.rs (#8311's
type_box_overflow and main's type_insertion_style), the canvas_geom test keeps the is_finite guard, docs
status lines keep both change lists, i18n catalogs keep both sides' rows (hi.tsv: main's wording where both
added the same string).
2026-10-30 14:28:02 +01:00
David Mann fea300dd4c Merge origin/main into integration batch 10 2026-10-30 01:47:01 +01:00
FredApps c93c0bb36d Merge main into trimmed integration; reconcile blur, pattern and Liquify behavior 2026-10-29 23:47:53 +01:00
David Mann 669b8c974e Merge PR #6169: Preserve imported PSD font metadata during text edits 2026-10-29 19:46:11 +01:00
FredApps 255ecc81d0 Remove 21 requested PR contributions from the 108-PR integration
Retain 87 PRs. Reconcile overlapping integration fixes and update testing scope.
Removed: #7153 #7742 #5493 #9282 #3455 #9172 #3139 #3967 #4783 #5453 #8347 #1991 #3345 #4042 #4305 #4883 #4890 #5090 #6621 #5870 #5939.

Combined CI pending for this source; historical portable artifact is not applicable.
2026-10-29 18:49:17 +01:00
Federico Olivares 796ef557cc Merge main into the integration batch
Main's noop_step now takes the step's params (an Arrange that cannot move the layer is a no-op too); it is public for the CLI's run and batch, which pass them.
2026-10-29 14:19:54 -03:00
David Mann d7ca7a0cf3 Merge PR #8571: Edit embedded vectors with original-format save-back through VectorCraft 2026-10-29 18:15:47 +01:00
David Mann afacb057f7 Merge origin/main into pr-8571-fixup
Conflicts: svg.rs (main's bufread inflate and shared render helper, kept
with the PR's bounded raster path), psd_import.rs (the PR's vector
preview moved into main's smart_content), menus/invoke.rs (the SVG
intercept moved into main's split invoke_unguarded), rendering.rs and
the ui-egui import lists (main's explicit imports), Cargo.lock, docs
status lines (unions).
2026-10-29 16:38:24 +01:00
a005704eda Integration: overflow mark is a + in the enlarged lower-right handle (#8311)
The reference application marks a paragraph box that can't hold its text
with a black "+" inside an enlarged lower-right handle; nothing is drawn
below the box. The PR drew a red disc 0-9 px below the box at a fixed
screen offset, so on a rotated layer it came loose from the box, a click
on it either grabbed the corner handle or ended the edit, and its hover
card popped up while dragging that same corner to enlarge the box.

Now the lower-right handle grows to 11 px and carries the black "+",
centred on the transformed corner, and the whole enlarged handle grabs
that corner, also in a rotated view. The disc, the hover card (state, its
tooltip row in every catalog), the GPU pixel test and its pollster
dev-dependency are gone; detection (`TextLayout::overflows`) and the
`typeBoxOverflow` inspect flag stay. docs/ui-parity.md: the overflow row
no longer claims box threading, the Guides row the PR duplicated is
removed, and the history has one row in date order.

Tests: `an_overflowing_box_shows_a_plus_in_its_enlarged_lower_right_handle`
(upright and rotated box; fails on the PR head with a 7 px handle) and
`the_whole_enlarged_overflow_handle_grabs_in_a_rotated_view`.

Co-authored-by: VladimirPozdnyakov <49595925+VladimirPozdnyakov@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:57:27 +01:00
David Mann 93f8cfd47a Merge main into #8311 (type_tool split into tools/type_tool, control split into control/) 2026-10-29 13:30:46 +01:00
FredApps c3d1e816c9 Reconcile integrated UI ownership, gestures and text regressions 2026-10-29 09:56:08 +01:00
FredApps 4f23f459ba Merge approved PhotoCraft PR #3464: fix(vector): target selected saved paths and own Pen gestures
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Reconcile captured Pen and Freeform targets with current resumed-subpath, Type Path, Path Operation and draft recovery behavior. Valid source-owned tab switches commit to captured original target; invalid snapshots cancel with a diagnostic; refused commits retain draft and suppress automatic retries. Preserve current Saved(index) API and hidden-work-path protections, register all new ownership/gesture tests and add original-target tab-switch/deleted-target cases. Includes verified compile/API corrections from prior checkpoints.
2026-10-29 06:33:02 +01:00
FredApps c9430c92e3 Merge approved PhotoCraft PR #3748: control: suggest near matches for unknown command ids and methods (#3674)
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Add bounded deterministic suggestions via current error/session/control modules while preserving authorization/modal checks. Register hints and all original tests; fix scrollbar choice import and updated PSD image fixture field found by 100-head check.
2026-10-29 06:14:31 +01:00
FredApps 1c6acdf90f Merge approved PhotoCraft PR #4763: refactor(liquify): reuse native brush resizing and anchored HUD feedback
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Use shared zoom-corrected brush resize and HUD while preserving immediate press ownership and anchored outline when hover is absent. Keep original resize regressions. Fix duplicate identical Dissolve test registration and missing test-only UnicodeString import reported by 94-head check.
2026-10-29 06:11:12 +01:00
FredApps 7836d4b9e6 Merge approved PhotoCraft PR #4783: Fix/4758 type extreme finite geometry 20261013s1215
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Apply stricter finite 1e9 geometry bound to current parse/import safety paths; prevent corrected legacy origin bypass and retain current full PSD unsafe-import warning/raster fallback regression. Keep split import/build modules and port new overflow tests into import module.
2026-10-29 06:10:30 +01:00
FredApps 78f9c137b3 Merge approved PhotoCraft PR #6621: Crop: right-click on the pending crop opens the crop menu (#6561)
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Extended current crop menu with Reset and shield toggles, preserving newer preset management, overlays, ownership checks and no-menu-during-gesture logic. Ported reset-frame no-op/undo semantics into current CropState/release and crop commit; retained original regression cases in split modules.
2026-10-29 05:29:44 +01:00
FredApps a62b9ee217 Merge approved PhotoCraft PR #8347: Type: Roman Hanging Punctuation (trailing marks hang, don't steer the alignment)
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Ported hanging punctuation into current line placement and kerning modules, preserving forced breaks, bounded edge-tip row merging, justified-last-line behavior, area exclusions and independent Roman orientation shaping; kept PSD/engine/UI regression coverage.
2026-10-29 05:22:57 +01:00
FredApps 2e2eec3a43 Merge approved PhotoCraft PR #9478: fix(text): vertical type sets roman letters upright, one per em, as Photoshop does (#9470)
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Ported upright Roman classification, independent unkerned/unligated em advances and tab reflow into current Para/VerticalPiece shaping modules; preserved area and paragraph flow; retained vertical geometry and PSD BaselineDirection regressions.
2026-10-29 05:18:29 +01:00
FredApps 555af65f07 Merge approved PhotoCraft PR #9523: Fix Type on a Path wrapping and caret positioning across closed-path seams
Preserve current module structure and port the approved source/test changes to unique current locations.
2026-10-29 03:59:21 +01:00
David MannandClaude Opus 5.5 fab71caf80 Web build: the interface language's served face goes first in the UI stack
Served script fallbacks were appended to every UI font family in the
order they arrived. A Japanese and a Simplified Chinese face both cover
Han, and egui draws a character with the first face in the stack that has
it, so a Chinese interface drew Han with the Japanese face when that one
had arrived first (fetched for a Japanese text layer, say). ui_faces now
puts the interface language's own served face first, so the stack a
language change rebuilds follows the language; and when that face arrives
after another served face, add_ui_face rebuilds the stack the same way
instead of appending. served::fallback_for_language gives the face.

This orders the served faces only; with an interface language that has
no served face (English), they stay in arrival order.

Review finding on #10006.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:45:35 +01:00
Federico Olivares 5a6270921c Merge main into the integration batch
Main now carries #9031 (Halftone) and #8923; this branch keeps only what it adds on top: the guard for a setting beside a Gallery stack's effect list, its test, and the PSD open warning for patterns that paint nothing.
2026-10-28 11:17:59 -03:00
Dylan M. Taylor ab9b27b80d Restore rationale comments dropped by the Clippy refactors 2026-10-28 02:13:42 +00:00
Dylan M. Taylor 9800f56dfb Refactor text for strict Clippy checks 2026-10-27 23:47:07 +00:00
David Mann 33e3fa2f8e Merge PR #6526: Font menu: favourites, recent fonts, class and similar-font filters, live preview 2026-10-27 15:53:56 +01:00
e3b2854838 Integration: font menu fix-ups after merging main (#6526)
- Character section: a style picked under a family's ">" with type layers
  selected set the layers but not the style for new type (the options bar
  did both). Pick "Inter > Thin" in Properties > Character: new type stayed
  Regular.
- Faux bold: a pointed tip moved r/sin(half its angle) along its bisector,
  uncapped (3.9 r at 30 degrees, an A apex grew a spike), and tips sharper
  than about 20 degrees did not move at all. Every tip now moves at most
  2r, continuously; inner corners (the crotch of a V) keep their exact
  miter.
- Faux bold: each point searched past runs of coincident points for its
  neighbours, O(n^2); a contour of 50 000 copies of one point took ~8 s per
  glyph. Neighbours now come from one O(n) pass over the runs.
- prefs.reset {"path":"type"} and Preferences > Type > Reset Section wiped
  the favourite fonts; they are kept now (type.favoriteFonts by name, or
  Reset All, still clear them).
- An expanded family whose own row scrolled out of view lost its style
  rows, so the list below jumped up.
- Show Similar Fonts re-sorted around each font an arrow step applied, so
  Down went back and forth between two fonts; the list now compares with
  the font current when the filter was turned on (or the menu opened).
- Docs: an uninstalled favourite is kept but not listed; FAUX_BOLD_RADIUS
  and the raster budget describe the thickened outline; the unused
  "Recent font list" rows are gone; docs/scorecard.md regenerated (the
  favourite fonts are one more setting).

Co-authored-by: D-monized <341904702+D-monized@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:52:20 +01:00
Federico Olivares e173a9be17 Merge main into the integration batch
Main reorganised the source tree (command modules under engine/src/cmd, tools and dialogs under ui-egui/src, large files split into modules, inline test modules moved to their own files). This branch's changes to the moved and split files are ported to their new locations.
2026-10-27 10:44:16 -03:00
jonathanwxh-cell 8286677a1f Merge upstream main and resolve conflicts for PR #6169
Preserve the PR's intended behavior and regressions while retaining upstream module extraction and changes. Formatting and independent static review passed.

Local verification: 247 text tests passed (5 ignored), including reordered duplicate-font metadata regressions. Clippy and WASM did not start because the local disk safety threshold was reached.

Local UI verification is incomplete. Hosted CI must establish the remaining gates; this commit does not claim the PR is ready to merge.
2026-10-27 19:37:33 +08:00
Don Omondi 285770532b Merge main into embedded vector save-back
Resolve conflicts with the engine's cmd/ module move and the per-crate
integration test binaries. Run a selected VectorCraft.app bundle directly
(identified by bundle id) and reject nested gzip in bounded SVG parsing.
2026-10-27 11:59:56 +03:00
David MannandClaude Opus 5.5 a538495ef3 Merge main into fix/system-tools-full-path
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 03:08:11 +01:00
David Mann 6566789ac9 Merge origin/main into pr-6526-fixup 2026-10-27 02:23:26 +01:00
aiongg 96e670150f refactor(text): split layout.rs into layout/ 2026-10-26 22:54:56 +00:00
aiongg 57bfd18bd7 refactor(text): split psd.rs into psd/ 2026-10-26 22:54:56 +00:00
aiongg 817d9e3b46 refactor(text): move font_names_tests.rs to fonts/font_names_tests.rs 2026-10-26 22:30:14 +00:00
aiongg dead547079 refactor(text): move psd_path.rs tests to psd_path/tests.rs 2026-10-26 22:30:12 +00:00
Don Omondi c129dd50e0 Merge main and preserve original-format embedded vector save-back 2026-10-27 00:45:48 +03:00
skylar taylor-barrick d641b96a52 Merge pull request #9518 from storytold/codex/egui-037-photo-followup-20261025
Preserve locale-aware CJK fonts and migration regressions after #9363
2026-10-26 13:16:03 -06:00
Federico Olivares 8aaa03e2db Merge main into the integration batch 2026-10-26 11:52:45 -03:00
David MannandClaude Opus 5.5 6c2e90c8bb Address CodeRabbit review: Windows tests follow the SystemRoot fallback
The tests built the expected explorer.exe / reg.exe path from SystemRoot with unwrap(),
so they panicked when it was unset and ignored the C:\Windows fallback for an unset or
relative value. The rule is now a small pure function windows_root(value) in each crate;
the tests use it and check the absolute, unset, relative, rooted-without-drive and empty
cases without touching the process environment.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 14:22:38 +01:00
David MannandClaude Opus 5.5 e47b2d8514 Windows: fall back to C:\Windows when SystemRoot is unset, as printing and fonts do
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 13:18:58 +01:00