@segnavia/format 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,10 +5,15 @@ import { MAX_SLIDE_TIMING_MS } from "./timing.js";
5
5
  import { DEFAULT_TRANSITION_DURATION_MS, MAX_TRANSITION_DURATION_MS, MIN_TRANSITION_DURATION_MS, Transition } from "./transition.js";
6
6
  import { Annotation, Box, Cue, DECK_SIZE, Deck, DeckShape, ImageFit, Manifest, Narration, PLAYBACK_MODES, Playback, Point, PublishedShot, SHOT_KEY, Shot, Size, Slide, SlideContent, Word, annotationCueIssues, exceedsLimits, parsePlayback } from "./deck.js";
7
7
  import { Board, BoardItem, BoardLabel, BoardSection, GridCell } from "./board.js";
8
- import { NoteMarker, NumberSystem, formatNumber, noteMarker } from "./numbering.js";
9
- import { CUSTOM_PAPER_MAX_MM, CUSTOM_PAPER_MIN_MM, MARK_GAP_MM, MARK_LENGTH_MM, MM_PER_PT, NAMED_PAPERS, ORIENTATIONS, Orientation, PAPER_GROUPS, PAPER_METADATA, PAPER_MM, PAPER_UNITS, PX_PER_MM, Paper, PaperUnit, PrintSetting, PrintSettingInput, fromMillimeters, paperPixels, printBoxes, printPadding, printProperties, roundMillimeters, sheetMm, toMillimeters } from "./paper.js";
10
- import { Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookShape, BookView, Chapter, isBookContents, isHebrew, noteMarkerIssues } from "./book.js";
8
+ import { APP_FONT_NAME, APP_FONT_NAME_MESSAGE, FONT_FORMATS, FONT_GENERICS, FontReference, FontReferenceInput, LocalFaceTransport, LocalFacesTransport, localFontReferences } from "./fonts.js";
9
+ import { NoteMarker, NumberSystem, defaultPageNumbering, formatNumber, isHebrew, noteMarker } from "./numbering.js";
10
+ import { CUSTOM_PAPER_MAX_MM, CUSTOM_PAPER_MIN_MM, FOLD_DASH_OFF_MM, FOLD_DASH_ON_MM, MARK_GAP_MM, MARK_LENGTH_MM, MM_PER_PT, NAMED_PAPERS, ORIENTATIONS, Orientation, PAPER_GROUPS, PAPER_METADATA, PAPER_MM, PAPER_UNITS, PX_PER_MM, Paper, PaperUnit, PrintSetting, PrintSettingInput, fromMillimeters, paperPixels, printBoxes, printPadding, printProperties, roundMillimeters, sheetMm, toMillimeters } from "./paper.js";
11
+ import { Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookShape, BookView, Chapter, isBookContents, noteMarkerIssues } from "./book.js";
12
+ import { PAPER_FIT_TOLERANCE_MM, refuseOverflow, refuseSafe } from "./imposition-fit.js";
13
+ import { FOLD_KINDS, FOLD_TUCK_IN_MM, foldCellPages, foldColumns, foldPageCountMessage, foldPanelMap, refineFold, resolveImposition } from "./fold.js";
11
14
  import { Lexicon, PronunciationEntry, lexiconVersion } from "./lexicon.js";
15
+ import { BOOK_FURNITURE, LIBRARY_MOTIF_PREFIX, MOTIF_ATTRIBUTE, Motif, MotifField, MotifFieldInput, MotifInput, MotifVersion, MotifVersionInput, PRINTED_PAGE_CONTEXT, PRINTED_PAGE_FOLIO_PATH, PRINTED_PAGE_NUMBER_PATH, PRINTED_PAGE_RUNNING_HEAD_PATH, PRINTED_PAGE_TOTAL_PATH, chooseMotifVersion, motifFieldAttribute } from "./motif.js";
16
+ import { BOOK_PARENT_NAMES, PRINTED_PAGE_LIMIT, PRINTED_PAGE_ROLES, ParentPage, ParentPageMargin, PrintedPageRules, PrintedPages, bookParentPages, pagePrintedPagesIssues, printedPagesIssues, resolveParentPages } from "./printed-pages.js";
12
17
  import { MIN_BOARD_ITEM_WIDTH, addItem, moveItems, moveSection, removeItems, resizeItem } from "./board-edit.js";
13
18
  import { MAX_BOARD_ZOOM, MIN_BOARD_ZOOM, arrange, boardBounds, boardToScreen, itemAt, itemHeight, revealView, screenToBoard, visibleItems, zoomAt, zoomToFit } from "./board-geometry.js";
14
19
  import { boardListing, boardListingIsEmpty, foldForSearch, nameMatches } from "./board-listing.js";
