@segnavia/format 0.3.0 → 0.5.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
@@ -1,38 +1,17 @@
1
- import { MARK_GAP_MM, MARK_LENGTH_MM, MM_PER_PT, PAPER_MM, PX_PER_MM, printBoxes, printPadding, printProperties, roundMillimeters, sheetMm, toMillimeters } from "./paper.js";
2
- import { MAX_SLIDE_TIMING_MS, SlideTiming, slideTimingIssues } from "./timing.js";
1
+ import { Direction, firstStrongDirection, languageDirection, resolveDirection } from "./direction.js";
2
+ import { NoteMarker, NumberSystem, formatNumber, noteMarker } from "./numbering.js";
3
+ import { MARK_GAP_MM, MARK_LENGTH_MM, MM_PER_PT, NAMED_PAPERS, PAPER_MM, PX_PER_MM, Paper, printBoxes, printPadding, printProperties, roundMillimeters, sheetMm, toMillimeters } from "./paper.js";
4
+ import { Scripts } from "./scripts.js";
5
+ import { Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookShape, Chapter, isBookContents, noteMarkerIssues } from "./book.js";
6
+ import { MAX_SLIDE_TIMING_MS } from "./timing.js";
3
7
  import { DEFAULT_TRANSITION_DURATION_MS, MAX_TRANSITION_DURATION_MS, MIN_TRANSITION_DURATION_MS, Transition } from "./transition.js";
4
- import { firstStrongDirection, languageDirection, resolveDirection } from "./direction.js";
8
+ import { Annotation, Box, Cue, DECK_SIZE, Deck, DeckShape, Manifest, Narration, Point, PublishedShot, SHOT_KEY, Shot, Size, Slide, SlideContent, Word, annotationCueIssues, exceedsLimits } from "./deck.js";
9
+ import { Lexicon, PronunciationEntry, lexiconVersion } from "./lexicon.js";
10
+ import { deckFromSlides } from "./deck-from-slides.js";
5
11
  import { fitPage, impose, mirrorPlacement, pagePlacementLayout } from "./imposition.js";
6
12
  import { shotsUsed } from "./shots-used.js";
7
13
  import { z } from "zod";
8
14
  //#region src/index.ts
