LYH Studio episode 6 (1:36-2:05) builds its gradient in the Gradient
panel: it drags C=0 M=100 Y=0 K=0 from the Swatches panel onto the left
stop, drops C=100 M=0 Y=0 K=0 on the ramp (a new stop at 68.636 %), then
drags the black stop off. DesignCraft's Gradient panel took no swatches,
showed only a hint until the fill was a gradient, and laid out its
fields differently.
- Gradient panel: InDesign's layout (thumbnail; Type; Location % and
Angle; Reverse; the ramp with midpoint diamonds above and stops below).
Until the fill is a gradient it shows the default White -> Black ramp;
clicking the thumbnail or using the ramp starts one. Midpoints drag
and take a Location. A stop's colour picker moved to its right-click
menu, so selecting a stop no longer grows the panel.
- Swatches panel rows are drag sources (the chip follows the pointer):
dropped on a stop a swatch colours it, elsewhere on the ramp it adds
a stop there.
- Stops keep their colour model through panel edits (CMYK used to turn
into hex RGB whenever a stop moved).
- engine: `object.gradient` stops accept `swatch` (its colour; gradient
swatches and [None] are refused) and colours accept `{gray}`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LYH Studio episode 6 (8:28) gives the "1969" headline the "New Gradient
Swatch" (magenta → cyan) from the Swatches panel. InDesign runs a gradient
on text across its text frame's box. DesignCraft resolved a character
fill to one solid colour, so a gradient swatch drew no text at all.
- render: a character fill or stroke that names a gradient swatch is
painted with that gradient over the frame's box in text space (type on
a path: the path's box).
- pdf: text runs take a gradient paint: the shading is laid out in the
frame's text space and mapped into each run's origin, so skew and
horizontal scale don't bend it; outlined runs and type on a path too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A gradient swatch on a stroke drew nothing: strokes were painted with a
single resolved colour. LYH Studio episode 6 gives the loop on the right
page (9:13) and, at first by accident, the circle (1:19, "right now it
is on the stroke") a gradient stroke.
- doc: Stroke::gradient_angle / gradient_vector (as Fill's) and
Stroke::as_fill(), the stroke's paint as a fill.
- render and PDF: a gradient stroke is painted like a gradient fill,
across the path's box (dash gaps and arrowheads keep their paints).
- engine: object.gradient {target: stroke} edits the stroke's
gradient (kind, stops, angle, the Gradient Swatch tool's from/to);
scaling moves the stroke's vector with the shape.
- IDML: GradientStrokeStart/Length/Angle round-trip.
- Gradient panel: acts on the stroke when the toolbox's Stroke box is
in front, as InDesign's follows the Fill/Stroke proxy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save and Save a Copy put a picture of the first page in the .designcraft
zip as preview.png (320 px on the longer side). The start screen reads
only that entry and shows the last eight recent files as cards with the
picture and the name, below the community card. Files saved before have
a blank page until they are saved again.
The package fallback-font test writes its fixture without Save's preview:
drawing it before the document's fonts load pulled in a system font that
covers the test's characters (Unifont CSUR) ahead of the document's own.
- describe style.paragraph.create (and .edit, style.character.create,
.edit) gave chars?: {…} and para?: {…}. The descriptions now say they
take type.char's and type.para's attributes and name the common ones.
- A wrong attribute gave "invalid parameters for `style`: unknown
attribute `font`". The error now names the command and the group,
and the attribute meant (nearest name, or one the key starts or ends)
or else lists them all: "invalid parameters for
`style.paragraph.create`: chars: unknown attribute `font`; did you
mean `fontFamily`?". The suggestion comes from set_json, so
type.char, type.para and find.change get it too.
- chars or para that isn't an object is an error instead of ignored.
Fixes#777
- Every font-family fell back to serif, sans-serif typefaces included.
The fallback now comes from the font's classification (PANOSE, OS/2
class, name: serif, sans-serif, monospace, cursive, fantasy), passed
in by the engine; without one, from the family name.
- file.exportEpub takes embedFonts: the faces the stylesheet names are
packaged (OEBPS/fonts, @font-face, manifest items with font/ttf, otf,
woff or woff2). Faces that aren't available, whose licence (OS/2
fsType) forbids embedding, or that sit in a font collection are left
out and listed in the new warnings array. Fonts are packaged whole;
subsetting is a follow-up.
Fixes#775
- An image without alt text (Object Export Options) got its file name as
alt, which a screen reader then reads aloud. It is now decorative
(alt=""), and file.exportEpub lists those images in a new warnings
array so they can be given alt text.
- Images and media were packaged as images/<asset id>.<ext>. They are
now <name>-<asset id>.<ext>, the name from the original file name
(lowercase ASCII); the id keeps two files of the same name apart.
Fixes#774
- dcterms:modified was always 2026-01-01T00:00:00Z. It is now the
export time (from the command; on the web, and in the crate without
one, the document's last save or creation time).
- The default dc:identifier was urn:designcraft:<title>, shared by
books with the same title. It is now a urn:uuid derived from the
title and the document's creation time: stable from export to export,
different between documents. file.exportEpub takes identifier to set
it (an ISBN, say).
- file.exportEpub takes coverImage, a PNG or JPEG file used as the
cover (cover-image); a JPEG is packaged as cover.jpg/image/jpeg.
Fixes#771Fixes#773
nav.xhtml had one entry per placed story, labelled with its first
paragraph (body text too), and always the title "Contents".
- Headings (h1-h6) get an id in the EPUB, and the navigation lists the
h1-h3 headings of the stories in reading order, nested by level and
linked to them. A document without headings keeps one entry per
story (its first paragraph).
- The navigation's title follows the language (Inhoud, Inhalt, Table
des matières, Índice, Indice, 目次, 目录; Contents otherwise), and
file.exportEpub takes tocTitle to set it.
Fixes#769Fixes#770
Table cell paragraphs go through the same run and inline path as body paragraphs, so deleted and moved-from text and field instructions stay out of cells, and field results, tabs and breaks come in.
Word's Normal imports as a paragraph style named Normal whose definition is w:docDefaults with Normal's own properties on top; paragraphs without a style use Word's default paragraph style. A styleMap entry can still map Normal to [Basic Paragraph].
to_bytes returns a Result and every caller propagates it, so a document
that can't be written is an error: the existing file, the unsaved state
and the recovery checkpoint stay. Asset entries near or over 4 GiB are
written with zip64 sizes. On the web, Save marks the document saved only
after the browser download has started.