Files
TREK/shared/src/book/book-schema-rev.ts
T
Maurice 75d0593198 feat(studio): text effects, fit to frame and 24 lockups in a Text styles shelf
- A text can be cased (capitals, lower case, small caps), placed at the top,
  middle or foot of its frame, outlined, given a hard shadow or a marker band,
  bent on an arc or round a circle, filled with a gallery photo or left
  hollow, and shrunk to fit its frame. Every effect prints as vector: copies
  and strokes for HTML type, textPath and a clip over an <image> for SVG.
- Fitting is solved when the text is edited, in the Studio and in the edit
  engine, and stored; the chosen size is kept as the ceiling.
- Text sizes reach 400 pt and tracking 1.5 em.
- The Elements panel opens on a Text tab: the plain styles and 24 lockups
  (titles, badges and stamps, chapter marks, quotes, captions, places), each
  drawn with the book's own title and placed as one group.
- The assistant sets every effect through style and add, places a lockup with
  add_lockup, reads the effects in describe_book_page and gets two new
  findings (curve_overflow, effect_heavy) beside photo-fill and band checks.
- Book format revision 6.
2026-10-23 22:08:13 +00:00

93 lines
4.8 KiB
TypeScript

import { normalizeBookDocument } from './book-normalize';
import type { BookDocument } from './book.schema';
/**
* Which revision of the book format a document was written by, and what a
* reader does with one that is newer than it knows.
*
* ── Why a revision beside `version` ──────────────────────────────────────
*
* `version` is the format itself and stays 1: every document ever saved
* reads under it, and nothing here asks for a migration. What grows is the
* vocabulary inside it, a new element kind, a new font, a new field the
* renderer draws, and an older reader cannot tell any of those from noise.
* `normalizeBookDocument` drops an element it cannot read and the contract
* strips a field it does not know, both on purpose, so a damaged book still
* opens. Saving what is left is the part that must not happen: an old tab, an
* offline copy or a replayed request would write the loss down for everyone.
*
* So the document says which revision wrote it. A reader that meets a newer
* one opens it read-only and asks for a reload, and the server refuses a save
* from a writer older than the book it would replace.
*
* ── When to bump ─────────────────────────────────────────────────────────
*
* `BOOK_SCHEMA_REV` goes up by one in the change that adds an element kind,
* an enum value or a drawn field to book.schema.ts, never for a change an
* older reader survives whole. A writer stamps its own revision on what it
* saves only once that is above 1, so a book saved today stores exactly what
* it stored before this existed.
*/
/**
* The revisions so far:
*
* - 2: the wider type, new families and weights (book-fonts.data.ts).
* - 3: paint on shapes and pages (`paint`, `backgroundPaint`, book-paint.schema.ts).
* - 4: the sticker kind (book-sticker.ts, stickers/).
* - 5: groups, names and hidden layers (`group`, `name`, `hidden`, a spread's `groups`).
* - 6: the photo look (`border`, `shadow`, `zoom`, `flipX`, `adjust`, `duotone`, `cutout`, book-photo-look.schema.ts).
* - 7: the second shelf of shapes, the cut edge of a shape and a photo (`edge`, `edgeSize`,
* book-shapes-more.ts) and the line kind (book-line.schema.ts).
* - 8: themes (the document's `theme`, and the `token`, `strokeToken`, `accentToken`,
* `fontRole` and `backgroundToken` links, theme/book-theme.schema.ts).
* - 9: text effects and fitting (`transform`, `vAlign`, `outline`, `shadow`, `highlight`,
* `curve`, `fillPaint`, `fit`, `fitMax`, book-text-effects.schema.ts), text up to 400 pt and
* tracking up to 1.5 em.
*/
export const BOOK_SCHEMA_REV = 9;
/** The `code` a refused save carries in its body when the stored book is newer than the writer. */
export const BOOK_SCHEMA_NEWER_CODE = 'book_schema_newer';
/**
* The revision a document says it was written by, read from the raw value
* rather than the parsed one: a document so new that it fails to parse whole
* still says how new it is. Anything that is not a whole number from 1 up,
* and every document from before the field, reads as 1.
*/
export function bookSchemaRev(raw: unknown): number {
if (!raw || typeof raw !== 'object') return 1;
const rev = (raw as { schemaRev?: unknown }).schemaRev;
return typeof rev === 'number' && Number.isInteger(rev) && rev >= 1 ? rev : 1;
}
/** Whether a document was written by a newer revision than `readerRev` (this build's by default). */
export function isBookNewerThanReader(raw: unknown, readerRev: number = BOOK_SCHEMA_REV): boolean {
return bookSchemaRev(raw) > readerRev;
}
/**
* The document as a writer of `writerRev` saves it: stamped with that
* revision, unless it already says as much or more. A document at revision 1
* is handed back as it is, so nothing changes on disk until the first bump.
*/
export function stampBookSchemaRev<T extends BookDocument>(doc: T, writerRev: number = BOOK_SCHEMA_REV): T {
return bookSchemaRev(doc) >= writerRev ? doc : { ...doc, schemaRev: writerRev };
}
/** A stored document as a reader holds it: normalised, and whether it was written by a newer revision. */
export interface BookRead {
document: BookDocument;
/** True when the document is newer than the reader: show it, never save it. */
newer: boolean;
}
/**
* Read a stored document the way normalizeBookDocument does, and say whether
* it is newer than this reader. The normalised copy of a newer book has lost
* what this build cannot read, which is why `newer` has to travel with it.
*/
export function readBookDocument(raw: unknown, readerRev: number = BOOK_SCHEMA_REV): BookRead {
return { document: normalizeBookDocument(raw), newer: isBookNewerThanReader(raw, readerRev) };
}