9
- const Size = z.object({
10
- width: z.number().int().positive(),
11
- height: z.number().int().positive()
12
- });
13
- const Scripts = z.enum(["refuse", "sandboxed"]);
14
- const Direction = z.enum([
15
- "ltr",
16
- "rtl",
17
- "auto"
18
- ]);
19
- const NAMED_PAPERS = [
20
- "letter",
21
- "legal",
22
- "tabloid",
23
- "a3",
24
- "a4",
25
- "a5"
26
- ];
27
- const Paper = z.union([z.enum(NAMED_PAPERS), z.object({
28
- width: z.number().positive(),
29
- height: z.number().positive(),
30
- unit: z.enum([
31
- "mm",
32
- "in",
33
- "pt"
34
- ])
35
- })]);
36
15
  const Margin = z.object({
37
16
  top: z.number().nonnegative().default(0),
38
17
  right: z.number().nonnegative().default(0),
@@ -159,193 +138,11 @@ const Page = z.object({
159
138
  lang: z.string().optional(),
160
139
  print: Print.optional()
161
140
  });
162
- const Box = z.object({
163
- x: z.number(),
164
- y: z.number(),
165
- width: z.number(),
166
- height: z.number()
167
- });
168
- const Point = z.object({
169
- x: z.number(),
170
- y: z.number()
171
- });
172
- const Annotation = z.object({
173
- ring: Box.nullable(),
174
- badge: z.object({
175
- center: Point,
176
- number: z.number().int()
177
- }).nullable(),
178
- arrow: z.object({
179
- head: Point,
180
- tail: Point,
181
- label: z.string().nullable()
182
- }).nullable()
183
- });
184
- const SHOT_KEY = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
185
- const Shot = z.object({
186
- key: z.string().regex(SHOT_KEY),
187
- title: z.string(),
188
- description: z.string().nullable().default(null),
189
- tags: z.array(z.string()).default([]),
190
- file: z.string(),
191
- annotatedFile: z.string().nullable().default(null),
192
- size: Size,
193
- scale: z.number().positive(),
194
- dir: z.enum(["ltr", "rtl"]).default("ltr"),
195
- annotations: z.array(Annotation),
196
- capturedAt: z.string(),
197
- meta: z.record(z.string(), z.unknown()).default({})
198
- });
199
- const PublishedShot = z.object({
200
- src: z.string(),
201
- shot: Shot
202
- });
203
- const Word = z.object({
204
- text: z.string(),
205
- startMs: z.number().nonnegative(),
206
- endMs: z.number().nonnegative()
207
- });
208
- const Cue = z.object({
209
- word: z.number().int().nonnegative(),
210
- annotation: z.number().int().nonnegative(),
211
- untilWord: z.number().int().nonnegative().optional()
212
- });
213
- const Narration = z.object({
214
- text: z.string(),
215
- lang: z.string().optional(),
216
- dir: Direction.optional(),
217
- voice: z.string().optional(),
218
- words: z.array(Word),
219
- durationMs: z.number().nonnegative(),
220
- audio: z.string().nullable(),
221
- live: z.boolean().default(false),
222
- cues: z.array(Cue).default([])
223
- });
224
- const SlideContent = z.discriminatedUnion("kind", [
225
- z.object({
226
- kind: z.literal("html"),
227
- html: z.string()
228
- }),
229
- z.object({
230
- kind: z.literal("url"),
231
- src: z.string()
232
- }),
233
- z.object({
234
- kind: z.literal("shot"),
235
- src: z.string(),
236
- shot: Shot
237
- }),
238
- z.object({
239
- kind: z.literal("shot-ref"),
240
- href: z.string()
241
- }),
242
- z.object({
243
- kind: z.literal("image"),
244
- src: z.string(),
245
- alt: z.string()
246
- })
247
- ]);
248
- const Slide = z.object({
249
- id: z.string(),
250
- content: SlideContent,
251
- notes: z.string().optional(),
252
- narration: Narration.optional(),
253
- beforeMs: SlideTiming.optional(),
254
- afterMs: SlideTiming.optional(),
255
- holdMs: SlideTiming.optional()
256
- });
257
- const DECK_SIZE = {
258
- width: 1280,
259
- height: 720
260
- };
261
- const DeckShape = z.object({
262
- title: z.string(),
263
- size: Size.default(DECK_SIZE),
264
- scripts: Scripts.default("refuse"),
265
- dir: Direction.optional(),
266
- lang: z.string().optional(),
267
- transition: Transition.optional(),
268
- playback: z.enum(["step", "continuous"]).optional(),
269
- captions: z.enum([
270
- "on",
271
- "off",
272
- "viewer"
273
- ]).optional(),
274
- slides: z.array(Slide).min(1)
275
- });
276
- function annotationCueIssues(narration, annotations) {
277
- if (!narration) return [];
278
- return narration.cues.filter((cue) => cue.annotation >= annotations).map((cue) => `a cue names annotation ${cue.annotation}, but the slide has ${annotations}`);
279
- }
280
- function cueIssues(slide) {
281
- const narration = slide.narration;
282
- if (!narration) return [];
283
- const annotations = slide.content.kind === "shot" ? slide.content.shot.annotations.length : null;
284
- const words = narration.live ? null : narration.words.length;
285
- const issues = [];
286
- for (const cue of narration.cues) {
287
- for (const word of [cue.word, cue.untilWord]) if (word !== void 0 && words !== null && word >= words) issues.push(`a cue names word ${word}, but the narration has ${words} words`);
288
- if (slide.content.kind !== "shot" && slide.content.kind !== "shot-ref") issues.push("a cue names an annotation, but only a shot carries annotations");
289
- }
290
- if (annotations !== null) issues.push(...annotationCueIssues(narration, annotations));
291
- return issues;
292
- }
293
- const Deck = DeckShape.superRefine((deck, ctx) => {
294
- deck.slides.forEach((slide, index) => {
295
- for (const message of cueIssues(slide)) ctx.addIssue({
296
- code: "custom",
297
- path: [
298
- "slides",
299
- index,
300
- "narration",
301
- "cues"
302
- ],
303
- message
304
- });
305
- for (const field of slideTimingIssues(slide)) {
306
- const kind = field === "holdMs" ? "without" : "with";
307
- ctx.addIssue({
308
- code: "custom",
309
- path: [
310
- "slides",
311
- index,
312
- field
313
- ],
314
- message: `${field} is allowed only ${kind} narration`
315
- });
316
- }
317
- });
318
- const ids = /* @__PURE__ */ new Set();
319
- deck.slides.forEach((slide, index) => {
320
- if (ids.has(slide.id)) ctx.addIssue({
321
- code: "custom",
322
- path: [
323
- "slides",
324
- index,
325
- "id"
326
- ],
327
- message: `slide id "${slide.id}" is used twice`
328
- });
329
- ids.add(slide.id);
330
- });
331
- });
332
- const Manifest = z.object({
333
- shots: z.record(z.string(), Shot),
334
- updatedAt: z.string()
335
- });
336
- function exceedsLimits(deck, limits) {
337
- const reasons = [];
338
- const bytes = (text) => new TextEncoder().encode(text).length;
339
- if (limits.maxSlides !== void 0 && deck.slides.length > limits.maxSlides) reasons.push(`the deck has ${deck.slides.length} slides; the limit is ${limits.maxSlides}`);
340
- deck.slides.forEach((slide, index) => {
341
- if (limits.maxSlideBytes !== void 0 && slide.content.kind === "html" && bytes(slide.content.html) > limits.maxSlideBytes) reasons.push(`slide ${index + 1} is ${bytes(slide.content.html)} bytes; the limit is ${limits.maxSlideBytes}`);
342
- if (limits.maxNotesBytes !== void 0 && slide.notes && bytes(slide.notes) > limits.maxNotesBytes) reasons.push(`slide ${index + 1} has ${bytes(slide.notes)} bytes of notes; the limit is ${limits.maxNotesBytes}`);
343
- });
344
- return reasons;
345
- }
346
141
  function jsonSchemas() {
347
142
  return {
143
+ book: z.toJSONSchema(BookShape, { io: "input" }),
348
144
  page: z.toJSONSchema(Page, { io: "input" }),
145
+ lexicon: z.toJSONSchema(Lexicon, { io: "input" }),
349
146
  imposition: z.toJSONSchema(Imposition, { io: "input" }),
350
147
  deck: z.toJSONSchema(DeckShape, { io: "input" }),
351
148
  manifest: z.toJSONSchema(Manifest, { io: "input" }),
@@ -353,4 +150,4 @@ function jsonSchemas() {
353
150
  };
354
151
  }
355
152
  //#endregion
356
- export { Annotation, Box, Cue, DECK_SIZE, DEFAULT_TRANSITION_DURATION_MS, Deck, Direction, Imposition, MARK_GAP_MM, MARK_LENGTH_MM, MAX_IMPOSITION_CELLS, MAX_SLIDE_TIMING_MS, MAX_TRANSITION_DURATION_MS, MIN_TRANSITION_DURATION_MS, MM_PER_PT, Manifest, Margin, NAMED_PAPERS, Narration, PAPER_MM, PX_PER_MM, Page, PageSize, Paper, Point, Print, PublishedShot, SHOT_KEY, Scripts, Shot, Size, Slide, SlideContent, Transition, Word, annotationCueIssues, exceedsLimits, firstStrongDirection, fitPage, impose, jsonSchemas, languageDirection, mirrorPlacement, pagePlacementLayout, printBoxes, printPadding, printProperties, resolveDirection, roundMillimeters, sheetMm, shotsUsed, toMillimeters };
153
+ export { Annotation, Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, Box, Chapter, Cue, DECK_SIZE, DEFAULT_TRANSITION_DURATION_MS, Deck, Direction, Imposition, Lexicon, MARK_GAP_MM, MARK_LENGTH_MM, MAX_IMPOSITION_CELLS, MAX_SLIDE_TIMING_MS, MAX_TRANSITION_DURATION_MS, MIN_TRANSITION_DURATION_MS, MM_PER_PT, Manifest, Margin, NAMED_PAPERS, Narration, NoteMarker, NumberSystem, PAPER_MM, PX_PER_MM, Page, PageSize, Paper, Point, Print, PronunciationEntry, PublishedShot, SHOT_KEY, Scripts, Shot, Size, Slide, SlideContent, Transition, Word, annotationCueIssues, deckFromSlides, exceedsLimits, firstStrongDirection, fitPage, formatNumber, impose, isBookContents, jsonSchemas, languageDirection, lexiconVersion, mirrorPlacement, noteMarker, noteMarkerIssues, pagePlacementLayout, printBoxes, printPadding, printProperties, resolveDirection, roundMillimeters, sheetMm, shotsUsed, toMillimeters };
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ //#region src/lexicon.d.ts
3
+ export declare const PronunciationEntry: z.ZodObject<{
4
+ term: z.ZodString;
5
+ say: z.ZodString;
6
+ ipa: z.ZodOptional<z.ZodString>;
7
+ }, z.core.$strip>;
8
+ export type PronunciationEntry = z.infer<typeof PronunciationEntry>;
9
+ export declare const Lexicon: z.ZodObject<{
10
+ entries: z.ZodArray<z.ZodObject<{
11
+ term: z.ZodString;
12
+ say: z.ZodString;
13
+ ipa: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>>;
15
+ }, z.core.$strip>;
16
+ export type Lexicon = z.infer<typeof Lexicon>;
17
+ export declare function lexiconVersion(lexicon: Lexicon): string;
18
+ //#endregion
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ //#region src/lexicon.ts
3
+ const SingleWord = z.string().min(1).regex(/^\S+$/u);
4
+ const PronunciationEntry = z.object({
5
+ term: SingleWord,
6
+ say: SingleWord,
7
+ ipa: z.string().optional()
8
+ });
9
+ const Lexicon = z.object({ entries: z.array(PronunciationEntry) }).superRefine((lexicon, ctx) => {
10
+ const terms = /* @__PURE__ */ new Set();
11
+ lexicon.entries.forEach((entry, index) => {
12
+ if (terms.has(entry.term)) ctx.addIssue({
13
+ code: "custom",
14
+ path: [
15
+ "entries",
16
+ index,
17
+ "term"
18
+ ],
19
+ message: `the term ${JSON.stringify(entry.term)} appears more than once`
20
+ });
21
+ terms.add(entry.term);
22
+ });
23
+ });
24
+ function lexiconVersion(lexicon) {
25
+ const canonical = [...lexicon.entries].sort((left, right) => left.term < right.term ? -1 : left.term > right.term ? 1 : 0).map(({ term, say, ipa }) => [
26
+ term,
27
+ say,
28
+ ipa ?? null
29
+ ]);
30
+ let hash = 14695981039346656037n;
31
+ for (const byte of new TextEncoder().encode(JSON.stringify(canonical))) {
32
+ hash ^= BigInt(byte);
33
+ hash = BigInt.asUintN(64, hash * 1099511628211n);
34
+ }
35
+ return hash.toString(16).padStart(16, "0").slice(0, 12);
36
+ }
37
+ //#endregion
38
+ export { Lexicon, PronunciationEntry, lexiconVersion };
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ //#region src/numbering.d.ts
3
+ export declare const NumberSystem: z.ZodEnum<{
4
+ arabic: "arabic";
5
+ "roman-lower": "roman-lower";
6
+ "roman-upper": "roman-upper";
7
+ hebrew: "hebrew";
8
+ }>;
9
+ export type NumberSystem = z.infer<typeof NumberSystem>;
10
+ export declare const NoteMarker: z.ZodUnion<readonly [z.ZodEnum<{
11
+ arabic: "arabic";
12
+ "roman-lower": "roman-lower";
13
+ "roman-upper": "roman-upper";
14
+ hebrew: "hebrew";
15
+ }>, z.ZodLiteral<"symbols">]>;
16
+ export type NoteMarker = z.infer<typeof NoteMarker>;
17
+ export declare function formatNumber(value: number, system: NumberSystem): string;
18
+ export declare function noteMarker(index: number, marker: NoteMarker): string;
19
+ //#endregion
@@ -0,0 +1,106 @@
1
+ import { z } from "zod";
2
+ //#region src/numbering.ts
3
+ const NumberSystem = z.enum([
4
+ "arabic",
5
+ "roman-lower",
6
+ "roman-upper",
7
+ "hebrew"
8
+ ]);
9
+ const NoteMarker = z.union([NumberSystem, z.literal("symbols")]);
10
+ const ROMAN_DIGITS = [
11
+ [1e3, "M"],
12
+ [900, "CM"],
13
+ [500, "D"],
14
+ [400, "CD"],
15
+ [100, "C"],
16
+ [90, "XC"],
17
+ [50, "L"],
18
+ [40, "XL"],
19
+ [10, "X"],
20
+ [9, "IX"],
21
+ [5, "V"],
22
+ [4, "IV"],
23
+ [1, "I"]
24
+ ];
25
+ const HEBREW_ONES = [
26
+ "",
27
+ "א",
28
+ "ב",
29
+ "ג",
30
+ "ד",
31
+ "ה",
32
+ "ו",
33
+ "ז",
34
+ "ח",
35
+ "ט"
36
+ ];
37
+ const HEBREW_TENS = [
38
+ "",
39
+ "י",
40
+ "כ",
41
+ "ל",
42
+ "מ",
43
+ "נ",
44
+ "ס",
45
+ "ע",
46
+ "פ",
47
+ "צ"
48
+ ];
49
+ const NOTE_SYMBOLS = [
50
+ "*",
51
+ "†",
52
+ "‡",
53
+ "§"
54
+ ];
55
+ function positiveInteger(value) {
56
+ if (!Number.isInteger(value) || value < 1) throw new RangeError(`value ${value} must be a positive integer`);
57
+ }
58
+ function inRange(value, maximum, system) {
59
+ if (value > maximum) throw new RangeError(`${system} value ${value} is outside the range 1 to ${maximum}`);
60
+ }
61
+ function roman(value) {
62
+ let remaining = value;
63
+ let result = "";
64
+ for (const [amount, digit] of ROMAN_DIGITS) while (remaining >= amount) {
65
+ result += digit;
66
+ remaining -= amount;
67
+ }
68
+ return result;
69
+ }
70
+ function hebrewBelowThousand(value) {
71
+ const hundreds = "ת".repeat(Math.floor(value / 400));
72
+ const underFourHundred = value % 400;
73
+ const hundredsLetter = [
74
+ "",
75
+ "ק",
76
+ "ר",
77
+ "ש"
78
+ ][Math.floor(underFourHundred / 100)];
79
+ const remainder = underFourHundred % 100;
80
+ if (remainder === 15) return `${hundreds}${hundredsLetter}טו`;
81
+ if (remainder === 16) return `${hundreds}${hundredsLetter}טז`;
82
+ return `${hundreds}${hundredsLetter}${HEBREW_TENS[Math.floor(remainder / 10)]}${HEBREW_ONES[remainder % 10]}`;
83
+ }
84
+ function hebrew(value) {
85
+ const thousands = Math.floor(value / 1e3);
86
+ const rest = value % 1e3;
87
+ return `${thousands === 0 ? "" : `${HEBREW_ONES[thousands]}׳`}${hebrewBelowThousand(rest)}`;
88
+ }
89
+ function formatNumber(value, system) {
90
+ positiveInteger(value);
91
+ if (system === "arabic") return String(value);
92
+ if (system === "hebrew") {
93
+ inRange(value, 9999, system);
94
+ return hebrew(value);
95
+ }
96
+ inRange(value, 3999, system);
97
+ const result = roman(value);
98
+ return system === "roman-lower" ? result.toLowerCase() : result;
99
+ }
100
+ function noteMarker(index, marker) {
101
+ positiveInteger(index);
102
+ if (marker !== "symbols") return formatNumber(index, marker);
103
+ return NOTE_SYMBOLS[(index - 1) % NOTE_SYMBOLS.length].repeat(Math.ceil(index / NOTE_SYMBOLS.length));
104
+ }
105
+ //#endregion
106
+ export { NoteMarker, NumberSystem, formatNumber, noteMarker };
package/dist/paper.d.ts CHANGED
@@ -1,5 +1,24 @@
1
- import { Paper, Print } from "./index.js";
1
+ import { Print } from "./index.js";
2
+ import { z } from "zod";
2
3
  //#region src/paper.d.ts
4
+ export declare const NAMED_PAPERS: readonly ["letter", "legal", "tabloid", "a3", "a4", "a5"];
5
+ export declare const Paper: z.ZodUnion<readonly [z.ZodEnum<{
6
+ letter: "letter";
7
+ legal: "legal";
8
+ tabloid: "tabloid";
9
+ a3: "a3";
10
+ a4: "a4";
11
+ a5: "a5";
12
+ }>, z.ZodObject<{
13
+ width: z.ZodNumber;
14
+ height: z.ZodNumber;
15
+ unit: z.ZodEnum<{
16
+ mm: "mm";
17
+ in: "in";
18
+ pt: "pt";
19
+ }>;
20
+ }, z.core.$strip>]>;
21
+ export type Paper = z.infer<typeof Paper>;
3
22
  export declare const MM_PER_PT: number;
4
23
  export declare const MARK_LENGTH_MM = 5;
5
24
  export declare const MARK_GAP_MM = 1;
package/dist/paper.js CHANGED
@@ -1,4 +1,22 @@
1
+ import { z } from "zod";
1
2
  //#region src/paper.ts
3
+ const NAMED_PAPERS = [
4
+ "letter",
5
+ "legal",
6
+ "tabloid",
7
+ "a3",
8
+ "a4",
9
+ "a5"
10
+ ];
11
+ const Paper = z.union([z.enum(NAMED_PAPERS), z.object({
12
+ width: z.number().positive(),
13
+ height: z.number().positive(),
14
+ unit: z.enum([
15
+ "mm",
16
+ "in",
17
+ "pt"
18
+ ])
19
+ })]);
2
20
  const MM_PER_PT = 25.4 / 72;
3
21
  const MARK_LENGTH_MM = 5;
4
22
  const MARK_GAP_MM = 1;
@@ -156,4 +174,4 @@ function printBoxes(print) {
156
174
  };
157
175
  }
158
176
  //#endregion
159
- export { MARK_GAP_MM, MARK_LENGTH_MM, MM_PER_PT, PAPER_MM, PX_PER_MM, printBoxes, printPadding, printProperties, roundMillimeters, sheetMm, toMillimeters };
177
+ export { MARK_GAP_MM, MARK_LENGTH_MM, MM_PER_PT, NAMED_PAPERS, PAPER_MM, PX_PER_MM, Paper, printBoxes, printPadding, printProperties, roundMillimeters, sheetMm, toMillimeters };
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ //#region src/scripts.d.ts
3
+ export declare const Scripts: z.ZodEnum<{
4
+ refuse: "refuse";
5
+ sandboxed: "sandboxed";
6
+ }>;
7
+ export type Scripts = z.infer<typeof Scripts>;
8
+ //#endregion
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+ //#region src/scripts.ts
3
+ const Scripts = z.enum(["refuse", "sandboxed"]);
4
+ //#endregion
5
+ export { Scripts };
@@ -1,4 +1,5 @@
1
- import { Deck } from "./index.js";
1
+ import { Deck } from "./deck.js";
2
+ import "./index.js";
2
3
  //#region src/shots-used.d.ts
3
4
  export interface ShotUse {
4
5
  slide: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segnavia/format",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {