init
This commit is contained in:
163
index.js
Normal file
163
index.js
Normal file
@@ -0,0 +1,163 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user