Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@fortawesome/free-regular-svg-icons": "^5.11.2",
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.6",
"@pdf-lib/fontkit": "0.0.4",
"antd": "^3.11.6",
"axios": "^0.18.0",
"bcrypt": "^3.0.4",
Expand All @@ -39,7 +40,7 @@
"nodemailer": "^5.1.1",
"passport": "^0.4.0",
"passport-local": "^1.0.0",
"pdf-fill-form": "^5.0.0",
"pdf-lib": "^1.3.1",
"query-string": "^6.8.2",
"react": "^16.5.2",
"react-dom": "^16.5.2",
Expand Down
Binary file added public/fonts/arial-unicode-ms.ttf
Binary file not shown.
159 changes: 142 additions & 17 deletions src/server/api/services/getdeckform.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,124 @@
'use strict';
var pdfFillForm = require('pdf-fill-form');
const fs = require('fs');
const fontkit = require('@pdf-lib/fontkit');
const {
PDFDocument,
PDFArray,
PDFHexString,
PDFNumber,
breakTextIntoLines,
PDFOperator,
degrees,
drawLinesOfText,
PDFOperatorNames: Ops,
PDFName,
rgb,
StandardFonts,
asPDFName,
PDFContentStream,
pushGraphicsState,
popGraphicsState,
} = require('pdf-lib');

const getAcroForm = pdfDoc => {
return pdfDoc.catalog.lookup(PDFName.of('AcroForm'));
};

const getAcroFields = pdfDoc => {
const acroForm = getAcroForm(pdfDoc);
if (!acroForm) return [];

const fieldRefs = acroForm.lookupMaybe(PDFName.of('Fields'), PDFArray);
if (!fieldRefs) return [];

const fields = new Array(fieldRefs.size());
for (let idx = 0, len = fieldRefs.size(); idx < len; idx++) {
fields[idx] = fieldRefs.lookup(idx);
}
return fields;
};

const findAcroFieldByName = (pdfDoc, name) => {
const acroFields = getAcroFields(pdfDoc);
return acroFields.find(acroField => {
const fieldName = acroField.get(PDFName.of('T'));
return !!fieldName && fieldName.value === name;
});
};

const fillAcroTextField = (acroField, text, font, multiline = false) => {
const rect = acroField.lookup(PDFName.of('Rect'), PDFArray);
const width =
rect.lookup(2, PDFNumber).value() - rect.lookup(0, PDFNumber).value();
const height =
rect.lookup(3, PDFNumber).value() - rect.lookup(1, PDFNumber).value();

const N = multiline
? multiLineAppearanceStream(font, text, width, height)
: singleLineAppearanceStream(font, text, width, height);

acroField.set(PDFName.of('AP'), acroField.context.obj({ N }));
acroField.set(PDFName.of('Ff'), PDFNumber.of(1 /* Read Only */));
acroField.set(PDFName.of('V'), PDFHexString.fromText(text));
};

const beginMarkedContent = tag =>
PDFOperator.of(Ops.BeginMarkedContent, [asPDFName(tag)]);

const endMarkedContent = () => PDFOperator.of(Ops.EndMarkedContent);

const singleLineAppearanceStream = (font, text, width, height) => {
const size = 12;
// const lineWidth = font.widthOfTextAtSize(text, fillingSize);
const lines = [font.encodeText(text)];
const x = 0;
const y = height - size;
return textFieldAppearanceStream(font, size, lines, x, y, width, height);
};

const multiLineAppearanceStream = (font, text, width, height) => {
const size = 9;
// const lineWidth = font.widthOfTextAtSize(text, fillingSize);
const textWidth = t => font.widthOfTextAtSize(t, size);
const lines = breakTextIntoLines(text, [' '], width, textWidth).map(line =>
font.encodeText(line),
);
const x = 0;
const y = height - size;
return textFieldAppearanceStream(font, size, lines, x, y, width, height);
};