@@ -18,6 +23,7 @@ import { assignMembership, fitSection, fitTouchedSections, makeRoom, settleBoard
18
23
  import { deckFromSlides } from "./deck-from-slides.js";
19
24
  import { fitPage, impose, mirrorPlacement, pagePlacementLayout } from "./imposition.js";
20
25
  import { MIN_MARK_LENGTH_MM, impositionMarks } from "./imposition-marks.js";
26
+ import { isPrintedPageIdentityImposition } from "./printed-page-imposition.js";
21
27
  import { shotsUsed } from "./shots-used.js";
22
28
  import { z } from "zod";
23
29
  //#region src/index.ts
@@ -27,14 +33,21 @@ const Margin = z.object({
27
33
  bottom: z.number().nonnegative().default(0),
28
34
  left: z.number().nonnegative().default(0)
29
35
  });
36
+ const MarkColor = z.enum(["black", "registration"]);
30
37
  const Print = z.object({
31
38
  paper: Paper,
32
39
  orientation: Orientation.default("portrait"),
33
40
  margin: Margin.optional(),
34
41
  bleed: z.number().nonnegative().default(0),
35
42
  safe: z.number().nonnegative().default(0),
36
- marks: z.boolean().default(false)
43
+ marks: z.boolean().default(false),
44
+ markColor: MarkColor.default("black")
37
45
  }).superRefine((print, ctx) => {
46
+ if (print.markColor === "registration" && !print.marks) ctx.addIssue({
47
+ code: "custom",
48
+ path: ["markColor"],
49
+ message: "markColor requires marks to be true"
50
+ });
38
51
  const trim = sheetMm(print);
39
52
  if (print.safe * 2 >= trim.width) ctx.addIssue({
40
53
  code: "custom",
@@ -62,10 +75,10 @@ const Imposition = z.object({
62
75
  bottom: 0,
63
76
  left: 0
64
77
  }),
65
- columns: z.number().int().positive(),
66
- rows: z.number().int().positive(),
78
+ columns: z.number().int().positive().optional(),
79
+ rows: z.number().int().positive().optional(),
67
80
  cell: z.object({
68
- width: z.number().positive(),
81
+ width: z.number().positive().optional(),
69
82
  height: z.number().positive()
70
83
  }),
71
84
  gap: ImpositionGap.default({
@@ -76,19 +89,61 @@ const Imposition = z.object({
76
89
  safe: z.number().nonnegative().default(0),
77
90
  fit: z.enum(["contain", "cover"]).default("contain"),
78
91
  fill: z.enum(["sequence", "repeat"]).default("sequence"),
79
- sides: z.enum(["one", "two"]).default("one"),
92
+ sides: z.enum(["one", "two"]).optional(),
80
93
  flip: z.enum(["long", "short"]).default("long"),
81
94
  skip: z.number().int().nonnegative().default(0),
82
95
  dir: z.enum(["ltr", "rtl"]).default("ltr"),
83
96
  marks: z.boolean().default(false),
84
- cornerMarks: z.boolean().default(false)
97
+ markColor: MarkColor.default("black"),
98
+ cornerMarks: z.boolean().default(false),
99
+ fold: z.enum(FOLD_KINDS).optional(),
100
+ panels: z.array(z.number().positive()).optional()
85
101
  }).superRefine((imposition, ctx) => {
86
- const cellCount = BigInt(imposition.columns) * BigInt(imposition.rows);
102
+ if (imposition.markColor === "registration" && !imposition.marks) ctx.addIssue({
103
+ code: "custom",
104
+ path: ["markColor"],
105
+ message: "markColor requires marks to be true"
106
+ });
87
107
  if (imposition.cornerMarks && !imposition.marks) ctx.addIssue({
88
108
  code: "custom",
89
109
  path: ["cornerMarks"],
90
110
  message: "cornerMarks requires marks to be true"
91
111
  });
112
+ if (imposition.panels !== void 0 && !imposition.fold) ctx.addIssue({
113
+ code: "custom",
114
+ path: ["panels"],
115
+ message: "panels needs fold; set fold or remove panels"
116
+ });
117
+ if (imposition.fold) {
118
+ const panelCount = imposition.panels?.length ?? 0;
119
+ if (panelCount > 1e4) {
120
+ ctx.addIssue({
121
+ code: "custom",
122
+ path: ["panels"],
123
+ message: `the fold has ${panelCount} panels and the ceiling is ${MAX_IMPOSITION_CELLS}; fewer panels clears this refusal`
124
+ });
125
+ return;
126
+ }
127
+ refineFold(imposition, ctx);
128
+ return;
129
+ }
130
+ if (imposition.columns === void 0) ctx.addIssue({
131
+ code: "custom",
132
+ path: ["columns"],
133
+ message: "columns is required; give a column count to clear this refusal"
134
+ });
135
+ if (imposition.rows === void 0) ctx.addIssue({
136
+ code: "custom",
137
+ path: ["rows"],
138
+ message: "rows is required; give a row count to clear this refusal"
139
+ });
140
+ if (imposition.cell.width === void 0) ctx.addIssue({
141
+ code: "custom",
142
+ path: ["cell", "width"],
143
+ message: "cell.width is required; give a cell width to clear this refusal"
144
+ });
145
+ if (imposition.columns === void 0 || imposition.rows === void 0 || imposition.cell.width === void 0) return;
146
+ const cellCount = BigInt(imposition.columns) * BigInt(imposition.rows);
92
147
  if (imposition.columns > 1e4 || imposition.rows > 1e4 || cellCount > BigInt(1e4)) {
93
148
  ctx.addIssue({
94
149
  code: "custom",
@@ -104,28 +159,13 @@ const Imposition = z.object({
104
159
  });
105
160
  const gridWidth = mm(imposition.margin.left + imposition.columns * imposition.cell.width + (imposition.columns - 1) * imposition.gap.column + imposition.margin.right);
106
161
  const gridHeight = mm(imposition.margin.top + imposition.rows * imposition.cell.height + (imposition.rows - 1) * imposition.gap.row + imposition.margin.bottom);
107
- if (gridWidth - paper.width > .01) ctx.addIssue({
108
- code: "custom",
109
- path: ["columns"],
110
- message: `the grid is ${roundMillimeters(gridWidth)}mm wide and the paper is ${paper.width}mm wide, an overflow of ${roundMillimeters(gridWidth - paper.width)}mm; reduce the horizontal margins, columns, cell width or column gap`
111
- });
112
- if (gridHeight - paper.height > .01) ctx.addIssue({
113
- code: "custom",
114
- path: ["rows"],
115
- message: `the grid is ${roundMillimeters(gridHeight)}mm high and the paper is ${paper.height}mm high, an overflow of ${roundMillimeters(gridHeight - paper.height)}mm; reduce the vertical margins, rows, cell height or row gap`
162
+ refuseOverflow(ctx, paper, {
163
+ width: gridWidth,
164
+ height: gridHeight
116
165
  });
117
- const safe = mm(imposition.safe);
118
- const cellWidth = mm(imposition.cell.width);
119
- const cellHeight = mm(imposition.cell.height);
120
- if (safe * 2 >= cellWidth) ctx.addIssue({
121
- code: "custom",
122
- path: ["safe"],
123
- message: `safe is ${roundMillimeters(safe)}mm on the left and right, but the cell is ${roundMillimeters(cellWidth)}mm wide; safe must leave area inside the cell`
124
- });
125
- if (safe * 2 >= cellHeight) ctx.addIssue({
126
- code: "custom",
127
- path: ["safe"],
128
- message: `safe is ${roundMillimeters(safe)}mm on the top and bottom, but the cell is ${roundMillimeters(cellHeight)}mm high; safe must leave area inside the cell`
166
+ refuseSafe(ctx, mm(imposition.safe), {
167
+ width: mm(imposition.cell.width),
168
+ height: mm(imposition.cell.height)
129
169
  });
130
170
  const cells = imposition.columns * imposition.rows;
131
171
  if (imposition.skip >= cells) ctx.addIssue({
@@ -133,7 +173,7 @@ const Imposition = z.object({
133
173
  path: ["skip"],
134
174
  message: `skip is ${imposition.skip}, but the grid has ${cells} cells; skip must be less than ${cells}`
135
175
  });
136
- });
176
+ }).transform(resolveImposition);
137
177
  const PageSize = z.union([
138
178
  z.literal("fluid"),
139
179
  Size,
@@ -150,7 +190,14 @@ const Page = z.object({
150
190
  dir: Direction.optional(),
151
191
  lang: z.string().optional(),
152
192
  print: Print.optional(),
153
- slots: Slots.optional()
193
+ printedPages: PrintedPages.optional(),
194
+ slots: Slots.optional(),
195
+ fonts: z.array(FontReference).optional()
196
+ }).superRefine((page, ctx) => {
197
+ for (const issue of pagePrintedPagesIssues(page)) ctx.addIssue({
198
+ code: "custom",
199
+ ...issue
200
+ });
154
201
  });
155
202
  function jsonSchemas() {
156
203
  return {
@@ -166,8 +213,14 @@ function jsonSchemas() {
166
213
  printSetting: z.toJSONSchema(PrintSetting, { io: "input" }),
167
214
  printSettingInput: z.toJSONSchema(PrintSettingInput, { io: "input" }),
168
215
  slotDeclaration: z.toJSONSchema(SlotDeclaration, { io: "input" }),
169
- slotDeclarationInput: z.toJSONSchema(SlotDeclarationInput, { io: "input" })
216
+ slotDeclarationInput: z.toJSONSchema(SlotDeclarationInput, { io: "input" }),
217
+ fontReference: z.toJSONSchema(FontReference, { io: "input" }),
218
+ fontReferenceInput: z.toJSONSchema(FontReferenceInput, { io: "input" }),
219
+ localFaceTransport: z.toJSONSchema(LocalFaceTransport, { io: "input" }),
220
+ localFacesTransport: z.toJSONSchema(LocalFacesTransport, { io: "input" }),
221
+ motif: z.toJSONSchema(Motif, { io: "input" }),
222
+ motifInput: z.toJSONSchema(MotifInput, { io: "input" })
170
223
  };
171
224
  }
172
225
  //#endregion
173
- export { Annotation, Board, BoardItem, BoardLabel, BoardSection, Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookView, Box, CUSTOM_PAPER_MAX_MM, CUSTOM_PAPER_MIN_MM, Chapter, Cue, DECK_SIZE, DEFAULT_TRANSITION_DURATION_MS, Deck, Direction, FillValues, GridCell, ImageFit, Imposition, Lexicon, MAKE_ROOM_LIMIT, MARK_GAP_MM, MARK_LENGTH_MM, MAX_BOARD_ZOOM, MAX_IMPOSITION_CELLS, MAX_SLIDE_TIMING_MS, MAX_TRANSITION_DURATION_MS, MIN_BOARD_ITEM_WIDTH, MIN_BOARD_ZOOM, MIN_MARK_LENGTH_MM, MIN_TRANSITION_DURATION_MS, MM_PER_PT, Manifest, Margin, NAMED_PAPERS, Narration, NoteMarker, NumberSystem, ORIENTATIONS, Orientation, PAPER_GROUPS, PAPER_METADATA, PAPER_MM, PAPER_UNITS, PLAYBACK_MODES, PX_PER_MM, Page, PageSize, Paper, PaperUnit, Playback, Point, Print, PrintSetting, PrintSettingInput, PronunciationEntry, PublishedShot, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM, SHOT_KEY, SLOT_TYPES, Scripts, Shot, Size, Slide, SlideContent, SlotDeclaration, SlotDeclarationInput, SlotKey, SlotType, SlotValue, Slots, SlotsInput, TextDirection, Transition, Word, addItem, annotationCueIssues, arrange, assignMembership, boardBounds, boardListing, boardListingIsEmpty, boardToScreen, deckFromSlides, deriveSectionCells, exceedsLimits, firstStrongDirection, fitPage, fitSection, fitTouchedSections, foldForSearch, formatNumber, fromMillimeters, gridDropMarkerBox, gridDropTarget, impose, impositionMarks, insertIntoGrid, isBookContents, isHebrew, itemAt, itemHeight, jsonSchemas, languageDirection, layoutSection, lexiconVersion, makeRoom, mergeSlotDeclarations, mirrorPlacement, moveItems, moveSection, nameMatches, noteMarker, noteMarkerIssues, pagePlacementLayout, paperPixels, parsePlayback, printBoxes, printPadding, printProperties, removeFromGrid, removeItems, resizeItem, resolveDirection, revealView, roundMillimeters, screenToBoard, settleBoard, sheetMm, shotsUsed, toMillimeters, visibleItems, zoomAt, zoomToFit };
226
+ export { APP_FONT_NAME, APP_FONT_NAME_MESSAGE, Annotation, BOOK_FURNITURE, BOOK_PARENT_NAMES, Board, BoardItem, BoardLabel, BoardSection, Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookView, Box, CUSTOM_PAPER_MAX_MM, CUSTOM_PAPER_MIN_MM, Chapter, Cue, DECK_SIZE, DEFAULT_TRANSITION_DURATION_MS, Deck, Direction, FOLD_DASH_OFF_MM, FOLD_DASH_ON_MM, FOLD_KINDS, FOLD_TUCK_IN_MM, FONT_FORMATS, FONT_GENERICS, FillValues, FontReference, FontReferenceInput, GridCell, ImageFit, Imposition, LIBRARY_MOTIF_PREFIX, Lexicon, LocalFaceTransport, LocalFacesTransport, MAKE_ROOM_LIMIT, MARK_GAP_MM, MARK_LENGTH_MM, MAX_BOARD_ZOOM, MAX_IMPOSITION_CELLS, MAX_SLIDE_TIMING_MS, MAX_TRANSITION_DURATION_MS, MIN_BOARD_ITEM_WIDTH, MIN_BOARD_ZOOM, MIN_MARK_LENGTH_MM, MIN_TRANSITION_DURATION_MS, MM_PER_PT, MOTIF_ATTRIBUTE, Manifest, Margin, MarkColor, Motif, MotifField, MotifFieldInput, MotifInput, MotifVersion, MotifVersionInput, NAMED_PAPERS, Narration, NoteMarker, NumberSystem, ORIENTATIONS, Orientation, PAPER_FIT_TOLERANCE_MM, PAPER_GROUPS, PAPER_METADATA, PAPER_MM, PAPER_UNITS, PLAYBACK_MODES, PRINTED_PAGE_CONTEXT, PRINTED_PAGE_FOLIO_PATH, PRINTED_PAGE_LIMIT, PRINTED_PAGE_NUMBER_PATH, PRINTED_PAGE_ROLES, PRINTED_PAGE_RUNNING_HEAD_PATH, PRINTED_PAGE_TOTAL_PATH, PX_PER_MM, Page, PageSize, Paper, PaperUnit, ParentPage, ParentPageMargin, Playback, Point, Print, PrintSetting, PrintSettingInput, PrintedPageRules, PrintedPages, PronunciationEntry, PublishedShot, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM, SHOT_KEY, SLOT_TYPES, Scripts, Shot, Size, Slide, SlideContent, SlotDeclaration, SlotDeclarationInput, SlotKey, SlotType, SlotValue, Slots, SlotsInput, TextDirection, Transition, Word, addItem, annotationCueIssues, arrange, assignMembership, boardBounds, boardListing, boardListingIsEmpty, boardToScreen, bookParentPages, chooseMotifVersion, deckFromSlides, defaultPageNumbering, deriveSectionCells, exceedsLimits, firstStrongDirection, fitPage, fitSection, fitTouchedSections, foldCellPages, foldColumns, foldForSearch, foldPageCountMessage, foldPanelMap, formatNumber, fromMillimeters, gridDropMarkerBox, gridDropTarget, impose, impositionMarks, insertIntoGrid, isBookContents, isHebrew, isPrintedPageIdentityImposition, itemAt, itemHeight, jsonSchemas, languageDirection, layoutSection, lexiconVersion, localFontReferences, makeRoom, mergeSlotDeclarations, mirrorPlacement, motifFieldAttribute, moveItems, moveSection, nameMatches, noteMarker, noteMarkerIssues, pagePlacementLayout, pagePrintedPagesIssues, paperPixels, parsePlayback, printBoxes, printPadding, printProperties, printedPagesIssues, removeFromGrid, removeItems, resizeItem, resolveDirection, resolveParentPages, revealView, roundMillimeters, screenToBoard, settleBoard, sheetMm, shotsUsed, toMillimeters, visibleItems, zoomAt, zoomToFit };
@@ -0,0 +1,143 @@
1
+ import { z } from "zod";
2
+ //#region src/motif.d.ts
3
+ export declare const MOTIF_ATTRIBUTE = "data-motif";
4
+ export declare const PRINTED_PAGE_CONTEXT = "printedPage";
5
+ export declare const PRINTED_PAGE_NUMBER_PATH = "printedPage.number";
6
+ export declare const PRINTED_PAGE_TOTAL_PATH = "printedPage.total";
7
+ export declare const PRINTED_PAGE_FOLIO_PATH = "printedPage.folio";
8
+ export declare const PRINTED_PAGE_RUNNING_HEAD_PATH = "printedPage.runningHead";
9
+ export declare const LIBRARY_MOTIF_PREFIX = "segnavia:";
10
+ export declare const BOOK_FURNITURE: readonly [{
11
+ readonly key: "segnavia:folio";
12
+ readonly path: "printedPage.folio";
13
+ readonly value: "folio";
14
+ readonly className: "segnavia-folio";
15
+ readonly region: "foot";
16
+ }, {
17
+ readonly key: "segnavia:running-head";
18
+ readonly path: "printedPage.runningHead";
19
+ readonly value: "runningHead";
20
+ readonly className: "segnavia-running-head";
21
+ readonly region: "head";
22
+ }];
23
+ export type BookFurniture = (typeof BOOK_FURNITURE)[number];
24
+ export declare const motifFieldAttribute: (key: string) => string;
25
+ export declare const MotifField: z.ZodObject<{
26
+ key: z.ZodString;
27
+ label: z.ZodString;
28
+ choices: z.ZodOptional<z.ZodArray<z.ZodString>>;
29
+ default: z.ZodOptional<z.ZodString>;
30
+ required: z.ZodDefault<z.ZodBoolean>;
31
+ }, z.core.$strip>;
32
+ export type MotifField = z.infer<typeof MotifField>;
33
+ export declare const MotifFieldInput: z.ZodObject<{
34
+ key: z.ZodString;
35
+ label: z.ZodString;
36
+ choices: z.ZodOptional<z.ZodArray<z.ZodString>>;
37
+ default: z.ZodOptional<z.ZodString>;
38
+ required: z.ZodDefault<z.ZodBoolean>;
39
+ }, z.core.$strict>;
40
+ export type MotifFieldInput = z.input<typeof MotifFieldInput>;
41
+ export declare const MotifVersion: z.ZodObject<{
42
+ lang: z.ZodString;
43
+ dir: z.ZodOptional<z.ZodEnum<{
44
+ ltr: "ltr";
45
+ rtl: "rtl";
46
+ }>>;
47
+ when: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
48
+ html: z.ZodString;
49
+ }, z.core.$strip>;
50
+ export type MotifVersion = z.infer<typeof MotifVersion>;
51
+ export declare const MotifVersionInput: z.ZodObject<{
52
+ lang: z.ZodString;
53
+ dir: z.ZodOptional<z.ZodEnum<{
54
+ ltr: "ltr";
55
+ rtl: "rtl";
56
+ }>>;
57
+ when: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
58
+ html: z.ZodString;
59
+ }, z.core.$strict>;
60
+ export type MotifVersionInput = z.input<typeof MotifVersionInput>;
61
+ export declare const Motif: z.ZodObject<{
62
+ key: z.ZodString;
63
+ revision: z.ZodString;
64
+ label: z.ZodString;
65
+ fields: z.ZodArray<z.ZodObject<{
66
+ key: z.ZodString;
67
+ label: z.ZodString;
68
+ choices: z.ZodOptional<z.ZodArray<z.ZodString>>;
69
+ default: z.ZodOptional<z.ZodString>;
70
+ required: z.ZodDefault<z.ZodBoolean>;
71
+ }, z.core.$strip>>;
72
+ versions: z.ZodArray<z.ZodObject<{
73
+ lang: z.ZodString;
74
+ dir: z.ZodOptional<z.ZodEnum<{
75
+ ltr: "ltr";
76
+ rtl: "rtl";
77
+ }>>;
78
+ when: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
79
+ html: z.ZodString;
80
+ }, z.core.$strip>>;
81
+ fonts: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
82
+ app: z.ZodString;
83
+ }, z.core.$strip>, z.ZodObject<{
84
+ local: z.ZodString;
85
+ fallback: z.ZodString;
86
+ }, z.core.$strip>]>>>;
87
+ }, z.core.$strip>;
88
+ export type Motif = z.infer<typeof Motif>;
89
+ export declare const MotifInput: z.ZodObject<{
90
+ fields: z.ZodArray<z.ZodObject<{
91
+ key: z.ZodString;
92
+ label: z.ZodString;
93
+ choices: z.ZodOptional<z.ZodArray<z.ZodString>>;
94
+ default: z.ZodOptional<z.ZodString>;
95
+ required: z.ZodDefault<z.ZodBoolean>;
96
+ }, z.core.$strict>>;
97
+ versions: z.ZodArray<z.ZodObject<{
98
+ lang: z.ZodString;
99
+ dir: z.ZodOptional<z.ZodEnum<{
100
+ ltr: "ltr";
101
+ rtl: "rtl";
102
+ }>>;
103
+ when: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
104
+ html: z.ZodString;
105
+ }, z.core.$strict>>;
106
+ fonts: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
107
+ app: z.ZodString;
108
+ }, z.core.$strict>, z.ZodObject<{
109
+ local: z.ZodString;
110
+ fallback: z.ZodString;
111
+ }, z.core.$strict>]>>>;
112
+ key: z.ZodString;
113
+ revision: z.ZodString;
114
+ label: z.ZodString;
115
+ }, z.core.$strict>;
116
+ export type MotifInput = z.input<typeof MotifInput>;
117
+ export interface MotifPlacement {
118
+ key: string;
119
+ revision: string;
120
+ lang: string;
121
+ fields: Record<string, string>;
122
+ }
123
+ export interface MotifSource {
124
+ motif(key: string, options: {
125
+ revision?: string;
126
+ signal: AbortSignal;
127
+ }): Promise<Motif | undefined>;
128
+ data?(placement: MotifPlacement, options: {
129
+ signal: AbortSignal;
130
+ }): Promise<Record<string, unknown>>;
131
+ }
132
+ export type MotifVersionChoice = {
133
+ ok: true;
134
+ version: MotifVersion;
135
+ index: number;
136
+ } | {
137
+ ok: false;
138
+ reason: string;
139
+ };
140
+ export declare function chooseMotifVersion(motif: Motif, values: {
141
+ lang?: string;
142
+ } & Record<string, string | undefined>): MotifVersionChoice;
143
+ //#endregion
package/dist/motif.js ADDED
@@ -0,0 +1,175 @@
1
+ import { SlotKey } from "./slots.js";
2
+ import { FontReference, FontReferenceInput } from "./fonts.js";
3
+ import { z } from "zod";
4
+ //#region src/motif.ts
5
+ const MOTIF_ATTRIBUTE = "data-motif";
6
+ const PRINTED_PAGE_CONTEXT = "printedPage";
7
+ const PRINTED_PAGE_NUMBER_PATH = `${PRINTED_PAGE_CONTEXT}.number`;
8
+ const PRINTED_PAGE_TOTAL_PATH = `${PRINTED_PAGE_CONTEXT}.total`;
9
+ const PRINTED_PAGE_FOLIO_PATH = `${PRINTED_PAGE_CONTEXT}.folio`;
10
+ const PRINTED_PAGE_RUNNING_HEAD_PATH = `${PRINTED_PAGE_CONTEXT}.runningHead`;
11
+ const LIBRARY_MOTIF_PREFIX = "segnavia:";
12
+ const BOOK_FURNITURE = [{
13
+ key: "segnavia:folio",
14
+ path: PRINTED_PAGE_FOLIO_PATH,
15
+ value: "folio",
16
+ className: "segnavia-folio",
17
+ region: "foot"
18
+ }, {
19
+ key: "segnavia:running-head",
20
+ path: PRINTED_PAGE_RUNNING_HEAD_PATH,
21
+ value: "runningHead",
22
+ className: "segnavia-running-head",
23
+ region: "head"
24
+ }];
25
+ const motifFieldAttribute = (key) => `${MOTIF_ATTRIBUTE}-${key}`;
26
+ const uniqueChoices = (field, ctx) => {
27
+ if (field.choices && new Set(field.choices).size !== field.choices.length) ctx.addIssue({
28
+ code: "custom",
29
+ path: ["choices"],
30
+ message: "choices must be unique"
31
+ });
32
+ if (field.choices && field.default !== void 0 && !field.choices.includes(field.default)) ctx.addIssue({
33
+ code: "custom",
34
+ path: ["default"],
35
+ message: "default must be one of the field choices"
36
+ });
37
+ };
38
+ const MotifFieldShape = {
39
+ key: SlotKey,
40
+ label: z.string().min(1),
41
+ choices: z.array(z.string()).min(1).optional(),
42
+ default: z.string().optional(),
43
+ required: z.boolean().default(false)
44
+ };
45
+ const MotifField = z.object(MotifFieldShape).superRefine(uniqueChoices);
46
+ const MotifFieldInput = z.strictObject(MotifFieldShape).superRefine(uniqueChoices);
47
+ const MotifVersionShape = {
48
+ lang: z.string().min(1),
49
+ dir: z.enum(["ltr", "rtl"]).optional(),
50
+ when: z.record(SlotKey, z.string()).optional(),
51
+ html: z.string()
52
+ };
53
+ const MotifVersion = z.object(MotifVersionShape);
54
+ const MotifVersionInput = z.strictObject(MotifVersionShape);
55
+ const validateMotif = (motif, ctx) => {
56
+ const fields = new Map(motif.fields.map((field) => [field.key, field]));
57
+ const fieldCases = /* @__PURE__ */ new Map();
58
+ motif.fields.forEach((field, index) => {
59
+ const normalized = field.key.toLowerCase();
60
+ if (field.key.toLowerCase() === "lang") ctx.addIssue({
61
+ code: "custom",
62
+ path: [
63
+ "fields",
64
+ index,
65
+ "key"
66
+ ],
67
+ message: "lang is always present and must not be declared"
68
+ });
69
+ const previous = fieldCases.get(normalized);
70
+ if (previous !== void 0) ctx.addIssue({
71
+ code: "custom",
72
+ path: [
73
+ "fields",
74
+ index,
75
+ "key"
76
+ ],
77
+ message: `field key differs only by case from field ${previous + 1}`
78
+ });
79
+ else fieldCases.set(normalized, index);
80
+ });
81
+ const seen = /* @__PURE__ */ new Map();
82
+ motif.versions.forEach((version, index) => {
83
+ for (const [key, value] of Object.entries(version.when ?? {})) {
84
+ const field = fields.get(key);
85
+ if (!field?.choices) ctx.addIssue({
86
+ code: "custom",
87
+ path: [
88
+ "versions",
89
+ index,
90
+ "when",
91
+ key
92
+ ],
93
+ message: `when key "${key}" must name a choice field`
94
+ });
95
+ else if (!field.choices.includes(value)) ctx.addIssue({
96
+ code: "custom",
97
+ path: [
98
+ "versions",
99
+ index,
100
+ "when",
101
+ key
102
+ ],
103
+ message: `when value "${value}" must be one of the choices for "${key}"`
104
+ });
105
+ }
106
+ const conditions = Object.entries(version.when ?? {}).sort(([left], [right]) => left.localeCompare(right));
107
+ const signature = JSON.stringify([version.lang.toLowerCase(), conditions]);
108
+ const previous = seen.get(signature);
109
+ if (previous !== void 0) ctx.addIssue({
110
+ code: "custom",
111
+ path: ["versions", index],
112
+ message: `version duplicates version ${previous + 1} for the same lang and when values`
113
+ });
114
+ else seen.set(signature, index);
115
+ });
116
+ };
117
+ const MotifShape = {
118
+ key: SlotKey,
119
+ revision: z.string().min(1),
120
+ label: z.string().min(1),
121
+ fields: z.array(MotifField),
122
+ versions: z.array(MotifVersion).min(1),
123
+ fonts: z.array(FontReference).default([])
124
+ };
125
+ const MotifInputShape = {
126
+ ...MotifShape,
127
+ fields: z.array(MotifFieldInput),
128
+ versions: z.array(MotifVersionInput).min(1),
129
+ fonts: z.array(FontReferenceInput).default([])
130
+ };
131
+ const Motif = z.object(MotifShape).superRefine(validateMotif);
132
+ const MotifInput = z.strictObject(MotifInputShape).superRefine(validateMotif);
133
+ function chooseMotifVersion(motif, values) {
134
+ const suppliedLang = values.lang;
135
+ const langs = new Map(motif.versions.map((version) => [version.lang.toLowerCase(), version.lang]));
136
+ const lang = suppliedLang ?? (langs.size === 1 ? langs.values().next().value : void 0);
137
+ if (!lang) return {
138
+ ok: false,
139
+ reason: "no language was supplied and the motif has more than one language"
140
+ };
141
+ const resolved = Object.fromEntries(motif.fields.map((field) => [field.key, values[field.key] ?? field.default]));
142
+ const normalized = lang.toLowerCase();
143
+ const primary = normalized.split("-")[0];
144
+ const matches = motif.versions.map((version, index) => ({
145
+ version,
146
+ index
147
+ })).filter(({ version }) => {
148
+ const versionLang = version.lang.toLowerCase();
149
+ const versionPrimary = versionLang.split("-")[0];
150
+ if (versionLang !== normalized && versionPrimary !== primary) return false;
151
+ return Object.entries(version.when ?? {}).every(([key, value]) => resolved[key] === value);
152
+ }).map((match) => ({
153
+ ...match,
154
+ exact: match.version.lang.toLowerCase() === normalized,
155
+ specificity: Object.keys(match.version.when ?? {}).length
156
+ }));
157
+ if (matches.length === 0) return {
158
+ ok: false,
159
+ reason: `no version matches language "${lang}" and the supplied field values`
160
+ };
161
+ matches.sort((left, right) => Number(right.exact) - Number(left.exact) || right.specificity - left.specificity);
162
+ const best = matches[0];
163
+ const tied = matches.filter((match) => match.exact === best.exact && match.specificity === best.specificity);
164
+ if (tied.length > 1) return {
165
+ ok: false,
166
+ reason: `versions ${tied.map((match) => match.index + 1).join(", ")} match equally specifically`
167
+ };
168
+ return {
169
+ ok: true,
170
+ version: best.version,
171
+ index: best.index
172
+ };
173
+ }
174
+ //#endregion
175
+ export { BOOK_FURNITURE, LIBRARY_MOTIF_PREFIX, MOTIF_ATTRIBUTE, Motif, MotifField, MotifFieldInput, MotifInput, MotifVersion, MotifVersionInput, PRINTED_PAGE_CONTEXT, PRINTED_PAGE_FOLIO_PATH, PRINTED_PAGE_NUMBER_PATH, PRINTED_PAGE_RUNNING_HEAD_PATH, PRINTED_PAGE_TOTAL_PATH, chooseMotifVersion, motifFieldAttribute };
@@ -7,6 +7,8 @@ export declare const NumberSystem: z.ZodEnum<{
7
7
  hebrew: "hebrew";
8
8
  }>;
9
9
  export type NumberSystem = z.infer<typeof NumberSystem>;
10
+ export declare function isHebrew(lang: string | undefined): boolean;
11
+ export declare function defaultPageNumbering(lang: string | undefined): NumberSystem;
10
12
  export declare const NoteMarker: z.ZodUnion<readonly [z.ZodEnum<{
11
13
  arabic: "arabic";
12
14
  "roman-lower": "roman-lower";
package/dist/numbering.js CHANGED
@@ -6,6 +6,13 @@ const NumberSystem = z.enum([
6
6
  "roman-upper",
7
7
  "hebrew"
8
8
  ]);
9
+ function isHebrew(lang) {
10
+ const primary = lang?.split("-")[0]?.toLowerCase();
11
+ return primary === "he" || primary === "iw";
12
+ }
13
+ function defaultPageNumbering(lang) {
14
+ return isHebrew(lang) ? "hebrew" : "arabic";
15
+ }
9
16
  const NoteMarker = z.union([NumberSystem, z.literal("symbols")]);
10
17
  const ROMAN_DIGITS = [
11
18
  [1e3, "M"],
@@ -103,4 +110,4 @@ function noteMarker(index, marker) {
103
110
  return NOTE_SYMBOLS[(index - 1) % NOTE_SYMBOLS.length].repeat(Math.ceil(index / NOTE_SYMBOLS.length));
104
111
  }
105
112
  //#endregion
106
- export { NoteMarker, NumberSystem, formatNumber, noteMarker };
113
+ export { NoteMarker, NumberSystem, defaultPageNumbering, formatNumber, isHebrew, noteMarker };
package/dist/paper.d.ts CHANGED
@@ -291,6 +291,8 @@ export type PrintSettingInput = z.input<typeof PrintSettingInput>;
291
291
  export declare const MM_PER_PT: number;
292
292
  export declare const MARK_LENGTH_MM = 5;
293
293
  export declare const MARK_GAP_MM = 1;
294
+ export declare const FOLD_DASH_ON_MM = 2;
295
+ export declare const FOLD_DASH_OFF_MM = 1;
294
296
  export declare const roundMillimeters: (mm: number) => number;
295
297
  export declare function toMillimeters(value: number, unit: PaperUnit): number;
296
298
  export declare function fromMillimeters(millimeters: number, unit: PaperUnit): number;
@@ -322,6 +324,7 @@ export interface MarkSegment {
322
324
  y1: number;
323
325
  x2: number;
324
326
  y2: number;
327
+ dashed?: boolean;
325
328
  }
326
329
  export interface PrintBoxes {
327
330
  media: {
package/dist/paper.js CHANGED
@@ -206,6 +206,8 @@ const PrintSettingInput = z.strictObject(PrintSettingShape);
206
206
  const MM_PER_PT = 25.4 / 72;
207
207
  const MARK_LENGTH_MM = 5;
208
208
  const MARK_GAP_MM = 1;
209
+ const FOLD_DASH_ON_MM = 2;
210
+ const FOLD_DASH_OFF_MM = 1;
209
211
  const roundMillimeters = (mm) => Math.round(mm * 100) / 100;
210
212
  function toMillimeters(value, unit) {
211
213
  return value * (unit === "in" ? 25.4 : unit === "pt" ? MM_PER_PT : 1);
@@ -483,4 +485,4 @@ function printBoxes(print) {
483
485
  };
484
486
  }
485
487
  //#endregion
486
- export { CUSTOM_PAPER_MAX_MM, CUSTOM_PAPER_MIN_MM, MARK_GAP_MM, MARK_LENGTH_MM, MM_PER_PT, NAMED_PAPERS, ORIENTATIONS, Orientation, PAPER_GROUPS, PAPER_METADATA, PAPER_MM, PAPER_UNITS, PX_PER_MM, Paper, PaperUnit, PrintSetting, PrintSettingInput, fromMillimeters, paperPixels, printBoxes, printPadding, printProperties, roundMillimeters, sheetMm, toMillimeters };
488
+ export { CUSTOM_PAPER_MAX_MM, CUSTOM_PAPER_MIN_MM, FOLD_DASH_OFF_MM, FOLD_DASH_ON_MM, MARK_GAP_MM, MARK_LENGTH_MM, MM_PER_PT, NAMED_PAPERS, ORIENTATIONS, Orientation, PAPER_GROUPS, PAPER_METADATA, PAPER_MM, PAPER_UNITS, PX_PER_MM, Paper, PaperUnit, PrintSetting, PrintSettingInput, fromMillimeters, paperPixels, printBoxes, printPadding, printProperties, roundMillimeters, sheetMm, toMillimeters };
@@ -0,0 +1,5 @@
1
+ import { Imposition, Print } from "./index.js";
2
+ //#region src/printed-page-imposition.d.ts
3
+ /** True when an imposition leaves one printed page unchanged on one paper. */
4
+ export declare function isPrintedPageIdentityImposition(print: Print, imposition: Imposition): boolean;
5
+ //#endregion
@@ -0,0 +1,12 @@
1
+ import { sheetMm, toMillimeters } from "./paper.js";
2
+ //#region src/printed-page-imposition.ts
3
+ const close = (left, right) => Math.abs(left - right) <= .01;
4
+ /** True when an imposition leaves one printed page unchanged on one paper. */
5
+ function isPrintedPageIdentityImposition(print, imposition) {
6
+ const pagePaper = sheetMm(print);
7
+ const imposedPaper = sheetMm(imposition);
8
+ const mm = (value) => toMillimeters(value, imposition.unit);
9
+ return print.orientation === imposition.orientation && close(pagePaper.width, imposedPaper.width) && close(pagePaper.height, imposedPaper.height) && imposition.columns === 1 && imposition.rows === 1 && close(mm(imposition.cell.width ?? 0), pagePaper.width) && close(mm(imposition.cell.height), pagePaper.height) && Object.values(imposition.margin).every((value) => value === 0) && imposition.gap.column === 0 && imposition.gap.row === 0 && imposition.bleed === 0 && imposition.marks === false && imposition.cornerMarks === false && imposition.fill === "sequence" && imposition.sides !== "two" && imposition.skip === 0 && imposition.fold === void 0;
10
+ }
11
+ //#endregion
12
+ export { isPrintedPageIdentityImposition };