Commit Graph
117 Commits
Author SHA1 Message Date
aiongg 576ba0e02b Integration: track t12
# Conflicts:
#	contributors/people.toml
#	crates/compose/src/lib.rs
#	crates/compose/src/tests.rs
#	crates/engine/src/print_cmds.rs
#	crates/engine/src/print_cmds/tests.rs
#	docs/target-app-parity.md
2026-10-22 15:38:33 +00:00
aiongg 5fd5b7adcb Merge PR #7152: Type: register craft fonts of every script, not only the Japanese ones 2026-10-22 02:25:56 +00:00
aiongg bf68c22689 Merge PR #6001: Text: admit the rasterizer's peak working bytes (#4979) 2026-10-22 02:23:35 +00:00
aiongg 9e97c9bd79 Merge PR #8146: fix(psd): an edited type layer updates the the reference app-era Txt2 objects too (#6971) 2026-10-22 02:23:11 +00:00
aiongg 11c3f1ec92 Merge PR #5180: Type tool: click a path to start type on it (TYPE-218-1, #218)
contributors/people.toml: union of both sides' entries.
crates/ui-egui/src/canvas.rs: main's Move cursor (arrow) plus the PR's Type path-cursor arm.
crates/ui-egui/src/type_tool.rs: main's has_transform in draw_overlay; the PR's earlier `shape` binding kept, the duplicate binding dropped.
crates/ui-egui/src/vector_ui.rs: main's draw_overlay (paths already show with any tool), the PR's vector_tool line dropped.
docs/gaps.md: header line from main, G8 text from main with the click-on-path clause, change-log lines of both.
2026-10-22 02:22:58 +00:00
aiongg 0fe1d3233d Merge PR #5907: Text: apply anti-alias mode shaping when rasterizing type (#5902) 2026-10-21 21:56:20 +00:00
David Mann 27b8d133d8 Merge PR #4771: Fix mixed-order layered TIFF type geometry and unsafe text fallback 2026-10-21 11:28:23 +02:00
VishnuByrraju 013863da95 fix(psd): an edited type layer updates the Photoshop-era Txt2 objects too (#6971)
Older Photoshop keeps each type layer's text object at
/DocumentObjects /TextObjects (named keys, run lengths under /Length) and
re-renders from it. type.edit refreshed TySh and the numbered /1 /1 list but
left the old text there, so the layer came back with its old text.

build_txt2_at now refits that entry like edited_object does for the numbered
ones: the model text, one ParagraphRun run per paragraph, every other run
array cut or extended to the new UTF-16 total. Entries are only edited in
place (never grown), a malformed run array leaves the entry as the file has
it, and an unchanged block is still written back byte for byte. A legacy
block holding both lists with a stale older copy is repaired on re-save.
2026-10-21 10:39:00 +05:30
aionggandGitHub b097800e78 Merge pull request #8095 from aiongg/integration-7
Integration batch: 9 PRs on main
2026-10-20 20:44:51 -04:00
152c9677da Integration: keep unreadable canonical type as a type layer (#4771)
A normal big-endian TySh that the typed reader rejects (an unknown text
descriptor version, a stub from another writer) opened as a text layer on
main, with the best-effort text and its TySh saved back verbatim. With the
PR it became a raster layer, and the PSD export strips TySh from rasters,
so open + save turned the type layer into pixels for Photoshop. Now only
foreign-order blocks and unsafe transforms become pixels; the old fallback
runs under the same transform gate (shared as
photocraft_text::psd::transform_is_safe). The warning no longer claims the
type data is kept.

Test: canonical_type_the_typed_reader_rejects_stays_type_and_saves_its_block
(descriptor version 17, ordinary matrix) fails without this change.

Co-authored-by: Sean Lavery <702713+TheSeanLavery@users.noreply.github.com>
Co-authored-by: Tanish Manku <13985398+tanish2k09@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 01:14:50 +02:00
David MannandClaude Opus 5.5 d41aae7849 Integration: merge main, resolve conflicts (#4771)
crates/text/src/psd.rs: keep both appended test modules (transform_safety_tests
and main's nonfinite_tests). docs/file-format-parity.md: add the #4758 item to
main's current change line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 00:54:54 +02:00
David Mann 0cbfb859ed Merge PR #6169: Preserve imported PSD font metadata during text edits 2026-10-20 22:39:15 +02:00
David Mann 2adfe0c4e2 Merge PR #5807: Type: keep superscript and subscript when a text layer is re-rendered (#5645) 2026-10-20 22:39:12 +02:00
aiongg 44ef277439 Merge PR #6693: macOS: list fonts activated outside the font folders (#6683) 2026-10-20 18:39:29 +00:00
aiongg b82540845f Merge PR #7773: Give a new type layer a full Txt2 object and its own frame 2026-10-20 18:39:28 +00:00
NetterSilverandClaude Opus 5.5 e86e6910ae fix(psd): bound the base style run a save copies per run
The base run comes from the file and is cloned once per character run, so
it now has to fit the template node budget, and each font is looked up
once per name. Also names the reopened-layer tuple in psd_kerning for
clippy::type_complexity.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 20:07:39 +02:00
NetterSilverandClaude Opus 5.5 20c9103148 fix(psd): write complete Txt2 style runs from the layer, number only file indices as kept
Photoshop reads the character style from Txt2 and quit on the bare run
(`/0 0 /11 mode`) that new objects got. New and edited objects now get one
style run per character run, built on one of the object's own runs with
every key kept, and font, size, faux styles, leading, scales, tracking,
fill colour, OpenType keys and auto-kern mode set from the layer. A font
the block's font list lacks is appended to it. Edited objects therefore no
longer keep the old text's features by position.

build_tysh no longer writes TextIndex 0 for a layer without one: the
export took it as a kept number, so a Type tool layer landed on object 0
(a deleted layer's, or the one of a layer above it). set_text_index puts a
missing TextIndex right before EngineData, where Photoshop writes it.

pad_frames pads to at most MAX_TEXT_INDEX + 1 frames.

edited_text_keeps_the_files_txt2_object (#7220) now expects style runs
that follow the layer's runs and size.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 19:48:40 +02:00
NetterSilverandClaude Opus 5.5 760109e23c fix(psd): give a new Txt2 object its own style and a number past the file's objects
A new object kept the template's style runs, and PhotoCraft's reader takes
font features from Txt2, so a new type layer came back with the template
layer's features (salt, ss01). Its style runs are now generated like the
stub's; only the template's structure is copied.

New type layers were numbered past the kept TextIndex values only, so after
deleting the layer with the highest index a new layer landed on its object
and took its style. They are now numbered past the file's Txt2 objects too.

txt2_extras_survive_a_resave_of_unchanged_text now gives its layer the TySh
index a file's layer has; without one the layer is new and no longer lands
on object 0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 15:57:10 +02:00
NetterSilverandClaude Opus 5.5 304f055021 fix(psd): point a new Txt2 object's frame strike at its own frame
new_object copied the template's rendered strikes, whose frame strike
(/99 /F) still named the template's frame in /10. It now names the new
object's frame, like the view's /0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 14:04:33 +02:00
nrq6993-a11yandCursor 5be2b29463 Merge upstream/main into stream/issue-5902-text-antialias.
Resolve render.rs by routing parallel composite through shape_antialias_coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 08:11:03 +03:00
jonathnwxh 28f27f5a11 Index imported font metadata fallback by name 2026-10-20 07:14:09 +08:00
aiongg b8e856dffe Integration: #7022 write_region stays serial; the text rasterizer uses the parallel variant 2026-10-19 17:18:46 +00:00
David Mann 8e54897d43 Merge PR #7022: perf(text): rasterize a type layer's pixels on all cores, bit for bit the same 2026-10-19 16:51:41 +02:00
NetterSilverandClaude Opus 5.5 604007490b fix(psd): replace an earlier save's Txt2 stub also when its text is edited
The stub filter applied only to the kept object, so an edited layer whose
slot held a stub from an earlier save went through edited_object and kept
the bare stub. The slot is now filtered before both lookups.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 15:33:05 +02:00
NetterSilverandClaude Opus 5.5 198fa59c7d fix(psd): a new type layer gets a full Txt2 object and its own frame (#7698)
A type layer the file's Txt2 has no object for (one added in PhotoCraft)
was written as a bare stub with no view, no paragraph runs and no frame
in /0 /8 /0. Photopea and Photoshop refused the file once the objects
outnumbered the frames.

build_txt2 now gives such a layer an object shaped like one of the
file's own (refitted with edited_object from #7220), with its view
naming its own frame, and pads the frame list to one per object. A stub
written by an earlier save is replaced the same way, so re-saving an
affected file repairs it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 14:56:15 +02:00
Brian TorresandClaude Sonnet 5.5 ea0eec98a3 macOS: list fonts activated outside the font folders (#6683)
The installed-font scan read only /System/Library/Fonts, /Library/Fonts and
~/Library/Fonts (plus PingFang). Fonts macOS offers from anywhere else, such
as those a font manager, Adobe Fonts or Office activated, never reached the
font menus although other apps showed them.

load_system_fonts now also registers every file Core Text offers. The list
comes from fontique's Core Text backend, enabled for macOS only, used as a
scratch collection whose families are discarded: only its file paths are read
and registered in our own collection, so generic-family defaults and the merge
of installed faces into the bundled families are unchanged. The reserved UI
fonts under /System/Library/PrivateFrameworks are skipped (fontique cannot
draw their outlines).

On a stock macOS install the folder scan already listed all 183 Core Text
families; the gap is fonts Core Text knows from elsewhere. With Hack activated
from a scratch folder via CTFontManagerRegisterFontsForURL, the list had 313
families before and 314 after (Hack added, none removed), type.fonts lists it
and a type layer renders in it. The scan stays 37-57 ms.

Tests: font_files_of_lists_each_font_file_once (all platforms);
coretext_lists_the_system_font_files and every_family_coretext_offers_is_listed
(macOS). The last fails without the call when a font is activated elsewhere
("lacks: Hack") and passes with it.

Cargo.lock gains fontique's optional per-platform dependencies for the
`system` feature; `cargo tree` shows the feature only for aarch64-apple-darwin.

Still open: Windows fonts outside the two folders (#3336) and the web build.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 16:36:36 -07:00
aiongg ccb62fcd8f Merge PR #6038: Type: Korean paragraph text wraps between words, not syllables (fixes #5959) 2026-10-18 14:18:59 +00:00
Higgs32584andClaude Opus 5.5 204c76ce36 fix(psd): an edited type layer keeps its Txt2 object instead of a bare stub (#6968)
When a type layer's text changed, build_txt2 replaced the layer's object in
Photoshop's own Txt2 block with a bare `<< /0 << /0 text /6 runs >> >>`:
no paragraph runs (/5), no view (/1, which names the object's frame) and none
of the style keys. Photoshop 27.10 quits while opening such a file
(layer_effects.psd after type.edit), as it refused stripped objects in #2374.

An edited object now starts from the file's object at its slot: the text is
replaced, the paragraph runs are remade one per paragraph of the new text,
and every other run array of the model (style runs, ...) is cut or extended
to the new length before the auto-kern modes are applied. The view and other
keys are kept; only the old text's glyph pen positions (/21) are dropped.
An object whose run arrays aren't shaped as Photoshop writes them still falls
back to a fresh one.

Test: text edited_text_keeps_the_files_txt2_object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:03:18 -04:00
dotCooCoo 263dd041a6 Type: register craft fonts of every script, not only the Japanese ones
build.rs embeds every font in craft-fonts' fonts/manifest.txt, but
FontDb::new registered only the entries whose scripts include Jpan.
craft-fonts 8dcdacd5 adds Noto Sans CJK SC and Noto Sans Arabic, and
3d9ab3a1 adds Noto Sans Thai. Built with either checkout, the binary
carried those fonts, but the font list did not show them, text layers
did not fall back to them, and works_without_craft_fonts failed with
"Noto Sans CJK SC registered under its manifest name". Chinese and
Arabic set in Inter in a TextEngine::new() engine drew .notdef glyphs.

FontDb::new now registers every CRAFT_FONTS entry. A single-face file
registers under its manifest family, whatever family its name table
gives; each face of a collection keeps the family its name table gives.

With Noto Sans CJK SC built in and no Simplified Chinese system font,
Japanese text set in Inter takes Noto Sans CJK SC when the UI locale is
not Japanese, following cjk::script_order.
craft_fonts_cover_japanese_without_system_fonts now expects the first
CJK craft family in that order.

New tests register in-memory craft entries made from Inter (Hans, Arab
and Jpan entries, two whose name tables give another family, and a
collection), and lay out Chinese, Arabic and Thai in Inter when the
build has a craft font for the script.
2026-10-17 09:17:03 -07:00
ebosido444-ai fcd337e1fc perf(text): rasterize a type layer's pixels on all cores, bit for bit the same
A type render spends most of its time after the glyphs are drawn: composing each
colour's coverage over the premultiplied accumulator, dividing it back to straight
colour, and converting the tile bytes. All three are per pixel - no pixel reads a
slot another wrote - so they ran one after another on one core for no reason.

Measured on a 3.75 MP paragraph (4000 characters, 14 pt at 300 dpi, 1851 glyphs),
release, Windows 11, i5-10300H: rasterize 98.3-103.9 ms before, 60.3-61.8 ms after
(1.6-1.7x). Whole-render stage profile before: ink rect 4.1, draw 18.6-22.9, prefix
sum 7.0-8.1, composite 22.6-33.4, unpremultiply 11.2-14.0, write_region 23.8, prune
3.1 ms.

The glyph drawing, the winding prefix sum and the ink-rect pass stay on one thread:
the first two carry a running accumulator across pixels, and the band split that
would keep them bit-identical costs a second glyph traversal per band.

Native only (rayon, as the other crates): the web build keeps the serial loops.
2026-10-17 17:42:50 +03:00
Muhammad Saad f13731a51a Text: count both coverage buffers on the plain path (#4979)
Coverage::finish allocates its output while the accumulator it reads is
still live, so a plain pass peaks at two coverage buffers rather than
one. The budget charged one, which contradicted finish's own doc comment
and left the plain path under its real peak. The bold path's three
buffers were already right.

The exactness assertion in the budget test now pins 18 bytes a pixel for
a grey 8-bit plain render, and fails if the undercount comes back.

Caught in review by @FredApps.
2026-10-17 14:26:50 +04:00
3d2914368d PSD type: clamp warp bend and distortion before the f32 cast (#6856) (#6857)
A TySh warpValue, warpPerspective or warpPerspectiveOther beyond f32 became inf and the warp was silently dropped; they are now clamped to -100..100 like type.warpText.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 20:17:27 -06:00
jonathnwxh 1dd50ba859 Merge current upstream while preserving font metadata repair 2026-10-17 09:24:30 +08:00
a1e19a0b87 fix(text): break paragraph lines at the horizontally scaled width (#5611)
Horizontal scale was applied after line breaking, so paragraph text below
100% wrapped early and text above 100% overflowed the box. Break box text
at the available width divided by the paragraph's horizontal scale (the
largest one when runs mix), scale the first-line indent and alignment
offset back to the box, and keep the scaled trailing space out of the
line extent.

Fixes storytold/photocraft#5565

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 19:19:59 -06:00
jonathnwxh b0484eb72c Remove extra blank line at merged test boundary 2026-10-17 07:24:29 +08:00
jonathnwxh 4d32089b4f Merge upstream main and retain both font metadata and paragraph-wrap regressions 2026-10-17 07:23:25 +08:00
wwewandGitHub e33384c9ba docs: write product name as PhotoCraft in crate module docs (#5455) 2026-10-16 16:58:50 -06:00
Diego ChagasandGitHub ad6463b50f Paragraph text: a word wider than the box breaks at the box edge (#6694) (#6696)
parley's default overflow wrap only breaks at UAX #14 opportunities, so
a word longer than the paragraph box ran out past its edge and resizing
the box changed nothing. Paragraph text now sets OverflowWrap::BreakWord,
so such a word wraps at the box edge as Photoshop does; a space or hyphen
still wins over a mid-word break, a box that fits the word lays out as
before, and point text is untouched.
2026-10-16 16:37:45 -06:00
vangel76andClaude Opus 5.5 489feb2333 Tests: pcraft keeps text run baseline; tighten sub/superscript size check (#5645)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:57:15 +02:00
nrq6993-a11y 801e229d76 style(text): rustfmt antialias fringe_mass chain for #5902 (PR #5907) 2026-10-16 06:47:56 +03:00
nrq6993-a11yandCursor 81b6cdf0d2 text: apply anti-alias mode shaping when rasterizing type
Photoshop's Sharp/Crisp/Strong/Smooth settings only changed metadata; shape edge coverage per mode (fixes storytold/photocraft#5902).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-15 23:45:10 +03:00
jonathnwxh e0ddefe7e0 test(text): retain raw TySh as the document importer does 2026-10-15 22:07:31 +08:00
jonathnwxh 2656f8db3d fix(text): preserve imported font records during text and style edits 2026-10-15 22:04:27 +08:00
jonathnwxh 70ffad5a56 test(text): retain imported font metadata after text edits 2026-10-15 22:01:09 +08:00
itsF4LCON 510636b19c Type: Korean paragraph text wraps between words, not syllables (#5959)
Unicode's line-breaking rules allow a break between any two Hangul
syllables, so Korean box text split words (설 / 정이나). Korean is set
with breaks at the spaces between words, as Photoshop does: runs of
Hangul now lay out with WordBreak::KeepAll. Chinese and Japanese keep
breaking between characters.
2026-10-15 09:34:09 +02:00
Muhammad Saad c77899b2a7 Text: admit the rasterizer's peak working bytes (#4979)
rasterize_warped guarded its size with MAX_PIXELS alone, which says nothing
about bytes. The buffers it keeps live at once are all as deep as the format:
the f32 accumulator and the output surface are both stride deep, a finished
coverage buffer sits beside them for the colour being drawn, and each faux-bold
pass holds a second Coverage and the vector its finish builds. A CMYK bold
render at f32 therefore reserves several times what a plain grey one does for
the same pixel count, and every one of those buffers went through vec! or
Vec::with_capacity, which abort instead of returning.

Admit the peak before reserving any of it: fits_raster_budget derives it from
the stride, the sample size and whether a bold pass runs, refuses an overflowing
size rather than wrapping, and is checked against MAX_RASTER_BYTES (4 GiB, 1 GiB
where usize is 32 bits, so the wasm build keeps room for the document). Coverage
now reserves fallibly and finish returns an Option, and the accumulator goes
through a fallible zeroed(). A refusal returns the empty Rendered the oversized
path already returned, so callers see the existing "too big to draw" result
rather than a crash, and the document and its cached appearance are untouched.

Ordinary text is unaffected: the budget only binds above what MAX_PIXELS
already allowed for narrow formats.

Test: the peak tracks channels, sample width and faux bold against small
injected allowances, an overflowing size is refused, and a buffer that cannot
be served reserves nothing.
2026-10-15 09:57:02 +04:00
vangel76andClaude Opus 5.5 5f83b53a32 Type: keep superscript and subscript (FontBaseline) when a text layer is re-rendered (#5645)
CharStyle had no superscript/subscript attribute, so PSD import ignored FontBaseline and the first re-render (entering text editing) drew the run as plain text. Model it as CharStyle::baseline, read and write FontBaseline in EngineData, draw such runs at 58.3% of their size shifted by 33.3% of it (Photoshop defaults), and accept "baseline" in the character params.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:13:41 +02:00
3888e95e47 Text: non-finite numbers and negative indices from PSD files are sanitized (#4429) (#4433)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:45:19 -04:00
zolyyandClaude Opus 5.5 c0faf7300c Type tool: click a path to start type on it (TYPE-218-1, #218)
A Type tool click on a shown path (the active shape layer's, else the work
path) starts type on that path at the point clicked, as in Photoshop: one
history step, placeholder selected, the paragraph alignment kept about the
click. Over such a path the pointer is the path-type I-beam, and the Type
tool shows the paths as the vector tools do.

- text: warp::arc_length_at, the arc length of the nearest point on a path.
- engine: type.attachToPath takes "at": [x, y] (a document point) instead
  of "start".
- ui: the caret, selection and underline follow the curve; a click in path
  type puts the caret at the glyph there; glyphs past the path end are not
  highlighted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:11:06 +03:00
ce6fccc3f5 fix(type): missing-font checks follow layout's PostScript resolution; split hyphenless style suffixes (#608)
* fix(text): split hyphenless PostScript style suffixes

A PostScript name without a hyphen kept its style in the guessed family:
`GmarketSansBold` became the family "Gmarket Sans Bold" at weight 400.
The guess now drops a trailing capitalised style word (Gmarket Sans, 700).
A lowercase ending ("Daylight") is not a style, and a style word after the
vendor tag MT stays in the legacy family ("Arial Rounded MT Bold").
Hyphenated names keep the last-hyphen parsing from #3878.

PSD import stores this guess when the exact face is absent. A legacy family
that ends in a style word without MT before it ("CooperBlack") is stored as
the shorter family (Cooper, 900) until the face is installed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(type): missing-font checks follow layout's PostScript resolution

When a run's stored family is not installed, layout draws it with its exact
PostScript face, or with the family guessed from that name if installed.
Missing-font detection compared only the stored family label, so such a run
was reported missing, and Replace Missing Fonts rewrote it whenever an
unresolved run shared the label. missing_fonts, missing_fonts_in_layer and
the replacement now use layout's decision.

#1406 already corrects the label when a PSD or Affinity file is opened with
the face installed, and #1844 finds exact faces outside the family prefix.
The label still stays on a run reopened from .pcraft after its font was
installed, on fonts that arrive after opening, and on a run created with
both "font" and "postscriptName". The tests take the .pcraft and command
paths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: kijoong <gijoong.gim@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:48:09 -04:00