Using Sharp to turn svg to png corrupts text in node.js - node.js

I am using sharp to convert an svg file into png format to upload to a slack workspace.
I found the same issue here
const options = {
d3Module: d3,
selector: '#chart',
container: '<div id="container"><div id="chart"></div></div>'
}
const d3n = new D3Node(options);
const margin = {
top: 10, right: 5, bottom: 30, left: 5
}
const width = 1000 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svgWidth = width + margin.left + margin.right;
const svgHeight = height + margin.top + margin.bottom;
const svg = d3n.createSVG(svgWidth, svgHeight);
const tempData = [{ year: 2020, value: 100 }, { year: 2019, value: 200 }, { year: 2018, value: 30 }, { year: 2017, value: 50 }, { year: 2016, value: 80 }];
const xScale = d3.scaleBand().range([0,width]).padding(0.5);
const yScale = d3.scaleLinear().range([height,0]);
let yMax = d3.max(tempData, (d) => {return d.value})
yMax += yMax * 0.3;
xScale.domain(tempData.map((d) => {return d.year} ))
yScale.domain([0,yMax]);
svg.append('rect')
.attr('width','100%')
.attr('height', '100%')
.style('fill','rgb(28, 35, 51);');
svg.append('text')
.attr('transform','translate(150,0)')
.attr('fill','#85ceff')
.attr('font-size','24px')
.attr('x',50)
.attr('y',50)
.text('Node and D3 Bar chart')
svg.append('g').attr('transform',`translate(${100}, ${100})`);
svg.append('g')
.attr('transform', `translate(50, ${height})`)
.call(d3.axisBottom(xScale))
.append('text')
.attr('y', height-380)
.attr('x',width-500)
.attr('text-anchor','end')
.attr('stroke','black')
.attr('fill','#85ceff')
.attr('font-size','20px')
.text('Year')
svg.append('g')
.attr('transform','translate(50,0)')
.call(d3.axisLeft(yScale).tickFormat((d) => {
return `$${d}`;
}).ticks(5))
.append('text')
.attr('transform','rotate(-90)')
.attr('y',150)
.attr('x',-150)
.attr('dy','-9.1em')
.attr('text-anchor','end')
.attr('stroke','black')
.attr('font-size','20px')
.attr('fill','#85ceff')
.text('Cost')
fs.writeFileSync('out.svg', d3n.svgString());
sharp('out.svg')
.png()
.toFile('sharp.png')
.then((info) => {
console.log("Svg to Png conversion completed", info);
})
.catch((error) => {
console.log(error)
})
However, when I process my svg to png my text gets corrupted and is no longer in the photo. What am I doing wrong here?
To clarify, I am running this server on replit. When I run this code locally, the png is not corrupted. Could the replit server be the issue?

After browsing several Github Issues about this [Link 1, Link 2 , Link 3] , I did more research and discovered that I needed a font-config file and fonts downloaded because the Replit server did not have the fonts installed.
File Structure
- index.js
- api
- fetch.js
- fonts
- fonts.conf
- SourceSansPro-Regular.ttf
fonts.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<dir>/home/runner/gittrack/fonts</dir>
<config></config>
</fontconfig>
Once I added the above changes, I am now getting fonts on my png

Related

Render svg from svg.js inside pixi.js

Hi just testing to see if I can render an SVG created using svg.js inside a PIXI application. Codpen here
import * as PIXI from 'https://cdnjs.cloudflare.com/ajax/libs/pixi.js/6.5.4/browser/pixi.min.mjs'
const draw = SVG().addTo('body').attr({
viewBox: '0 0 100 100',
width: '100',
height: '100',
"enable-background":"new 0 0 64 64",
style: 'display: block'
})
draw.circle(100).x(50).fill('red')
console.log(draw.node)
const app = new PIXI.Application({
resolution: window.deviceAspectRatio || 1,
resizeTo: window
})
document.body.appendChild(app.view)
const {width,height} = app.renderer.view
console.log(width,height)
PIXI.Loader.shared.onProgress.add(loadProgressHandler)
PIXI.Loader.shared.add("svg",draw.node).load(setup)
function loadProgressHandler(){
console.log('loading')
}
function setup(){
console.log("setup")
const svgCanvas = new PIXI.Sprite(PIXI.Loader.shared.resources.svg.texture)
console.log(svgCanvas)
const container = new PIXI.Container()
container.addChild(svgCanvas)
app.stage.addChild(container)
}
Not getting any errors but also not rendering svg on canvas. Thanks in advance.

