@gmoney2000/dygarn-pdf-kit 0.1.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/README.md +155 -0
- package/dist/index.cjs +332 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +142 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +292 -0
- package/dist/index.js.map +1 -0
- package/dist/pdf-lib.cjs +474 -0
- package/dist/pdf-lib.cjs.map +1 -0
- package/dist/pdf-lib.d.cts +119 -0
- package/dist/pdf-lib.d.ts +119 -0
- package/dist/pdf-lib.js +440 -0
- package/dist/pdf-lib.js.map +1 -0
- package/dist/react-pdf.cjs +241 -0
- package/dist/react-pdf.cjs.map +1 -0
- package/dist/react-pdf.d.cts +69 -0
- package/dist/react-pdf.d.ts +69 -0
- package/dist/react-pdf.js +211 -0
- package/dist/react-pdf.js.map +1 -0
- package/dist/types-CHgvU4U1.d.cts +37 -0
- package/dist/types-CHgvU4U1.d.ts +37 -0
- package/package.json +61 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { PDFFont, PDFDocument, PDFImage, RGB, PDFPage } from 'pdf-lib';
|
|
2
|
+
import { B as BrandInput } from './types-CHgvU4U1.js';
|
|
3
|
+
|
|
4
|
+
interface BookendFonts {
|
|
5
|
+
helv: PDFFont;
|
|
6
|
+
helvBold: PDFFont;
|
|
7
|
+
helvOblique: PDFFont;
|
|
8
|
+
}
|
|
9
|
+
declare function loadBookendFonts(doc: PDFDocument): Promise<BookendFonts>;
|
|
10
|
+
|
|
11
|
+
interface ResolvedBookendAssets {
|
|
12
|
+
logoImage: PDFImage | null;
|
|
13
|
+
logoAspect: number;
|
|
14
|
+
bandColor: RGB;
|
|
15
|
+
accentColor: RGB;
|
|
16
|
+
cardColors: {
|
|
17
|
+
cream: RGB;
|
|
18
|
+
cardBorder: RGB;
|
|
19
|
+
chipBg: RGB;
|
|
20
|
+
};
|
|
21
|
+
themeMode: "dark" | "light";
|
|
22
|
+
}
|
|
23
|
+
interface LoadBookendAssetsOptions {
|
|
24
|
+
brand: BrandInput;
|
|
25
|
+
doc: PDFDocument;
|
|
26
|
+
/**
|
|
27
|
+
* Optional URL resolver. Use when your app needs to convert relative paths
|
|
28
|
+
* (e.g. /demo-files/logo.png) into absolute URLs for server-side fetching.
|
|
29
|
+
* Defaults to identity (returns the URL unchanged).
|
|
30
|
+
*/
|
|
31
|
+
resolveUrl?: (url: string) => string;
|
|
32
|
+
/** Optional fetch timeout in ms. Default 8s. */
|
|
33
|
+
fetchTimeoutMs?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve brand colors + load logo PDFImage from URL.
|
|
37
|
+
* Mirrors RepFirm's loadTenantBookendAssets behavior.
|
|
38
|
+
*/
|
|
39
|
+
declare function loadBookendAssets(opts: LoadBookendAssetsOptions): Promise<ResolvedBookendAssets>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Per-document context built from a BrandInput + loaded assets.
|
|
43
|
+
* Each page that uses drawBrandedHeader / drawBrandedFooter needs this
|
|
44
|
+
* (with per-page page_number + page_count updated).
|
|
45
|
+
*/
|
|
46
|
+
interface BookendContext {
|
|
47
|
+
/** Source brand for this document */
|
|
48
|
+
brand: BrandInput;
|
|
49
|
+
/** Embedded logo image (null if none) */
|
|
50
|
+
logoImage: PDFImage | null;
|
|
51
|
+
/** Logo width / height aspect ratio (1 if no image) */
|
|
52
|
+
logoAspect: number;
|
|
53
|
+
/** Resolved band color (depends on themeMode) */
|
|
54
|
+
bandColor: RGB;
|
|
55
|
+
/** Resolved accent stripe color */
|
|
56
|
+
accentColor: RGB;
|
|
57
|
+
/** Cream / card / chip colors used in info zone */
|
|
58
|
+
cardColors: {
|
|
59
|
+
cream: RGB;
|
|
60
|
+
cardBorder: RGB;
|
|
61
|
+
chipBg: RGB;
|
|
62
|
+
};
|
|
63
|
+
/** Resolved theme mode (dark = dark band + white logo) */
|
|
64
|
+
themeMode: "dark" | "light";
|
|
65
|
+
/** Doc type label like "Quote" | "Purchase Order" | "Drawings Submittal" | "Line Card" */
|
|
66
|
+
docType: string;
|
|
67
|
+
docNumber: string | null;
|
|
68
|
+
projectName: string | null;
|
|
69
|
+
location?: string | null;
|
|
70
|
+
repName?: string | null;
|
|
71
|
+
repEmail?: string | null;
|
|
72
|
+
repPhone?: string | null;
|
|
73
|
+
/** Legacy: distributor recipient (auto-labeled "DISTRIBUTOR" if recipientLabel empty) */
|
|
74
|
+
distributorName?: string | null;
|
|
75
|
+
/** Generalized recipient label (e.g. "ENGINEER", "SPECIFIER", "ARCHITECT") */
|
|
76
|
+
recipientLabel?: string | null;
|
|
77
|
+
recipientName?: string | null;
|
|
78
|
+
pageNumber: number;
|
|
79
|
+
pageCount: number;
|
|
80
|
+
dateStamp: string;
|
|
81
|
+
fixtureType?: string | null;
|
|
82
|
+
fixtureMfr?: string | null;
|
|
83
|
+
fixturePartNumber?: string | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Draw a branded cover page. Logo centered on the band, doc type label,
|
|
88
|
+
* project name, location, doc number, recipient, prepared-by signature block,
|
|
89
|
+
* bottom metadata line.
|
|
90
|
+
*
|
|
91
|
+
* Designed for 612x792 (US Letter) page size. Caller is responsible for
|
|
92
|
+
* adding the page to the document.
|
|
93
|
+
*/
|
|
94
|
+
declare function drawBrandedCover(page: PDFPage, ctx: BookendContext, fonts: BookendFonts): void;
|
|
95
|
+
|
|
96
|
+
declare const BRANDED_HEADER_HEIGHT = 110;
|
|
97
|
+
declare const BRANDED_HEADER_MINIMAL_H = 56;
|
|
98
|
+
/**
|
|
99
|
+
* Draw the per-page branded header.
|
|
100
|
+
*
|
|
101
|
+
* showFixtureCard=true (datasheet pages): dark band 38pt + stripe 3pt + cream
|
|
102
|
+
* info zone with white card (Job Name / Manufacturer / Model Number) + type
|
|
103
|
+
* chip on right. Use for submittal datasheet pages.
|
|
104
|
+
*
|
|
105
|
+
* showFixtureCard=false (default): dark band 38pt + stripe 3pt + thin cream
|
|
106
|
+
* strip with project name + doc number. Use for non-datasheet pages.
|
|
107
|
+
*/
|
|
108
|
+
declare function drawBrandedHeader(page: PDFPage, ctx: BookendContext, fonts: BookendFonts, opts?: {
|
|
109
|
+
showFixtureCard?: boolean;
|
|
110
|
+
}): void;
|
|
111
|
+
|
|
112
|
+
declare const BRANDED_FOOTER_HEIGHT = 38;
|
|
113
|
+
/**
|
|
114
|
+
* Per-page branded footer with prepared-by line, date, page numbers, and a
|
|
115
|
+
* clickable "Index" link that jumps to page 1.
|
|
116
|
+
*/
|
|
117
|
+
declare function drawBrandedFooter(page: PDFPage, ctx: BookendContext, fonts: BookendFonts): void;
|
|
118
|
+
|
|
119
|
+
export { BRANDED_FOOTER_HEIGHT, BRANDED_HEADER_HEIGHT, BRANDED_HEADER_MINIMAL_H, type BookendContext, type BookendFonts, type LoadBookendAssetsOptions, type ResolvedBookendAssets, drawBrandedCover, drawBrandedFooter, drawBrandedHeader, loadBookendAssets, loadBookendFonts };
|
package/dist/pdf-lib.js
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
// src/pdf-lib/fonts.ts
|
|
2
|
+
import { StandardFonts } from "pdf-lib";
|
|
3
|
+
async function loadBookendFonts(doc) {
|
|
4
|
+
const [helv, helvBold, helvOblique] = await Promise.all([
|
|
5
|
+
doc.embedFont(StandardFonts.Helvetica),
|
|
6
|
+
doc.embedFont(StandardFonts.HelveticaBold),
|
|
7
|
+
doc.embedFont(StandardFonts.HelveticaOblique)
|
|
8
|
+
]);
|
|
9
|
+
return { helv, helvBold, helvOblique };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/pdf-lib/assets.ts
|
|
13
|
+
import { rgb } from "pdf-lib";
|
|
14
|
+
|
|
15
|
+
// src/brand.ts
|
|
16
|
+
function resolveBandThemeMode(brand) {
|
|
17
|
+
if (brand.pdfHeaderTheme === "dark") return "dark";
|
|
18
|
+
if (brand.pdfHeaderTheme === "light") return "light";
|
|
19
|
+
if (brand.logoDarkUrl) return "dark";
|
|
20
|
+
const candidate = (brand.logoWordmarkUrl ?? brand.logoUrl ?? brand.logoMarkUrl ?? "").toLowerCase();
|
|
21
|
+
if (candidate.includes("white") || candidate.includes("-on-dark") || candidate.includes("_dark")) return "dark";
|
|
22
|
+
return "light";
|
|
23
|
+
}
|
|
24
|
+
function resolveBestLogoUrl(brand, mode) {
|
|
25
|
+
if (mode === "dark") {
|
|
26
|
+
return brand.logoDarkUrl ?? brand.logoUrl ?? brand.logoWordmarkUrl ?? brand.logoMarkUrl ?? null;
|
|
27
|
+
}
|
|
28
|
+
return brand.logoLightUrl ?? brand.logoUrl ?? brand.logoWordmarkUrl ?? brand.logoMarkUrl ?? brand.logoDarkUrl ?? null;
|
|
29
|
+
}
|
|
30
|
+
function darkenHex(hex, amount) {
|
|
31
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex);
|
|
32
|
+
if (!m) return hex;
|
|
33
|
+
const n = Number.parseInt(m[1], 16);
|
|
34
|
+
let r = n >> 16 & 255;
|
|
35
|
+
let g = n >> 8 & 255;
|
|
36
|
+
let b = n & 255;
|
|
37
|
+
r = Math.max(0, Math.round(r * (1 - amount)));
|
|
38
|
+
g = Math.max(0, Math.round(g * (1 - amount)));
|
|
39
|
+
b = Math.max(0, Math.round(b * (1 - amount)));
|
|
40
|
+
return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
41
|
+
}
|
|
42
|
+
function hexToRgb01(hex) {
|
|
43
|
+
const h = hex.replace("#", "").trim();
|
|
44
|
+
const r = Number.parseInt(h.slice(0, 2), 16) / 255;
|
|
45
|
+
const g = Number.parseInt(h.slice(2, 4), 16) / 255;
|
|
46
|
+
const b = Number.parseInt(h.slice(4, 6), 16) / 255;
|
|
47
|
+
return [Number.isFinite(r) ? r : 0.12, Number.isFinite(g) ? g : 0.23, Number.isFinite(b) ? b : 0.37];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/pdf-lib/assets.ts
|
|
51
|
+
async function loadBookendAssets(opts) {
|
|
52
|
+
const { brand, doc, resolveUrl = (u) => u, fetchTimeoutMs = 8e3 } = opts;
|
|
53
|
+
const themeMode = resolveBandThemeMode(brand);
|
|
54
|
+
const themeVars = brand.themeVars ?? {};
|
|
55
|
+
let bandColorHex;
|
|
56
|
+
if (themeMode === "dark") {
|
|
57
|
+
bandColorHex = themeVars["--sidebar-bg"] ?? themeVars["--header-bg"] ?? (brand.primaryColor ? darkenHex(brand.primaryColor, 0.45) : "#1e3a5f");
|
|
58
|
+
} else {
|
|
59
|
+
bandColorHex = "#fbfaf6";
|
|
60
|
+
}
|
|
61
|
+
const accentHex = brand.primaryColor ?? themeVars["--accent"] ?? "#1e3a5f";
|
|
62
|
+
const [br, bg2, bb] = hexToRgb01(bandColorHex);
|
|
63
|
+
const [ar, ag, ab] = hexToRgb01(accentHex);
|
|
64
|
+
const cardColors = {
|
|
65
|
+
cream: rgb(0.984, 0.98, 0.965),
|
|
66
|
+
cardBorder: rgb(0.78, 0.78, 0.78),
|
|
67
|
+
chipBg: rgb(0.937, 0.937, 0.933)
|
|
68
|
+
};
|
|
69
|
+
const candidate = resolveBestLogoUrl(brand, themeMode);
|
|
70
|
+
const logoUrl = candidate ? resolveUrl(candidate) : null;
|
|
71
|
+
let logoImage = null;
|
|
72
|
+
let logoAspect = 1;
|
|
73
|
+
if (logoUrl && !logoUrl.toLowerCase().endsWith(".svg")) {
|
|
74
|
+
try {
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const timeout = setTimeout(() => controller.abort(), fetchTimeoutMs);
|
|
77
|
+
let res;
|
|
78
|
+
try {
|
|
79
|
+
res = await fetch(logoUrl, { signal: controller.signal });
|
|
80
|
+
} finally {
|
|
81
|
+
clearTimeout(timeout);
|
|
82
|
+
}
|
|
83
|
+
if (res.ok) {
|
|
84
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
85
|
+
const lower = logoUrl.toLowerCase();
|
|
86
|
+
if (lower.includes(".png") || (res.headers.get("content-type") ?? "").includes("png")) {
|
|
87
|
+
logoImage = await doc.embedPng(buf);
|
|
88
|
+
} else {
|
|
89
|
+
logoImage = await doc.embedJpg(buf);
|
|
90
|
+
}
|
|
91
|
+
const dims = logoImage.scale(1);
|
|
92
|
+
logoAspect = dims.width / dims.height;
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
logoImage,
|
|
99
|
+
logoAspect,
|
|
100
|
+
bandColor: rgb(br, bg2, bb),
|
|
101
|
+
accentColor: rgb(ar, ag, ab),
|
|
102
|
+
cardColors,
|
|
103
|
+
themeMode
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/pdf-lib/cover.ts
|
|
108
|
+
import { rgb as rgb2 } from "pdf-lib";
|
|
109
|
+
function drawBrandedCover(page, ctx, fonts) {
|
|
110
|
+
const { helv, helvBold, helvOblique } = fonts;
|
|
111
|
+
const { bandColor, accentColor, logoImage, logoAspect, themeMode } = ctx;
|
|
112
|
+
const W = page.getWidth();
|
|
113
|
+
const H = page.getHeight();
|
|
114
|
+
const CX = W / 2;
|
|
115
|
+
const agencyName = ctx.brand.agencyName;
|
|
116
|
+
const COVER_HEADER_H = 130;
|
|
117
|
+
page.drawRectangle({ x: 0, y: H - COVER_HEADER_H, width: W, height: COVER_HEADER_H, color: bandColor });
|
|
118
|
+
page.drawRectangle({ x: 0, y: H - COVER_HEADER_H - 4, width: W, height: 4, color: accentColor });
|
|
119
|
+
const COVER_LOGO_H = 70;
|
|
120
|
+
const COVER_LOGO_W = Math.round(COVER_LOGO_H * (logoAspect || 1));
|
|
121
|
+
if (logoImage) {
|
|
122
|
+
page.drawImage(logoImage, {
|
|
123
|
+
x: CX - COVER_LOGO_W / 2,
|
|
124
|
+
y: H - COVER_HEADER_H + (COVER_HEADER_H - COVER_LOGO_H) / 2,
|
|
125
|
+
width: COVER_LOGO_W,
|
|
126
|
+
height: COVER_LOGO_H
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
const nameColor = themeMode === "dark" ? rgb2(1, 1, 1) : rgb2(0.08, 0.08, 0.08);
|
|
130
|
+
const nameW = helvBold.widthOfTextAtSize(agencyName, 28);
|
|
131
|
+
page.drawText(agencyName, {
|
|
132
|
+
x: CX - nameW / 2,
|
|
133
|
+
y: H - COVER_HEADER_H / 2 - 10,
|
|
134
|
+
size: 28,
|
|
135
|
+
font: helvBold,
|
|
136
|
+
color: nameColor
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
let cursorY = H - COVER_HEADER_H - 70;
|
|
140
|
+
const docTypeUpper = ctx.docType.toUpperCase();
|
|
141
|
+
const tagW = helvBold.widthOfTextAtSize(docTypeUpper, 11);
|
|
142
|
+
page.drawText(docTypeUpper, {
|
|
143
|
+
x: CX - tagW / 2,
|
|
144
|
+
y: cursorY,
|
|
145
|
+
size: 11,
|
|
146
|
+
font: helvBold,
|
|
147
|
+
color: rgb2(0.45, 0.45, 0.45)
|
|
148
|
+
});
|
|
149
|
+
cursorY -= 30;
|
|
150
|
+
if (ctx.projectName) {
|
|
151
|
+
const projW = helvBold.widthOfTextAtSize(ctx.projectName, 22);
|
|
152
|
+
page.drawText(ctx.projectName, {
|
|
153
|
+
x: CX - projW / 2,
|
|
154
|
+
y: cursorY,
|
|
155
|
+
size: 22,
|
|
156
|
+
font: helvBold,
|
|
157
|
+
color: rgb2(0.08, 0.08, 0.08)
|
|
158
|
+
});
|
|
159
|
+
cursorY -= 24;
|
|
160
|
+
}
|
|
161
|
+
if (ctx.location) {
|
|
162
|
+
const locW = helv.widthOfTextAtSize(ctx.location, 12);
|
|
163
|
+
page.drawText(ctx.location, {
|
|
164
|
+
x: CX - locW / 2,
|
|
165
|
+
y: cursorY,
|
|
166
|
+
size: 12,
|
|
167
|
+
font: helv,
|
|
168
|
+
color: rgb2(0.4, 0.4, 0.4)
|
|
169
|
+
});
|
|
170
|
+
cursorY -= 36;
|
|
171
|
+
} else {
|
|
172
|
+
cursorY -= 24;
|
|
173
|
+
}
|
|
174
|
+
if (ctx.docNumber) {
|
|
175
|
+
const numLabel = ctx.docNumber;
|
|
176
|
+
const numW = helvBold.widthOfTextAtSize(numLabel, 12);
|
|
177
|
+
page.drawText(numLabel, {
|
|
178
|
+
x: CX - numW / 2,
|
|
179
|
+
y: cursorY,
|
|
180
|
+
size: 12,
|
|
181
|
+
font: helvBold,
|
|
182
|
+
color: rgb2(0.25, 0.25, 0.25)
|
|
183
|
+
});
|
|
184
|
+
cursorY -= 28;
|
|
185
|
+
}
|
|
186
|
+
const recipientName = ctx.recipientName ?? ctx.distributorName ?? null;
|
|
187
|
+
const recipientLabel = ctx.recipientLabel ?? (ctx.distributorName ? "DISTRIBUTOR" : null);
|
|
188
|
+
if (recipientName && recipientLabel) {
|
|
189
|
+
const labelW = helvBold.widthOfTextAtSize(recipientLabel, 11);
|
|
190
|
+
const gap = 14;
|
|
191
|
+
const valueW = helvBold.widthOfTextAtSize(recipientName, 13);
|
|
192
|
+
const totalW = labelW + gap + valueW;
|
|
193
|
+
const rowX = CX - totalW / 2;
|
|
194
|
+
page.drawText(recipientLabel, { x: rowX, y: cursorY, size: 11, font: helvBold, color: rgb2(0.45, 0.45, 0.45) });
|
|
195
|
+
page.drawText(recipientName, { x: rowX + labelW + gap, y: cursorY, size: 13, font: helvBold, color: rgb2(0.1, 0.1, 0.1) });
|
|
196
|
+
}
|
|
197
|
+
const SIG_BLOCK_TOP = 140;
|
|
198
|
+
let sigY = SIG_BLOCK_TOP;
|
|
199
|
+
page.drawRectangle({ x: CX - 100, y: sigY, width: 200, height: 0.5, color: rgb2(0.7, 0.7, 0.7) });
|
|
200
|
+
sigY -= 18;
|
|
201
|
+
const prepLabel = "- Prepared By -";
|
|
202
|
+
const prepLabelW = helvOblique.widthOfTextAtSize(prepLabel, 10);
|
|
203
|
+
page.drawText(prepLabel, { x: CX - prepLabelW / 2, y: sigY, size: 10, font: helvOblique, color: rgb2(0.5, 0.5, 0.5) });
|
|
204
|
+
sigY -= 18;
|
|
205
|
+
if (ctx.repName) {
|
|
206
|
+
const repNameW = helvBold.widthOfTextAtSize(ctx.repName, 12);
|
|
207
|
+
page.drawText(ctx.repName, { x: CX - repNameW / 2, y: sigY, size: 12, font: helvBold, color: rgb2(0.1, 0.1, 0.1) });
|
|
208
|
+
sigY -= 16;
|
|
209
|
+
}
|
|
210
|
+
if (ctx.repEmail) {
|
|
211
|
+
const repEmailW = helv.widthOfTextAtSize(ctx.repEmail, 10);
|
|
212
|
+
page.drawText(ctx.repEmail, { x: CX - repEmailW / 2, y: sigY, size: 10, font: helv, color: rgb2(0.16, 0.42, 0.68) });
|
|
213
|
+
sigY -= 14;
|
|
214
|
+
}
|
|
215
|
+
const tnW = helvOblique.widthOfTextAtSize(agencyName, 10);
|
|
216
|
+
page.drawText(agencyName, { x: CX - tnW / 2, y: sigY, size: 10, font: helvOblique, color: rgb2(0.4, 0.4, 0.4) });
|
|
217
|
+
const footerLine = [ctx.docNumber, ctx.dateStamp].filter(Boolean).join(" | ");
|
|
218
|
+
if (footerLine) {
|
|
219
|
+
const flW = helv.widthOfTextAtSize(footerLine, 9);
|
|
220
|
+
page.drawText(footerLine, { x: CX - flW / 2, y: 36, size: 9, font: helv, color: rgb2(0.55, 0.55, 0.55) });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/pdf-lib/header.ts
|
|
225
|
+
import { rgb as rgb3 } from "pdf-lib";
|
|
226
|
+
var BRANDED_HEADER_HEIGHT = 110;
|
|
227
|
+
var BRANDED_HEADER_MINIMAL_H = 56;
|
|
228
|
+
function clip(s, max) {
|
|
229
|
+
if (s.length <= max) return s;
|
|
230
|
+
return `${s.slice(0, max - 1)}\u2026`;
|
|
231
|
+
}
|
|
232
|
+
function drawBrandedHeader(page, ctx, fonts, opts) {
|
|
233
|
+
const showFixtureCard = opts?.showFixtureCard ?? false;
|
|
234
|
+
const { helv, helvBold } = fonts;
|
|
235
|
+
const { bandColor, accentColor, cardColors, logoImage, logoAspect, themeMode } = ctx;
|
|
236
|
+
const W = page.getWidth();
|
|
237
|
+
const H = page.getHeight();
|
|
238
|
+
const TOP_BANNER_H = showFixtureCard ? BRANDED_HEADER_HEIGHT : BRANDED_HEADER_MINIMAL_H;
|
|
239
|
+
const PAD = 12;
|
|
240
|
+
const DARK_H = 38;
|
|
241
|
+
const STRIPE_H = 3;
|
|
242
|
+
const CREAM_H = TOP_BANNER_H - DARK_H - STRIPE_H;
|
|
243
|
+
const darkBandY = H - DARK_H;
|
|
244
|
+
const stripeY = darkBandY - STRIPE_H;
|
|
245
|
+
const headerTop = H - TOP_BANNER_H;
|
|
246
|
+
page.drawRectangle({ x: 0, y: darkBandY, width: W, height: DARK_H, color: bandColor });
|
|
247
|
+
page.drawRectangle({ x: 0, y: stripeY, width: W, height: STRIPE_H, color: accentColor });
|
|
248
|
+
page.drawRectangle({ x: 0, y: headerTop, width: W, height: CREAM_H, color: cardColors.cream });
|
|
249
|
+
page.drawLine({
|
|
250
|
+
start: { x: 0, y: headerTop },
|
|
251
|
+
end: { x: W, y: headerTop },
|
|
252
|
+
thickness: 0.5,
|
|
253
|
+
color: rgb3(0.8, 0.8, 0.78)
|
|
254
|
+
});
|
|
255
|
+
if (logoImage) {
|
|
256
|
+
const bandLogoH = 26;
|
|
257
|
+
const logoW = Math.round(bandLogoH * logoAspect);
|
|
258
|
+
page.drawImage(logoImage, { x: PAD, y: darkBandY + (DARK_H - bandLogoH) / 2, width: logoW, height: bandLogoH });
|
|
259
|
+
} else {
|
|
260
|
+
const nameColor = themeMode === "dark" ? rgb3(1, 1, 1) : rgb3(0.08, 0.08, 0.08);
|
|
261
|
+
page.drawText(ctx.brand.agencyName, { x: PAD, y: darkBandY + 12, size: 12, font: helvBold, color: nameColor });
|
|
262
|
+
}
|
|
263
|
+
if (showFixtureCard) {
|
|
264
|
+
drawFixtureCard(page, ctx, fonts, W, TOP_BANNER_H, PAD, CREAM_H, headerTop);
|
|
265
|
+
} else {
|
|
266
|
+
drawSimpleInfoStrip(page, ctx, fonts, W, CREAM_H, headerTop, PAD);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function drawFixtureCard(page, ctx, fonts, W, _TOP_BANNER_H, PAD, CREAM_H, headerTop) {
|
|
270
|
+
const { helv, helvBold } = fonts;
|
|
271
|
+
const { cardColors } = ctx;
|
|
272
|
+
const CHIP_W_RESERVE = 60;
|
|
273
|
+
const CARD_X = PAD * 2;
|
|
274
|
+
const CARD_W = W - CARD_X - CHIP_W_RESERVE - PAD;
|
|
275
|
+
const CARD_H = Math.min(CREAM_H - 8, 60);
|
|
276
|
+
const CARD_Y = headerTop + (CREAM_H - CARD_H) / 2;
|
|
277
|
+
page.drawRectangle({
|
|
278
|
+
x: CARD_X,
|
|
279
|
+
y: CARD_Y,
|
|
280
|
+
width: CARD_W,
|
|
281
|
+
height: CARD_H,
|
|
282
|
+
borderColor: cardColors.cardBorder,
|
|
283
|
+
borderWidth: 0.75,
|
|
284
|
+
color: rgb3(1, 1, 1)
|
|
285
|
+
});
|
|
286
|
+
const LABEL_X = CARD_X + 14;
|
|
287
|
+
const VALUE_X = CARD_X + 100;
|
|
288
|
+
const ROW1_Y = CARD_Y + CARD_H - 17;
|
|
289
|
+
const ROW2_Y = ROW1_Y - 18;
|
|
290
|
+
const ROW3_Y = ROW2_Y - 18;
|
|
291
|
+
const jobName = (ctx.projectName || "\u2014").toUpperCase();
|
|
292
|
+
const rows = [
|
|
293
|
+
["Job Name:", clip(jobName, 48)],
|
|
294
|
+
["Manufacturer:", clip(ctx.fixtureMfr ?? "\u2014", 44)],
|
|
295
|
+
["Model Number:", clip(ctx.fixturePartNumber ?? "\u2014", 44)]
|
|
296
|
+
];
|
|
297
|
+
const rowYs = [ROW1_Y, ROW2_Y, ROW3_Y];
|
|
298
|
+
for (let i = 0; i < rows.length; i++) {
|
|
299
|
+
const [label, value] = rows[i];
|
|
300
|
+
const y = rowYs[i];
|
|
301
|
+
page.drawText(label, { x: LABEL_X, y, size: 9, font: helvBold, color: rgb3(0.27, 0.27, 0.27) });
|
|
302
|
+
page.drawText(value, { x: VALUE_X, y, size: 10.5, font: helvBold, color: rgb3(0.08, 0.08, 0.08) });
|
|
303
|
+
}
|
|
304
|
+
const CHIP_W = 50;
|
|
305
|
+
const CHIP_H = Math.min(CREAM_H - 8, 50);
|
|
306
|
+
const CHIP_X = W - PAD - CHIP_W;
|
|
307
|
+
const CHIP_Y = headerTop + (CREAM_H - CHIP_H) / 2;
|
|
308
|
+
const typeCode = clip(ctx.fixtureType ?? "\u2014", 4);
|
|
309
|
+
page.drawRectangle({ x: CHIP_X, y: CHIP_Y, width: CHIP_W, height: CHIP_H, color: cardColors.chipBg });
|
|
310
|
+
const typeLabelW = helv.widthOfTextAtSize("Type:", 7);
|
|
311
|
+
page.drawText("Type:", {
|
|
312
|
+
x: CHIP_X + (CHIP_W - typeLabelW) / 2,
|
|
313
|
+
y: CHIP_Y + CHIP_H - 13,
|
|
314
|
+
size: 7,
|
|
315
|
+
font: helv,
|
|
316
|
+
color: rgb3(0.5, 0.5, 0.5)
|
|
317
|
+
});
|
|
318
|
+
const codeSize = typeCode.length <= 2 ? 22 : typeCode.length <= 3 ? 17 : 13;
|
|
319
|
+
const codeW = helvBold.widthOfTextAtSize(typeCode, codeSize);
|
|
320
|
+
page.drawText(typeCode, {
|
|
321
|
+
x: CHIP_X + (CHIP_W - codeW) / 2,
|
|
322
|
+
y: CHIP_Y + (CHIP_H - 13) / 2 - codeSize * 0.35,
|
|
323
|
+
size: codeSize,
|
|
324
|
+
font: helvBold,
|
|
325
|
+
color: rgb3(0.133, 0.133, 0.133)
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function drawSimpleInfoStrip(page, ctx, fonts, W, CREAM_H, headerTop, PAD) {
|
|
329
|
+
const { helv, helvBold } = fonts;
|
|
330
|
+
const stripY = headerTop + (CREAM_H - 9) / 2;
|
|
331
|
+
if (ctx.projectName) {
|
|
332
|
+
page.drawText(clip(ctx.projectName, 60), {
|
|
333
|
+
x: PAD * 2,
|
|
334
|
+
y: stripY,
|
|
335
|
+
size: 9,
|
|
336
|
+
font: helvBold,
|
|
337
|
+
color: rgb3(0.2, 0.2, 0.2)
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
if (ctx.docNumber) {
|
|
341
|
+
const numW = helv.widthOfTextAtSize(ctx.docNumber, 8);
|
|
342
|
+
page.drawText(ctx.docNumber, { x: W - PAD - numW, y: stripY, size: 8, font: helv, color: rgb3(0.4, 0.4, 0.4) });
|
|
343
|
+
} else if (ctx.docType) {
|
|
344
|
+
const dtW = helv.widthOfTextAtSize(ctx.docType, 8);
|
|
345
|
+
page.drawText(ctx.docType, { x: W - PAD - dtW, y: stripY, size: 8, font: helv, color: rgb3(0.4, 0.4, 0.4) });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// src/pdf-lib/footer.ts
|
|
350
|
+
import { PDFArray, PDFName, PDFNumber, PDFRef, rgb as rgb4 } from "pdf-lib";
|
|
351
|
+
var BRANDED_FOOTER_HEIGHT = 38;
|
|
352
|
+
function drawBrandedFooter(page, ctx, fonts) {
|
|
353
|
+
const { helv, helvBold } = fonts;
|
|
354
|
+
const W = page.getWidth();
|
|
355
|
+
const BOT_BANNER_H = BRANDED_FOOTER_HEIGHT;
|
|
356
|
+
const PAD = 10;
|
|
357
|
+
const darkGrey = rgb4(0.25, 0.25, 0.25);
|
|
358
|
+
const midGrey = rgb4(0.55, 0.55, 0.55);
|
|
359
|
+
page.drawRectangle({ x: 0, y: 0, width: W, height: BOT_BANNER_H, color: ctx.cardColors.cream });
|
|
360
|
+
page.drawLine({
|
|
361
|
+
start: { x: 0, y: BOT_BANNER_H },
|
|
362
|
+
end: { x: W, y: BOT_BANNER_H },
|
|
363
|
+
thickness: 0.5,
|
|
364
|
+
color: rgb4(0.8, 0.8, 0.78)
|
|
365
|
+
});
|
|
366
|
+
const topRowY = BOT_BANNER_H - 13;
|
|
367
|
+
page.drawText("Prepared By: ", { x: PAD, y: topRowY, size: 9, font: helv, color: darkGrey });
|
|
368
|
+
const prepByW = helv.widthOfTextAtSize("Prepared By: ", 9);
|
|
369
|
+
page.drawText(ctx.brand.agencyName, { x: PAD + prepByW, y: topRowY, size: 9, font: helvBold, color: darkGrey });
|
|
370
|
+
const repParts = [];
|
|
371
|
+
if (ctx.repName) repParts.push(ctx.repName);
|
|
372
|
+
if (ctx.repEmail) repParts.push(ctx.repEmail);
|
|
373
|
+
if (repParts.length > 0) {
|
|
374
|
+
const repStr = repParts.join(" | ");
|
|
375
|
+
const repStrW = helv.widthOfTextAtSize(repStr, 9);
|
|
376
|
+
page.drawText(repStr, { x: W - PAD - repStrW, y: topRowY, size: 9, font: helv, color: darkGrey });
|
|
377
|
+
}
|
|
378
|
+
const divY = Math.round(BOT_BANNER_H / 2) - 1;
|
|
379
|
+
page.drawLine({
|
|
380
|
+
start: { x: PAD, y: divY },
|
|
381
|
+
end: { x: W - PAD, y: divY },
|
|
382
|
+
thickness: 0.4,
|
|
383
|
+
color: rgb4(0.82, 0.82, 0.8)
|
|
384
|
+
});
|
|
385
|
+
const botRowY = 5;
|
|
386
|
+
page.drawText(ctx.dateStamp, { x: PAD, y: botRowY, size: 8, font: helv, color: midGrey });
|
|
387
|
+
const pageStr = ctx.pageCount > 0 ? `${ctx.pageNumber} of ${ctx.pageCount}` : String(ctx.pageNumber);
|
|
388
|
+
const pageStrW = helv.widthOfTextAtSize(pageStr, 8);
|
|
389
|
+
page.drawText(pageStr, { x: (W - pageStrW) / 2, y: botRowY, size: 8, font: helv, color: midGrey });
|
|
390
|
+
const indexLabel = "Index";
|
|
391
|
+
const indexW = helv.widthOfTextAtSize(indexLabel, 8);
|
|
392
|
+
const indexX = W - PAD - indexW;
|
|
393
|
+
page.drawText(indexLabel, { x: indexX, y: botRowY, size: 8, font: helv, color: ctx.accentColor });
|
|
394
|
+
if (ctx.pageNumber > 1) {
|
|
395
|
+
addGoToFirstPageLink(page, {
|
|
396
|
+
x: indexX - 1,
|
|
397
|
+
y: botRowY - 1,
|
|
398
|
+
width: indexW + 2,
|
|
399
|
+
height: 10
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function addGoToFirstPageLink(page, rect) {
|
|
404
|
+
const doc = page.doc;
|
|
405
|
+
const pages = doc.getPages();
|
|
406
|
+
if (pages.length === 0) return;
|
|
407
|
+
const firstPageRef = doc.context.getObjectRef(pages[0].node);
|
|
408
|
+
if (!firstPageRef) return;
|
|
409
|
+
const linkDict = doc.context.obj({
|
|
410
|
+
Type: "Annot",
|
|
411
|
+
Subtype: "Link",
|
|
412
|
+
Rect: [rect.x, rect.y, rect.x + rect.width, rect.y + rect.height],
|
|
413
|
+
Border: [0, 0, 0],
|
|
414
|
+
A: {
|
|
415
|
+
Type: "Action",
|
|
416
|
+
S: "GoTo",
|
|
417
|
+
D: [firstPageRef, PDFName.of("Fit")]
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
const linkRef = doc.context.register(linkDict);
|
|
421
|
+
const existing = page.node.Annots();
|
|
422
|
+
if (existing instanceof PDFArray) {
|
|
423
|
+
existing.push(linkRef);
|
|
424
|
+
} else {
|
|
425
|
+
page.node.set(PDFName.of("Annots"), doc.context.obj([linkRef]));
|
|
426
|
+
}
|
|
427
|
+
void PDFRef;
|
|
428
|
+
void PDFNumber;
|
|
429
|
+
}
|
|
430
|
+
export {
|
|
431
|
+
BRANDED_FOOTER_HEIGHT,
|
|
432
|
+
BRANDED_HEADER_HEIGHT,
|
|
433
|
+
BRANDED_HEADER_MINIMAL_H,
|
|
434
|
+
drawBrandedCover,
|
|
435
|
+
drawBrandedFooter,
|
|
436
|
+
drawBrandedHeader,
|
|
437
|
+
loadBookendAssets,
|
|
438
|
+
loadBookendFonts
|
|
439
|
+
};
|
|
440
|
+
//# sourceMappingURL=pdf-lib.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pdf-lib/fonts.ts","../src/pdf-lib/assets.ts","../src/brand.ts","../src/pdf-lib/cover.ts","../src/pdf-lib/header.ts","../src/pdf-lib/footer.ts"],"sourcesContent":["import type { PDFDocument, PDFFont } from \"pdf-lib\";\nimport { StandardFonts } from \"pdf-lib\";\n\nexport interface BookendFonts {\n helv: PDFFont;\n helvBold: PDFFont;\n helvOblique: PDFFont;\n}\n\nexport async function loadBookendFonts(doc: PDFDocument): Promise<BookendFonts> {\n const [helv, helvBold, helvOblique] = await Promise.all([\n doc.embedFont(StandardFonts.Helvetica),\n doc.embedFont(StandardFonts.HelveticaBold),\n doc.embedFont(StandardFonts.HelveticaOblique),\n ]);\n return { helv, helvBold, helvOblique };\n}\n","import type { PDFDocument, PDFImage, RGB } from \"pdf-lib\";\nimport { rgb } from \"pdf-lib\";\n\nimport { darkenHex, hexToRgb01, resolveBandThemeMode, resolveBestLogoUrl } from \"../brand\";\nimport type { BrandInput } from \"../types\";\n\nexport interface ResolvedBookendAssets {\n logoImage: PDFImage | null;\n logoAspect: number;\n bandColor: RGB;\n accentColor: RGB;\n cardColors: {\n cream: RGB;\n cardBorder: RGB;\n chipBg: RGB;\n };\n themeMode: \"dark\" | \"light\";\n}\n\nexport interface LoadBookendAssetsOptions {\n brand: BrandInput;\n doc: PDFDocument;\n /**\n * Optional URL resolver. Use when your app needs to convert relative paths\n * (e.g. /demo-files/logo.png) into absolute URLs for server-side fetching.\n * Defaults to identity (returns the URL unchanged).\n */\n resolveUrl?: (url: string) => string;\n /** Optional fetch timeout in ms. Default 8s. */\n fetchTimeoutMs?: number;\n}\n\n/**\n * Resolve brand colors + load logo PDFImage from URL.\n * Mirrors RepFirm's loadTenantBookendAssets behavior.\n */\nexport async function loadBookendAssets(opts: LoadBookendAssetsOptions): Promise<ResolvedBookendAssets> {\n const { brand, doc, resolveUrl = (u) => u, fetchTimeoutMs = 8_000 } = opts;\n const themeMode = resolveBandThemeMode(brand);\n const themeVars = brand.themeVars ?? {};\n\n // Band color\n let bandColorHex: string;\n if (themeMode === \"dark\") {\n bandColorHex =\n themeVars[\"--sidebar-bg\"] ??\n themeVars[\"--header-bg\"] ??\n (brand.primaryColor ? darkenHex(brand.primaryColor, 0.45) : \"#1e3a5f\");\n } else {\n bandColorHex = \"#fbfaf6\";\n }\n\n const accentHex = brand.primaryColor ?? themeVars[\"--accent\"] ?? \"#1e3a5f\";\n\n const [br, bg2, bb] = hexToRgb01(bandColorHex);\n const [ar, ag, ab] = hexToRgb01(accentHex);\n\n const cardColors = {\n cream: rgb(0.984, 0.98, 0.965),\n cardBorder: rgb(0.78, 0.78, 0.78),\n chipBg: rgb(0.937, 0.937, 0.933),\n };\n\n // Logo URL resolution\n const candidate = resolveBestLogoUrl(brand, themeMode);\n const logoUrl = candidate ? resolveUrl(candidate) : null;\n\n let logoImage: PDFImage | null = null;\n let logoAspect = 1;\n\n if (logoUrl && !logoUrl.toLowerCase().endsWith(\".svg\")) {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), fetchTimeoutMs);\n let res: Response;\n try {\n res = await fetch(logoUrl, { signal: controller.signal });\n } finally {\n clearTimeout(timeout);\n }\n if (res.ok) {\n const buf = new Uint8Array(await res.arrayBuffer());\n const lower = logoUrl.toLowerCase();\n if (lower.includes(\".png\") || (res.headers.get(\"content-type\") ?? \"\").includes(\"png\")) {\n logoImage = await doc.embedPng(buf);\n } else {\n logoImage = await doc.embedJpg(buf);\n }\n const dims = logoImage.scale(1);\n logoAspect = dims.width / dims.height;\n }\n } catch {\n // fall back to text-only header\n }\n }\n\n return {\n logoImage,\n logoAspect,\n bandColor: rgb(br, bg2, bb),\n accentColor: rgb(ar, ag, ab),\n cardColors,\n themeMode,\n };\n}\n","import type { BrandInput } from \"./types\";\n\n/**\n * Adapter: RepFirm TenantBranding shape -> BrandInput.\n *\n * RepFirm tenants store branding on the `tenants.branding` JSONB column with\n * keys like primary_color, logo_url, logo_dark_url, etc. (snake_case). Pair\n * with the tenant name from the parent row when calling.\n */\nexport interface TenantBrandingShape {\n primary_color?: string;\n logo_url?: string | null;\n logo_wordmark_url?: string | null;\n logo_mark_url?: string | null;\n logo_dark_url?: string | null;\n logo_light_url?: string | null;\n pdf_header_theme?: \"dark\" | \"light\" | \"auto\";\n theme_vars?: Record<string, string>;\n tagline?: string | null;\n company_address?: string | null;\n company_phone?: string | null;\n company_email?: string | null;\n}\n\nexport function brandFromTenantBranding(\n tenantName: string,\n branding: TenantBrandingShape | null | undefined,\n fallbackPrimary = \"#1e3a5f\",\n): BrandInput {\n const b = branding ?? {};\n return {\n agencyName: tenantName,\n primaryColor: b.primary_color ?? fallbackPrimary,\n logoUrl: b.logo_url ?? null,\n logoDarkUrl: b.logo_dark_url ?? null,\n logoLightUrl: b.logo_light_url ?? null,\n logoWordmarkUrl: b.logo_wordmark_url ?? null,\n logoMarkUrl: b.logo_mark_url ?? null,\n pdfHeaderTheme: b.pdf_header_theme ?? \"auto\",\n themeVars: b.theme_vars ?? {},\n tagline: b.tagline ?? null,\n address: b.company_address ?? null,\n phone: b.company_phone ?? null,\n email: b.company_email ?? null,\n };\n}\n\n/**\n * Adapter: dygarn-dashboard prospect brand shape -> BrandInput.\n *\n * Dygarn-dashboard stores per-prospect brand kits in outreach_prospect_brands\n * with simpler shape (camelCase or snake — be liberal in what we accept).\n */\nexport interface ProspectBrandShape {\n agency_name?: string;\n agencyName?: string;\n legal_name?: string | null;\n legalName?: string | null;\n logo_url?: string | null;\n logoUrl?: string | null;\n primary_color?: string;\n primaryColor?: string;\n secondary_color?: string;\n secondaryColor?: string;\n address?: string | null;\n phone?: string | null;\n website?: string | null;\n}\n\nexport function brandFromProspectBrand(p: ProspectBrandShape): BrandInput {\n const agency = p.agencyName ?? p.agency_name ?? \"Agency\";\n return {\n agencyName: agency,\n legalName: p.legalName ?? p.legal_name ?? null,\n logoUrl: p.logoUrl ?? p.logo_url ?? null,\n primaryColor: p.primaryColor ?? p.primary_color ?? \"#1a73e8\",\n address: p.address ?? null,\n phone: p.phone ?? null,\n website: p.website ?? null,\n pdfHeaderTheme: \"auto\",\n };\n}\n\n/**\n * Determine whether to use a dark or light header band for this brand.\n *\n * - Explicit override: respect pdfHeaderTheme if set to 'dark' or 'light'.\n * - Auto (default): use dark theme only when a logoDarkUrl (white-on-dark\n * variant) is available, OR when the logo filename hints at a white/dark\n * variant. Otherwise default to LIGHT (safe for multi-color logos).\n */\nexport function resolveBandThemeMode(brand: BrandInput): \"dark\" | \"light\" {\n if (brand.pdfHeaderTheme === \"dark\") return \"dark\";\n if (brand.pdfHeaderTheme === \"light\") return \"light\";\n if (brand.logoDarkUrl) return \"dark\";\n const candidate = (brand.logoWordmarkUrl ?? brand.logoUrl ?? brand.logoMarkUrl ?? \"\").toLowerCase();\n if (candidate.includes(\"white\") || candidate.includes(\"-on-dark\") || candidate.includes(\"_dark\")) return \"dark\";\n return \"light\";\n}\n\n/**\n * Pick the best logo URL for the resolved theme mode.\n * Returns null if no logo available.\n */\nexport function resolveBestLogoUrl(brand: BrandInput, mode: \"dark\" | \"light\"): string | null {\n if (mode === \"dark\") {\n return brand.logoDarkUrl ?? brand.logoUrl ?? brand.logoWordmarkUrl ?? brand.logoMarkUrl ?? null;\n }\n return (\n brand.logoLightUrl ?? brand.logoUrl ?? brand.logoWordmarkUrl ?? brand.logoMarkUrl ?? brand.logoDarkUrl ?? null\n );\n}\n\n/**\n * Darken a hex color by a 0-1 amount. Used to derive a dark band color from\n * a brand primary when no explicit dark variant exists.\n */\nexport function darkenHex(hex: string, amount: number): string {\n const m = /^#?([0-9a-f]{6})$/i.exec(hex);\n if (!m) return hex;\n const n = Number.parseInt(m[1], 16);\n let r = (n >> 16) & 0xff;\n let g = (n >> 8) & 0xff;\n let b = n & 0xff;\n r = Math.max(0, Math.round(r * (1 - amount)));\n g = Math.max(0, Math.round(g * (1 - amount)));\n b = Math.max(0, Math.round(b * (1 - amount)));\n return `#${[r, g, b].map((c) => c.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\n/**\n * Convert hex to {r,g,b} normalized 0-1. Used by both React-PDF (color objects)\n * and pdf-lib (rgb() arguments).\n */\nexport function hexToRgb01(hex: string): [number, number, number] {\n const h = hex.replace(\"#\", \"\").trim();\n const r = Number.parseInt(h.slice(0, 2), 16) / 255;\n const g = Number.parseInt(h.slice(2, 4), 16) / 255;\n const b = Number.parseInt(h.slice(4, 6), 16) / 255;\n return [Number.isFinite(r) ? r : 0.12, Number.isFinite(g) ? g : 0.23, Number.isFinite(b) ? b : 0.37];\n}\n","import type { PDFPage } from \"pdf-lib\";\nimport { rgb } from \"pdf-lib\";\n\nimport type { BookendContext } from \"./context\";\nimport type { BookendFonts } from \"./fonts\";\n\n/**\n * Draw a branded cover page. Logo centered on the band, doc type label,\n * project name, location, doc number, recipient, prepared-by signature block,\n * bottom metadata line.\n *\n * Designed for 612x792 (US Letter) page size. Caller is responsible for\n * adding the page to the document.\n */\nexport function drawBrandedCover(page: PDFPage, ctx: BookendContext, fonts: BookendFonts): void {\n const { helv, helvBold, helvOblique } = fonts;\n const { bandColor, accentColor, logoImage, logoAspect, themeMode } = ctx;\n const W = page.getWidth();\n const H = page.getHeight();\n const CX = W / 2;\n const agencyName = ctx.brand.agencyName;\n\n // Top branded band\n const COVER_HEADER_H = 130;\n page.drawRectangle({ x: 0, y: H - COVER_HEADER_H, width: W, height: COVER_HEADER_H, color: bandColor });\n page.drawRectangle({ x: 0, y: H - COVER_HEADER_H - 4, width: W, height: 4, color: accentColor });\n\n // Logo or name inside the band\n const COVER_LOGO_H = 70;\n const COVER_LOGO_W = Math.round(COVER_LOGO_H * (logoAspect || 1));\n if (logoImage) {\n page.drawImage(logoImage, {\n x: CX - COVER_LOGO_W / 2,\n y: H - COVER_HEADER_H + (COVER_HEADER_H - COVER_LOGO_H) / 2,\n width: COVER_LOGO_W,\n height: COVER_LOGO_H,\n });\n } else {\n const nameColor = themeMode === \"dark\" ? rgb(1, 1, 1) : rgb(0.08, 0.08, 0.08);\n const nameW = helvBold.widthOfTextAtSize(agencyName, 28);\n page.drawText(agencyName, {\n x: CX - nameW / 2,\n y: H - COVER_HEADER_H / 2 - 10,\n size: 28,\n font: helvBold,\n color: nameColor,\n });\n }\n\n // Project / document block (centered, upper-middle)\n let cursorY = H - COVER_HEADER_H - 70;\n\n const docTypeUpper = ctx.docType.toUpperCase();\n const tagW = helvBold.widthOfTextAtSize(docTypeUpper, 11);\n page.drawText(docTypeUpper, {\n x: CX - tagW / 2,\n y: cursorY,\n size: 11,\n font: helvBold,\n color: rgb(0.45, 0.45, 0.45),\n });\n cursorY -= 30;\n\n if (ctx.projectName) {\n const projW = helvBold.widthOfTextAtSize(ctx.projectName, 22);\n page.drawText(ctx.projectName, {\n x: CX - projW / 2,\n y: cursorY,\n size: 22,\n font: helvBold,\n color: rgb(0.08, 0.08, 0.08),\n });\n cursorY -= 24;\n }\n\n if (ctx.location) {\n const locW = helv.widthOfTextAtSize(ctx.location, 12);\n page.drawText(ctx.location, {\n x: CX - locW / 2,\n y: cursorY,\n size: 12,\n font: helv,\n color: rgb(0.4, 0.4, 0.4),\n });\n cursorY -= 36;\n } else {\n cursorY -= 24;\n }\n\n if (ctx.docNumber) {\n const numLabel = ctx.docNumber;\n const numW = helvBold.widthOfTextAtSize(numLabel, 12);\n page.drawText(numLabel, {\n x: CX - numW / 2,\n y: cursorY,\n size: 12,\n font: helvBold,\n color: rgb(0.25, 0.25, 0.25),\n });\n cursorY -= 28;\n }\n\n const recipientName = ctx.recipientName ?? ctx.distributorName ?? null;\n const recipientLabel = ctx.recipientLabel ?? (ctx.distributorName ? \"DISTRIBUTOR\" : null);\n if (recipientName && recipientLabel) {\n const labelW = helvBold.widthOfTextAtSize(recipientLabel, 11);\n const gap = 14;\n const valueW = helvBold.widthOfTextAtSize(recipientName, 13);\n const totalW = labelW + gap + valueW;\n const rowX = CX - totalW / 2;\n page.drawText(recipientLabel, { x: rowX, y: cursorY, size: 11, font: helvBold, color: rgb(0.45, 0.45, 0.45) });\n page.drawText(recipientName, { x: rowX + labelW + gap, y: cursorY, size: 13, font: helvBold, color: rgb(0.1, 0.1, 0.1) });\n }\n\n // Bottom signature block\n const SIG_BLOCK_TOP = 140;\n let sigY = SIG_BLOCK_TOP;\n\n page.drawRectangle({ x: CX - 100, y: sigY, width: 200, height: 0.5, color: rgb(0.7, 0.7, 0.7) });\n sigY -= 18;\n\n const prepLabel = \"- Prepared By -\";\n const prepLabelW = helvOblique.widthOfTextAtSize(prepLabel, 10);\n page.drawText(prepLabel, { x: CX - prepLabelW / 2, y: sigY, size: 10, font: helvOblique, color: rgb(0.5, 0.5, 0.5) });\n sigY -= 18;\n\n if (ctx.repName) {\n const repNameW = helvBold.widthOfTextAtSize(ctx.repName, 12);\n page.drawText(ctx.repName, { x: CX - repNameW / 2, y: sigY, size: 12, font: helvBold, color: rgb(0.1, 0.1, 0.1) });\n sigY -= 16;\n }\n if (ctx.repEmail) {\n const repEmailW = helv.widthOfTextAtSize(ctx.repEmail, 10);\n page.drawText(ctx.repEmail, { x: CX - repEmailW / 2, y: sigY, size: 10, font: helv, color: rgb(0.16, 0.42, 0.68) });\n sigY -= 14;\n }\n\n const tnW = helvOblique.widthOfTextAtSize(agencyName, 10);\n page.drawText(agencyName, { x: CX - tnW / 2, y: sigY, size: 10, font: helvOblique, color: rgb(0.4, 0.4, 0.4) });\n\n // Bottom-most: doc number + date\n const footerLine = [ctx.docNumber, ctx.dateStamp].filter(Boolean).join(\" | \");\n if (footerLine) {\n const flW = helv.widthOfTextAtSize(footerLine, 9);\n page.drawText(footerLine, { x: CX - flW / 2, y: 36, size: 9, font: helv, color: rgb(0.55, 0.55, 0.55) });\n }\n}\n","import type { PDFPage } from \"pdf-lib\";\nimport { rgb } from \"pdf-lib\";\n\nimport type { BookendContext } from \"./context\";\nimport type { BookendFonts } from \"./fonts\";\n\nexport const BRANDED_HEADER_HEIGHT = 110; // dark band + stripe + cream info zone\nexport const BRANDED_HEADER_MINIMAL_H = 56; // dark band + stripe + thin cream strip\n\nfunction clip(s: string, max: number): string {\n if (s.length <= max) return s;\n return `${s.slice(0, max - 1)}…`;\n}\n\n/**\n * Draw the per-page branded header.\n *\n * showFixtureCard=true (datasheet pages): dark band 38pt + stripe 3pt + cream\n * info zone with white card (Job Name / Manufacturer / Model Number) + type\n * chip on right. Use for submittal datasheet pages.\n *\n * showFixtureCard=false (default): dark band 38pt + stripe 3pt + thin cream\n * strip with project name + doc number. Use for non-datasheet pages.\n */\nexport function drawBrandedHeader(\n page: PDFPage,\n ctx: BookendContext,\n fonts: BookendFonts,\n opts?: { showFixtureCard?: boolean },\n): void {\n const showFixtureCard = opts?.showFixtureCard ?? false;\n const { helv, helvBold } = fonts;\n const { bandColor, accentColor, cardColors, logoImage, logoAspect, themeMode } = ctx;\n const W = page.getWidth();\n const H = page.getHeight();\n const TOP_BANNER_H = showFixtureCard ? BRANDED_HEADER_HEIGHT : BRANDED_HEADER_MINIMAL_H;\n\n const PAD = 12;\n const DARK_H = 38;\n const STRIPE_H = 3;\n const CREAM_H = TOP_BANNER_H - DARK_H - STRIPE_H;\n\n const darkBandY = H - DARK_H;\n const stripeY = darkBandY - STRIPE_H;\n const headerTop = H - TOP_BANNER_H;\n\n page.drawRectangle({ x: 0, y: darkBandY, width: W, height: DARK_H, color: bandColor });\n page.drawRectangle({ x: 0, y: stripeY, width: W, height: STRIPE_H, color: accentColor });\n page.drawRectangle({ x: 0, y: headerTop, width: W, height: CREAM_H, color: cardColors.cream });\n\n page.drawLine({\n start: { x: 0, y: headerTop },\n end: { x: W, y: headerTop },\n thickness: 0.5,\n color: rgb(0.8, 0.8, 0.78),\n });\n\n if (logoImage) {\n const bandLogoH = 26;\n const logoW = Math.round(bandLogoH * logoAspect);\n page.drawImage(logoImage, { x: PAD, y: darkBandY + (DARK_H - bandLogoH) / 2, width: logoW, height: bandLogoH });\n } else {\n const nameColor = themeMode === \"dark\" ? rgb(1, 1, 1) : rgb(0.08, 0.08, 0.08);\n page.drawText(ctx.brand.agencyName, { x: PAD, y: darkBandY + 12, size: 12, font: helvBold, color: nameColor });\n }\n\n if (showFixtureCard) {\n drawFixtureCard(page, ctx, fonts, W, TOP_BANNER_H, PAD, CREAM_H, headerTop);\n } else {\n drawSimpleInfoStrip(page, ctx, fonts, W, CREAM_H, headerTop, PAD);\n }\n}\n\nfunction drawFixtureCard(\n page: PDFPage,\n ctx: BookendContext,\n fonts: BookendFonts,\n W: number,\n _TOP_BANNER_H: number,\n PAD: number,\n CREAM_H: number,\n headerTop: number,\n): void {\n const { helv, helvBold } = fonts;\n const { cardColors } = ctx;\n\n const CHIP_W_RESERVE = 60;\n const CARD_X = PAD * 2;\n const CARD_W = W - CARD_X - CHIP_W_RESERVE - PAD;\n const CARD_H = Math.min(CREAM_H - 8, 60);\n const CARD_Y = headerTop + (CREAM_H - CARD_H) / 2;\n\n page.drawRectangle({\n x: CARD_X,\n y: CARD_Y,\n width: CARD_W,\n height: CARD_H,\n borderColor: cardColors.cardBorder,\n borderWidth: 0.75,\n color: rgb(1, 1, 1),\n });\n\n const LABEL_X = CARD_X + 14;\n const VALUE_X = CARD_X + 100;\n const ROW1_Y = CARD_Y + CARD_H - 17;\n const ROW2_Y = ROW1_Y - 18;\n const ROW3_Y = ROW2_Y - 18;\n\n const jobName = (ctx.projectName || \"—\").toUpperCase();\n const rows: Array<[string, string]> = [\n [\"Job Name:\", clip(jobName, 48)],\n [\"Manufacturer:\", clip(ctx.fixtureMfr ?? \"—\", 44)],\n [\"Model Number:\", clip(ctx.fixturePartNumber ?? \"—\", 44)],\n ];\n const rowYs = [ROW1_Y, ROW2_Y, ROW3_Y];\n for (let i = 0; i < rows.length; i++) {\n const [label, value] = rows[i];\n const y = rowYs[i];\n page.drawText(label, { x: LABEL_X, y, size: 9, font: helvBold, color: rgb(0.27, 0.27, 0.27) });\n page.drawText(value, { x: VALUE_X, y, size: 10.5, font: helvBold, color: rgb(0.08, 0.08, 0.08) });\n }\n\n // Type chip\n const CHIP_W = 50;\n const CHIP_H = Math.min(CREAM_H - 8, 50);\n const CHIP_X = W - PAD - CHIP_W;\n const CHIP_Y = headerTop + (CREAM_H - CHIP_H) / 2;\n const typeCode = clip(ctx.fixtureType ?? \"—\", 4);\n\n page.drawRectangle({ x: CHIP_X, y: CHIP_Y, width: CHIP_W, height: CHIP_H, color: cardColors.chipBg });\n\n const typeLabelW = helv.widthOfTextAtSize(\"Type:\", 7);\n page.drawText(\"Type:\", {\n x: CHIP_X + (CHIP_W - typeLabelW) / 2,\n y: CHIP_Y + CHIP_H - 13,\n size: 7,\n font: helv,\n color: rgb(0.5, 0.5, 0.5),\n });\n\n const codeSize = typeCode.length <= 2 ? 22 : typeCode.length <= 3 ? 17 : 13;\n const codeW = helvBold.widthOfTextAtSize(typeCode, codeSize);\n page.drawText(typeCode, {\n x: CHIP_X + (CHIP_W - codeW) / 2,\n y: CHIP_Y + (CHIP_H - 13) / 2 - codeSize * 0.35,\n size: codeSize,\n font: helvBold,\n color: rgb(0.133, 0.133, 0.133),\n });\n}\n\nfunction drawSimpleInfoStrip(\n page: PDFPage,\n ctx: BookendContext,\n fonts: BookendFonts,\n W: number,\n CREAM_H: number,\n headerTop: number,\n PAD: number,\n): void {\n const { helv, helvBold } = fonts;\n const stripY = headerTop + (CREAM_H - 9) / 2;\n\n if (ctx.projectName) {\n page.drawText(clip(ctx.projectName, 60), {\n x: PAD * 2,\n y: stripY,\n size: 9,\n font: helvBold,\n color: rgb(0.2, 0.2, 0.2),\n });\n }\n if (ctx.docNumber) {\n const numW = helv.widthOfTextAtSize(ctx.docNumber, 8);\n page.drawText(ctx.docNumber, { x: W - PAD - numW, y: stripY, size: 8, font: helv, color: rgb(0.4, 0.4, 0.4) });\n } else if (ctx.docType) {\n const dtW = helv.widthOfTextAtSize(ctx.docType, 8);\n page.drawText(ctx.docType, { x: W - PAD - dtW, y: stripY, size: 8, font: helv, color: rgb(0.4, 0.4, 0.4) });\n }\n}\n","import type { PDFPage } from \"pdf-lib\";\nimport { PDFArray, PDFName, PDFNumber, PDFRef, rgb } from \"pdf-lib\";\n\nimport type { BookendContext } from \"./context\";\nimport type { BookendFonts } from \"./fonts\";\n\nexport const BRANDED_FOOTER_HEIGHT = 38;\n\n/**\n * Per-page branded footer with prepared-by line, date, page numbers, and a\n * clickable \"Index\" link that jumps to page 1.\n */\nexport function drawBrandedFooter(page: PDFPage, ctx: BookendContext, fonts: BookendFonts): void {\n const { helv, helvBold } = fonts;\n const W = page.getWidth();\n const BOT_BANNER_H = BRANDED_FOOTER_HEIGHT;\n const PAD = 10;\n const darkGrey = rgb(0.25, 0.25, 0.25);\n const midGrey = rgb(0.55, 0.55, 0.55);\n\n page.drawRectangle({ x: 0, y: 0, width: W, height: BOT_BANNER_H, color: ctx.cardColors.cream });\n\n page.drawLine({\n start: { x: 0, y: BOT_BANNER_H },\n end: { x: W, y: BOT_BANNER_H },\n thickness: 0.5,\n color: rgb(0.8, 0.8, 0.78),\n });\n\n // TOP ROW\n const topRowY = BOT_BANNER_H - 13;\n page.drawText(\"Prepared By: \", { x: PAD, y: topRowY, size: 9, font: helv, color: darkGrey });\n const prepByW = helv.widthOfTextAtSize(\"Prepared By: \", 9);\n page.drawText(ctx.brand.agencyName, { x: PAD + prepByW, y: topRowY, size: 9, font: helvBold, color: darkGrey });\n\n const repParts: string[] = [];\n if (ctx.repName) repParts.push(ctx.repName);\n if (ctx.repEmail) repParts.push(ctx.repEmail);\n if (repParts.length > 0) {\n const repStr = repParts.join(\" | \");\n const repStrW = helv.widthOfTextAtSize(repStr, 9);\n page.drawText(repStr, { x: W - PAD - repStrW, y: topRowY, size: 9, font: helv, color: darkGrey });\n }\n\n // Thin divider\n const divY = Math.round(BOT_BANNER_H / 2) - 1;\n page.drawLine({\n start: { x: PAD, y: divY },\n end: { x: W - PAD, y: divY },\n thickness: 0.4,\n color: rgb(0.82, 0.82, 0.8),\n });\n\n // BOTTOM ROW\n const botRowY = 5;\n page.drawText(ctx.dateStamp, { x: PAD, y: botRowY, size: 8, font: helv, color: midGrey });\n\n const pageStr = ctx.pageCount > 0 ? `${ctx.pageNumber} of ${ctx.pageCount}` : String(ctx.pageNumber);\n const pageStrW = helv.widthOfTextAtSize(pageStr, 8);\n page.drawText(pageStr, { x: (W - pageStrW) / 2, y: botRowY, size: 8, font: helv, color: midGrey });\n\n const indexLabel = \"Index\";\n const indexW = helv.widthOfTextAtSize(indexLabel, 8);\n const indexX = W - PAD - indexW;\n page.drawText(indexLabel, { x: indexX, y: botRowY, size: 8, font: helv, color: ctx.accentColor });\n\n if (ctx.pageNumber > 1) {\n addGoToFirstPageLink(page, {\n x: indexX - 1,\n y: botRowY - 1,\n width: indexW + 2,\n height: 10,\n });\n }\n}\n\nfunction addGoToFirstPageLink(\n page: PDFPage,\n rect: { x: number; y: number; width: number; height: number },\n): void {\n const doc = page.doc;\n const pages = doc.getPages();\n if (pages.length === 0) return;\n const firstPageRef = doc.context.getObjectRef(pages[0].node);\n if (!firstPageRef) return;\n\n const linkDict = doc.context.obj({\n Type: \"Annot\",\n Subtype: \"Link\",\n Rect: [rect.x, rect.y, rect.x + rect.width, rect.y + rect.height],\n Border: [0, 0, 0],\n A: {\n Type: \"Action\",\n S: \"GoTo\",\n D: [firstPageRef, PDFName.of(\"Fit\")],\n },\n });\n const linkRef = doc.context.register(linkDict);\n\n const existing = page.node.Annots();\n if (existing instanceof PDFArray) {\n existing.push(linkRef);\n } else {\n page.node.set(PDFName.of(\"Annots\"), doc.context.obj([linkRef]));\n }\n\n void PDFRef;\n void PDFNumber;\n}\n"],"mappings":"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","names":["rgb","rgb","rgb"]}
|