const textFieldAppearanceStream = (font, size, lines, x, y, width, height) => {
const dict = font.doc.context.obj({
Type: 'XObject',
Subtype: 'Form',
FormType: 1,
BBox: [0, 0, width, height],
Resources: { Font: { F0: font.ref } },
});

const operators = [
beginMarkedContent('Tx'),
pushGraphicsState(),
...drawLinesOfText(lines, {
color: rgb(0, 0, 0),
font: 'F0',
size: size,
rotate: degrees(0),
xSkew: degrees(0),
ySkew: degrees(0),
x: x,
y: y,
lineHeight: size + 2,
}),
popGraphicsState(),
endMarkedContent(),
];

const stream = PDFContentStream.of(dict, operators);

return font.doc.context.register(stream);
};

import GetDeckById from '../helpers/get-deck-by-id'
import Forms from '../../config/forms';
Expand All @@ -24,6 +143,19 @@ const filterCardQuantity = (cards) =>{

module.exports = async (req, res, next) => {
const Form = req.params.formtype ? Forms[req.params.formtype] : Forms.BSNA;

const notoFontBytes = await fs.readFileSync('../../../public/fonts/arial-unicode-ms.ttf')
const pdfDoc = await PDFDocument.load(fs.readFileSync(Form.path));
pdfDoc.registerFontkit(fontkit);
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman, { subset: true });
const notoFont = await pdfDoc.embedFont(notoFontBytes, { subset: true });

const fillInField = (fieldName, text, font, multiline = false) => {
const field = findAcroFieldByName(pdfDoc, fieldName);
if (!field) throw new Error(`Missing AcroField: ${fieldName}`);
fillAcroTextField(field, text, font, multiline);
};

if(!Form){
res.status(500).json({
success: false,
Expand Down Expand Up @@ -54,9 +186,7 @@ module.exports = async (req, res, next) => {
return 0;
})

let FillData = {
[Form.fields.DeckName]: DeckName
}
fillInField([Form.fields.DeckName], DeckName, timesRomanFont, true)

Form.cardtypes.map( (type) => {
let TypeCards = Cards;
Expand All @@ -66,32 +196,27 @@ module.exports = async (req, res, next) => {
}

TypeCards.map( (card, i) => {

const locale = card.locale[Form.lang].name ? Form.lang : 'NP';
const quantity = type ? Form.fields[type].Quantity(i) : Form.fields.Quantity(i);
const code = type ? Form.fields[type].Code(i) : Form.fields.Code(i);
const level = type ? Form.fields[type].Level(i) : Form.fields.Level(i);
const name = type ? Form.fields[type].Name(i) : Form.fields.Name(i);

//console.log(quantity, code, level, name)

FillData = {
...FillData,
[quantity]: card.quantity,
//TODO change this to static card code when available
[code]: `${card.set}/${card.side}${card.release}${ card.side && card.release ? '-' : '' }${card.sid} ${card.rarity}`,
[level]: card.level,
[name]: card.locale[locale].name,
}
fillInField(quantity, card.quantity, timesRomanFont)
fillInField(code, `${card.set}/${card.side}${card.release}${ card.side && card.release ? '-' : '' }${card.sid} ${card.rarity}`, timesRomanFont)
fillInField(level, card.level, timesRomanFont)
fillInField(name, card.locale[locale].name, locale !== 'JP' ? timesRomanFont : notoFont, true)
})
})

try {
var pdf = pdfFillForm.writeSync(Form.path,
FillData, { "save": "pdf" } );
const pdfBytes = await pdfDoc.save();
const pdfBuffer = Buffer.from(pdfBytes.buffer, 'binary');
res.setHeader('Content-Disposition', 'attachment; filename=' + `${Deck.name}.pdf`);
res.type("application/pdf");
res.send(pdf);
res.send(pdfBuffer);

} catch (error) {
console.log(error);
Expand Down