@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/book.d.ts +24 -1
- package/dist/book.js +6 -7
- package/dist/fold.d.ts +16 -0
- package/dist/fold.js +175 -0
- package/dist/fonts.d.ts +77 -0
- package/dist/fonts.js +49 -0
- package/dist/imposition-fit.d.ts +3 -0
- package/dist/imposition-fit.js +33 -0
- package/dist/imposition-marks.js +27 -20
- package/dist/imposition.js +55 -3
- package/dist/index.d.ts +419 -16
- package/dist/index.js +88 -35
- package/dist/motif.d.ts +143 -0
- package/dist/motif.js +175 -0
- package/dist/numbering.d.ts +2 -0
- package/dist/numbering.js +8 -1
- package/dist/paper.d.ts +3 -0
- package/dist/paper.js +3 -1
- package/dist/printed-page-imposition.d.ts +5 -0
- package/dist/printed-page-imposition.js +12 -0
- package/dist/printed-pages.d.ts +118 -0
- package/dist/printed-pages.js +188 -0
- package/package.json +1 -1
package/dist/book.d.ts
CHANGED
|
@@ -93,12 +93,17 @@ export declare const BookView: z.ZodObject<{
|
|
|
93
93
|
sandboxed: "sandboxed";
|
|
94
94
|
}>>;
|
|
95
95
|
lang: z.ZodOptional<z.ZodString>;
|
|
96
|
+
fonts: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
97
|
+
app: z.ZodString;
|
|
98
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
99
|
+
local: z.ZodString;
|
|
100
|
+
fallback: z.ZodString;
|
|
101
|
+
}, z.core.$strip>]>>>;
|
|
96
102
|
title: z.ZodOptional<z.ZodString>;
|
|
97
103
|
html: z.ZodString;
|
|
98
104
|
}, z.core.$strip>;
|
|
99
105
|
export type BookView = z.infer<typeof BookView>;
|
|
100
106
|
export type BookViewInput = z.input<typeof BookView>;
|
|
101
|
-
export declare function isHebrew(lang: string | undefined): boolean;
|
|
102
107
|
export declare const Book: z.ZodPipe<z.ZodObject<{
|
|
103
108
|
title: z.ZodString;
|
|
104
109
|
mode: z.ZodEnum<{
|
|
@@ -177,6 +182,12 @@ export declare const Book: z.ZodPipe<z.ZodObject<{
|
|
|
177
182
|
html: z.ZodString;
|
|
178
183
|
runningHead: z.ZodOptional<z.ZodString>;
|
|
179
184
|
}, z.core.$strip>>;
|
|
185
|
+
fonts: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
186
|
+
app: z.ZodString;
|
|
187
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
188
|
+
local: z.ZodString;
|
|
189
|
+
fallback: z.ZodString;
|
|
190
|
+
}, z.core.$strip>]>>>;
|
|
180
191
|
numbering: z.ZodDefault<z.ZodObject<{
|
|
181
192
|
frontMatter: z.ZodOptional<z.ZodEnum<{
|
|
182
193
|
arabic: "arabic";
|
|
@@ -272,6 +283,12 @@ export declare const Book: z.ZodPipe<z.ZodObject<{
|
|
|
272
283
|
} | undefined;
|
|
273
284
|
dir?: "ltr" | "rtl" | "auto" | undefined;
|
|
274
285
|
lang?: string | undefined;
|
|
286
|
+
fonts?: ({
|
|
287
|
+
app: string;
|
|
288
|
+
} | {
|
|
289
|
+
local: string;
|
|
290
|
+
fallback: string;
|
|
291
|
+
})[] | undefined;
|
|
275
292
|
}, {
|
|
276
293
|
title: string;
|
|
277
294
|
mode: "continuous" | "paginated";
|
|
@@ -323,6 +340,12 @@ export declare const Book: z.ZodPipe<z.ZodObject<{
|
|
|
323
340
|
} | undefined;
|
|
324
341
|
dir?: "ltr" | "rtl" | "auto" | undefined;
|
|
325
342
|
lang?: string | undefined;
|
|
343
|
+
fonts?: ({
|
|
344
|
+
app: string;
|
|
345
|
+
} | {
|
|
346
|
+
local: string;
|
|
347
|
+
fallback: string;
|
|
348
|
+
})[] | undefined;
|
|
326
349
|
}>>;
|
|
327
350
|
export type Book = z.infer<typeof Book>;
|
|
328
351
|
export type BookInput = z.input<typeof Book>;
|
package/dist/book.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Direction } from "./direction.js";
|
|
2
2
|
import { Scripts } from "./scripts.js";
|
|
3
|
-
import {
|
|
3
|
+
import { FontReference } from "./fonts.js";
|
|
4
|
+
import { NoteMarker, NumberSystem, isHebrew } from "./numbering.js";
|
|
4
5
|
import { Orientation, Paper, sheetMm } from "./paper.js";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
//#region src/book.ts
|
|
@@ -42,6 +43,7 @@ const BookShape = z.object({
|
|
|
42
43
|
lang: z.string().optional(),
|
|
43
44
|
frontMatter: z.array(BookFrontMatterEntry).default([]),
|
|
44
45
|
chapters: z.array(Chapter).min(1),
|
|
46
|
+
fonts: z.array(FontReference).optional(),
|
|
45
47
|
numbering: z.object({
|
|
46
48
|
frontMatter: NumberSystem.optional(),
|
|
47
49
|
body: NumberSystem.optional()
|
|
@@ -68,15 +70,12 @@ const BookView = BookShape.pick({
|
|
|
68
70
|
title: true,
|
|
69
71
|
dir: true,
|
|
70
72
|
lang: true,
|
|
71
|
-
scripts: true
|
|
73
|
+
scripts: true,
|
|
74
|
+
fonts: true
|
|
72
75
|
}).extend({
|
|
73
76
|
title: BookShape.shape.title.optional(),
|
|
74
77
|
html: z.string()
|
|
75
78
|
});
|
|
76
|
-
function isHebrew(lang) {
|
|
77
|
-
const primary = lang?.split("-")[0]?.toLowerCase();
|
|
78
|
-
return primary === "he" || primary === "iw";
|
|
79
|
-
}
|
|
80
79
|
const Book = BookShape.transform((book) => {
|
|
81
80
|
const hebrew = isHebrew(book.lang);
|
|
82
81
|
return {
|
|
@@ -137,4 +136,4 @@ function noteMarkerIssues(book, used) {
|
|
|
137
136
|
return [`footnote and endnote markers are both ${book.footnotes.marker}; the two must differ when both kinds are used`];
|
|
138
137
|
}
|
|
139
138
|
//#endregion
|
|
140
|
-
export { Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookShape, BookView, Chapter, isBookContents,
|
|
139
|
+
export { Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookShape, BookView, Chapter, isBookContents, noteMarkerIssues };
|
package/dist/fold.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import "./paper.js";
|
|
2
|
+
//#region src/fold.d.ts
|
|
3
|
+
export declare const FOLD_KINDS: readonly ["half", "letter", "z", "gate", "custom"];
|
|
4
|
+
export type FoldKind = (typeof FOLD_KINDS)[number];
|
|
5
|
+
export declare const FOLD_TUCK_IN_MM = 1.5;
|
|
6
|
+
export declare function foldColumns(fold: FoldKind, panelCount?: number): number;
|
|
7
|
+
export declare function foldPanelMap(fold: FoldKind, columns: number): {
|
|
8
|
+
front: number[];
|
|
9
|
+
back: number[];
|
|
10
|
+
};
|
|
11
|
+
export declare function foldCellPages(fold: FoldKind, columns: number): Array<{
|
|
12
|
+
front: number;
|
|
13
|
+
back: number;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function foldPageCountMessage(fold: FoldKind, pageCount: number, columns: number): string;
|
|
16
|
+
//#endregion
|
package/dist/fold.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { fromMillimeters, roundMillimeters, sheetMm, toMillimeters } from "./paper.js";
|
|
2
|
+
import { refuseOverflow, refuseSafe } from "./imposition-fit.js";
|
|
3
|
+
//#region src/fold.ts
|
|
4
|
+
const FOLD_KINDS = [
|
|
5
|
+
"half",
|
|
6
|
+
"letter",
|
|
7
|
+
"z",
|
|
8
|
+
"gate",
|
|
9
|
+
"custom"
|
|
10
|
+
];
|
|
11
|
+
const FOLD_TUCK_IN_MM = 1.5;
|
|
12
|
+
const FOLD_HEIGHT_REMEDY = "reduce the vertical margins or cell height";
|
|
13
|
+
function foldColumns(fold, panelCount = 0) {
|
|
14
|
+
if (fold === "custom") return panelCount;
|
|
15
|
+
return fold === "half" ? 2 : 3;
|
|
16
|
+
}
|
|
17
|
+
function foldPanelMap(fold, columns) {
|
|
18
|
+
if (fold === "gate") return {
|
|
19
|
+
front: [
|
|
20
|
+
2,
|
|
21
|
+
6,
|
|
22
|
+
1
|
|
23
|
+
],
|
|
24
|
+
back: [
|
|
25
|
+
3,
|
|
26
|
+
4,
|
|
27
|
+
5
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
front: [...Array.from({ length: columns - 1 }, (_, index) => columns + 2 + index), 1],
|
|
32
|
+
back: Array.from({ length: columns }, (_, index) => index + 2)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function foldCellPages(fold, columns) {
|
|
36
|
+
const map = foldPanelMap(fold, columns);
|
|
37
|
+
return Array.from({ length: columns }, (_, cell) => ({
|
|
38
|
+
front: (map.front[cell] ?? 1) - 1,
|
|
39
|
+
back: (map.back[columns - 1 - cell] ?? 1) - 1
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
function foldPageCountMessage(fold, pageCount, columns) {
|
|
43
|
+
const need = columns * 2;
|
|
44
|
+
return `this ${fold} fold has ${pageCount} pages and needs ${need} (2 × ${columns} panels); pass ${need} pages to clear this refusal`;
|
|
45
|
+
}
|
|
46
|
+
function splitSum(available, count) {
|
|
47
|
+
const share = available / count;
|
|
48
|
+
const widths = Array.from({ length: count - 1 }, () => roundMillimeters(share));
|
|
49
|
+
const used = widths.reduce((sum, width) => sum + width, 0);
|
|
50
|
+
widths.push(roundMillimeters(available - used));
|
|
51
|
+
return widths;
|
|
52
|
+
}
|
|
53
|
+
function presetPanelWidthsMm(fold, availableMm) {
|
|
54
|
+
if (fold === "letter") {
|
|
55
|
+
const tuck = roundMillimeters(availableMm / 3 - FOLD_TUCK_IN_MM);
|
|
56
|
+
const rest = availableMm - tuck;
|
|
57
|
+
const second = roundMillimeters(rest / 2);
|
|
58
|
+
return [
|
|
59
|
+
tuck,
|
|
60
|
+
second,
|
|
61
|
+
roundMillimeters(rest - second)
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
if (fold === "gate") {
|
|
65
|
+
const flap = roundMillimeters(availableMm / 4 - FOLD_TUCK_IN_MM);
|
|
66
|
+
return [
|
|
67
|
+
flap,
|
|
68
|
+
roundMillimeters(availableMm - flap * 2),
|
|
69
|
+
flap
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
return splitSum(availableMm, foldColumns(fold));
|
|
73
|
+
}
|
|
74
|
+
function availableWidthMm(draft) {
|
|
75
|
+
const paper = sheetMm({
|
|
76
|
+
paper: draft.paper,
|
|
77
|
+
orientation: draft.orientation
|
|
78
|
+
});
|
|
79
|
+
return roundMillimeters(paper.width - toMillimeters(draft.margin.left, draft.unit) - toMillimeters(draft.margin.right, draft.unit));
|
|
80
|
+
}
|
|
81
|
+
function panelWidthsMm(draft) {
|
|
82
|
+
if (draft.panels) return draft.panels.map((width) => toMillimeters(width, draft.unit));
|
|
83
|
+
if (!draft.fold || draft.fold === "custom") return [];
|
|
84
|
+
return presetPanelWidthsMm(draft.fold, availableWidthMm(draft));
|
|
85
|
+
}
|
|
86
|
+
function panelsInUnit(draft) {
|
|
87
|
+
if (draft.panels) return draft.panels;
|
|
88
|
+
return panelWidthsMm(draft).map((width) => draft.unit === "mm" ? width : fromMillimeters(width, draft.unit));
|
|
89
|
+
}
|
|
90
|
+
function issue(ctx, path, message) {
|
|
91
|
+
ctx.addIssue({
|
|
92
|
+
code: "custom",
|
|
93
|
+
path,
|
|
94
|
+
message
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function refuseShape(draft, ctx, columns) {
|
|
98
|
+
if (draft.rows !== void 0 && draft.rows !== 1) issue(ctx, ["rows"], `rows is ${draft.rows}, but a fold is one row; set rows to 1 or omit it to clear this refusal`);
|
|
99
|
+
if (draft.sides === "one") issue(ctx, ["sides"], "sides is one, but a fold is two-sided; set sides to two or omit it to clear this refusal");
|
|
100
|
+
if (draft.fill === "repeat") issue(ctx, ["fill"], "fill is repeat, but a fold fills in sequence; set fill to sequence or omit it to clear this refusal");
|
|
101
|
+
if (draft.skip !== 0) issue(ctx, ["skip"], `skip is ${draft.skip}, but a fold has no skipped cells; set skip to 0 or omit it to clear this refusal`);
|
|
102
|
+
if (draft.gap.column !== 0) issue(ctx, ["gap", "column"], `column gap is ${draft.gap.column}, but a fold's panels touch; set gap.column to 0 or omit it to clear this refusal`);
|
|
103
|
+
if (draft.columns !== void 0 && draft.columns !== columns) issue(ctx, ["columns"], `columns is ${draft.columns}, but a ${draft.fold} fold has ${columns} panels; set columns to ${columns} or omit it to clear this refusal`);
|
|
104
|
+
}
|
|
105
|
+
function refineFold(draft, ctx) {
|
|
106
|
+
const fold = draft.fold;
|
|
107
|
+
if (!fold) return;
|
|
108
|
+
if (fold === "custom" && (draft.panels === void 0 || draft.panels.length < 2)) {
|
|
109
|
+
issue(ctx, ["panels"], "a custom fold needs panels with at least two widths; give panel widths that sum to the paper width to clear this refusal");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const columns = foldColumns(fold, draft.panels?.length);
|
|
113
|
+
if (fold !== "custom" && draft.panels !== void 0 && draft.panels.length !== columns) {
|
|
114
|
+
issue(ctx, ["panels"], `panels has ${draft.panels.length} widths and a ${fold} fold has ${columns} columns; give ${columns} widths or omit panels to use the preset`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
refuseShape(draft, ctx, columns);
|
|
118
|
+
const paper = sheetMm({
|
|
119
|
+
paper: draft.paper,
|
|
120
|
+
orientation: draft.orientation
|
|
121
|
+
});
|
|
122
|
+
const available = availableWidthMm(draft);
|
|
123
|
+
if (available <= 0) {
|
|
124
|
+
issue(ctx, ["margin"], `the horizontal margins leave ${available}mm of the ${paper.width}mm paper; reduce them`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const widths = panelWidthsMm(draft);
|
|
128
|
+
if (widths.some((width) => width <= 0)) {
|
|
129
|
+
issue(ctx, ["panels"], `a ${fold} fold has a non-positive panel after the ${FOLD_TUCK_IN_MM}mm tuck-in allowance; use a wider paper or override panels to clear this refusal`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const marginLeft = toMillimeters(draft.margin.left, draft.unit);
|
|
133
|
+
const marginRight = toMillimeters(draft.margin.right, draft.unit);
|
|
134
|
+
const sum = roundMillimeters(marginLeft + widths.reduce((total, width) => total + width, 0) + marginRight);
|
|
135
|
+
if (Math.abs(sum - paper.width) > .01) issue(ctx, ["panels"], `the panels and horizontal margins sum to ${sum}mm and the paper is ${paper.width}mm wide; change panels or margins so they sum to ${paper.width}mm to clear this refusal`);
|
|
136
|
+
const height = toMillimeters(draft.margin.top, draft.unit) + toMillimeters(draft.cell.height, draft.unit) + toMillimeters(draft.margin.bottom, draft.unit);
|
|
137
|
+
refuseOverflow(ctx, paper, { height }, { height: {
|
|
138
|
+
path: ["cell", "height"],
|
|
139
|
+
subject: "the folded piece",
|
|
140
|
+
remedy: FOLD_HEIGHT_REMEDY
|
|
141
|
+
} });
|
|
142
|
+
refuseSafe(ctx, toMillimeters(draft.safe, draft.unit), {
|
|
143
|
+
width: Math.min(...widths),
|
|
144
|
+
height: toMillimeters(draft.cell.height, draft.unit)
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function resolveImposition(value) {
|
|
148
|
+
if (!value.fold) return {
|
|
149
|
+
...value,
|
|
150
|
+
columns: value.columns ?? 1,
|
|
151
|
+
rows: value.rows ?? 1,
|
|
152
|
+
sides: value.sides ?? "one",
|
|
153
|
+
cell: {
|
|
154
|
+
width: value.cell.width ?? 1,
|
|
155
|
+
height: value.cell.height
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const columns = foldColumns(value.fold, value.panels?.length);
|
|
159
|
+
const panels = panelsInUnit(value);
|
|
160
|
+
return {
|
|
161
|
+
...value,
|
|
162
|
+
columns,
|
|
163
|
+
rows: 1,
|
|
164
|
+
sides: "two",
|
|
165
|
+
fill: "sequence",
|
|
166
|
+
skip: 0,
|
|
167
|
+
gap: {
|
|
168
|
+
column: 0,
|
|
169
|
+
row: value.gap.row
|
|
170
|
+
},
|
|
171
|
+
panels
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
export { FOLD_KINDS, FOLD_TUCK_IN_MM, foldCellPages, foldColumns, foldPageCountMessage, foldPanelMap, panelWidthsMm, presetPanelWidthsMm, refineFold, resolveImposition };
|
package/dist/fonts.d.ts
ADDED
|
@@ -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 };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { roundMillimeters } from "./paper.js";
|
|
2
|
+
//#region src/imposition-fit.ts
|
|
3
|
+
const PAPER_FIT_TOLERANCE_MM = .01;
|
|
4
|
+
const GRID_WIDTH_REMEDY = "reduce the horizontal margins, columns, cell width or column gap";
|
|
5
|
+
const GRID_HEIGHT_REMEDY = "reduce the vertical margins, rows, cell height or row gap";
|
|
6
|
+
function refuseOverflow(ctx, paper, used, axis = {}) {
|
|
7
|
+
const width = used.width;
|
|
8
|
+
if (width !== void 0 && width - paper.width > .01) ctx.addIssue({
|
|
9
|
+
code: "custom",
|
|
10
|
+
path: axis.width?.path ?? ["columns"],
|
|
11
|
+
message: `${axis.width?.subject ?? "the grid"} is ${roundMillimeters(width)}mm wide and the paper is ${paper.width}mm wide, an overflow of ${roundMillimeters(width - paper.width)}mm; ${axis.width?.remedy ?? GRID_WIDTH_REMEDY}`
|
|
12
|
+
});
|
|
13
|
+
const height = used.height;
|
|
14
|
+
if (height !== void 0 && height - paper.height > .01) ctx.addIssue({
|
|
15
|
+
code: "custom",
|
|
16
|
+
path: axis.height?.path ?? ["rows"],
|
|
17
|
+
message: `${axis.height?.subject ?? "the grid"} is ${roundMillimeters(height)}mm high and the paper is ${paper.height}mm high, an overflow of ${roundMillimeters(height - paper.height)}mm; ${axis.height?.remedy ?? GRID_HEIGHT_REMEDY}`
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function refuseSafe(ctx, safe, cell) {
|
|
21
|
+
if (safe * 2 >= cell.width) ctx.addIssue({
|
|
22
|
+
code: "custom",
|
|
23
|
+
path: ["safe"],
|
|
24
|
+
message: `safe is ${roundMillimeters(safe)}mm on the left and right, but the cell is ${roundMillimeters(cell.width)}mm wide; safe must leave area inside the cell`
|
|
25
|
+
});
|
|
26
|
+
if (safe * 2 >= cell.height) ctx.addIssue({
|
|
27
|
+
code: "custom",
|
|
28
|
+
path: ["safe"],
|
|
29
|
+
message: `safe is ${roundMillimeters(safe)}mm on the top and bottom, but the cell is ${roundMillimeters(cell.height)}mm high; safe must leave area inside the cell`
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { PAPER_FIT_TOLERANCE_MM, refuseOverflow, refuseSafe };
|
package/dist/imposition-marks.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { roundMillimeters } from "./paper.js";
|
|
2
|
-
import {
|
|
2
|
+
import { gridPlacements } from "./imposition.js";
|
|
3
3
|
//#region src/imposition-marks.ts
|
|
4
4
|
const MIN_MARK_LENGTH_MM = 2;
|
|
5
5
|
const unique = (values) => [...new Set(values.map(roundMillimeters))].sort((a, b) => a - b);
|
|
6
|
-
function addMark(result, edge, cutPosition, start, available, horizontal, direction) {
|
|
6
|
+
function addMark(result, edge, cutPosition, start, available, horizontal, direction, dashed = false) {
|
|
7
7
|
const length = Math.min(5, roundMillimeters(available));
|
|
8
8
|
if (length < 2) {
|
|
9
9
|
result.omittedMarks.push({
|
|
@@ -14,19 +14,22 @@ function addMark(result, edge, cutPosition, start, available, horizontal, direct
|
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
const end = roundMillimeters(start + direction * length);
|
|
17
|
+
const dash = dashed ? { dashed: true } : {};
|
|
17
18
|
result.marks.push(horizontal ? {
|
|
18
19
|
x1: Math.min(start, end),
|
|
19
20
|
y1: cutPosition,
|
|
20
21
|
x2: Math.max(start, end),
|
|
21
|
-
y2: cutPosition
|
|
22
|
+
y2: cutPosition,
|
|
23
|
+
...dash
|
|
22
24
|
} : {
|
|
23
25
|
x1: cutPosition,
|
|
24
26
|
y1: Math.min(start, end),
|
|
25
27
|
x2: cutPosition,
|
|
26
|
-
y2: Math.max(start, end)
|
|
28
|
+
y2: Math.max(start, end),
|
|
29
|
+
...dash
|
|
27
30
|
});
|
|
28
31
|
}
|
|
29
|
-
function outerMarks(side, placements, paper) {
|
|
32
|
+
function outerMarks(side, placements, paper, foldCuts) {
|
|
30
33
|
const verticalCuts = unique(placements.flatMap(({ trim }) => [trim.x, trim.x + trim.width]));
|
|
31
34
|
const horizontalCuts = unique(placements.flatMap(({ trim }) => [trim.y, trim.y + trim.height]));
|
|
32
35
|
const top = Math.min(...placements.map(({ bleedBox }) => bleedBox.y));
|
|
@@ -34,6 +37,7 @@ function outerMarks(side, placements, paper) {
|
|
|
34
37
|
const bottom = Math.max(...placements.map(({ bleedBox }) => bleedBox.y + bleedBox.height));
|
|
35
38
|
const left = Math.min(...placements.map(({ bleedBox }) => bleedBox.x));
|
|
36
39
|
for (const cut of verticalCuts) {
|
|
40
|
+
if (foldCuts.has(cut)) continue;
|
|
37
41
|
addMark(side, "top", cut, roundMillimeters(top - 1), top - 1, false, -1);
|
|
38
42
|
addMark(side, "bottom", cut, roundMillimeters(bottom + 1), paper.height - bottom - 1, false, 1);
|
|
39
43
|
}
|
|
@@ -41,6 +45,10 @@ function outerMarks(side, placements, paper) {
|
|
|
41
45
|
addMark(side, "left", cut, roundMillimeters(left - 1), left - 1, true, -1);
|
|
42
46
|
addMark(side, "right", cut, roundMillimeters(right + 1), paper.width - right - 1, true, 1);
|
|
43
47
|
}
|
|
48
|
+
for (const cut of foldCuts) {
|
|
49
|
+
addMark(side, "top", cut, roundMillimeters(top - 1), top - 1, false, -1, true);
|
|
50
|
+
addMark(side, "bottom", cut, roundMillimeters(bottom + 1), paper.height - bottom - 1, false, 1, true);
|
|
51
|
+
}
|
|
44
52
|
}
|
|
45
53
|
function intersectsBleed(mark, boxes) {
|
|
46
54
|
return boxes.some((box) => Math.max(mark.x1, box.x) <= Math.min(mark.x2, box.x + box.width) && Math.max(mark.y1, box.y) <= Math.min(mark.y2, box.y + box.height));
|
|
@@ -146,39 +154,38 @@ function cornerMarks(side, placements, rows) {
|
|
|
146
154
|
}
|
|
147
155
|
}
|
|
148
156
|
}
|
|
157
|
+
function foldCutPositions(placements) {
|
|
158
|
+
const left = Math.min(...placements.map(({ trim }) => trim.x));
|
|
159
|
+
const right = Math.max(...placements.map(({ trim }) => trim.x + trim.width));
|
|
160
|
+
return new Set(unique(placements.flatMap(({ trim }) => [trim.x, trim.x + trim.width])).filter((cut) => cut !== left && cut !== right));
|
|
161
|
+
}
|
|
149
162
|
/** Computes physical crop-mark geometry without changing the fixed paper size. */
|
|
150
163
|
function impositionMarks(imposition) {
|
|
151
164
|
if (!imposition.marks) return [];
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
...imposition,
|
|
155
|
-
sides: "one",
|
|
156
|
-
fill: "sequence",
|
|
157
|
-
skip: 0
|
|
158
|
-
}, cellCount);
|
|
159
|
-
const frontPlacements = frontImposed.placements;
|
|
165
|
+
const { paper, placements: frontPlacements } = gridPlacements(imposition);
|
|
166
|
+
const foldCuts = imposition.fold ? foldCutPositions(frontPlacements) : /* @__PURE__ */ new Set();
|
|
160
167
|
const build = (sideName, placements) => {
|
|
161
168
|
const side = {
|
|
162
169
|
side: sideName,
|
|
163
170
|
marks: [],
|
|
164
171
|
omittedMarks: []
|
|
165
172
|
};
|
|
166
|
-
outerMarks(side, placements,
|
|
173
|
+
outerMarks(side, placements, paper, foldCuts);
|
|
167
174
|
if (imposition.cornerMarks) cornerMarks(side, placements, imposition.rows);
|
|
168
175
|
return side;
|
|
169
176
|
};
|
|
170
177
|
const front = build("front", frontPlacements);
|
|
171
178
|
if (imposition.sides === "one") return [front];
|
|
172
|
-
const portrait =
|
|
179
|
+
const portrait = paper.width <= paper.height;
|
|
173
180
|
const axis = imposition.flip === "long" === portrait ? "x" : "y";
|
|
174
181
|
const mirrorMark = (mark) => axis === "x" ? {
|
|
175
182
|
...mark,
|
|
176
|
-
x1: roundMillimeters(
|
|
177
|
-
x2: roundMillimeters(
|
|
183
|
+
x1: roundMillimeters(paper.width - mark.x1),
|
|
184
|
+
x2: roundMillimeters(paper.width - mark.x2)
|
|
178
185
|
} : {
|
|
179
186
|
...mark,
|
|
180
|
-
y1: roundMillimeters(
|
|
181
|
-
y2: roundMillimeters(
|
|
187
|
+
y1: roundMillimeters(paper.height - mark.y1),
|
|
188
|
+
y2: roundMillimeters(paper.height - mark.y2)
|
|
182
189
|
};
|
|
183
190
|
const mirrorEdge = (edge) => {
|
|
184
191
|
if (axis === "x") return edge === "left" ? "right" : edge === "right" ? "left" : edge;
|
|
@@ -190,7 +197,7 @@ function impositionMarks(imposition) {
|
|
|
190
197
|
omittedMarks: front.omittedMarks.map(({ edge, cutPosition }) => ({
|
|
191
198
|
side: "back",
|
|
192
199
|
edge: mirrorEdge(edge),
|
|
193
|
-
cutPosition: roundMillimeters(axis === "x" && (edge === "top" || edge === "bottom") ?
|
|
200
|
+
cutPosition: roundMillimeters(axis === "x" && (edge === "top" || edge === "bottom") ? paper.width - cutPosition : axis === "y" && (edge === "left" || edge === "right") ? paper.height - cutPosition : cutPosition)
|
|
194
201
|
}))
|
|
195
202
|
}];
|
|
196
203
|
}
|
package/dist/imposition.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PX_PER_MM, roundMillimeters, sheetMm, toMillimeters } from "./paper.js";
|
|
2
|
+
import { foldCellPages, foldPageCountMessage } from "./fold.js";
|
|
2
3
|
//#region src/imposition.ts
|
|
3
4
|
const zeroCrop = () => ({
|
|
4
5
|
top: 0,
|
|
@@ -6,16 +7,34 @@ const zeroCrop = () => ({
|
|
|
6
7
|
bottom: 0,
|
|
7
8
|
left: 0
|
|
8
9
|
});
|
|
10
|
+
function columnWidthsMm(imposition) {
|
|
11
|
+
if (imposition.fold) return (imposition.panels ?? []).map((width) => toMillimeters(width, imposition.unit));
|
|
12
|
+
return Array.from({ length: imposition.columns }, () => toMillimeters(imposition.cell.width ?? 0, imposition.unit));
|
|
13
|
+
}
|
|
14
|
+
function columnXMm(imposition, column, widths, paperWidth) {
|
|
15
|
+
const mm = (value) => toMillimeters(value, imposition.unit);
|
|
16
|
+
const gap = mm(imposition.gap.column);
|
|
17
|
+
if (imposition.dir === "rtl") {
|
|
18
|
+
let x = paperWidth - mm(imposition.margin.right);
|
|
19
|
+
for (let index = 0; index <= column; index += 1) x -= widths[index] ?? 0;
|
|
20
|
+
for (let index = 0; index < column; index += 1) x -= gap;
|
|
21
|
+
return x;
|
|
22
|
+
}
|
|
23
|
+
let x = mm(imposition.margin.left);
|
|
24
|
+
for (let index = 0; index < column; index += 1) x += (widths[index] ?? 0) + gap;
|
|
25
|
+
return x;
|
|
26
|
+
}
|
|
9
27
|
function placementAt(imposition, paper, cell, page, paperSize) {
|
|
10
28
|
const mm = (value) => toMillimeters(value, imposition.unit);
|
|
11
29
|
const column = cell % imposition.columns;
|
|
12
30
|
const row = Math.floor(cell / imposition.columns);
|
|
13
31
|
const physicalColumn = imposition.dir === "rtl" ? imposition.columns - column - 1 : column;
|
|
14
|
-
const
|
|
32
|
+
const widths = columnWidthsMm(imposition);
|
|
33
|
+
const cellWidth = widths[column] ?? mm(imposition.cell.width ?? 0);
|
|
15
34
|
const cellHeight = mm(imposition.cell.height);
|
|
16
35
|
const columnGap = mm(imposition.gap.column);
|
|
17
36
|
const rowGap = mm(imposition.gap.row);
|
|
18
|
-
const x = imposition
|
|
37
|
+
const x = columnXMm(imposition, column, widths, paperSize.width);
|
|
19
38
|
const y = mm(imposition.margin.top) + row * (cellHeight + rowGap);
|
|
20
39
|
const requested = mm(imposition.bleed);
|
|
21
40
|
const bleed = {
|
|
@@ -145,6 +164,38 @@ function expandAssignments(imposition, assignments, pageCount, paper) {
|
|
|
145
164
|
flush();
|
|
146
165
|
return placements;
|
|
147
166
|
}
|
|
167
|
+
function imposeFold(imposition, pageCount, paper) {
|
|
168
|
+
const fold = imposition.fold;
|
|
169
|
+
if (!fold) throw new Error("imposeFold requires a fold");
|
|
170
|
+
const columns = imposition.columns;
|
|
171
|
+
if (pageCount !== columns * 2) throw new Error(foldPageCountMessage(fold, pageCount, columns));
|
|
172
|
+
const cells = foldCellPages(fold, columns);
|
|
173
|
+
const fronts = [];
|
|
174
|
+
const backs = [];
|
|
175
|
+
for (const [cell, pages] of cells.entries()) {
|
|
176
|
+
if (!pages) continue;
|
|
177
|
+
const front = placementAt(imposition, 0, cell, pages.front, paper);
|
|
178
|
+
fronts.push(front);
|
|
179
|
+
backs.push(mirrorPlacement(front, paper, imposition.flip, pages.back));
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
paper,
|
|
183
|
+
papers: 1,
|
|
184
|
+
sides: 2,
|
|
185
|
+
placements: [...fronts, ...backs]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function gridPlacements(imposition) {
|
|
189
|
+
const paper = sheetMm({
|
|
190
|
+
paper: imposition.paper,
|
|
191
|
+
orientation: imposition.orientation
|
|
192
|
+
});
|
|
193
|
+
const cells = imposition.columns * imposition.rows;
|
|
194
|
+
return {
|
|
195
|
+
paper,
|
|
196
|
+
placements: Array.from({ length: cells }, (_, cell) => placementAt(imposition, 0, cell, 0, paper))
|
|
197
|
+
};
|
|
198
|
+
}
|
|
148
199
|
function impose(imposition, pageCount) {
|
|
149
200
|
if (!Number.isInteger(pageCount) || pageCount < 0) throw new RangeError(`pageCount must be a nonnegative integer; received ${String(pageCount)}`);
|
|
150
201
|
const paper = sheetMm({
|
|
@@ -152,6 +203,7 @@ function impose(imposition, pageCount) {
|
|
|
152
203
|
orientation: imposition.orientation
|
|
153
204
|
});
|
|
154
205
|
const sides = imposition.sides === "two" ? 2 : 1;
|
|
206
|
+
if (imposition.fold) return imposeFold(imposition, pageCount, paper);
|
|
155
207
|
if (pageCount === 0) return {
|
|
156
208
|
paper,
|
|
157
209
|
papers: 0,
|
|
@@ -291,4 +343,4 @@ function pagePlacementLayout(size, placement, fit) {
|
|
|
291
343
|
};
|
|
292
344
|
}
|
|
293
345
|
//#endregion
|
|
294
|
-
export { fitPage, impose, mirrorPlacement, pagePlacementLayout };
|
|
346
|
+
export { fitPage, gridPlacements, impose, mirrorPlacement, pagePlacementLayout };
|