Add a reproducible pitch URL and link annotation to the CV.
The script is idempotent, keeps the twelve A4 pages, and writes antoniolede.de/pitch on page 1 with a matching URI annotation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
50
package-lock.json
generated
50
package-lock.json
generated
@@ -27,7 +27,7 @@
|
|||||||
"@axe-core/playwright": "^4.13.0",
|
"@axe-core/playwright": "^4.13.0",
|
||||||
"@eslint/js": "^9.39.5",
|
"@eslint/js": "^9.39.5",
|
||||||
"@lhci/cli": "^0.15.1",
|
"@lhci/cli": "^0.15.1",
|
||||||
"@playwright/test": "^1.62.1",
|
"@playwright/test": "1.62.1",
|
||||||
"@types/express": "^5.0.1",
|
"@types/express": "^5.0.1",
|
||||||
"@types/node": "^20.17.19",
|
"@types/node": "^20.17.19",
|
||||||
"angular-eslint": "^21.4.0",
|
"angular-eslint": "^21.4.0",
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
"eslint": "^9.39.5",
|
"eslint": "^9.39.5",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"jsdom": "^27.1.0",
|
"jsdom": "^27.1.0",
|
||||||
|
"pdf-lib": "^1.17.1",
|
||||||
"prettier": "^3.9.6",
|
"prettier": "^3.9.6",
|
||||||
"typescript": "~5.9.2",
|
"typescript": "~5.9.2",
|
||||||
"typescript-eslint": "^8.68.0",
|
"typescript-eslint": "^8.68.0",
|
||||||
@@ -4285,6 +4286,26 @@
|
|||||||
"third-party-web": "latest"
|
"third-party-web": "latest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@pdf-lib/standard-fonts": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pako": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@pdf-lib/upng": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pako": "^1.0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@playwright/test": {
|
"node_modules/@playwright/test": {
|
||||||
"version": "1.62.1",
|
"version": "1.62.1",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz",
|
||||||
@@ -10888,6 +10909,13 @@
|
|||||||
"node": "^20.17.0 || >=22.9.0"
|
"node": "^20.17.0 || >=22.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pako": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "(MIT AND Zlib)"
|
||||||
|
},
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
@@ -11064,6 +11092,26 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pdf-lib": {
|
||||||
|
"version": "1.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
|
||||||
|
"integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@pdf-lib/standard-fonts": "^1.0.0",
|
||||||
|
"@pdf-lib/upng": "^1.0.1",
|
||||||
|
"pako": "^1.0.11",
|
||||||
|
"tslib": "^1.11.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pdf-lib/node_modules/tslib": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/pend": {
|
"node_modules/pend": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
"ci": "npm run lint && npm run format:check && npm run test:ci && npm run build",
|
"ci": "npm run lint && npm run format:check && npm run test:ci && npm run build",
|
||||||
"e2e": "playwright test",
|
"e2e": "playwright test",
|
||||||
"e2e:install": "playwright install chromium",
|
"e2e:install": "playwright install chromium",
|
||||||
"lighthouse": "npm run build && lhci autorun"
|
"lighthouse": "npm run build && lhci autorun",
|
||||||
|
"cv:link": "node scripts/add-cv-pitch-link.mjs"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
@@ -61,6 +62,7 @@
|
|||||||
"eslint": "^9.39.5",
|
"eslint": "^9.39.5",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"jsdom": "^27.1.0",
|
"jsdom": "^27.1.0",
|
||||||
|
"pdf-lib": "^1.17.1",
|
||||||
"prettier": "^3.9.6",
|
"prettier": "^3.9.6",
|
||||||
"typescript": "~5.9.2",
|
"typescript": "~5.9.2",
|
||||||
"typescript-eslint": "^8.68.0",
|
"typescript-eslint": "^8.68.0",
|
||||||
|
|||||||
107
scripts/add-cv-pitch-link.mjs
Normal file
107
scripts/add-cv-pitch-link.mjs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { readFileSync, writeFileSync } from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { PDFDocument, PDFName, PDFString, rgb, StandardFonts } from 'pdf-lib';
|
||||||
|
|
||||||
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
const CV_PATH = join(ROOT, 'CV.pdf');
|
||||||
|
const PITCH_URI = 'https://antoniolede.de/pitch';
|
||||||
|
const VISIBLE_TEXT = 'antoniolede.de/pitch';
|
||||||
|
const TEXT_X = 59.6;
|
||||||
|
const TEXT_Y = 215.4;
|
||||||
|
const FONT_SIZE = 10.5;
|
||||||
|
const PADDING = 2;
|
||||||
|
|
||||||
|
function asString(value) {
|
||||||
|
if (!value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value.decodeText === 'function') {
|
||||||
|
return value.decodeText();
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function annotationUri(annotation) {
|
||||||
|
const action = annotation.lookup(PDFName.of('A'));
|
||||||
|
if (!action || typeof action.lookup !== 'function') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const uri = action.lookup(PDFName.of('URI'));
|
||||||
|
return asString(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageHasPitchLink(page) {
|
||||||
|
const annots = page.node.lookup(PDFName.of('Annots'));
|
||||||
|
if (!annots || typeof annots.size !== 'function') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < annots.size(); index += 1) {
|
||||||
|
const annotation = annots.lookup(index);
|
||||||
|
if (annotationUri(annotation) === PITCH_URI) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytes = readFileSync(CV_PATH);
|
||||||
|
const pdfDoc = await PDFDocument.load(bytes);
|
||||||
|
const title = pdfDoc.getTitle();
|
||||||
|
const author = pdfDoc.getAuthor();
|
||||||
|
const creator = pdfDoc.getCreator();
|
||||||
|
const page = pdfDoc.getPage(0);
|
||||||
|
|
||||||
|
if (pageHasPitchLink(page)) {
|
||||||
|
console.log('Pitch link annotation already present; leaving CV.pdf unchanged.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||||
|
const textWidth = font.widthOfTextAtSize(VISIBLE_TEXT, FONT_SIZE);
|
||||||
|
|
||||||
|
page.drawText(VISIBLE_TEXT, {
|
||||||
|
x: TEXT_X,
|
||||||
|
y: TEXT_Y,
|
||||||
|
size: FONT_SIZE,
|
||||||
|
font,
|
||||||
|
color: rgb(54 / 255, 125 / 255, 162 / 255),
|
||||||
|
});
|
||||||
|
|
||||||
|
const link = pdfDoc.context.register(
|
||||||
|
pdfDoc.context.obj({
|
||||||
|
Type: 'Annot',
|
||||||
|
Subtype: 'Link',
|
||||||
|
Rect: [
|
||||||
|
TEXT_X - PADDING,
|
||||||
|
TEXT_Y - PADDING,
|
||||||
|
TEXT_X + textWidth + PADDING,
|
||||||
|
TEXT_Y + FONT_SIZE + PADDING,
|
||||||
|
],
|
||||||
|
Border: [0, 0, 0],
|
||||||
|
A: {
|
||||||
|
S: 'URI',
|
||||||
|
URI: PDFString.of(PITCH_URI),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
page.node.addAnnot(link);
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
pdfDoc.setTitle(title);
|
||||||
|
}
|
||||||
|
if (author) {
|
||||||
|
pdfDoc.setAuthor(author);
|
||||||
|
}
|
||||||
|
if (creator) {
|
||||||
|
pdfDoc.setCreator(creator);
|
||||||
|
}
|
||||||
|
|
||||||
|
const saved = await pdfDoc.save({ useObjectStreams: false });
|
||||||
|
writeFileSync(CV_PATH, saved);
|
||||||
|
console.log('Added visible pitch URL and link annotation to page 1 of CV.pdf.');
|
||||||
Reference in New Issue
Block a user