Unable to save SVG to PDF using jsPDF - javascript

I'm trying to download PDF with SVG content using jsPDF library, it is able to download the file, but there is no content inside it, it is empty PDF.
This is my code:
const downloadPDF = (goJSDiagram) => {
const svg = goJSDiagram.makeSvg({scale: 1, background: "white"});
const svgStr = new XMLSerializer().serializeToString(svg);
const pdfDoc = new jsPDF();
pdfDoc.addSvgAsImage(svgStr, 0, 0, pdfDoc.internal.pageSize.width, pdfDoc.internal.pageSize.height)
pdfDoc.save(props.model[0].cName?.split(" (")[0] + ".pdf");
}
When I do console.log(svgStr), I can see the SVG XML string. What changes should I make to render the content inside PDF?

I think I know what is going on after getting a good hint from this Github issue:
There's the issue that addSvgAsImage() is asynchronous
You are not awaiting the call to finish before calling save! That means you are trying to save before the SVG has started rendering to the PDF.
See the quite simple code in question:
jsPDFAPI.addSvgAsImage = function(
// ... bla bla
return loadCanvg()
.then(
function(canvg) {
return canvg.fromString(ctx, svg, options);
},
function() {
return Promise.reject(new Error("Could not load canvg."));
}
)
.then(function(instance) {
return instance.render(options);
})
.then(function() {
doc.addImage(
canvas.toDataURL("image/jpeg", 1.0),
x,
y,
w,
h,
compression,
rotation
);
});
As you see, it is just a chain of Thenables. So you simply need to await the Promise, which means your code would look something like this in ES2015+:
const downloadPDF = async (goJSDiagram) => {
const svg = goJSDiagram.makeSvg({scale: 1, background: "white"});
const svgStr = new XMLSerializer().serializeToString(svg);
const pdfDoc = new jsPDF();
await pdfDoc.addSvgAsImage(svgStr, 0, 0, pdfDoc.internal.pageSize.width, pdfDoc.internal.pageSize.height)
pdfDoc.save(props.model[0].cName?.split(" (")[0] + ".pdf");
}

After lot of searching, I found the right way to do this, though the content rendered is little blurred.
const waitForImage = imgElem => new Promise(resolve => imgElem.complete ? resolve() : imgElem.onload = imgElem.onerror = resolve);
const downloadPDF = async (goJSDiagram) => {
const svg = goJSDiagram.makeSvg({scale: 1, background: "white"});
const svgStr = new XMLSerializer().serializeToString(svg);
const img = document.createElement('img');
img.src = 'data:image/svg+xml;base64,' + window.btoa(svgStr);
waitForImage(img)
.then(_ => {
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 500;
canvas.getContext('2d').drawImage(img, 0, 0, 500, 500);
const pdfDoc = new jsPDF('p', 'pt', 'a4');
pdfDoc.addImage(canvas.toDataURL('image/png', 1.0), 0, 200, 500, 500);
pdfDoc.save(props.model[0].cName?.split(" (")[0] + ".pdf");
});
}

Related

How to move image processing code entirely to web-worker

I want to transfer full control to worker and use off screen canvas. But there is an Image() which is tied to UI, see function makeImg. I am not intending to show the image, it has pure data usage for building a mesh. The highlighted code fully depends on UI. Is it possible (and how exactly) to do it entirely in web worker, without interchanging data with UI until calculations done and the final mesh fully generated, ready to be shown? For instance following bitmap contains the heights:
The page without perspective with full source codes is here.
I am building height terrain using above bitmap as heightmap, code is here on GitHub, in the page heightMap.html. So, I use pixel values being used to generate vertices, calculate normals, texture coordinate. The result is the terrain going to be shown in the page, here shown without texture:
async function readImgHeightMap (src, crossOrigin) {
return new Promise ((resolve, reject) => {
readImg (src, crossOrigin).then ((imgData) => {
let heightmap = [];
//j, row -- z coordinate; i, column -- x coordinate
//imgData.data, height -- y coordinate
for (let j = 0, j0 = 0; j < imgData.height; j++, j0 += imgData.width * 4) {
heightmap[j] = [];
for (let i = 0, i0 = 0; i < imgData.width; i++, i0 += 4)
heightmap[j][i] = imgData.data[j0 + i0];
}
resolve( {data:heightmap, height:imgData.height, width:imgData.width} );
});
});
}
async function readImg (src, crossOrigin) {
return new Promise ( (resolve, reject) => {
makeOffscreenFromImg (src, crossOrigin).then((canvas) => {
let ctx = canvas.getContext("2d");
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height, { colorSpace: "srgb" });
resolve(imgData);
});
});
}
async function makeOffscreenFromImg (src, crossOrigin) {
let img = makeImg(src, crossOrigin);
return new Promise((resolve, reject) => {
img.addEventListener('load', () => {
let cnv = new OffscreenCanvas(img.width, img.height);
cnv.getContext("2d").drawImage(img, 0, 0);
resolve(cnv);
});
img.addEventListener('error', (event) => { console.log(event); reject (event); } );
});
}
function makeImg (src, crossOrigin)
{
let image = new Image ();
let canvas = document.createElement("canvas");
if (crossOrigin) image.crossOrigin = crossOrigin;
image.src = src;
return image;
}
##################
PS: Just in case, to see the crater from different angles, camera can be moved with mouse when pressing SHIFT, or rotated when pressing CTRL. Also click event for permanent animation, or tap if on mobile device.
PS1: Please do not use heightmap images for personal purposes. These have commercial copyright.
Use createImageBitmap from your Worker, passing a Blob you'd have fetched from the image URL:
const resp = await fetch(imageURL);
if (!resp.ok) {
throw "network error";
}
const blob = await resp.blob();
const bmp = await createImageBitmap(blob);
const { width, height } = bmp;
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext("2d");
ctx.drawImage(bmp, 0, 0);
bmp.close();
const imgData = ctx.getImageData(0, 0, width, height);
If required, you could also create the ImageBitmap from the <img> tag in the main thread, and transfer it to your Worker.

Conversion of html string to pdf in angular results distorted page in downloaded pdf using jsPDF

I am getting a html page's complete code as a string in an api response. I need to convert it to PDF.
I have tried using jsPDF.i successfully converted the page but the page i not exactly same as its html view. The design is distorted. i have given reference to the expected image and what i am getting below.
the funtion in which the conversion code is written .
getHtmlForPdf(id, name) {
this.appService
.GET('Order/details?OrderId=' + id + '&ReportName=' + name)
.subscribe((response) => {
// console.log(response?.result.html_ContentForPdf);
this.htmlData = response?.result.html_ContentForPdf
});
// this.saveAsPdf(response.result.html_ContentForPdf);
const options = {
background: 'white',
scale: 3
};
html2canvas(this.htmlData, options)
.then((canvas) => {
var img = canvas.toDataURL('image/PNG');
var doc = new jsPDF('l', 'mm', 'a4');
// Add image Canvas to PDF
const bufferX = 5;
const bufferY = 5;
const imgProps = (<any>doc).getImageProperties(img);
const pdfWidth = doc.internal.pageSize.getWidth() - 2 * bufferX;
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.addImage(
img,
'PNG',
bufferX,
bufferY,
pdfWidth,
pdfHeight,
undefined,
'FAST'
);
return doc;
})
.then((doc) => {
doc.save('postres.pdf');
});
}
I have tried without using html canvas also.still not working
saveAsPdf(data) {
window['html2canvas'] = html2canvas;
var doc = new jsPDF('p', 'pt', 'a4');
doc.html(data, {
callback: function (pdf) {
pdf.save('cv-a4.pdf');
},
});
}
I found many solution for converting html file to pdf. But in my case i am converting the html code as a string to pdf file. Please give me a solution what I am missing.

How to generate SVG dataURL with p5.js and p5.js-svg?

I am trying to generate an image using p5.js and p5.js-svg and then save the SVG xml as a dataURL for storage. Here is an example script that I've simplified for this question:
const canvasWidth = 600;
const canvasHeight = 600;
const bgColor = "#fffcf3";
let unencodedDataURL = "";
const sketch = (p) => {
p.setup = () => {
p.createCanvas(canvasWidth, canvasHeight, p.SVG);
p.background(bgColor);
p.noLoop();
}
p.draw = () => {
p.noFill();
p.ellipse(z
canvasWidth / 2,
canvasHeight / 2,
150,
150
);
unencodedDataURL = p.getDataURL();
console.log(`inside: ${unencodedDataURL}`);
}
}
const test = new p5(sketch, document.body);
console.log(`outside: ${unencodedDataURL}`);
The first log statement prints the right dataURL. But of course the second log statement executes before the one inside draw() and I cannot figure out how to capture the dataURL correctly. I'm sure I am missing something in the p5.js or p5.js-svg libraries and there is an easier way. But I am stuck. Anyone have an idea here? Thanks in advance!
Because p5js run asynchronous,
You can add callback or write a Promise to get data after sketch draw, like this
var handleDataCallback = null;
//........
p.ellipse(
canvasWidth / 2,
canvasHeight / 2,
150,
150
);
unencodedDataURL = p.getDataURL();
if (handleDataCallback) handleDataCallback(unencodedDataURL);
//....
//for callback
handleDataCallback = dataURL => {
//todo something with dataURL
}
if you want to write as a function to generate image
function generateImage(param, callback) {
return new Promise(resolve => {
//......
// do something
p.ellipse(
canvasWidth / 2,
canvasHeight / 2,
150,
150
);
var unencodedDataURL = p.getDataURL();
if (callback) callback(unencodedDataURL);
resolve(unencodedDataURL);
// ....
})
}
//use Promise
generateImage(yourParam, null).then(unencodedDataURL=>{
console.log(`outside: ${unencodedDataURL}`);
})
//or callback
generateImage(yourParam, unencodedDataURL=> {
console.log(`outside: ${unencodedDataURL}`);
});

Images onload in one function

