@segnavia/format 0.26.0 → 0.28.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/book.js CHANGED
@@ -1,28 +1,14 @@
1
- import { Direction } from "./direction.js";
1
+ import { Direction, resolveDirection } from "./direction.js";
2
2
  import { Scripts } from "./scripts.js";
3
- import { NoteMarker, NumberSystem } from "./numbering.js";
3
+ import { Slots } from "./slots.js";
4
+ import { FontReference } from "./fonts.js";
5
+ import { NoteMarker, NumberSystem, isHebrew } from "./numbering.js";
6
+ import { ParentPage, bookParentPages } from "./printed-pages.js";
7
+ import { BookChapterEntry, BookContents, BookFrontMatterEntry, BookOpens, BookSection, BookSectionParents, BookTemplate, Chapter, DesignedPage, DesignedSection, REGION_NAME, TemplatedPage, TemplatedSection, bookParentIssues, isBookContents, isChapter, isDesignedSection, isTemplatedSection } from "./book-section.js";
4
8
  import { Orientation, Paper, sheetMm } from "./paper.js";
5
9
  import { z } from "zod";
6
10
  //#region src/book.ts
7
11
  const BookMode = z.enum(["continuous", "paginated"]);
8
- const BookSection = z.object({
9
- title: z.string().optional(),
10
- html: z.string(),
11
- contents: z.boolean().default(true)
12
- });
13
- const BookContents = z.object({
14
- kind: z.literal("contents"),
15
- title: z.string().optional()
16
- });
17
- const BookFrontMatterEntry = z.union([BookSection, BookContents]);
18
- function isBookContents(entry) {
19
- return "kind" in entry && entry.kind === "contents";
20
- }
21
- const Chapter = z.object({
22
- title: z.string(),
23
- html: z.string(),
24
- runningHead: z.string().optional()
25
- });
26
12
  const BookPage = z.object({
27
13
  paper: Paper,
28
14
  orientation: Orientation.default("portrait"),
@@ -31,7 +17,16 @@ const BookPage = z.object({
31
17
  bottom: z.number().nonnegative(),
32
18
  inner: z.number().nonnegative(),
33
19
  outer: z.number().nonnegative()
34
- })
20
+ }),
21
+ columnGap: z.number().nonnegative().default(4)
22
+ });
23
+ const BookRunningHeadSource = z.union([z.literal("title"), z.object({
24
+ mark: z.string().min(1),
25
+ which: z.enum(["first", "last"])
26
+ })]);
27
+ const BookRunningHead = z.object({
28
+ recto: BookRunningHeadSource.default("title"),
29
+ verso: BookRunningHeadSource.default("title")
35
30
  });
