M13.12: docs (pdf README, parity, plug-ins), EPS Type 3 fonts reported

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-03 09:51:22 -04:00
parent 2f1788bdf6
commit 5db4bff461
4 changed files with 98 additions and 26 deletions

View File

@ -12,17 +12,22 @@ fonts, images, patterns, transparency (blend modes, soft masks) and optional con
**Technical Note #5176** (The Compact Font Format Specification) and **#5177** (The Type 2
Charstring Format) for CFF font programs; **Adobe Type 1 Font Format** (version 1.1) for Type 1
font programs; the **PostScript Language Reference, third edition** and the **Encapsulated
PostScript File Format Specification 3.0** for EPS. TrueType font programs are read through
skrifa and JPEG (DCT) images through zune-jpeg (both permissively licensed, pure Rust). No
third-party PDF/PostScript/font code was read; test files (including the CFF and Type 1 fonts)
are generated in code ([`write`](src/write.rs) and the tests).
PostScript File Format Specification 3.0** for EPS; **ITU-T T.4** (07/2003) and **ITU-T T.6**
(11/1988) for CCITT fax images. TrueType font programs are read through skrifa and JPEG (DCT)
images through zune-jpeg (both permissively licensed, pure Rust). No third-party
PDF/PostScript/font/codec code was read; test files (including the CFF and Type 1 fonts and the
CCITT data) are generated in code ([`write`](src/write.rs) and the tests). One CCITT fixture was
produced once by libtiff's `tiffcp` (an external encoder used only as a fixture generator, like
ffmpeg elsewhere) to check the code tables against an independent implementation.
## What is read
- **Structure:** indirect objects found by scanning (damaged or incremental cross-reference
sections do not matter), object streams, the page tree with inherited resources, MediaBox /
CropBox and `/Rotate`. Filters: Flate (with PNG predictors), LZW, ASCIIHex, ASCII85,
RunLength. Encrypted files are refused.
RunLength, CCITTFax (Group 3 one- and two-dimensional, Group 4; `K`, `Columns`, `Rows`,
`EndOfLine`, `EncodedByteAlign`, `EndOfBlock`, `BlackIs1`; damaged data keeps the rows
decoded before the damage). Encrypted files are refused.
- **Content:** `q`/`Q`, `cm`, line width / cap / join / miter / dash, ExtGState (`CA`, `ca`, `LW`,
`LC`, `LJ`, `ML`, `D`), all path construction and painting operators (non-zero and even-odd),
clipping (`W`, `W*`) as clipping groups, colour in DeviceGray / RGB / CMYK, ICCBased (by
@ -39,7 +44,15 @@ are generated in code ([`write`](src/write.rs) and the tests).
construction including `arc`/`arcn` and relative operators, `fill`/`eofill`/`stroke`,
`clip`/`eoclip`, `rectfill`/`rectstroke`/`rectclip`, gray / RGB / CMYK / HSB colour. DOS EPS
binary headers and `%%HiResBoundingBox` / `%%BoundingBox` are honoured; `%%BeginData` /
`%%BeginBinary` / preview sections are skipped.
`%%BeginBinary` / preview sections are skipped. **Text:** `findfont` / `findresource`,
`scalefont`, `makefont`, `selectfont`, `setfont`, `currentfont`, `definefont` (re-encoded
copies made with `forall` and a new `/Encoding`), `StandardEncoding` / `ISOLatin1Encoding`,
`show`, `ashow`, `widthshow`, `awidthshow`, `xshow` / `yshow` / `xyshow`, `kshow` / `cshow`
(the procedure is not run), `glyphshow`, `charpath` (glyph outlines join the path, e.g. for
clipping) and `stringwidth`. Fonts embedded in the file — Type 1 programs (`… currentfile
eexec … cleartomark`, binary or hexadecimal) and CFF font sets (`StartData`) — are found by
name before the program runs; other fonts are drawn with the bundled fonts as for PDF. Each
`show` becomes one compound shape named "Text: …".
- **Text** (§9): text objects and state (`Tc`, `Tw`, `Tz`, `TL`, `Ts`, `Tr`, `Tf`, `Td`,
`TD`, `Tm`, `T*`), `Tj`, `TJ` (with kerning), `'` and `"`; render modes fill / stroke /
@ -47,8 +60,9 @@ are generated in code ([`write`](src/write.rs) and the tests).
become outlines (one compound shape per show operator, named "Text: …" after the string
through `ToUnicode` or the glyph names). Fonts: simple fonts with `/Widths`, base encodings
(Standard, WinAnsi, MacRoman), `/Differences` and built-in encodings; composite (Type 0) fonts
with Identity-H / Identity-V and embedded CMaps (`cidrange` / `cidchar`), `/W` and `/DW`
widths and `CIDToGIDMap`. Embedded programs: TrueType (`FontFile2`, cmap (3,1) / (3,0) /
with Identity-H / Identity-V and embedded CMaps (`cidrange` / `cidchar`, `/WMode`), `/W` and
`/DW` widths, vertical metrics (`/W2`, `/DW2`: the vertical displacement and the position
vector from the horizontal to the vertical origin) and `CIDToGIDMap`. Embedded programs: TrueType (`FontFile2`, cmap (3,1) / (3,0) /
(1,0) and `post` names), CFF (`FontFile3` `/Type1C`, `/CIDFontType0C`, `/OpenType`;
name-keyed and CID-keyed with FDArray / FDSelect, flex, hint masks, `seac`), Type 1
(`FontFile`, eexec, `/Subrs`, flex and hint replacement through othersubrs, `seac`) and Type 3
@ -64,29 +78,54 @@ are generated in code ([`write`](src/write.rs) and the tests).
of the render tree (bilinear, box-filtered when minified).
- **Transparency:** blend modes (`/BM`, all 16 PDF modes) and soft masks in the graphics state
(`/SMask` with `/S /Luminosity` and `/BC` backdrops, or `/S /Alpha`) as groups of the render
tree, open until the state changes or `Q`.
tree, open until the state changes or `Q`: what is painted while a soft mask is set is
rendered to an offscreen buffer and the mask applies to that combined result. Transparency
group XObjects (§11.6.6) composite as one object: the current alpha, blend mode and soft mask
apply to the group's result, and its contents start from the initial alpha. Isolated groups
(`/I true`) composite over a transparent backdrop; non-isolated groups over the page, with
the backdrop's contribution removed again (§11.4.8) so group alpha and masks apply only to
the group; knockout groups (`/K true`) composite each child with the group's initial
backdrop. Clipping groups are not transparency groups: blend modes inside them reach the
backdrop.
- **Patterns:** tiling patterns (coloured and uncoloured, `XStep` / `YStep`, the cell clipped
to `/BBox`, at most 4096 tiles per fill) for fills; strokes with a tiling pattern use its
colour (uncoloured) or grey.
- **Functions:** sampled, exponential, stitching and PostScript calculator (type 4) functions.
- **Shadings** (§8.7.4.5): axial and radial shadings become gradients. Function-based shadings
(type 1, `Domain` and `Matrix`) and the mesh shadings — free-form (type 4, edge flags 0–2)
and lattice-form (type 5) Gouraud-shaded triangle meshes, Coons patch meshes (type 6) and
tensor-product patch meshes (type 7, flags 0–3 sharing the previous patch's edges) with
`BitsPerCoordinate` / `BitsPerComponent` / `BitsPerFlag` / `Decode` and an optional
parametric `Function` — are rasterised into an image node in page space (up to 4 pixels per
point, at most 1024 pixels a side, 2×2 supersampled): patches are tessellated into triangles
(up to 48×48 cells), colours (or `t`) interpolated across each triangle. They paint through
`sh` (their own extent only) and as shading-pattern fills (clipped to the path).
- **Functions:** sampled (one or several inputs, multilinear interpolation), exponential,
stitching and PostScript calculator (type 4) functions.
- **Pages:** any page ([`parse_page`], [`page_count`]); File ▸ Import takes `page` (from 1) and
the Import dialog asks for it; footage remembers its page (`Footage::page`).
## Limitations
- Mesh shadings (types 4–7) and function-based shadings (type 1), JPX / CCITT / JBIG2 images,
predefined CJK CMaps other than Identity, vertical metrics (`/W2`; vertical text advances by
the font size) and transfer functions (`/TR`) are not read; knockout and isolated transparency
groups composite as normal groups, and a soft mask applies to each painting operation while
it is set rather than to their composite. What a document skipped is listed in `Doc::skipped`.
- JPX and JBIG2 images, predefined CJK CMaps other than Identity-H / Identity-V, and transfer
functions (`/TR`) are not read. JBIG2 is deferred. Predefined CMaps (`UniGB-UCS2-H`,
`90ms-RKSJ-H` …) need the CMap data, and there is no permissively licensed pure-Rust source
of it (Adobe's CMap resources are Adobe assets, which this project does not ship); text in
such fonts is reported in `Doc::skipped`. Mesh shadings are raster images in the render tree
(sharp to about 4× the document size). Knockout uses each child's coverage as its shape (a
child with constant opacity knocks out fully where it covers). What a document skipped is
listed in `Doc::skipped`.
- Illustrator EPS files whose drawing depends on Adobe's procedure-set resources (defined in
the file through constructs beyond this subset) and Level 3 `shfill` gradients in EPS draw
only what the subset understands; save such artwork as PDF-compatible `.ai` or PDF instead.
EPS text is not drawn.
EPS images are skipped; Type 3 (procedure) fonts defined in EPS files draw nothing (reported).
- **Create Shapes from Vector Layer:** clips that enclose the whole page become layer masks
(the first clip Add, later ones Intersect). Nested clipping groups become Merge Paths: each
closed shape inside them is a group with its geometry merged into one compound path, the clip
paths, and Merge Paths ▸ Intersect before its fill and stroke, so fills match exactly and
strokes follow the clipped outline; open stroked paths inside clips stay unclipped. Blend
modes become the groups' Blend Mode; images and soft masks are not converted (shape layers
have no images; use the footage, or Import As: Composition).
modes become the groups' Blend Mode. Images become footage layers: each is written as
`<file> Image <n>.png` next to the source and imported, placed by its matrix (anchor at its
top-left corner; position, rotation and scale — a skew is reported) and parented to the shape
layer; the shapes drawn between images go to shape layers of their own (`<name> Outlines
<n>`), so the layer stack keeps the document's paint order. Soft masks, and the clips and
masks of images, are not converted (the command's `skipped` list says so).

View File

@ -1555,6 +1555,11 @@ impl Interp {
/// The drawable font of a font dictionary (its base program and encoding) and the matrix
/// from text space (font size 1) to user space.
fn font_of(&mut self, d: &DictRef) -> Option<(Rc<Font>, Affine)> {
if matches!(d.borrow().get("FontType"), Some(V::Num(t)) if *t == 3.0) {
// Type 3 fonts (glyph procedures) are not run.
self.b.skip("EPS Type 3 font");
return None;
}
let (base, names) = {
let m = d.borrow();
let base = m.get("__base").or_else(|| m.get("FontName")).map(V::key)?;

View File

@ -35,8 +35,8 @@ Tracking 96%, Effects 85%, Web 65%.
What is left, in priority order: the web app's depth (threads, storage, audio, non-blocking
renders); stroke taper/wave and multi-segment dashes; variable mask feather points; camera iris/bokeh and focus-link commands;
approximated effects (Key Cleaner) and Liquify's viewer brush; mesh shadings and JPX images in
imported PDF/AI files and Illustrator procset EPS; and more codecs. Face tracking and a true Subspace Warp
approximated effects (Key Cleaner) and Liquify's viewer brush; JPX / JBIG2 images in
imported PDF/AI files (mesh shadings and CCITT landed in M13.12) and Illustrator procset EPS; and more codecs. Face tracking and a true Subspace Warp
landed in M13.3. ScriptUI (dialogs that block `show()`, palettes, dockable ScriptUI panels, File ▸ Scripts install
and sample scripts), puppet pin recording and the remaining "better than After Effects" items — a
versioned effect plug-in API with sandboxed WebAssembly plug-ins ([plugins.md](plugins.md)),
@ -128,12 +128,12 @@ the disk cache in the formats wave; the 3D Camera Tracker in M12.6.)
| Layers | 88% | 4.8 | frame blending, collapse transformations, slip edit |
| Output | 97% | 0.8 | Render Settings complete (field render + 3:2 pulldown, effects/solo/guide/depth/blending/blur overrides, time sampling, storage overflow), Output Module crop/ROI/resize, alpha modes, post-render actions, PCM formats, templates with defaults, render logs, Notify (M10.2); WebM VP9 key + inter frames with motion search, loop filter and rate control. Left: Opus is CELT-only, Photoshop sequence output, overflow for movies only checks at file creation |
| Audio | 85% | 0.5 | audio to keyframes |
| Import | 96% | 0.6 | mesh shadings (types 4–7), JPX / CCITT / JBIG2 images and non-Identity CJK CMaps inside PDF/AI files, Illustrator EPS relying on Adobe procsets, PSD 3D layers (M13.6: PDF/AI text with embedded TrueType / CFF / Type 1 / Type 3 fonts and standard-14 fallback to the bundled fonts, Flate / DCT / inline / stencil images with soft masks, Indexed and ICC alternates, luminosity / alpha soft masks, the 16 blend modes, tiling patterns, calculator functions, any page via `file.import page` and the Import dialog, clip groups kept by Create Shapes from Vector Layer as layer masks / Merge Paths; PSD smart-object perspective quads and placed-layer warps — named styles and custom quilt meshes — baked as placed; PDF / PDF-compatible AI / EPS vector footage with Continuously Rasterize, layered composition import and Create Shapes from Vector Layer, and PSD smart objects with embedded files landed in M13.2; PSD as footage/composition/retain layer sizes, SVG footage earlier) |
| Import | 97% | 0.5 | JPX / JBIG2 images and predefined non-Identity CJK CMaps inside PDF/AI files, Illustrator EPS relying on Adobe procsets, PSD 3D layers (M13.12: mesh shadings — free-form / lattice Gouraud triangles, Coons and tensor patches — and function-based shadings, CCITT G3 / G4 images, knockout / isolated transparency groups with group alpha and soft masks on the group's result, `/W2` vertical metrics, EPS text with embedded Type 1 / CFF or bundled fonts, and Create Shapes from Vector Layer keeping images as parented footage layers in paint order; M13.6: PDF/AI text with embedded TrueType / CFF / Type 1 / Type 3 fonts and standard-14 fallback to the bundled fonts, Flate / DCT / inline / stencil images with soft masks, Indexed and ICC alternates, luminosity / alpha soft masks, the 16 blend modes, tiling patterns, calculator functions, any page via `file.import page` and the Import dialog, clip groups kept by Create Shapes from Vector Layer as layer masks / Merge Paths; PSD smart-object perspective quads and placed-layer warps — named styles and custom quilt meshes — baked as placed; PDF / PDF-compatible AI / EPS vector footage with Continuously Rasterize, layered composition import and Create Shapes from Vector Layer, and PSD smart objects with embedded files landed in M13.2; PSD as footage/composition/retain layer sizes, SVG footage earlier) |
| Automation | ≈ 99% | 0.1 | `.jsxbin` (AUT-2: ScriptUI resource strings, `onDraw`/ScriptUIGraphics, live `onChanging`, `Socket`, Essential Graphics hooks — `addToMotionGraphicsTemplate(As)`, `canAddToMotionGraphicsTemplate`, `exportAsMotionGraphicsTemplate`, `motionGraphicsTemplateName`, controller count/names — and Watch Folder landed in M13.7; the core object model landed in M14.4; ScriptUI windows/dialogs/dockable panels with `scriptui.*` agent commands, File ▸ Scripts install + sample scripts, and the effect plug-in API (EFF-6, WebAssembly) landed in M13.1) |
| Shapes | ≈ 80% | 1.5 | Lottie can't carry stroke taper/wave (stroke Taper and Wave, Dash 2/Gap 2/Dash 3/Gap 3 and radial-gradient Highlight Length/Angle landed in M13.5; pen tool for shape paths and vertex editing in M6.5) |
| Compositions | ≈ 80% | 3.5 | Mocha-style planar tracks for templates, Essential Graphics' mirrored properties (CMP-7: Font and uniform Scale controls, Composition ▸ Open in Essential Graphics, Save Frame As ▸ Photoshop Layers / ProEXR and the VR Comp Editor landed in M13.7; the marker dialog, Composition Flowchart, Essential Graphics with master properties, `.ectemplate` templates and Responsive Design — Time landed: CMP-6, CMP-7) |
| Animation | 70% | 9.0 | puppet depth beyond pins and recording (puppet pin recording with Record Options landed in M13.1), Wiggler/Smoother/Motion Sketch (motion-path handles and the graph editor transform box landed in M5.8; keyframe colour labels and Select Keyframe Label Group, Graph Editor snapping to markers / layer ends in M13.5) |
| Text | ≈ 95% | 0.4 | no extruded strokes, the Variable Font Axes animator changes outlines but not advances (M13.6: variable font axes in the character style — `layer.setText variations`, the Character panel's Variable Font Axes fields — shape with HVAR / gvar advances and draw at that design-space position; OpenType features — stylistic sets, discretionary ligatures, contextual / stylistic alternates, swash, titling, ordinals, fractions, figure styles, true small caps / all small caps and superior / inferior glyphs with faux fallback — per character with the Character panel's OpenType popup and `text.fontFeatures` landed in M13.2; vertical Roman / Tate-Chu-Yoko, forced LTR paragraphs, caret on animated and path text, Variable Font Axes and Lottie style runs landed in M13.5; extruded, bevelled text in M7.6; per-character styles, paragraph settings, on-canvas editing and the `sourceText` style API in M9.9–M9.10) |
| Text | ≈ 96% | 0.3 | no extruded strokes (M13.12: the Variable Font Axes animator re-spaces the text — advances follow the animated axes; M13.6: variable font axes in the character style — `layer.setText variations`, the Character panel's Variable Font Axes fields — shape with HVAR / gvar advances and draw at that design-space position; OpenType features — stylistic sets, discretionary ligatures, contextual / stylistic alternates, swash, titling, ordinals, fractions, figure styles, true small caps / all small caps and superior / inferior glyphs with faux fallback — per character with the Character panel's OpenType popup and `text.fontFeatures` landed in M13.2; vertical Roman / Tate-Chu-Yoko, forced LTR paragraphs, caret on animated and path text, Variable Font Axes and Lottie style runs landed in M13.5; extruded, bevelled text in M7.6; per-character styles, paragraph settings, on-canvas editing and the `sourceText` style API in M9.9–M9.10) |
| Web | 85% | 1.0 | viewer frames and Roto Brush propagation still on the page's thread; GPU effects in the browser (browser storage, Web Audio, Web Worker renders/analyses, WebGPU viewer and offline install landed in M15.2) |
| 3D | 88% | 5.5 | multi-view layouts, the Extended Viewer for Advanced 3D comps (Classic 3D Extended Viewer landed in M13.5 UI completion); collapsed precomps of another size seen through the parent's camera render (fixed in M13.2); stereo rigs, orbit nulls, lights controlled by the camera, cameras/lights from glTF models, environment backgrounds, Advanced 3D motion blur, blend modes and track mattes landed in M7.7; Classic 3D iris-shaped bokeh with highlights, progressive depth of field on tilted layers and the focus-link commands landed in M13.5; Advanced 3D depth of field with the iris and highlight options, collapsed precomps as real Advanced 3D geometry and extruded text/shape strokes landed in M13.8; Advanced 3D (glTF/OBJ models, primitives, extruded text and shapes, PBR, image-based light, shadow maps, GPU rasteriser) in M7.4–M7.6 |
| Effects | ≈ 80% | 4.0 | GPU versions of the remaining effects (58 run on the GPU since M12.7) and the missing controls listed as partial in [effects.md](effects.md) (every After Effects effect exists since M9.11, M12.5 and M12.6; parameter names, order, twirl-downs, popups, units and defaults were aligned in M9.12) |
@ -180,8 +180,8 @@ Tate-Chu-Yoko, forced LTR paragraphs, the editing caret on animated / path text
style runs; the Layer Style dialog (LYR-9); Graph Editor snapping to markers and layer ends
(ANM-4; the reference graph existed); viewer ROI resize handles, Pan Behind snapping and 3D
Reference Axes; Animate Text ▸ Variable Font Axes. Remaining in these rows: Lottie can't carry
taper/wave and the Variable Font Axes animator doesn't change advances (the Character panel's
axes do since M13.6; Advanced 3D's DOF has iris shapes since M13.8).
taper/wave (the Variable Font Axes animator changes advances since M13.12, the Character panel's
axes since M13.6; Advanced 3D's DOF has iris shapes since M13.8).
## Update: M13.8 Preview panel, Align panel, Advanced 3D completion
@ -241,6 +241,32 @@ menu entries. Not yet: creating a reference frame by editing a still (After Effe
Photoshop), Content-Aware Fill's learned model (ours is PatchMatch + flow propagation), and Media
Browser in the web app.
## Update: M13.12 import and text leftovers, test hardening
- **PDF / AI**: mesh shadings — free-form and lattice-form Gouraud triangle meshes (types 4, 5),
Coons and tensor-product patch meshes (6, 7, tessellated to triangles) — and function-based
shadings (type 1, with multi-input sampled functions), for `sh` and shading-pattern fills,
rasterised into image nodes; CCITT Group 3 (1-D and 2-D) and Group 4 images from ITU-T T.4 /
T.6 (checked against an independent encoder's output); knockout and isolated transparency
groups, the current alpha / blend mode / soft mask applied to a transparency group XObject's
combined result, and blend modes inside clipping groups reaching the backdrop; `/W2` / `/DW2`
vertical metrics and CMap `WMode`. Not done: JBIG2 images (deferred) and predefined CJK CMaps (no permissively licensed
pure-Rust source of the CMap data; Adobe's CMap resources are Adobe assets).
- **EPS**: text through the PostScript interpreter — `findfont` / `scalefont` / `makefont` /
`selectfont` / `setfont`, `show`, `ashow`, `widthshow`, `awidthshow`, `xshow` / `yshow` /
`xyshow`, `kshow`, `glyphshow`, `charpath`, `stringwidth`, re-encoded fonts (`definefont`
copies with a new `/Encoding`) — with embedded Type 1 and CFF programs or the bundled fonts.
- **Create Shapes from Vector Layer**: images become PNG footage layers parented to the shape
layer, and the shapes between images are split into layers of their own so the stack keeps
the document's paint order; soft masks and clipped images are reported.
- **Text**: the Variable Font Axes animator re-spaces the text (advances at the animated
design-space position), not only the outlines.
- **Hardening**: PDF / EPS parsers survive truncated and corrupted files (fixed an
out-of-range stream slice and unbounded PostScript recursion); WebAssembly plug-ins get a
memory cap, a manifest length limit and slider schema checks, with tests for missing exports,
wrong signatures and versions, fuel exhaustion, traps and bit-identical output; script
`Socket` validates ports, and ScriptUI resource strings accept trailing array commas.
## Highest-value gaps, in order
1. ~~On-canvas text editing and per-character styles~~ (landed: M9.9–M9.10).

View File

@ -12,7 +12,7 @@ There are two ways to write one:
|---|---|---|
| What | a `.wasm` (or `.wat`) module | a type implementing `effectcraft_effects::plugin::EffectPlugin` |
| Loaded by | Effect ▸ Load Effect Plug-in…, `effect.plugins.load {path \| folder}`, or the `Plug-ins` folder next to the settings at start-up | `register_plugin(Arc::new(MyEffect))` in an app that links EffectCraft's crates |
| Sandbox | yes: no imports (no files, clock or network), fuel-limited, deterministic floats | none (it is your code) |
| Sandbox | yes: no imports (no files, clock or network), fuel-limited, at most 1 GiB of memory, deterministic floats | none (it is your code) |
| Platforms | desktop and CLI (the wasmi interpreter, crate feature `effectcraft-plugin/wasm`); not the web build | everywhere |
`effect.plugins.list` lists what is loaded: `{api, wasm, plugins: [{id, name, category, version,
@ -60,7 +60,9 @@ Every plug-in describes itself with a JSON manifest:
* Parameter types: `slider` (`default`, `min`, `max`, optional `sliderMin`, `sliderMax`,
`decimals`), `angle` (`default` degrees), `checkbox` (`default` bool), `popup` (`options`,
`default` index), `point` (`default` as fractions of the layer size, `[0.5, 0.5]` = centre),
`color` (`default` RGBA 0–1). Parameter ids are letters, digits and `_`.
`color` (`default` RGBA 0–1). Parameter ids are letters, digits and `_`, unique; at most 256
parameters; a slider needs `min ≤ default ≤ max` (and `sliderMin ≤ sliderMax`); the manifest
is at most 1 MiB.
## WebAssembly ABI (v1)