Convert SVG to PNG with Arabic characters

204 views Asked by At

I'm creating family tree using d3 and in the front-end I convert my SVG into string

if(document.querySelector('svg')){
        const svgString = new XMLSerializer().serializeToString(document.querySelector('svg'))
        dispatch(convertToPNG(svgString))
    }

and dispatch it to send it to backend, in the back-end I'm using convert-svg-to-png package to convert my SVG to PNG and this is my controller

import {convert} from 'convert-svg-to-png'
const SvgToPng = async(req, res, next) => {
try {
    if(!req.body.svg){
        res.status(400)
        throw new Error('Please upload the required svg file')
    }
    const png = await convert(req.body.svg,{
        puppeteer:{args: ['--no-sandbox'] }
    });
    res.set('Content-Type', 'image/png');
    res.send({png});
} catch (error) {
    next(error)
}

then back to the front-end I received the buffer and convert it to png

        const source = bufferToPng(pngData.data)
        const hiddenElement = document.createElement('a');
              hiddenElement.href = source;
              hiddenElement.download = 'family-tree.png'
              hiddenElement.click();

function buffertoPng(buffer){
    const arrayBufferView = new Uint8Array(buffer)
    const blob = new Blob([ arrayBufferView ], { type: 'image/png' })
    const urlCreator = window.URL || window.webkitURL
    const imageUrl = urlCreator.createObjectURL(blob)
    return imageUrl
  }

My issue is when I try to convert small SVG with Arabic names the library doing great job but when it comes to large SVG full of Arabic names I start to see weird characters instead of Arabic ones, instead of seeing Arabic name like that (أحمد) I see that (Ù�اطمة)

Note: the package use another package call puppeteer

And this is my d3 code

import {useEffect, useRef} from 'react'
import * as d3 from 'd3'
import { useHistory} from 'react-router'

const Tree = ({familyData,isProfile, isPoint}) => {
    const wrapperRef = useRef(null)
    const history = useHistory()
    const radialPoint = (x, y) => {
        return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
    }
    const genColors = [
            '#000','#7d1e01', '#2c3e50',
            '#80016e','#673ab7',
            '#ff9800','#795548','#142796',
            '#61b301','#19a0b1','#a0342c',
            '#CEE397','#F5A25D','#625261',
            '#87556F','#E5EDB7','#231E23',
            '#6F0000','#FFF0F5','#FFEBD9',
            '#BEEBE9','#B0A160','#E4F9FF',
        ]
    
    const createTree = () => {
        wrapperRef.current.innerHTML = '';
        const margin = {top: 140, right: 0, bottom: 20, left: 0}
        wrapperRef.current.width = wrapperRef.current.getBoundingClientRect().width
        wrapperRef.current.height = wrapperRef.current.getBoundingClientRect().height
        const innerHeight = 2000 - margin.top - margin.bottom;
        const svg = d3.select(wrapperRef.current)
                    .append('svg')
                    .attr('width', 4000)
                    .attr('height', 2200)
                    .attr('encoding', 'UTF-8')
                    
        const width = +svg.attr("width")
        const height = +svg.attr("height")
        const g = svg.append("g")
        .attr("transform", `translate(${(width/2+ 40)},${(height - 30)})`);
        
        const dataStructure = d3.stratify().id(d => d._id).parentId(d => d.parentId)(familyData)
        const treeLayout = d3.tree().size([(1 * Math.PI), innerHeight])
        const root = treeLayout(dataStructure)
        
        
        g.selectAll(".link")
            .data(root.links())
            .enter().append("path")
            .attr('fill', 'none')
            .attr('stroke', '#555')
            .attr('stroke-opacity', '0.4')
            .attr('stroke-width', (d) => {
                if(d.target.depth <= 9){
                    return 8 - d.target.depth
                } else {
                    return 1
                }
            })
            .attr("d", d3.linkRadial()
            .angle(d => d.x-Math.PI/2)
            .radius(d => d.y + 10))
            .attr('id',  d => 'link_' + d.target.data._id)
            .attr('stroke-dasharray', function(){
                const length = this.getTotalLength()
                return `${length} ${length}`
            })
            .attr('stroke-dashoffset', function(){
                const length = this.getTotalLength()
                return length
            })
            .transition()
            .duration(1500)
            .delay(d => d.source.depth * 500)
            .attr('stroke-dashoffset', 0)

        const node = g.selectAll(".node")
            .data(root.descendants())
            .enter().append("g")
            .attr("class", d => d.children ? " node--internal" : " node--leaf")
            .attr('transform', d =>  "translate(" + radialPoint(d.x-Math.PI/2, (d.y+10)) + ")");
            node.append("circle")
            .attr("r", d => !d.children && d.depth === 1 ? 3 : 7 - (d.depth))
            .style("stroke","white")
             .style("fill", d => {   
                if(d.children){
                    return genColors[d.depth]
                } else {
                    return '#04a21a'
                }
            })
            .attr('opacity', 0)
            .transition()
            .duration(1500)
            .delay(d => d.depth * 500)
            .attr('opacity', 1)
        
        node.append("image")
            .attr("xlink:href", d => !d.children ? "/image/leaf.png":"")
            .attr('x', d => d.x > Math.PI/2 ? '-5px': '-10px')
            .attr('y', d => !d.children && d.depth === 1 ? '-55px':"-90px")
            .attr("transform", d => `rotate(${(d.x > Math.PI/2 ? d.x-Math.PI/2  : d.x-Math.PI/2 ) * 180 / Math.PI})`)
            .attr('width', d => d.depth === 1 ? '1rem' : '1.5rem')
            .attr('opacity', 0)
            .transition()
            .duration(1500)
            .delay(d => d.depth * 500)
            .attr('opacity', 1)
            
        node.append("text")
            .attr("dy","0.32em")
            .attr('y', (d) => d.depth === 1 ? -1: 2)
            .attr("x", (d) => {
                if(!d.children){
                    if(d.depth === 1){
                        return 20
                    }
                    if(d.x > Math.PI/2){
                        return 30
                    }else {
                        return -40
                    }
                }else {
                    return -80
                }
            })
            // eslint-disable-next-line
            .attr("text-anchor", d => d.x > Math.PI/2 === !d.children ? "middle" : "end")
            .attr("transform", d => d.depth > 1 ? `rotate(${(d.x > Math.PI/2 ? d.x-Math.PI/2 - Math.PI / 2 : d.x-Math.PI/2 + Math.PI / 2) * 180 / Math.PI})` : '')
            .style("font-size", d => {
                return d.children  ?  2 - (d.depth * 2) /10 + 'em' 
                :d.depth === 1 ?'0.37em' : d.depth === 2 ? '0.6em' :'0.85em'
            })
            .text(d => d.data.firstName)
            .attr('id',  d => 'name_' + d.data._id)
            .style('cursor', 'pointer')
            .style(' z-index', '9999999')
            .attr('fill', (d) =>{
                if(d.children){
                    return genColors[d.depth]
                } else {
                    return '#04a21a'
                }
            })
            .on('mouseover', (e, d) => {
                if(isPoint){
                    d3.selectAll('path').style('stroke', '#2c3e50').style('opacity', 0.2)
                    d3.selectAll('text').style('fill', '#2c3e50').style('opacity', 0.2)
                    d3.selectAll('circle').style('fill', '#2c3e50').style('opacity', 0.2)
                    d3.selectAll('image').style('opacity', '0.1')
                    while(d){
                        if(!d.data.parentId ) {
                            d3.select(`#name_${d.data._id}`).style('fill','#000').style('opacity','1')
                        }
                        if(d.data.parentId !== null){
                            d3.select(`#link_${d.data._id}`).style('stroke','#b70202').style('opacity','1')
                            d3.select(`#name_${d.data._id}`).attr('fill','#000').style('opacity','1')
                            .style('font-size', '3rem')
                            .transition()
                            .duration(500)
                            .style('font-size', '5rem');
                        }
                        d = d.parent
                    }
                }    
            })
            .on('mouseout', () => {
                if(isPoint){
                    d3.selectAll('path').style('stroke', '#555').style('opacity','0.4')
                d3.selectAll('text').style('fill', (d) => {
                    if(d.children){
                        return genColors[d.depth]
                    } else {
                        return '#04a21a'
                    }
                }).style('font-size', d => {
                    return d.children  ?  2 - (d.depth * 2) /10 + 'em' 
                    :d.depth === 1 ?'0.4em' :'0.5em'
                }).style('opacity','1')
                d3.selectAll('image').style('opacity', '1')
                d3.selectAll('circle').style('fill', (d) => {
                    if(d.children){
                        return genColors[d.depth]
                    } else {
                        return '#04a21a'
                    }
                }).style('opacity','1')
                }      
        })
        .on('click', (e, d) => {
            if(!isProfile){
                history.push(`/info/${d.data._id}`)
            } 
        })
        .attr('opacity', 0)
            .transition()
            .duration(1500)
            .delay(d => d.depth * 600)
            .attr('opacity', 1)
        
       
    }
    useEffect(() => {
       createTree()   
    })
   return (
        <div className="tree__wrapper" ref={wrapperRef}></div>
    )
}

export default Tree
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

and this is a portion of svg generated by d3

 <svg xmlns="http://www.w3.org/2000/svg" width="4000" height="2200" encoding="UTF-8"><g transform="translate(2040,2170)"><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,-222.4503319176191,-90.08801157613091,-435.6319000053374,-176.42235600325637" id="link_60f2c8e7131cad0723062518" stroke-dasharray="540.5701293945312 540.5701293945312" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,-127.22830174504648,-203.50174258485208,-249.155424250716,-398.52424589533535" id="link_60f2c8e7131cad0723062519" stroke-dasharray="490.6072692871094 490.6072692871094" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,127.2283017450465,-203.50174258485208,249.15542425071607,-398.52424589533535" id="link_60f2c8e7131cad072306251a" stroke-dasharray="470.35516357421875 470.35516357421875" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,238.08207476904383,-30.280780598713555,466.2440630893775,-59.29986200581405" id="link_60f2c8e7131cad072306251b" stroke-dasharray="535.5938720703125 535.5938720703125" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,238.38806663242283,-27.7692219418467,466.84329715516134,-54.381392969449784" id="link_60f2c8e7131cad072306251c" stroke-dasharray="536.4296875 536.4296875" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,238.66756448516003,-25.254577064800976,467.39064711677173,-49.45688008523525" id="link_60f2c8e7131cad072306251d" stroke-dasharray="537.2632446289062 537.2632446289062" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,238.92053726437896,-22.73712544057634,467.88605214274213,-44.526870654462" id="link_60f2c8e7131cad072306251e" stroke-dasharray="538.0941772460938 538.0941772460938" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,239.14695685515082,-20.21714685410944,468.3294571746704,-39.59191258929766" id="link_60f2c8e7131cad072306251f" stroke-dasharray="538.92236328125 538.92236328125" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,239.34679809361936,-17.694921371178975,468.7208129333379,-34.652554351892164" id="link_60f2c8e7131cad0723062520" stroke-dasharray="539.747802734375 539.747802734375" stroke-dashoffset="0"/>

0

There are 0 answers