I'm trying to learn JavaScript, making my first game. How I can make all images onload in one function and later draw it in the canvas making my code shorter?
How can I put a lot of images in an array and later us it in a function.
This is my third day of learning JavaScript.
Thanks in advance.
var cvs = document.getElementById('canvas');
var ctx = cvs.getContext('2d');
//load images
var bird = new Image();
var bg = new Image();
var fg = new Image();
var pipeNorth = new Image();
var pipeSouth = new Image();
//images directions
bg.src = "assets/bg.png";
bird.src = "assets/bird.png";
fg.src = "assets/fg.png";
pipeNorth.src = "assets/pipeNorth.png";
pipeSouth.src = "assets/pipeSouth.png";
var heightnum = 80;
var myHeight = pipeSouth.height+heightnum;
var bX = 10;
var bY = 150;
var gravity = 0.5;
// Key Control :D
document.addEventListener("keydown",moveUP)
function moveUP(){
bY -= 20;
}
//pipe coordinates
var pipe = [];
pipe[0] = {
x : cvs.width,
y : 0
}
//draw images
//Background img
bg.onload = function back(){
ctx.drawImage(bg,0,0);
}
//pipe north
pipeNorth.onload = function tubo(){
for(var i = 0; i < pipe.length; i++){
ctx.drawImage(pipeNorth,pipe[i].x,pipe[i].y);
pipe[i].x--;
}
}
pipeSouth.onload = function tuba(){
ctx.drawImage(pipeSouth,pipe[i].x,pipe[i].y+myHeight);
}
bird.onload = function pajaro(){
ctx.drawImage(bird,bX,bY);
bY += gravity;
requestAnimationFrame(pajaro);
}
fg.onload = function flor(){
ctx.drawImage(fg,0,cvs.height - fg.height);
}
moveUP();
back();
tuba();
pajaro();
flor();
This can be done with Promise.all. We'll make a new promise for each image we want to load, resolving when onload is called. Once Promise.all resolves, we can call our initialize function and continue on with the rest of our logic. This avoids race conditions where the main game loop's requestAnimationFrame is called from bird.onload, but it's possible that pipe entities and so forth haven't loaded yet.
Here's a minimal, complete example:
const initialize = images => {
// images are loaded here and we can go about our business
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = 400;
canvas.height = 200;
const ctx = canvas.getContext("2d");
Object.values(images).forEach((e, i) =>
ctx.drawImage(e, i * 100, 0)
);
};
const imageUrls = [
"http://placekitten.com/90/100",
"http://placekitten.com/90/130",
"http://placekitten.com/90/160",
"http://placekitten.com/90/190",
];
Promise.all(imageUrls.map(e =>
new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = e;
})
)).then(initialize);
Notice that I used an array in the above example to store the images. The problem this solves is that the
var foo = ...
var bar = ...
var baz = ...
var qux = ...
foo.src = ...
bar.src = ...
baz.src = ...
qux.src = ...
foo.onload = ...
bar.onload = ...
baz.onload = ...
qux.onload = ...
pattern is extremely difficult to manage and scale. If you decide to add another thing into the game, then the code needs to be re-written to account for it and game logic becomes very wet. Bugs become difficult to spot and eliminate. Also, if we want a specific image, we'd prefer to access it like images.bird rather than images[1], preserving the semantics of the individual variables, but giving us the power to loop through the object and call each entity's render function, for example.
All of this motivates an object to aggregate game entities. Some information we'd like to have per entity might include, for example, the entity's current position, dead/alive status, functions for moving and rendering it, etc.
It's also a nice idea to have some kind of separate raw data object that contains all of the initial game state (this would typically be an external JSON file).
Clearly, this can turn into a significant refactor, but it's a necessary step when the game grows beyond small (and we can incrementally adopt these design ideas). It's generally a good idea to bite the bullet up front.
Here's a proof-of-concept illustrating some of the the musings above. Hopefully this offers some ideas for how you might manage game state and logic.
const entityData = [
{
name: "foo",
path: "http://placekitten.com/80/80",
x: 0,
y: 0
},
{
name: "baz",
path: "http://placekitten.com/80/150",
x: 0,
y: 90
},
{
name: "quux",
path: "http://placekitten.com/100/130",
x: 90,
y: 110
},
{
name: "corge",
path: "http://placekitten.com/200/240",
x: 200,
y: 0
},
{
name: "bar",
path: "http://placekitten.com/100/100",
x: 90,
y: 0
}
/* you can add more properties and functions
(movement, etc) to each entity
... try adding more entities ...
*/
];
const entities = entityData.reduce((a, e) => {
a[e.name] = {...e, image: new Image(), path: e.path};
return a;
}, {});
const initialize = () => {
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = innerWidth;
canvas.height = innerHeight;
const ctx = canvas.getContext("2d");
for (const key of Object.keys(entities)) {
entities[key].alpha = Math.random();
}
(function render () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
Object.values(entities).forEach(e => {
ctx.globalAlpha = Math.abs(Math.sin(e.alpha += 0.005));
ctx.drawImage(e.image, e.x, e.y);
ctx.globalAlpha = 1;
});
requestAnimationFrame(render);
})();
};
Promise.all(Object.values(entities).map(e =>
new Promise((resolve, reject) => {
e.image.onload = () => resolve(e.image);
e.image.onerror = () =>
reject(`${e.path} failed to load`)
;
e.image.src = e.path;
})
))
.then(initialize)
.catch(err => console.error(err))
;

Why I cannot decode QR codes in Javascript?