Increase svg path size on leaflet

I have a path included in an svg tag, I am looking for a way to double the size of the printing path. Here is my code. It does not work. The path is note showing at all when I add this
pathImageStation: "l0.01,-0.01l-3.72,-3.72l-1.06,1.06l2.11,2.11c-0.94,0.36,-1.61,1.26,-1.61,2.33a2.5,2.5,0,0,0,2.5,2.5c0.36,0,0.69,-0.08,1,-0.21v7.21a1,1,0,0,1,-1,1a1,1,0,0,1,-1,-1v-4.5a2,2,0,0,0,-2,-2h-1v-7a2,2,0,0,0,-2,-2h-6a2,2,0,0,0,-2,2v16h10v-7.5h1.5v5a2.5,2.5,0,0,0,2.5,2.5a2.5,2.5,0,0,0,2.5,-2.5v-9.5c0,-0.69,-0.28,-1.32,-0.73,-1.77m-1.77,2.77a1,1,0,0,1,-1,-1a1,1,0,0,1,1,-1a1,1,0,0,1,1,1a1,1,0,0,1,-1,1m-10,8v-4.5h-2l4,-7.5v5h2l-4,7z",
// "l0.01,-0.01l-3.72,-3.72l-1.06,1.06l2.11,2.11c-0.94,0.36,-1.61,1.26,-1.61,2.33a2.5,2.5,0,0,0,2.5,2.5c0.36,0,0.69,-0.08,1,-0.21v7.21a1,1,0,0,1,-1,1a1,1,0,0,1,-1,-1v-4.5a2,2,0,0,0,-2,-2h-1v-7a2,2,0,0,0,-2,-2h-6a2,2,0,0,0,-2,2v16h10v-7.5h1.5v5a2.5,2.5,0,0,0,2.5,2.5a2.5,2.5,0,0,0,2.5,-2.5v-9.5c0,-0.69,-0.28,-1.32,-0.73,-1.77m-1.77,2.77a1,1,0,0,1,-1,-1a1,1,0,0,1,1,-1a1,1,0,0,1,1,1a1,1,0,0,1,-1,1m-10,8v-4.5h-2l4,-7.5v5h2l-4,7z",
pathImageCity:
"h-2v-2h2m0,6h-2v-2h2m-6,-10h-2v-2h2m0,6h-2v-2h2m0,6h-2v-2h2m0,6h-2v-2h2m-6,-6h-2v-2h2m0,6h-2v-2h2m0,6h-2v-2h2m8,-6v-6l-3,-3l-3,3v2h-6v14h18v-10h-6z",
node_figure
.append("svg")
.attr("width","24px")
.attr("height", "24px" )
.attr("viewBox",(d: Node)=>{
const position = layer_point(vis, d);
return `${position.x} ${position.y} 24 24`
})
.append("path")
.attr("d", (d: Node) => {
const position = layer_point(vis, d);
const path =
find_node_type(d) == "city"
? `M${position.x + 8}, ${position.y - 14} ${
vis.value.pathImageCity
}`
: `M${position.x + 10.25}, ${position.y - 22} ${
vis.value.pathImageStation
}`;
return path;
})
.attr("stroke", (d: Node) =>
find_node_type(d) == "city" ? NODE_CITY_COLOR : NODE_HUB_COLOR
);
The path is too small, I added this to increase the size but is not showing at all.
.attr("width","24px")
.attr("height", "24px" )
.attr("viewBox",(d: Node)=>{
const position = layer_point(vis, d);
return `${position.x} ${position.y} 24 24`
})

How to install Google Font on nodejs-canvas?

I'm trying to get Google Fonts dynamically with nodejs-canvas and I'm not sure what is the best way to do that.
I understand it needs to be with: "registerFont" method.
This is the code I have now:
const { registerFont, createCanvas, loadImage } = require('canvas')
//registerFont('comicsans.ttf', { family: 'Comic Sans' })
const canvas = createCanvas(200, 200)
const ctx = canvas.getContext('2d')
let name = req.query.name;
// Write "Awesome!"
ctx.font = '30px Impact'
ctx.rotate(0.1)
ctx.fillText(name, 50, 100)
// Draw line under text
var text = ctx.measureText(name)
ctx.strokeStyle = 'rgba(0,0,0,0.5)'
ctx.beginPath()
ctx.lineTo(50, 102)
ctx.lineTo(50 + text.width, 102)
ctx.stroke()
//console.log(canvas.toDataURL());
res.write('<img style="border:1px solid #000;" src="' + canvas.toDataURL() + '" />');
res.end();
Using PT Mono as an example this is how you do it.
First go to the font directly in this case PT Mono.
Next click the "download family" link in the top right. That will give you a zip file which contains the TTF font.
Now copy the ttf file into your node application directory and use registerFont in your file like this.
registerFont('PtMono-Regular.ttf', { family: 'PT Mono' });
As per the documentation make sure you register your fonts before creating the canvas.
Then to use your font on your canvas you'd just use the following to set the font for the context.
ctx.font = "16px 'PT Mono'";
Full Example using our google font just modifying the original example in the node-canvs repo.
const { registerFont, createCanvas } = require('canvas');
registerFont('PtMono-Regular.ttf', { family: 'PT Mono' });
const canvas = createCanvas(500, 500);
const ctx = canvas.getContext('2d');
ctx.font = '16px 'PT Mono'"'
ctx.fillText('This is the font.', 250, 10);

Overlaying png onto SVG using Sharp?

I've got a set of png files, that are 4500x5400.
What I am trying to do is the following:
Draw a circle that is 485x485 at 300dpi
Overlay the png inside the circle, so that its resized (scaled)
I've been messing around all night but I'm not getting very far:
I've got the code for my circle:
'<svg height="485" width="485"><circle cx="242.5" cy="242.5" r="242.5" fill="#3a4458"/></svg>'
and then some resize code that resizes my png, and masks it.
sharp(`${toConvert}/${file}`)
.trim()
.resize(485, 485)
.embed()
.overlayWith('overlay.png', { cutout: true } )
.toFile(`./converted/${file}-pop.png`)
.catch(err => {
console.log(err)
})
Does anyone know how I can combine the 2 so I can get a coloured circle with my png in it?
For reference, Sharp is an image processing library: https://github.com/lovell/sharp
This is from my project that has a circle cover pages,
First creating a circle SVG with the help of SVG.js you can use background property to get the colored circle.
const window = require('svgdom');
const SVG = require('svg.js')(window);
const document = window.document;
const ShapeUtil = {
drawCircle: function (width, height) {
return new Promise(function (resolve, reject) {
var circleDraw = SVG(document.documentElement).size(width, height);
circleDraw.clear();
circleDraw.viewbox(0, 0, width, height);
circleDraw.circle(width).cx(Math.abs(width / 2)).cy(Math.abs(width / 2));
resolve(circleDraw.svg());
});
}
};
Then overlay created SVG dom to cropped image with Sharp.io :
ShapeUtil.drawCircle( width, height)
.then(function (resultSVG) {
sharp(croppedImage)
.overlayWith(new Buffer(resultSVG), {cutout: true})
.png().toBuffer();
}
For anyone reading this in 2022, svg.js has changed a bit. I'm using Typescript and this is what I did to get it working (based off of #erhanasikoglu's answer):
const { createSVGWindow } = require('svgdom');
const window = createSVGWindow();
import { registerWindow, SVG } from '#svgdotjs/svg.js';
registerWindow(window, window.document);
export const ShapeUtil = {
drawCircle(width: number, height: number) {
var circleDraw = SVG().size(width, height);
circleDraw.clear();
circleDraw.viewbox(0, 0, width, height);
circleDraw
.circle(width)
.cx(Math.abs(width / 2))
.cy(Math.abs(width / 2));
return circleDraw.svg();
},
};
Note that #svgdotjs/svg.js is now the latest package for svgdotjs. It also has typings which is nice. :D

Pop-Up Window, Center Screen

