This working CodePen demo shows a number which is represented as a vector.
May I ask how to convert the number vector (SVG Format) as shown in demo to a real number?
From my search on the web, Tesseract do not read vector graphics (SVG), can anyone show some demo or samples of how to read number vector?
For Example, this vector graphics code below should be read as a number 9 in the console.log
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1066.6667 800"
height="800"
width="1066.6667"
xml:space="preserve"
id="svg2"
version="1.1"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs6" /><g
transform="matrix(1.3333333,0,0,-1.3333333,0,800)"
id="g10"><path
id="path20"
style="fill:none;stroke:#000000;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 294.09,426.05 -0.46,-1.38 -0.92,-0.92 -1.38,-0.46 h -0.46 l -1.37,0.46 -0.92,0.92 -0.46,1.38 v 0.46 l 0.46,1.37 0.92,0.92 1.37,0.46 h 0.46 l 1.38,-0.46 0.92,-0.92 0.46,-1.83 v -2.3 l -0.46,-2.29 -0.92,-1.38 -1.38,-0.46 h -0.91 l -1.38,0.46 -0.46,0.92" /></g></svg>
As already commented, you need to convert the svg to a raster image for OCR.
Fortunately we have a lot of options to create a bitmap temporarily using <canvas>.
Apart from the fact, tesseract's OCR might struggle with different shapes, low contrasts – you might not get the desired result, if your svg contains many shapes.
Single Glyph
svg2PngAndOCR("svg");
function svg2PngAndOCR(selector) {
const svgEl = document.querySelector(selector);
let viewBox = svgEl.viewBox.baseVal;
let svgBB = svgEl.getBBox();
let svgW = viewBox.width ? viewBox.width : svgBB.width;
let svgH = viewBox.height ? viewBox.height : svgBB.height;
/**
* convert svg to png via canvas
*/
let blob = new Blob([svgEl.outerHTML], { type: "image/svg+xml" });
let URL = window.URL;
let blobURL = URL.createObjectURL(blob);
let tmpImg = new Image();
tmpImg.src = blobURL;
tmpImg.width = svgW;
tmpImg.height = svgH;
tmpImg.onload = () => {
let canvas = document.createElement("canvas");
canvas.width = svgW;
canvas.height = svgH;
let context = canvas.getContext("2d");
context.drawImage(tmpImg, 0, 0, svgW, svgH);
let pngDataUrl = canvas.toDataURL();
let svgImg = document.createElement("img");
svgImg.width = svgW;
svgImg.height = svgH;
svgImg.class = "svgImg";
svgImg.src = pngDataUrl;
// just additional wrapping for example usage
let imgWrp = document.querySelector('.img-wrp');
if(!imgWrp){
imgWrp = document.createElement("div");
imgWrp.setAttribute("class", "img-wrp img-wrp-vanilla");
imgWrp.appendChild(svgImg);
document.body.appendChild(imgWrp);
}
/**
* OCR: recognize text
* via tesseract
*/
Tesseract.recognize(svgImg, "eng", {
//logger: (m) => console.log(m)
}).then(({ data: { text } }) => {
ocrOutput.textContent = text;
});
};
}
svg,
.img-wrp
{
border:1px solid #ccc;
width:40%;
height:auto;
display:inline-block;
}
img{
max-width:100%;
height:auto;
}
.hidden{
display:none
}
<script src='https://unpkg.com/tesseract.js#4.0.2/dist/tesseract.min.js'></script>
<p>OCR Output: <span id="ocrOutput"></span></p>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1066.6667 800" height="800" width="1066.6667" xml:space="preserve" id="svg2" version="1.1">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs6" />
<g transform="matrix(1.3333333,0,0,-1.3333333,0,800)" id="g10">
<path id="path20" style="fill:none;stroke:#000000;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" d="m 294.09,426.05 -0.46,-1.38 -0.92,-0.92 -1.38,-0.46 h -0.46 l -1.37,0.46 -0.92,0.92 -0.46,1.38 v 0.46 l 0.46,1.37 0.92,0.92 1.37,0.46 h 0.46 l 1.38,-0.46 0.92,-0.92 0.46,-1.83 v -2.3 l -0.46,-2.29 -0.92,-1.38 -1.38,-0.46 h -0.91 l -1.38,0.46 -0.46,0.92" />
</g>
</svg>
Multiple elements
svg2PngAndOCR("svg");
function svg2PngAndOCR(selector) {
const svgEl = document.querySelector(selector);
let viewBox = svgEl.viewBox.baseVal;
let svgBB = svgEl.getBBox();
let svgW = viewBox.width ? viewBox.width : svgBB.width;
let svgH = viewBox.height ? viewBox.height : svgBB.height;
/**
* convert svg to png via canvas
*/
let blob = new Blob([svgEl.outerHTML], { type: "image/svg+xml" });
let URL = window.URL;
let blobURL = URL.createObjectURL(blob);
let tmpImg = new Image();
tmpImg.src = blobURL;
tmpImg.width = svgW;
tmpImg.height = svgH;
tmpImg.onload = () => {
let canvas = document.createElement("canvas");
canvas.width = svgW;
canvas.height = svgH;
let context = canvas.getContext("2d");
context.drawImage(tmpImg, 0, 0, svgW, svgH);
let pngDataUrl = canvas.toDataURL();
let svgImg = document.createElement("img");
svgImg.width = svgW;
svgImg.height = svgH;
svgImg.class = "svgImg";
svgImg.src = pngDataUrl;
// just additional wrapping for example usage
let imgWrp = document.querySelector('.img-wrp');
if(!imgWrp){
imgWrp = document.createElement("div");
imgWrp.setAttribute("class", "img-wrp img-wrp-vanilla");
imgWrp.appendChild(svgImg);
document.body.appendChild(imgWrp);
}
/**
* OCR: recognize text
* via tesseract
*/
Tesseract.recognize(svgImg, "eng", {
//logger: (m) => console.log(m)
}).then(({ data: { text } }) => {
ocrOutput.textContent = text;
});
};
}
svg,
.img-wrp
{
border:1px solid #ccc;
width:40%;
height:auto;
display:inline-block;
}
img{
max-width:100%;
height:auto;
}
.hidden{
display:none
}
<script src='https://unpkg.com/tesseract.js#4.0.2/dist/tesseract.min.js'></script>
<p>OCR Output: <span id="ocrOutput"></span></p>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1066.6667 800" height="800" width="1066.6667" xml:space="preserve" id="svg2" version="1.1">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs6" />
<g transform="matrix(1.3333333,0,0,-1.3333333,0,800)" id="g10">
<path id="path20" style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" d="m 294.09,426.05 -0.46,-1.38 -0.92,-0.92 -1.38,-0.46 h -0.46 l -1.37,0.46 -0.92,0.92 -0.46,1.38 v 0.46 l 0.46,1.37 0.92,0.92 1.37,0.46 h 0.46 l 1.38,-0.46 0.92,-0.92 0.46,-1.83 v -2.3 l -0.46,-2.29 -0.92,-1.38 -1.38,-0.46 h -0.91 l -1.38,0.46 -0.46,0.92" />
</g>
<path id="hello" transform="scale(1)" class="glyph" d="M4.8 200.5h-0.9v-3.3h-3v3.3h-0.9v-6.9h0.9v2.8h3v-2.8h0.9v6.9zm5.9-2.8q0 0.2 0 0.4l0 0h-3.4q0.1 0.9 0.5 1.3t1 0.4l0 0q0.4 0 0.7-0.1t0.6-0.3l0 0l0.4 0.5q-0.8 0.7-1.8 0.7l0 0q-1.1 0-1.7-0.7q-0.6-0.8-0.6-2l0 0q0-0.8 0.2-1.5q0.3-0.6 0.8-1q0.5-0.3 1.2-0.3l0 0q1 0 1.6 0.7q0.5 0.6 0.5 1.9l0 0zm-0.9-0.2v-0.1q0-0.8-0.3-1.2t-0.9-0.4l0 0q-1.1 0-1.3 1.7l0 0h2.5zm3.5 3.1q-0.6 0-0.9-0.3q-0.2-0.3-0.2-0.9l0 0v-6.3l0.9-0.1v6.4q0 0.2 0 0.3q0.1 0.1 0.3 0.1l0 0q0.2 0 0.3 0l0 0l0.3 0.6q-0.3 0.2-0.7 0.2l0 0zm2.9 0q-0.5 0-0.8-0.3t-0.3-0.9l0 0v-6.3l0.9-0.1v6.4q0 0.2 0.1 0.3q0 0.1 0.2 0.1l0 0q0.2 0 0.4 0l0 0l0.2 0.6q-0.3 0.2-0.7 0.2l0 0zm3.8-5.5q1.1 0 1.8 0.7q0.6 0.7 0.6 2l0 0q0 0.8-0.3 1.5q-0.3 0.6-0.8 0.9q-0.5 0.4-1.3 0.4l0 0q-1.1 0-1.7-0.8q-0.6-0.7-0.6-2l0 0q0-0.8 0.3-1.4q0.2-0.6 0.8-1q0.5-0.3 1.2-0.3l0 0zm0 0.7q-1.3 0-1.3 2l0 0q0 2 1.3 2l0 0q1.4 0 1.4-2l0 0q0-2-1.4-2l0 0zm12.7-2.2h0.9l-1.4 6.9h-1.2l-1.3-5.8l-1.3 5.8h-1.2l-1.4-6.9h1l1.1 6l1.3-6h1l1.4 6l1.1-6zm4.1 1.5q1.1 0 1.7 0.7t0.6 2l0 0q0 0.8-0.2 1.5q-0.3 0.6-0.9 0.9q-0.5 0.4-1.2 0.4l0 0q-1.1 0-1.8-0.8q-0.6-0.7-0.6-2l0 0q0-0.8 0.3-1.4t0.8-1q0.5-0.3 1.3-0.3l0 0zm0 0.7q-1.4 0-1.4 2l0 0q0 2 1.4 2l0 0q1.3 0 1.3-2l0 0q0-2-1.3-2l0 0zm6.1-0.7q0.3 0 0.6 0l0 0l-0.2 0.9q-0.2 0-0.5 0l0 0q-0.5 0-0.8 0.3q-0.3 0.4-0.4 1.1l0 0v3.1h-0.9v-5.3h0.7l0.1 1.1q0.2-0.6 0.6-0.9t0.8-0.3l0 0zm2.7 5.5q-0.5 0-0.8-0.3t-0.3-0.9l0 0v-6.3l0.9-0.1v6.4q0 0.2 0.1 0.3q0 0.1 0.2 0.1l0 0q0.2 0 0.4 0l0 0l0.2 0.6q-0.3 0.2-0.7 0.2l0 0zm5-7.6l0.9 0.1v7.4h-0.8l-0.1-0.8q-0.2 0.4-0.6 0.6q-0.4 0.3-0.9 0.3l0 0q-0.9 0-1.5-0.8q-0.5-0.7-0.5-2l0 0q0-0.8 0.3-1.4q0.2-0.6 0.7-1q0.5-0.3 1.1-0.3l0 0q0.8 0 1.4 0.6l0 0v-2.7zm-1.3 6.8q0.4 0 0.7-0.2q0.3-0.1 0.6-0.5l0 0v-2.6q-0.3-0.4-0.6-0.5q-0.2-0.2-0.6-0.2l0 0q-0.6 0-1 0.5q-0.3 0.5-0.3 1.5l0 0q0 1 0.3 1.5t0.9 0.5l0 0z " />
<use href="#hello" x="200" transform="scale(2)"/>
</svg>
Here's a rough cut but keep in mind that OCR is not always 100% accurate, and the results will be poor if the svg is too abstract.
// Convert to a base64 encoded image
const inputSvg = '<svg><path fill="black" d="m 294.09,426.05 -0.46,-1.38 -0.92,-0.92 -1.38,-0.46 h -0.46 l -1.37,0.46 -0.92,0.92 -0.46,1.38 v 0.46 l 0.46,1.37 0.92,0.92 1.37,0.46 h 0.46 l 1.38,-0.46 0.92,-0.92 0.46,-1.83 v -2.3 l -0.46,-2.29 -0.92,-1.38 -1.38,-0.46 h -0.91 l -1.38,0.46 -0.46,0.92" /></svg>';
const inputBase64Image = Buffer.from(inputSvg).toString('base64');
// Convert the encoded image to a buffer
const imageBuffer = Buffer.from(inputBase64Image, 'base64');
// Convert the buffer to a png
sharp(imageBuffer)
.png()
.toBuffer()
.then(buffer => {
console.log('SVG converted to PNG buffer');
// Configure the recognition process
const config = {
lang: 'eng',
oem: 1,
psm: 3,
};
// Recognise the character
tesseract.recognize(buffer, config)
.then(character => {
console.log("What is 6 + 3?", character);
})
.catch(error => {
console.error(error);
});
})
.catch(error => {
console.error(error);
});
You can use a machine learning or computer vision library such as TensorFlow.js or OpenCV. These libraries allow you to train a model using labelled examples of characters and then use the model to classify new, unseen characters based on their visual appearance.
Here's a high-level overview of the process:
Prepare a dataset of labelled examples of characters in the form of SVGs.
Train a machine learning model, such as a Convolutional Neural Network (CNN), using the labelled examples.
Use the trained model to classify new, unseen SVGs by running them through the model and determining the most likely character based on the model's output.
Keep in mind that this is a complex task and requires a solid understanding of machine learning and computer vision. If you are new to these fields, you may want to start with some tutorials or courses to familiarize yourself with the concepts before attempting to build a character recognition system.
https://www.tensorflow.org/js
https://opencv.org
I am trying to implement the Gauss Formula Visually so I am having a little bit of problems with JavaScript since I never worked with it before. My question would be how do I color certain squares of my grid
{
const w = width, h = w * .6, mx = w/2 * margin, my = h/2 * margin;
const s = DOM.svg(w, h);
const path = `M${mx},${my}${gridPath(count+1, count, w - mx * 2, h - my * 2, false)}`;
s.appendChild(svg`<path stroke=#555 d="${path}">`);
s[0].sty
return s;
}
and this is the function that computes the grid
function gridPath(cols, rows, width = 1, height = 1, initPosition = true) {
// Line distances.
const sx = width / cols, sy = height / rows;
// Horizontal and vertical path segments, joined by relative move commands.
const px = Array(rows+1).fill(`h${width}`).join(`m${-width},${sy}`);
const py = Array(cols+1).fill(`v${height}`).join(`m${sx},${-height}`);
// Paths require an initial move command. It can be set either by this function
// or appended to the returned path.
return `${initPosition ? 'M0,0' : ''}${px}m${-width}${-height}${py}`;
}
You need to create solid rectangles that could be filled.
Your script generates an array of lines – so you don't get any surface area.
Besides, all lines are currently defined as concatenated commands in a single <path> element.
That's great for optimizing filesize – but you won't be able to select individual sub paths (grid lines).
Example 1: Svg markup for a 3x3 grid changing fill color on hover
svg {
display: block;
overflow: visible;
width: 75vmin
}
path {
fill: #fff;
stroke-width: 1;
stroke: #000
}
path:hover {
fill: red
}
<svg viewBox="0 0 90 90">
<!-- 1. row -->
<path d="M 0 0 h30 v30 h-30z" />
<path d="M 30 0 h30 v30 h-30z" />
<path d="M 60 0 h30 v30 h-30z" />
<!-- 2. row -->
<path d="M 0 30 h30 v30 h-30z" />
<path d="M 30 30 h30 v30 h-30z" />
<path d="M 60 30 h30 v30 h-30z" />
<!-- 3. row -->
<path d="M 0 60 h30 v30 h-30z" />
<path d="M 30 60 h30 v30 h-30z" />
<path d="M 60 60 h30 v30 h-30z" />
</svg>
Example 2: Create grid svg dynamically. Set fill colors by class
let svg = document.querySelector('svg');
// generate grid svg
let gridSVG1 = gridPaths(4, 4, 100, 100, 0.25, '#ccc', true);
// highlight columns
highlightColumns(gridSVG1, ['1-2', '2-3', '3-4']);
function highlightColumns(svg, col_array) {
col_array.forEach(function(colIndex, i) {
// select
let currentCol = svg.querySelector('.col-' + colIndex);
currentCol.classList.add('highlight');
})
}
function gridPaths(cols, rows, width = 1, height = 1, strokeWidth = 1, strokeColor = '#000', render = false) {
let gridSvg = `<g fill="#fff" stroke-width="${strokeWidth}" stroke="${strokeColor}">\n`;
let offsetX = width / cols;
let offsetY = height / rows;
let currentRow = 1;
let currentCol = 1;
// add initial y/x offset according to stroke width to avoid cropped outer strokes
let shiftX = 0;
let shiftY = 0;
let cellCount = cols * rows;
for (let i = 0; i < cellCount; i++) {
// if current index is divisible by columns – move to next row
if (i > 0 && i % (cols) === 0) {
shiftX = 0;
shiftY += offsetY;
currentCol = 1;
currentRow++;
}
// add classnames for rows and columns to make each cell selectable
let className = `col row-${currentRow} col-${currentCol} col-${currentRow}-${currentCol}`;
// add new cell to output
gridSvg += `<path class="${className}" d="M ${shiftX} ${shiftY} h${offsetX} v${offsetY} h${-offsetX}z"/>\n`;
// increment x offset for next column
shiftX += offsetX;
currentCol++;
}
//close group
gridSvg += '\n</g>';
// create new svg
let nameSpace = 'http://www.w3.org/2000/svg';
let xlink = 'http://www.w3.org/1999/xlink';
let gridSvgEl = document.createElementNS(nameSpace, 'svg');
gridSvgEl.setAttribute('xmlns', nameSpace);
gridSvgEl.setAttribute('xmlns:xlink', xlink);
gridSvgEl.setAttribute('overflow', 'visible');
// add stroke width to viewBox to avoid cropped outer strokes
gridSvgEl.setAttribute('viewBox', [0, 0, width, height].join(' '));
gridSvgEl.innerHTML = gridSvg;
// optional: render grid
if (render) {
document.body.appendChild(gridSvgEl);
}
return gridSvgEl;
}
svg {
display: block;
overflow: visible;
width: 75vmin
}
.row-3 {
fill: #eee
}
.col-2 {
fill: #fee
}
.col:hover,
.highlight {
fill: lime
}
The gridPaths() function loops through the total amount of columns/cells. It also adds class names according to the current row and column index.
This way you can select individual columns and rows in css or js.
All paths are grouped in a <g> element for styling – you could also style each column in css alone.
How do you apply path rotation to graphic elements such as a rectangle or a path?
For example, applying rotation to a path:
<svg width="200px" height="200px" viewbox="0 0 200 200">
<rect x="100" y="0" width="20" height="20" fill="red" transform="rotate(45)"/>
</svg>
IMPORTANT NOTE:
I'm not running an browser. I remember seeing solutions that use browser or canvas to do the calculations.
I only have the markup. For a path I have the path data, for a rectangle the position and width and height, and the line the x1 y1 and x2 y2 data.
UPDATE:
It's important to know the transform origin. That would be rotated from the element center.
I would use an array of points to draw the path. For the rotation I would rotate the points and draw the rotated shape.Please read the comments in my code. I hope this is what you were asking.
const SVG_NS = svg.namespaceURI;
// the rotation
let angle = Math.PI/4;
// the points used to rotate the initial rect
let theRect = [
{ x: 100, y: 0 },
{ x: 100 + 20, y: 0 },
{ x: 100 + 20, y: 0 + 20 },
{ x: 100, y: 0 + 20 }
];
// calculating the rotated points
let rotatedRect = [];
theRect.forEach(p => {
rotatedRect.push(rotatePoint(p, angle));
});
drawRect(theRect);
drawRect(rotatedRect);
// a function to draw the rect. For this I'm using the points array
function drawRect(ry) {
let d = `M${ry[0].x},${ry[0].y}L${ry[1].x},${ry[1].y} ${ry[2].x},${ry[2].y} ${
ry[3].x
},${ry[3].y}`;
drawSVGelmt({ d: d }, "path", svg);
}
// a function used to rotate a point around the origin {0,0}
function rotatePoint(p, rot) {
let cos = Math.cos(rot);
let sin = Math.sin(rot);
return {
x: p.x * cos - p.y * sin,
y: p.x * sin + p.y * cos
};
}
// a function to draw an svg element
function drawSVGelmt(o, tag, parent) {
let elmt = document.createElementNS(SVG_NS, tag);
for (let name in o) {
if (o.hasOwnProperty(name)) {
elmt.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(elmt);
return elmt;
}
svg{border:1px solid; max-width:90vh;}
<svg id="svg" viewbox="0 0 200 200">
<rect x="100" y="0" width="20" height="20" fill="red" transform="rotate(45)"/>
</svg>
I have the following SVG:
<svg>
<g>
<path id="k9ffd8001" d="M64.5 45.5 82.5 45.5 82.5 64.5 64.5 64.5 z" stroke="#808600" stroke-width="0" transform="rotate(0 0 0)" stroke-linecap="square" stroke-linejoin="round" fill-opacity="1" stroke-opacity="1" fill="#a0a700"></path>
<path id="kb8000001" d="M64.5 45.5 82.5 45.5 82.5 64.5 64.5 64.5 z" stroke="#808600" stroke-width="0" transform="rotate(0 0 0)" stroke-linecap="square" stroke-linejoin="round" fill-opacity="1" stroke-opacity="1" fill="url(#k9ffb0001)"></path>
</g>
</svg>
I want to get a CSS-like border-top-right-radius and border-top-bottom-radius effect.
How can I achieve that rounded corner effect?
Here is how you can create a rounded rectangle with SVG Path:
<path d="M100,100 h200 a20,20 0 0 1 20,20 v200 a20,20 0 0 1 -20,20 h-200 a20,20 0 0 1 -20,-20 v-200 a20,20 0 0 1 20,-20 z" />
Explanation
m100,100: move to point(100,100)
h200: draw a 200px horizontal line from where we are
a20,20 0 0 1 20,20: draw an arc with 20px X radius, 20px Y radius, clockwise, to a point with 20px difference in X and Y axis
v200: draw a 200px vertical line from where we are
a20,20 0 0 1 -20,20: draw an arc with 20px X and Y radius, clockwise, to a point with -20px difference in X and 20px difference in Y axis
h-200: draw a -200px horizontal line from where we are
a20,20 0 0 1 -20,-20: draw an arc with 20px X and Y radius, clockwise, to a point with -20px difference in X and -20px difference in Y axis
v-200: draw a -200px vertical line from where we are
a20,20 0 0 1 20,-20: draw an arc with 20px X and Y radius, clockwise, to a point with 20px difference in X and -20px difference in Y axis
z: close the path
<svg width="440" height="440">
<path d="M100,100 h200 a20,20 0 0 1 20,20 v200 a20,20 0 0 1 -20,20 h-200 a20,20 0 0 1 -20,-20 v-200 a20,20 0 0 1 20,-20 z" fill="none" stroke="black" stroke-width="3" />
</svg>
Not sure why nobody posted an actual SVG answer. Here is an SVG rectangle with rounded corners (radius 3) on the top:
<path d="M0,0 L0,27 A3,3 0 0,0 3,30 L7,30 A3,3 0 0,0 10,27 L10,0 Z" />
This is a Move To (M), Line To (L), Arc To (A), Line To (L), Arc To (A), Line To (L), Close Path (Z).
The comma-delimited numbers are absolute coordinates. The arcs are defined with additional parameters specifying the radius and type of arc. This could also be accomplished with relative coordinates (use lower-case letters for L and A).
The complete reference for those commands is on the W3C SVG Paths page, and additional reference material on SVG paths can be found in this article.
As referenced in my answer to Applying rounded corners to paths/polygons, I have written a routine in javascript for generically rounding corners of SVG paths, with examples, here: http://plnkr.co/edit/kGnGGyoOCKil02k04snu.
It will work independently from any stroke effects you may have. To use, include the rounding.js file from the Plnkr and call the function like so:
roundPathCorners(pathString, radius, useFractionalRadius)
The result will be the rounded path.
The results look like this:
You have explicitly set your stroke-linejoin to round but your stroke-width to 0, so of course you're not going to see rounded corners if you have no stroke to round.
Here's a modified example with rounded corners made through strokes:
http://jsfiddle.net/8uxqK/1/
<path d="M64.5 45.5 82.5 45.5 82.5 64.5 64.5 64.5 z"
stroke-width="5"
stroke-linejoin="round"
stroke="#808600"
fill="#a0a700" />
Otherwise—if you need an actual rounded shape fill and not just a rounded fatty stroke—you must do what #Jlange says and make an actual rounded shape.
I'd also consider using a plain old <rect> which provides the rx and ry attributes
MDN SVG docs <- note the second drawn rect element
I've happened upon this problem today myself and managed to solve it by writing a small JavaScript function.
From what I can tell, there is no easy way to give a path element in an SVG rounded corners except if you only need the borders to be rounded, in which case the (CSS) attributes stroke, stroke-width and most importantly stroke-linejoin="round" are perfectly sufficient.
However, in my case I used a path object to create custom shapes with n corners that are filled out with a certain color and don't have visible borders, much like this:
I managed to write a quick function that takes an array of coordinates for an SVG path and returns the finished path string to put in the d attribute of the path html element. The resulting shape will then look something like this:
Here is the function:
/**
* Creates a coordinate path for the Path SVG element with rounded corners
* #param pathCoords - An array of coordinates in the form [{x: Number, y: Number}, ...]
*/
function createRoundedPathString(pathCoords) {
const path = [];
const curveRadius = 3;
// Reset indexes, so there are no gaps
pathCoords = pathCoords.slice();
for (let i = 0; i < pathCoords.length; i++) {
// 1. Get current coord and the next two (startpoint, cornerpoint, endpoint) to calculate rounded curve
const c2Index = ((i + 1) > pathCoords.length - 1) ? (i + 1) % pathCoords.length : i + 1;
const c3Index = ((i + 2) > pathCoords.length - 1) ? (i + 2) % pathCoords.length : i + 2;
const c1 = pathCoords[i];
const c2 = pathCoords[c2Index];
const c3 = pathCoords[c3Index];
// 2. For each 3 coords, enter two new path commands: Line to start of curve, bezier curve around corner.
// Calculate curvePoint c1 -> c2
const c1c2Distance = Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2));
const c1c2DistanceRatio = (c1c2Distance - curveRadius) / c1c2Distance;
const c1c2CurvePoint = [
((1 - c1c2DistanceRatio) * c1.x + c1c2DistanceRatio * c2.x).toFixed(1),
((1 - c1c2DistanceRatio) * c1.y + c1c2DistanceRatio * c2.y).toFixed(1)
];
// Calculate curvePoint c2 -> c3
const c2c3Distance = Math.sqrt(Math.pow(c2.x - c3.x, 2) + Math.pow(c2.y - c3.y, 2));
const c2c3DistanceRatio = curveRadius / c2c3Distance;
const c2c3CurvePoint = [
((1 - c2c3DistanceRatio) * c2.x + c2c3DistanceRatio * c3.x).toFixed(1),
((1 - c2c3DistanceRatio) * c2.y + c2c3DistanceRatio * c3.y).toFixed(1)
];
// If at last coord of polygon, also save that as starting point
if (i === pathCoords.length - 1) {
path.unshift('M' + c2c3CurvePoint.join(','));
}
// Line to start of curve (L endcoord)
path.push('L' + c1c2CurvePoint.join(','));
// Bezier line around curve (Q controlcoord endcoord)
path.push('Q' + c2.x + ',' + c2.y + ',' + c2c3CurvePoint.join(','));
}
// Logically connect path to starting point again (shouldn't be necessary as path ends there anyway, but seems cleaner)
path.push('Z');
return path.join(' ');
}
You can determine the rounding strength by setting the curveRadius variable at the top. The default is 3 for a 100x100 (viewport) coordinate system, but depending on the size of your SVG, you may need to adjust this.
For my case I need to radius begin and end of path:
With stroke-linecap: round; I change it to what I want:
This question is the first result for Googling "svg rounded corners path". Phrogz suggestion to use stroke has some limitations (namely, that I cannot use stroke for other purposes, and that the dimensions have to be corrected for the stroke width).
Jlange suggestion to use a curve is better, but not very concrete. I ended up using quadratic Bézier curves for drawing rounded corners. Consider this picture of a corner marked with a blue dot and two red points on adjacent edges:
The two lines could be made with the L command. To turn this sharp corner into a rounded corner, start drawing a curve from the left red point (use M x,y to move to that point). Now a quadratic Bézier curve has just a single control point which you must set on the blue point. Set the end of the curve at the right red point. As the tangent at the two red points are in the direction of the previous lines, you will see a fluent transition, "rounded corners".
Now to continue the shape after the rounded corner, a straight line in a Bézier curve can be achieved by setting the control point between on the line between the two corners.
To help me with determining the path, I wrote this Python script that accepts edges and a radius. Vector math makes this actually very easy. The resulting image from the output:
#!/usr/bin/env python
# Given some vectors and a border-radius, output a SVG path with rounded
# corners.
#
# Copyright (C) Peter Wu <peter#lekensteyn.nl>
from math import sqrt
class Vector(object):
def __init__(self, x, y):
self.x = x
self.y = y
def sub(self, vec):
return Vector(self.x - vec.x, self.y - vec.y)
def add(self, vec):
return Vector(self.x + vec.x, self.y + vec.y)
def scale(self, n):
return Vector(self.x * n, self.y * n)
def length(self):
return sqrt(self.x**2 + self.y**2)
def normal(self):
length = self.length()
return Vector(self.x / length, self.y / length)
def __str__(self):
x = round(self.x, 2)
y = round(self.y, 2)
return '{},{}'.format(x, y)
# A line from vec_from to vec_to
def line(vec_from, vec_to):
half_vec = vec_from.add(vec_to.sub(vec_from).scale(.5))
return '{} {}'.format(half_vec, vec_to)
# Adds 'n' units to vec_from pointing in direction vec_to
def vecDir(vec_from, vec_to, n):
return vec_from.add(vec_to.sub(vec_from).normal().scale(n))
# Draws a line, but skips 'r' units from the begin and end
def lineR(vec_from, vec_to, r):
vec = vec_to.sub(vec_from).normal().scale(r)
return line(vec_from.add(vec), vec_to.sub(vec))
# An edge in vec_from, to vec_to with radius r
def edge(vec_from, vec_to, r):
v = vecDir(vec_from, vec_to, r)
return '{} {}'.format(vec_from, v)
# Hard-coded border-radius and vectors
r = 5
a = Vector( 0, 60)
b = Vector(100, 0)
c = Vector(100, 200)
d = Vector( 0, 200 - 60)
path = []
# Start below top-left edge
path.append('M {} Q'.format(a.add(Vector(0, r))))
# top-left edge...
path.append(edge(a, b, r))
path.append(lineR(a, b, r))
path.append(edge(b, c, r))
path.append(lineR(b, c, r))
path.append(edge(c, d, r))
path.append(lineR(c, d, r))
path.append(edge(d, a, r))
path.append(lineR(d, a, r))
# Show results that can be pushed into a <path d="..." />
for part in path:
print(part)
Here are some paths for tabs:
https://codepen.io/mochime/pen/VxxzMW
<!-- left tab -->
<div>
<svg width="60" height="60">
<path d="M10,10
a10 10 0 0 1 10 -10
h 50
v 47
h -50
a10 10 0 0 1 -10 -10
z"
fill="#ff3600"></path>
</svg>
</div>
<!-- right tab -->
<div>
<svg width="60" height="60">
<path d="M10 0
h 40
a10 10 0 0 1 10 10
v 27
a10 10 0 0 1 -10 10
h -40
z"
fill="#ff3600"></path>
</svg>
</div>
<!-- tab tab :) -->
<div>
<svg width="60" height="60">
<path d="M10,40
v -30
a10 10 0 0 1 10 -10
h 30
a10 10 0 0 1 10 10
v 30
z"
fill="#ff3600"></path>
</svg>
</div>
The other answers explained the mechanics. I especially liked hossein-maktoobian's answer.
The paths in the pen do the brunt of the work, the values can be modified to suite whatever desired dimensions.
This basically does the same as Mvins answer, but is a more compressed down and simplified version. It works by going back the distance of the radius of the lines adjacent to the corner and connecting both ends with a bezier curve whose control point is at the original corner point.
function createRoundedPath(coords, radius, close) {
let path = ""
const length = coords.length + (close ? 1 : -1)
for (let i = 0; i < length; i++) {
const a = coords[i % coords.length]
const b = coords[(i + 1) % coords.length]
const t = Math.min(radius / Math.hypot(b.x - a.x, b.y - a.y), 0.5)
if (i > 0) path += `Q${a.x},${a.y} ${a.x * (1 - t) + b.x * t},${a.y * (1 - t) + b.y * t}`
if (!close && i == 0) path += `M${a.x},${a.y}`
else if (i == 0) path += `M${a.x * (1 - t) + b.x * t},${a.y * (1 - t) + b.y * t}`
if (!close && i == length - 1) path += `L${b.x},${b.y}`
else if (i < length - 1) path += `L${a.x * t + b.x * (1 - t)},${a.y * t + b.y * (1 - t)}`
}
if (close) path += "Z"
return path
}
Here’s a piece of react code to generate rectangles with different corner radiuses:
const Rect = ({width, height, tl, tr, br, bl}) => {
const top = width - tl - tr;
const right = height - tr - br;
const bottom = width - br - bl;
const left = height - bl - tl;
const d = `
M${tl},0
h${top}
a${tr},${tr} 0 0 1 ${tr},${tr}
v${right}
a${br},${br} 0 0 1 -${br},${br}
h-${bottom}
a${bl},${bl} 0 0 1 -${bl},-${bl}
v-${left}
a${tl},${tl} 0 0 1 ${tl},-${tl}
z
`;
return (
<svg width={width} height={height}>
<path d={d} fill="black" />
</svg>
);
};
ReactDOM.render(
<Rect width={200} height={100} tl={20} tr={0} br={20} bl={60} />,
document.querySelector('#app'),
);
https://jsfiddle.net/v1Ljpxh7/
Just to simplify implementing answer of #hmak.me, here's a commented piece of React code to generate rounded rectangles.
const Rect = ({width, height, round, strokeWidth}) => {
// overhang over given width and height that we get due to stroke width
const s = strokeWidth / 2;
// how many pixels do we need to cut from vertical and horizontal parts
// due to rounded corners and stroke width
const over = 2 * round + strokeWidth;
// lengths of straight lines
const w = width - over;
const h = height - over;
// beware that extra spaces will not be minified
// they are added for clarity
const d = `
M${round + s},${s}
h${w}
a${round},${round} 0 0 1 ${round},${round}
v${h}
a${round},${round} 0 0 1 -${round},${round}
h-${w}
a${round},${round} 0 0 1 -${round},-${round}
v-${h}
a${round},${round} 0 0 1 ${round},-${round}
z
`;
return (
<svg width={width} height={height}>
<path d={d} fill="none" stroke="black" strokeWidth={strokeWidth} />
</svg>
);
};
ReactDOM.render(
<Rect width={64} height={32} strokeWidth={2} round={4} />,
document.querySelector('#app'),
);
Jsfiddle link.
I wrote this little typescript function so I can dynamically create the path for a complex rounded rectangle that function similar to a div with border-radius.
export function roundedRectPath(
x: number,
y: number,
width: number,
height: number,
bevel: [number, number, number, number] = [3, 3, 3, 3]
): string {
return "M" + x + "," + y
+ `m 0 ${bevel[0]}`
+ `q 0 -${bevel[0]} ${bevel[0]} -${bevel[0]}`
+ `l ${width - bevel[0] - bevel[1]} 0`
+ `q ${bevel[1]} 0 ${bevel[1]} ${bevel[1]}`
+ `l 0 ${height - bevel[1] - bevel[2]}`
+ `q 0 ${bevel[2]} -${bevel[2]} ${bevel[2]}`
+ `l -${width - bevel[2] - bevel[3]} 0`
+ `q -${bevel[3]} 0 -${bevel[3]} -${bevel[3]}`
+ `z`;
}
I found a solution but it is a bit hacky so it may not always work. I found that if you have an arc (A or a) with really small values it forces it to create a curve in one spot thus forming a rounded comer...
<svg viewBox="0 0 1 0.6" stroke="black" fill="grey" style="stroke-width:0.05px;">
<path d="M0.7 0.2 L0.1 0.1 A0.0001 0.0001 0 0 0 0.099 0.101 L0.5 0.5Z"></path>
</svg>
You are using a path element, why don't you just give the path a curve? See here for how to make curves using path elements: http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands