164 lines
4.1 KiB
JavaScript
164 lines
4.1 KiB
JavaScript
import Progress from 'cli-progress'
|
|
import chalk from 'chalk'
|
|
import * as Log from './log.js'
|
|
import * as Card from './card'
|
|
import * as Mpc from './mpcfill.js'
|
|
import * as Drive from './drive'
|
|
import * as Pdf from 'pdf-lib'
|
|
|
|
/** @type {(from: number) => (to: number) => Array<number>} */
|
|
const range =
|
|
from =>
|
|
to =>
|
|
new Array(to - from)
|
|
.fill(undefined)
|
|
.map((_, ix) => ix + from)
|
|
|
|
const dumpPath = process.argv[2]
|
|
if (!dumpPath) {
|
|
Log.error('missing DUMP_PATH:\n bun run index.js DUMP_PATH.xml\n')
|
|
process.exit(1)
|
|
}
|
|
|
|
const dump = Mpc.readDump(await Bun.file(dumpPath).text())
|
|
Log.info(`successfully read XML dump with ${dump.order.details.quantity} cards`)
|
|
|
|
const driveAuth = await Drive.init()
|
|
Log.debug('authenticated to google drive')
|
|
|
|
Log.info(`Downloading card fronts...`)
|
|
let progress = new Progress.SingleBar({}, Progress.Presets.shades_classic)
|
|
|
|
progress.start(dump.order.fronts.card.length, 0)
|
|
|
|
/** @type {Array<Card.Card>} */
|
|
const fronts = []
|
|
|
|
/** @type {Array<Card.Card>} */
|
|
const backs = []
|
|
|
|
/** @type {Array<Promise<void>>} */
|
|
let parChunk = []
|
|
|
|
for (const card of dump.order.fronts.card) {
|
|
parChunk.push(
|
|
Card
|
|
.get({ driveAuth, id: card.id, dump: card })
|
|
.then(a => {
|
|
fronts.push(a)
|
|
progress.increment()
|
|
})
|
|
)
|
|
|
|
if (parChunk.length >= 5) {
|
|
await Promise.all(parChunk)
|
|
parChunk = []
|
|
}
|
|
}
|
|
|
|
await Promise.all(parChunk)
|
|
parChunk = []
|
|
|
|
for (const card of dump.order.backs.card) {
|
|
parChunk.push(
|
|
Card
|
|
.get({ driveAuth, id: card.id, dump: card })
|
|
.then(a => {
|
|
backs.push(a)
|
|
progress.increment()
|
|
})
|
|
)
|
|
|
|
if (parChunk.length >= 5) {
|
|
await Promise.all(parChunk)
|
|
parChunk = []
|
|
}
|
|
}
|
|
|
|
await Promise.all(parChunk)
|
|
parChunk = []
|
|
|
|
const defaultCardback = await Card.get({ driveAuth, id: dump.order.cardback, dump: undefined })
|
|
|
|
Log.info(`Done!`)
|
|
Log.info(`Building pdf...`)
|
|
|
|
const doc = await Pdf.PDFDocument.create()
|
|
|
|
/**
|
|
* @param {Card.Card} card
|
|
*/
|
|
const embedCardImage = async card => {
|
|
try {
|
|
return card.type === 'jpg'
|
|
? await doc.embedJpg(await Card.image({ id: card.id, driveAuth }))
|
|
: await doc.embedPng(await Card.image({ id: card.id, driveAuth }))
|
|
} catch (e) {
|
|
Log.error(`card id ${card.id}: ${e.toString()}`)
|
|
throw e
|
|
}
|
|
}
|
|
|
|
const firstPage = doc.addPage(Pdf.PageSizes.Letter)
|
|
const firstBackPage = doc.addPage(Pdf.PageSizes.Letter)
|
|
const defaultCardbackPdfImage = await embedCardImage(defaultCardback)
|
|
|
|
const cardCenters = (() => {
|
|
const trim = firstPage.getTrimBox()
|
|
const xDelta = trim.width / 3
|
|
const yDelta = trim.height / 3
|
|
|
|
/** @type {(n: number) => number} */
|
|
const xOff = n => trim.x + (xDelta / 2) + (xDelta * n);
|
|
|
|
/** @type {(n: number) => number} */
|
|
const yOff = n => trim.y + (yDelta / 2) + (yDelta * n);
|
|
|
|
const xs = range(0)(3).map(n => xOff(n))
|
|
const ys = range(0)(3).map(n => yOff(n))
|
|
|
|
return xs.flatMap(x =>
|
|
ys.map(/** @returns {[number, number]} */ y => [x, y])
|
|
)
|
|
})()
|
|
|
|
/**
|
|
* @param {Pdf.PDFPage} page
|
|
* @param {Card.Card} card
|
|
* @param {Pdf.PDFImage} img
|
|
* @param {number} position
|
|
*/
|
|
const renderAt = (page, card, img, position) => {
|
|
const [width, height] = [card.width.pt, card.height.pt]
|
|
const [centerX, centerY] = cardCenters[position]
|
|
const [x, y] = [centerX - (width / 2), centerY - (height / 2)]
|
|
page.drawImage(img, {width, height, x, y })
|
|
}
|
|
|
|
let frontPage = firstPage
|
|
let backPage = firstBackPage
|
|
|
|
const numPages = Math.ceil(fronts.length / 9)
|
|
for (let pageIx = 0; pageIx < numPages; pageIx++) {
|
|
const cardIxOffset = pageIx * 9;
|
|
for (let cardPos = 0; cardPos < 9; cardPos++) {
|
|
const cardIx = cardPos + cardIxOffset
|
|
const front = fronts[cardIx]
|
|
if (!front) break;
|
|
const frontImg = await embedCardImage(front)
|
|
|
|
const back = backs.find(back => back.dump.slots === front.dump.slots)
|
|
const backImg = back ? await embedCardImage(back) : defaultCardbackPdfImage;
|
|
|
|
renderAt(frontPage, front, frontImg, cardPos)
|
|
renderAt(backPage, back || defaultCardback, backImg, cardPos)
|
|
}
|
|
|
|
frontPage = doc.addPage(Pdf.PageSizes.Letter)
|
|
backPage = doc.addPage(Pdf.PageSizes.Letter)
|
|
}
|
|
|
|
await Bun.write('out.pdf', await doc.save())
|
|
Log.info('Wrote to out.pdf')
|
|
process.exit(0)
|