I'm using the following code to open a pop-up window in a Google Chrome extension, my one question is, how do I get the pop-up window to open in the centre of the users screen?
<script>
chrome.browserAction.onClicked.addListener(function() {
var left = (screen.width/2)-(w/2);
var top = (screen.height/2)-(h/2);
chrome.windows.create({'url': 'redirect.html', 'type': 'popup', 'width': 440, 'height': 220, 'left': '+left+', 'top': '+top+', } , function(window) {
});
});
</script>
I've also tried this, which resulted in no such luck.
<script>
chrome.browserAction.onClicked.addListener(function() {
chrome.windows.create({'url': 'redirect.html', 'type': 'popup', 'width': 440, 'height': 220, 'left': (screen.width/2)-(w/2), 'top': (screen.height/2)-(h/2), } , function(window) {
});
});
</script>
When you see var obj = {property: value} structure in JS it is an object creation. In your code you are trying to pass an object containing window properties to chrome.windows.create() function.
Correct code should be:
chrome.browserAction.onClicked.addListener(function() {
var w = 440;
var h = 220;
var left = (screen.width/2)-(w/2);
var top = (screen.height/2)-(h/2);
chrome.windows.create({'url': 'redirect.html', 'type': 'popup', 'width': w, 'height': h, 'left': left, 'top': top} , function(window) {
});
});
If you want the centering to also work with a dual monitor, you'll need to get the current window object from the extension and center your popup relative to that. Like so:
chrome.browserAction.onClicked.addListener(function() {
chrome.windows.getCurrent(function(win) {
var width = 440;
var height = 220;
var left = ((screen.width / 2) - (width / 2)) + win.left;
var top = ((screen.height / 2) - (height / 2)) + win.top;
chrome.windows.create({
url: 'redirect.html',
width: width,
height: height,
top: Math.round(top),
left: Math.round(left),
type: 'popup'
});
});
});
chrome.windows.create expects an integer for top and left, so it is recommended to wrap those values in Math.round.
If you want the screen to appear in the middle of the browser (not the center of the screen), and dynamic window size, you can do this.
chrome.windows.getCurrent((tabWindow) => {
const width = Math.round(tabWindow.width * 0.5) // dynamic width
const height = Math.round(tabWindow.height * 0.75) // dynamic height
const left = Math.round((tabWindow.width - width) * 0.5 + tabWindow.left)
const top = Math.round((tabWindow.height - height) * 0.5 + tabWindow.top)
chrome.windows.create( // https://developer.chrome.com/docs/extensions/reference/windows/#method-create
{
focused: true,
url: targetURL,
type: 'popup', // https://developer.chrome.com/docs/extensions/reference/windows/#type-WindowType
width, height,
left, top
},
(subWindow) => {}
)
})
Desc
Image
Show you what is the tabWindow.top and left
And you can use some filters to check whether the window has been created or not, to determine to create a new one, or show the window that you made.
chrome.windows.getAll({populate : true, windowTypes:['popup']}, (windowArray)=>{})
example code
chrome.windows.getCurrent((tabWindow) => { // https://developer.chrome.com/docs/extensions/reference/windows/#type-Window
const targetURL = 'yourTemplates/yourFile.html'
chrome.windows.getAll({populate : true, windowTypes:['popup']}, (windowArray)=>{
const queryURL = `chrome-extension://${chrome.runtime.id}/${targetURL}`
const target = windowArray.find(item=>item.tabs[0].url === queryURL) // ❗ make sure manifest.json => permissions including "tabs"
if (windowArray.length > 0 && target !== undefined) {
// Show the window that you made before.
chrome.windows.update(target.id, {focused: true}) // https://developer.chrome.com/docs/extensions/reference/windows/#method-update
return
}
// Otherwise, Create
const width = Math.round(tabWindow.width * 0.5)
const height = Math.round(tabWindow.height * 0.75)
const left = Math.round((tabWindow.width - width) * 0.5 + tabWindow.left)
const top = Math.round((tabWindow.height - height) * 0.5 + tabWindow.top)
chrome.windows.create( // https://developer.chrome.com/docs/extensions/reference/windows/#method-create
{
focused: true,
url: targetURL,
type: 'popup', // https://developer.chrome.com/docs/extensions/reference/windows/#type-WindowType
width, height,
left, top
},
(subWindow) => {
}
)
})
})
As an addendum to this answer, if you want to retrieve popup dimensions from localstorage--which are saved as strings--this will convert the variables to the necessary integers for the pop-up to work.
var w = parseInt(localStorage.getItem('key'));
var h = parseInt(localStorage.getItem('key'));

Resources