How to center continued text using PdfKit? - node.js

I have this code:
const PDFDocument = require("pdfkit");
const QRCode = require("qrcode");
const fs = require("fs");
const exec = async () => {
const doc = new PDFDocument({ layout: "landscape" });
doc.pipe(fs.createWriteStream("output.pdf"));
for (let pageNumber = 1; pageNumber <= 1000; pageNumber++) {
const url = await QRCode.toDataURL("I am a url!");
doc
.image(url, 10, 100, {
width: 420,
height: 420,
align: "center",
valign: "center",
})
doc
.font("Helvetica")
.fontSize(50)
.fillColor("#000")
.text(`Item `, 465, 200, { continued: true })
.fontSize(55)
.font("Courier-Bold")
.fillColor("#1b83c5")
.text(`${pageNumber}`);
doc
.font("Helvetica-Bold")
.fontSize(40)
.fillColor("#000")
.text("Order and Pay", 420, 320);
doc.addPage();
}
doc.end();
};
exec();
Which would produce something like this:
It looks centered and all, but as pages increase it will no longer be centered since the numbers are fixed.
I saw in the docs that there's an align property, but the docs didn't explain how to handle continued text.
Any working examples?

Maybe it's a little late, but I found a solution battling with the same problem:
First you need to create a constant with the width of the container you want to center your text in: (this value you have to calculate or invent, but it's easy to do that)
const containerWidth = 100 // as an example
Then, you need to create a variable that contains a plain string that contains all the text you want to center, in your example:
var appendedText = `Item ${pageNumber}`
To finish, you need to use pdf-kit's function: widthOfString, and add the text in the document as follows:
const xOffset = 465 // The original X offset value
doc
.text(`Item `, xOffset + (containerWidth / 2) - (doc.widthOfString(appendedText) / 2), 200, { continued: true })
.text(`${pageNumber}`);
Removed the text styling lines for clarity, but you have to add them later.

Related

PDFKit split text into two equal columns while using for loop

Im trying to use PDFKit to generate a simple pdf, for the most part the pdf works but albeit in a very non useful way, what i have is a deck building API that takes in a number of cards, each of these objects i want to export to a pdf, its as simple as displaying their name, but as it is, the pdf only renders one card at a time, and only on one line, what id like to happen is to get it to split the text into columns so itd look similar to this.
column 1 | column 2
c1 c8
c2 c9
c3 c10
c4 c(n)
here is my code so far,
module.exports = asyncHandler(async (req, res, next) => {
try {
// find the deck
const deck = await Deck.findById(req.params.deckId);
// need to sort cards by name
await deck.cards.sort((a, b) => {
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
// Create a new PDF document
const doc = new PDFDocument();
// Pipe its output somewhere, like to a file or HTTP response
doc.pipe(
fs.createWriteStream(
`${__dirname}/../../public/pdf/${deck.deck_name}.pdf`
)
);
// Embed a font, set the font size, and render some text
doc.fontSize(25).text(`${deck.deck_name} Deck List`, {
align: "center",
underline: true,
underlineColor: "#000000",
underlineThickness: 2,
});
// We need to create two columns for the cards
// The first column will be the card name
// The second column will continue the cards listed
const section = doc.struct("P");
doc.addStructure(section);
for (const card of deck.cards) {
doc.text(`${card.name}`, {
color: "#000000",
fontSize: 10,
columns: 2,
columnGap: 10,
continued: true,
});
}
section.end();
// finalize the PDF and end the response
doc.end();
res.status(200).json({ message: "PDF generated successfully" });
} catch (error) {
console.error(error);
res.status(500).json({
success: false,
message: `Server Error - ${error.message}`,
});
}
});
At Present this does generate a column order like i want, however theres and extreme caveat to this solution and that is, if the card text isnt very long, the next card will start on that same line, it'd be useful if i could find a way to make the text take up the full width of that row, but i havent seen anything to do that with.
I think the problem is that you're relying on PDFKit's text "flow" API/logic, and you're having problems when two cards are not big enough to flow across your columns and you get two cards in one column.
I'd say that what you really want is to create a table—based on your initial text sample.
PDFKit doesn't have a table API (yet), so you'll have to make one up for yourself.
Here's an approach where you figure out the dimensions of things:
the page size
the size of your cells of text (either manually choose for yourself, or use PDFKit to tell you how big some piece of text is)
margins
Then you use those sizes to calculate how many rows and columns of your text can fit on your page.
Finally you iterate of over columns then rows for each page, writing text into those column-by-row "coordinates" (which I track through "offsets" and use to calculate the final "position").
const PDFDocument = require('pdfkit');
const fs = require('fs');
// Create mock-up Cards for OP
const cards = [];
for (let i = 0; i < 100; i++) {
cards.push(`Card ${i + 1}`);
}
// Set a sensible starting point for each page
const originX = 50;
const originY = 50;
const doc = new PDFDocument({ size: 'LETTER' });
// Define row height and column widths, based on font size; either manually,
// or use commented-out heightOf and widthOf methods to dynamically pick sizes
doc.fontSize(24);
const rowH = 50; // doc.heightOfString(cards[cards.length - 1]);
const colW = 150; // doc.widthOfString(cards[cards.length - 1]); // because the last card is the "longest" piece of text
// Margins aren't really discussed in the documentation; I can ignore the top and left margin by
// placing the text at (0,0), but I cannot write below the bottom margin
const pageH = doc.page.height;
const rowsPerPage = parseInt((pageH - originY - doc.page.margins.bottom) / rowH);
const colsPerPage = 2;
var cardIdx = 0;
while (cardIdx < cards.length) {
var colOffset = 0;
while (colOffset < colsPerPage) {
const posX = originX + (colOffset * colW);
var rowOffset = 0;
while (rowOffset < rowsPerPage) {
const posY = originY + (rowOffset * rowH);
doc.text(cards[cardIdx], posX, posY);
cardIdx += 1;
rowOffset += 1;
}
colOffset += 1;
}
// This is hacky, but PDFKit adds a page by default so the loop doesn't 100% control when a page is added;
// this prevents an empty trailing page from being added
if (cardIdx < cards.length) {
doc.addPage();
}
}
// Finalize PDF file
doc.pipe(fs.createWriteStream('output.pdf'));
doc.end();
When I run that I get a PDF with 4 pages that looks like this:
Changing colW = 250 and colsPerPage = 3:

Tensorflow js server side classification with mobilenet and blazeface

I'm trying to use tensorflowjs with an already built model, at the beginning I had a problem with blazeface because it didn't find face on photo (that display just faces) and so I'm trying with mobilenet and same probleme the result are non sens. So pretty sur it came from the format of image I'm sending.
So this is my code:
module.exports = {
blazeface: require('#tensorflow-models/blazeface'),
mobilenet: require('#tensorflow-models/mobilenet'),
tf: require("#tensorflow/tfjs-node"),
fs: require("fs"),
sizeOf: require("image-size"),
async structureData(imageLink) {
let data = this.fs.readFileSync(imageLink);//return a buffer
const dimension = await this.sizeOf(imageLink);
const tensorflowImage = {
data: data,
width: dimension.width,
height: dimension.height
};
console.log(tensorflowImage)
return tensorflowImage;
},
async detectFace(imageLink) {
let image = await this.structureData(imageLink);
const model = await this./*blazeface*/mobilenet.load();
const returnTensors = false;
const predictions = await model.classify(image);
console.log(predictions);
}
}
So this code do not result of an error but the result are this =>
[
{
className: 'theater curtain, theatre curtain',
probability: 0.03815646469593048
},
{
className: 'web site, website, internet site, site',
probability: 0.0255243219435215
},
{ className: 'matchstick', probability: 0.02520526386797428 }
]
and with any photo (here it's a banana in white background).
So I'm pretty sur i need to rebuilt the structureData function but I don't know how ...
I also tried this with uint32array
async structureData(imageLink) {
let data = this.fs.readFileSync(imageLink);
data = new Uint32Array(data);
const dimension = await this.sizeOf(imageLink);
const tensorflowImage = {
data: data,
width: dimension.width,
height: dimension.height
};
console.log(tensorflowImage)
return tensorflowImage;
},
But I'm getting this error.
Error: pixels passed to tf.browser.fromPixels() must be either an HTMLVideoElement, HTMLImageElement, HTMLCanvasElement, ImageData in browser, or OffscreenCanvas, ImageData in webworker or {data: Uint32Array, width: number, height: number}, but was Object
remember that i'm using node and so I can't (or don't think I can) us HTMLimageelement
Thanks a lot :)
By using a tensor, a video or image element as parameter to the model, it will be able to do the classification.
let data = this.fs.readFileSync(imageLink);
tensor = tf.node.decodeImage(data, 3)
await model.classify(tensor)

Expiry date of PDFs in Node

I'm trying to generate a PDF with an expiry date (auto expiring after 24 hours).
I have strong experience with pdf-lib, but searching through their repo there is no mention of expiry dates.
I've also found two articles on how to do it in C# and Python:
C#/VB:
https://www.e-iceblue.com/Tutorials/Spire.PDF/Spire.PDF-Program-Guide/Security/How-to-Add-Expiry-Date-to-PDF-Files-in-C-VB.NET.html
https://docs.aspose.com/pdf/java/set-pdf-expiration-in-python/
I'm trying to make a PDF expire on a person's device after they have downloaded it from the server and surprised to see there isn't much support in the node/pdf area.
Any suggestions? Is this possible?
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'
const pdfDoc = await PDFDocument.create()
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman)
const page = pdfDoc.addPage()
const { width, height } = page.getSize()
const fontSize = 30
page.drawText('Creating PDFs in JavaScript would be great if the PDF had an expiry date!', {
x: 50,
y: height - 4 * fontSize,
size: fontSize,
font: timesRomanFont,
color: rgb(0, 0.53, 0.71),
})
// TODO: add expiry before the save.
const pdfBytes = await pdfDoc.save()
You can use addJavascript method, so your code will look like:
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'
const pdfDoc = await PDFDocument.create()
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman)
pdfDoc.addJavaScript(
'main',
'var year=2020; var month=11;today = new Date();today = new Date(today.getFullYear(), today.getMonth());expiry = new Date(year, month);if (today.getTime() > expiry.getTime())app.alert("The file is expired. You need a new one.");',
);
const page = pdfDoc.addPage()
const { width, height } = page.getSize()
const fontSize = 30
page.drawText('Creating PDFs in JavaScript would be great if the PDF had an expiry date!', {
x: 50,
y: height - 4 * fontSize,
size: fontSize,
font: timesRomanFont,
color: rgb(0, 0.53, 0.71),
})
const pdfBytes = await pdfDoc.save()
You can more investigate it there https://github.com/Hopding/pdf-lib/commit/30d2aa22c0c0d694189ae3202562d4c0565cce42

Pixi.js v5 - apply alpha to displacement map

I'm scaling a displacement map on click but would like that map to fade away once it reaches almost full scale. The idea is that the filter should be non-existent after a couple of seconds.
const app = new PIXI.Application({
view: document.querySelector("#canvas"),
width: 512,
height: 512
});
const logo = PIXI.Sprite.fromImage("https://unsplash.it/600");
const displacement = PIXI.Sprite.fromImage("https://images.unsplash.com/photo-1541701494587-cb58502866ab?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80");
const filter = new PIXI.filters.DisplacementFilter(displacement);
logo.anchor.set(0.5);
logo.position.set(256);
logo.interactive = true;
displacement.anchor.set(0.5);
displacement.position.set(256);
displacement.scale.set(0.05)
displacement.alpha = 1
app.stage.filterArea = app.screen;
app.stage.filters = [filter];
app.stage.addChild(logo, displacement);
app.ticker.add(function() {
displacement.scale.x += 0.05
displacement.scale.y += 0.05
if (displacement.scale.x > 10) app.ticker.stop()
});
logo.on('mousedown', function() {
displacement.scale.set(0.05)
app.ticker.start()
});
Here's what I have so far:
https://codepen.io/mariojankovic/pen/pojjNae?editors=0111
I've only just started looking at Pixi but I think you want to use the scale property of the displacement filter. This value says how far to shift. Reducing this value to 0 will lessen its effect to none.
https://pixijs.download/dev/docs/PIXI.filters.DisplacementFilter.html
https://pixijs.download/dev/docs/PIXI.filters.DisplacementFilter.html#scale
The way it works is it uses the values of the displacement map to look
up the correct pixels to output. This means it's not technically
moving the original. Instead, it's starting at the output and asking
"which pixel from the original goes here". For example, if a
displacement map pixel has red = 1 and the filter scale is 20, this
filter will output the pixel approximately 20 pixels to the right of
the original.
https://codepen.io/PAEz/pen/BaoREwv
const app = new PIXI.Application({
view: document.querySelector("#canvas"),
width: 512,
height: 512
});
const logo = PIXI.Sprite.fromImage("https://unsplash.it/600");
const displacement = PIXI.Sprite.fromImage(
"https://images.unsplash.com/photo-1541701494587-cb58502866ab?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80"
);
const filter = new PIXI.filters.DisplacementFilter(displacement);
logo.anchor.set(0.5);
logo.position.set(256);
logo.interactive = true;
displacement.anchor.set(0.5);
displacement.position.set(256);
displacement.scale.set(0.0);
displacement.alpha = 1;
app.stage.filterArea = app.screen;
app.stage.filters = [filter];
app.stage.addChild(logo, displacement);
const displacementScaleFrom = 0.05;
const displacementScaleTo = 10 ;
const displacementStep = 0.05;
const filterScaleFrom = 20;// the default value for the filter is 20
const filterStep = filterScaleFrom / ((displacementScaleTo-displacementScaleFrom) / displacementStep);
app.ticker.add(function () {
displacement.scale.x += displacementStep;
displacement.scale.y += displacementStep;
filter.scale.x -= filterStep;
filter.scale.y -= filterStep;
if (displacement.scale.x >= displacementScaleTo) {
app.ticker.stop();
filter.scale.x = 0;
filter.scale.y = 0;
}
});
logo.on("mousedown", function () {
displacement.scale.set(displacementScaleFrom);
filter.scale.x = filterScaleFrom;
filter.scale.y = filterScaleFrom;
app.ticker.start();
});

How and when to use PIXI.RenderTexture

I am building a tool which visualises bicycle wheels. It uses approximately 100 PIXI.Graphics to build the entire wheel, which is all placed in a PIXI.Container and then rendered. It seems like quite a lot to render every frame, so I was looking into the PIXI.RenderTexture class and thought it might make sense to use it in this case. So question 1, is this a good use case? and question 2, how can I use it because I am having trouble working it out.
const options = {
transparent: true,
antialias: true,
backgroundColor: 0xffffff,
resolution: window.devicePixelRatio,
view: canvasEle,
};
const app = new PIXI.Application(width, height, options);
const wrapper = new PIXI.Container(); // Wrapper is used for zooming and panning the wheel
app.stage.addChild(wrapper);
const wheel = new Wheel(wheelOpts); // Returns PIXI.Container full of PIXI.Graphics
wrapper.addChild(wheel);
And my attempt to use the renderTexture is as follows. But I can't seem to work it out
const options = {
transparent: true,
antialias: true,
backgroundColor: 0xffffff,
resolution: window.devicePixelRatio,
view: canvasEle,
};
const app = new PIXI.Application(width, height, options);
const wrapper = new PIXI.Container(); // Wrapper is used for zooming and panning the wheel
app.stage.addChild(wrapper);
const wheelRenderTexture = new PIXI.RenderTexture.create(width, height);
const wheelSprite = new PIXI.Sprite(wheelRenderTexture)
wrapper.addChild(wheelSprite)
const wheel = new Wheel(wheelOpts); // Returns PIXI.Container full of PIXI.Graphics
app.ticker.add(() =>
{
app.renderer.render(wheel, wheelRenderTexture);
})
Thanks for any help
I worked out how to use it and made a small jsfiddle https://jsfiddle.net/hp98ygz5/1/
const width = 600
const height = 600
var app = new PIXI.Application(width, height, {backgroundColor : 0xffffff});
document.body.appendChild(app.view);
const wheelRenderTexture = PIXI.RenderTexture.create(width, height);
const wheelRenderSprite = new PIXI.Sprite(wheelRenderTexture);
app.stage.addChild(wheelRenderSprite)
const wheelContainer = new PIXI.Container()
//app.stage.addChild(wheelContainer)
wheelContainer.addChild(drawCircle(100,100,50,0xfec3dc,2,0Xfe68a4))
wheelContainer.addChild(drawCircle(100,100,20,0xFFCC66,2,0X55ff77))
app.renderer.render(wheelContainer,wheelRenderTexture)
I am not sure what was wrong with the above example but it works now

Resources