I am developing angular application and trying to decode image with QR code on client side only and facing with next errors.
I have next flow:
User uploads image.
I decode qr code from image.
<input type="file" name="file" id="file" accept="image/*"(change)="qrCodeUploaded($event.target.files)"/>
I have tried next libraries:
https://github.com/zxing-js/library
qrCodeUploaded(files: FileList): void {
const fileReader = new FileReader();
const codeReader = new BrowserQRCodeReader();
fileReader.readAsDataURL(files[0]);
fileReader.onload = (e: any) => {
var image = document.createElement("img");
image.src = e.target.result;
setTimeout(() => codeReader.decodeFromImage(image, e.target.result).then(res => console.log(res)), 100);
};
}
Works for some of qr codes, but issues on mobile. If you will take a photo of QR code with your phone, it will be not decoded. So for mobile you will need to implement video.
https://github.com/cozmo/jsQR
qrCodeUploaded(files: FileList): void {
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(files[0]);
fileReader.onload = (e: any) => {
console.log(new Uint8ClampedArray(e.target.result));
console.log(jsQR(new Uint8ClampedArray(e.target.result), 256, 256));
};
}
I get next error for any QR images I upload:
core.js:15714 ERROR Error: Malformed data passed to binarizer.
at Object.binarize (jsQR.js:408)
at jsQR (jsQR.js:368)
gist link:
https://gist.github.com/er-ant/b5c75c822eb085e70035cf333bb0fb55
Please, tell me what I am doing wrong and propose some solution for QR codes decoding. Open for any thoughts :)
For second library I missed that it expects ImageData and I pass Binary Data.
Thus, we have 3 solutions how to convert Binary Data to ImageData:
Use createImageBitmap Kaiido solution with some updates, as proposed in comments doesn't work.
qrCodeUploadedHandler(files: FileList): void {
const file: File = files[0];
createImageBitmap(files[0])
.then(bmp => {
const canvas = document.createElement('canvas');
const width: number = bmp.width;
const height: number = bmp.height;
canvas.width = bmp.width;
canvas.height = bmp.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(bmp, 0, 0);
const qrCodeImageFormat = ctx.getImageData(0,0,bmp.width,bmp.height);
const qrDecoded = jsQR(qrCodeImageFormat.data, qrCodeImageFormat.width, qrCodeImageFormat.height);
});
}
Get ImageData from canvas.
qrCodeUploadedHandler(files: FileList): void {
const file: File = files[0];
const fileReader: FileReader = new FileReader();
fileReader.readAsDataURL(files[0]);
fileReader.onload = (event: ProgressEvent) => {
const img: HTMLImageElement = new Image();
img.onload = () => {
const canvas: HTMLCanvasElement = document.createElement('canvas');
const width: number = img.width;
const height: number = img.height;
canvas.width = width;
canvas.height = height;
const canvasRenderingContext: CanvasRenderingContext2D = canvas.getContext('2d');
console.log(canvasRenderingContext);
canvasRenderingContext.drawImage(img, 0, 0);
const qrCodeImageFormat: ImageData = canvasRenderingContext.getImageData(0, 0, width, height);
const qrDecoded = jsQR(qrCodeImageFormat.data, qrCodeImageFormat.width, qrCodeImageFormat.height);
canvas.remove();
};
img.onerror = () => console.error('Upload file of image format please.');
img.src = (<any>event.target).result;
}
You can parse images with png.js and jpeg-js libraries for ImageData.
step:
Create a new image object from image file
Create a canvas
Draw the image to the canvas
Get ImageData through the context of canvas
Scan QR code with jsQR.
install:
npm install jsqr --save
code:
// utils/qrcode.js
import jsQR from "jsqr"
export const scanQrCode = (file, callback) => {
const image = new Image()
image.src = file.content
image.addEventListener("load", (e) => {
console.log(
"image on load, image.naturalWidth, image.naturalHeight",
image.naturalWidth,
image.naturalHeight
)
const canvas = document.createElement("canvas") // Creates a canvas object
canvas.width = image.naturalWidth // Assigns image's width to canvas
canvas.height = image.naturalHeight // Assigns image's height to canvas
const context = canvas.getContext("2d") // Creates a contect object
context.imageSmoothingEnabled = false
context.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight) // Draws the image on canvas
const imageData = context.getImageData(0, 0, image.naturalWidth, image.naturalHeight) // Assigns image base64 string in jpeg format to a variable
const code = jsQR(imageData.data, image.naturalWidth, image.naturalHeight)
if (code) {
console.log("Found QR code", code)
callback(code)
}
})
}
use:
scanQrCode(file, (code) => {
console.log(code.data);
});

Categories