36
31
  const BookShape = z.object({
37
32
  title: z.string(),
@@ -41,7 +36,12 @@ const BookShape = z.object({
41
36
  dir: Direction.optional(),
42
37
  lang: z.string().optional(),
43
38
  frontMatter: z.array(BookFrontMatterEntry).default([]),
44
- chapters: z.array(Chapter).min(1),
39
+ chapters: z.array(BookChapterEntry).min(1),
40
+ parentPages: z.array(ParentPage).default([]),
41
+ templates: z.array(BookTemplate).default([]),
42
+ slots: Slots.optional(),
43
+ fonts: z.array(FontReference).optional(),
44
+ css: z.string().optional(),
45
45
  numbering: z.object({
46
46
  frontMatter: NumberSystem.optional(),
47
47
  body: NumberSystem.optional()
@@ -56,6 +56,12 @@ const BookShape = z.object({
56
56
  title: z.string().optional()
57
57
  }).default({ placement: "book" }),
58
58
  rectoOpenings: z.boolean().default(false),
59
+ orphans: z.number().int().min(1).default(2),
60
+ widows: z.number().int().min(1).default(2),
61
+ runningHead: BookRunningHead.default({
62
+ recto: "title",
63
+ verso: "title"
64
+ }),
59
65
  contents: z.object({
60
66
  pageNumbers: z.boolean().default(true),
61
67
  depth: z.number().int().min(1).max(6).default(2)
@@ -64,19 +70,41 @@ const BookShape = z.object({
64
70
  depth: 2
65
71
  })
66
72
  });
73
+ const BookViewPagination = z.object({
74
+ paper: z.object({
75
+ width: z.number().positive(),
76
+ height: z.number().positive()
77
+ }),
78
+ margin: BookPage.shape.margin,
79
+ columnGap: z.number().nonnegative(),
80
+ direction: z.enum(["ltr", "rtl"]),
81
+ numbering: z.object({
82
+ frontMatter: NumberSystem,
83
+ body: NumberSystem
84
+ }),
85
+ rectoOpenings: z.boolean(),
86
+ orphans: z.number().int().min(1),
87
+ widows: z.number().int().min(1),
88
+ parents: z.array(ParentPage),
89
+ endnotePlacement: z.enum(["book", "chapter"]),
90
+ contents: z.object({
91
+ pageNumbers: z.boolean(),
92
+ depth: z.number().int().min(1).max(6)
93
+ }),
94
+ runningHead: BookRunningHead
95
+ });
67
96
  const BookView = BookShape.pick({
68
97
  title: true,
69
98
  dir: true,
70
99
  lang: true,
71
- scripts: true
100
+ scripts: true,
101
+ fonts: true,
102
+ css: true
72
103
  }).extend({
73
104
  title: BookShape.shape.title.optional(),
74
- html: z.string()
105
+ html: z.string(),
106
+ pagination: BookViewPagination.optional()
75
107
  });
76
- function isHebrew(lang) {
77
- const primary = lang?.split("-")[0]?.toLowerCase();
78
- return primary === "he" || primary === "iw";
79
- }
80
108
  const Book = BookShape.transform((book) => {
81
109
  const hebrew = isHebrew(book.lang);
82
110
  return {
@@ -117,6 +145,10 @@ const Book = BookShape.transform((book) => {
117
145
  path: ["endnotes", "placement"],
118
146
  message: "endnotes are placed at the end of each chapter only in paginated mode; use book placement or paginated mode"
119
147
  });
148
+ for (const issue of bookParentIssues(book)) ctx.addIssue({
149
+ code: "custom",
150
+ ...issue
151
+ });
120
152
  if (!book.page) return;
121
153
  const trim = sheetMm(book.page);
122
154
  const vertical = book.page.margin.top + book.page.margin.bottom;
@@ -132,9 +164,26 @@ const Book = BookShape.transform((book) => {
132
164
  message: `the inner and outer margins total ${horizontal}mm and the page is ${trim.width}mm wide; reduce the horizontal margins to leave a text area`
133
165
  });
134
166
  });
167
+ function resolveBookPagination(book) {
168
+ if (book.mode !== "paginated" || !book.page) return void 0;
169
+ return {
170
+ paper: sheetMm(book.page),
171
+ margin: book.page.margin,
172
+ columnGap: book.page.columnGap,
173
+ direction: resolveDirection(book.dir, book.lang, book.title),
174
+ numbering: book.numbering,
175
+ rectoOpenings: book.rectoOpenings,
176
+ orphans: book.orphans,
177
+ widows: book.widows,
178
+ parents: [...bookParentPages(book.page.margin), ...book.parentPages],
179
+ endnotePlacement: book.endnotes.placement,
180
+ contents: book.contents,
181
+ runningHead: book.runningHead
182
+ };
183
+ }
135
184
  function noteMarkerIssues(book, used) {
136
185
  if (!used.footnotes || !used.endnotes || book.footnotes.marker !== book.endnotes.marker) return [];
137
186
  return [`footnote and endnote markers are both ${book.footnotes.marker}; the two must differ when both kinds are used`];
138
187
  }
139
188
  //#endregion
140
- export { Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookShape, BookView, Chapter, isBookContents, isHebrew, noteMarkerIssues };
189
+ export { Book, BookChapterEntry, BookContents, BookFrontMatterEntry, BookMode, BookOpens, BookPage, BookRunningHead, BookRunningHeadSource, BookSection, BookSectionParents, BookShape, BookTemplate, BookView, BookViewPagination, Chapter, DesignedPage, DesignedSection, REGION_NAME, TemplatedPage, TemplatedSection, isBookContents, isChapter, isDesignedSection, isTemplatedSection, noteMarkerIssues, resolveBookPagination };
@@ -10,6 +10,7 @@ export declare const TextDirection: z.ZodEnum<{
10
10
  rtl: "rtl";
11
11
  }>;
12
12
  export type TextDirection = z.infer<typeof TextDirection>;
13
+ export declare function textDirection(dir: string | undefined, lang: string | undefined, sample: string): TextDirection | null;
13
14
  export declare function firstStrongDirection(text: string): TextDirection | null;
14
15
  export declare function languageDirection(lang: string | undefined): TextDirection | null;
15
16
  export declare function resolveDirection(dir: 'ltr' | 'rtl' | 'auto' | undefined, lang: string | undefined, sample: string): TextDirection;
package/dist/direction.js CHANGED
@@ -6,41 +6,44 @@ const Direction = z.enum([
6
6
  "auto"
7
7
  ]);
8
8
  const TextDirection = z.enum(["ltr", "rtl"]);
9
- const RTL_LETTER = /[\p{Script=Hebrew}\p{Script=Arabic}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Samaritan}\p{Script=Mandaic}\p{Script=Adlam}\p{Script=Hanifi_Rohingya}\p{Script=Mende_Kikakui}\p{Script=Old_Hungarian}\p{Script=Imperial_Aramaic}\p{Script=Phoenician}]/u;
10
- const LETTER = /\p{L}/u;
11
- const RTL_LANGUAGES = /* @__PURE__ */ new Set([
12
- "ar",
13
- "arc",
14
- "ckb",
15
- "dv",
16
- "fa",
17
- "he",
18
- "iw",
19
- "ji",
20
- "ks",
21
- "ku",
22
- "nqo",
23
- "ps",
24
- "sd",
25
- "syr",
26
- "ug",
27
- "ur",
28
- "yi"
29
- ]);
30
- function firstStrongDirection(text) {
31
- for (const char of text) {
32
- if (RTL_LETTER.test(char)) return "rtl";
33
- if (LETTER.test(char)) return "ltr";
9
+ function textDirection(dir, lang, sample) {
10
+ const rtlLetter = /[\p{Script=Hebrew}\p{Script=Arabic}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Samaritan}\p{Script=Mandaic}\p{Script=Adlam}\p{Script=Hanifi_Rohingya}\p{Script=Mende_Kikakui}\p{Script=Old_Hungarian}\p{Script=Imperial_Aramaic}\p{Script=Phoenician}]/u;
11
+ const letter = /\p{L}/u;
12
+ const rtlLanguages = /* @__PURE__ */ new Set([
13
+ "ar",
14
+ "arc",
15
+ "ckb",
16
+ "dv",
17
+ "fa",
18
+ "he",
19
+ "iw",
20
+ "ji",
21
+ "ks",
22
+ "ku",
23
+ "nqo",
24
+ "ps",
25
+ "sd",
26
+ "syr",
27
+ "ug",
28
+ "ur",
29
+ "yi"
30
+ ]);
31
+ if (dir === "ltr" || dir === "rtl") return dir;
32
+ if (lang) return rtlLanguages.has(lang.toLowerCase().split("-")[0] ?? "") ? "rtl" : "ltr";
33
+ for (const char of sample) {
34
+ if (rtlLetter.test(char)) return "rtl";
35
+ if (letter.test(char)) return "ltr";
34
36
  }
35
37
  return null;
36
38
  }
39
+ function firstStrongDirection(text) {
40
+ return textDirection(void 0, void 0, text);
41
+ }
37
42
  function languageDirection(lang) {
38
- if (!lang) return null;
39
- return RTL_LANGUAGES.has(lang.toLowerCase().split("-")[0] ?? "") ? "rtl" : "ltr";
43
+ return lang ? textDirection(void 0, lang, "") : null;
40
44
  }
41
45
  function resolveDirection(dir, lang, sample) {
42
- if (dir === "ltr" || dir === "rtl") return dir;
43
- return languageDirection(lang) ?? firstStrongDirection(sample) ?? "ltr";
46
+ return textDirection(dir, lang, sample) ?? "ltr";
44
47
  }
45
48
  //#endregion
46
- export { Direction, TextDirection, firstStrongDirection, languageDirection, resolveDirection };
49
+ export { Direction, TextDirection, firstStrongDirection, languageDirection, resolveDirection, textDirection };
@@ -0,0 +1,77 @@
1
+ import { z } from "zod";
2
+ //#region src/fonts.d.ts
3
+ export declare const APP_FONT_NAME: RegExp;
4
+ export declare const APP_FONT_NAME_MESSAGE = "app font names are lowercase letters, digits and hyphens";
5
+ export declare const FontReference: z.ZodUnion<readonly [z.ZodObject<{
6
+ app: z.ZodString;
7
+ }, z.core.$strip>, z.ZodObject<{
8
+ local: z.ZodString;
9
+ fallback: z.ZodString;
10
+ }, z.core.$strip>]>;
11
+ export type FontReference = z.infer<typeof FontReference>;
12
+ export declare const FontReferenceInput: z.ZodUnion<readonly [z.ZodObject<{
13
+ app: z.ZodString;
14
+ }, z.core.$strict>, z.ZodObject<{
15
+ local: z.ZodString;
16
+ fallback: z.ZodString;
17
+ }, z.core.$strict>]>;
18
+ export type FontReferenceInput = z.input<typeof FontReferenceInput>;
19
+ export declare function localFontReferences(references: readonly FontReference[]): Array<{
20
+ local: string;
21
+ fallback: string;
22
+ }>;
23
+ export declare const FONT_FORMATS: readonly ["woff2", "woff", "truetype", "opentype"];
24
+ export type FontFormat = (typeof FONT_FORMATS)[number];
25
+ export declare const LocalFaceTransport: z.ZodObject<{
26
+ family: z.ZodString;
27
+ format: z.ZodEnum<{
28
+ woff2: "woff2";
29
+ woff: "woff";
30
+ truetype: "truetype";
31
+ opentype: "opentype";
32
+ }>;
33
+ weight: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
34
+ style: z.ZodOptional<z.ZodEnum<{
35
+ normal: "normal";
36
+ italic: "italic";
37
+ }>>;
38
+ data: z.ZodString;
39
+ }, z.core.$strict>;
40
+ export type LocalFaceTransport = z.infer<typeof LocalFaceTransport>;
41
+ export declare const LocalFacesTransport: z.ZodArray<z.ZodObject<{
42
+ family: z.ZodString;
43
+ format: z.ZodEnum<{
44
+ woff2: "woff2";
45
+ woff: "woff";
46
+ truetype: "truetype";
47
+ opentype: "opentype";
48
+ }>;
49
+ weight: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
50
+ style: z.ZodOptional<z.ZodEnum<{
51
+ normal: "normal";
52
+ italic: "italic";
53
+ }>>;
54
+ data: z.ZodString;
55
+ }, z.core.$strict>>;
56
+ export type LocalFacesInput = LocalFaceTransport[];
57
+ export declare const FONT_GENERICS: readonly ["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui"];
58
+ export type FontGeneric = (typeof FONT_GENERICS)[number];
59
+ export interface FontFace {
60
+ family: string;
61
+ data: Uint8Array;
62
+ format: FontFormat;
63
+ weight?: number | string;
64
+ style?: 'normal' | 'italic';
65
+ unicodeRange?: string;
66
+ }
67
+ export interface AppFont {
68
+ family: string;
69
+ faces: FontFace[];
70
+ generic?: FontGeneric;
71
+ }
72
+ export interface FontSource {
73
+ font(name: string, options: {
74
+ signal: AbortSignal;
75
+ }): Promise<AppFont | undefined>;
76
+ }
77
+ //#endregion
package/dist/fonts.js ADDED
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ //#region src/fonts.ts
3
+ const APP_FONT_NAME = /^[a-z0-9-]+$/;
4
+ const APP_FONT_NAME_MESSAGE = "app font names are lowercase letters, digits and hyphens";
5
+ const AppFontName = z.string().min(1).regex(APP_FONT_NAME, APP_FONT_NAME_MESSAGE);
6
+ const AppFontShape = { app: AppFontName };
7
+ const LocalFontShape = {
8
+ local: z.string().min(1),
9
+ fallback: AppFontName
10
+ };
11
+ const FontReference = z.union([z.object(AppFontShape), z.object(LocalFontShape)]);
12
+ const FontReferenceInput = z.union([z.strictObject(AppFontShape), z.strictObject(LocalFontShape)]);
13
+ function localFontReferences(references) {
14
+ const seen = /* @__PURE__ */ new Set();
15
+ const locals = [];
16
+ for (const reference of references) {
17
+ if (!("local" in reference)) continue;
18
+ const key = JSON.stringify(reference);
19
+ if (seen.has(key)) continue;
20
+ seen.add(key);
21
+ locals.push(reference);
22
+ }
23
+ return locals;
24
+ }
25
+ const FONT_FORMATS = [
26
+ "woff2",
27
+ "woff",
28
+ "truetype",
29
+ "opentype"
30
+ ];
31
+ const LocalFaceTransportShape = {
32
+ family: z.string().min(1),
33
+ format: z.enum(FONT_FORMATS),
34
+ weight: z.union([z.number(), z.string()]).optional(),
35
+ style: z.enum(["normal", "italic"]).optional(),
36
+ data: z.string()
37
+ };
38
+ const LocalFaceTransport = z.strictObject(LocalFaceTransportShape);
39
+ const LocalFacesTransport = z.array(LocalFaceTransport);
40
+ const FONT_GENERICS = [
41
+ "serif",
42
+ "sans-serif",
43
+ "monospace",
44
+ "cursive",
45
+ "fantasy",
46
+ "system-ui"
47
+ ];
48
+ //#endregion
49
+ export { APP_FONT_NAME, APP_FONT_NAME_MESSAGE, FONT_FORMATS, FONT_GENERICS, FontReference, FontReferenceInput, LocalFaceTransport, LocalFacesTransport, localFontReferences };