diff --git a/README.md b/README.md
index 510e6b9f..e2ffaf74 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,7 @@ QR Code options:
-m, --mask Mask pattern (0 - 7) [number]
Renderer options:
- -t, --type Output type [choices: "png", "svg", "utf8"]
+ -t, --type Output type [choices: "png", "svg", "svg-round", "utf8"]
-w, --width Image width (px) [number]
-s, --scale Scale factor [number]
-q, --qzone Quiet zone size [number]
@@ -513,7 +513,7 @@ Text to encode or a list of objects describing segments.
Default: `utf8`
Output format.
- Possible values are: `terminal`,`utf8`, and `svg`.
+ Possible values are: `terminal`, `utf8`, `svg` and `svg-round`.
See [Options](#options) for other settings.
@@ -578,7 +578,7 @@ Callback function called on finish.
#### `toString(text, [options], [cb(error, string)])`
Returns a string representation of the QR Code.
-If choosen output format is `svg` it will returns a string containing xml code.
+If chosen output format is `svg` or `svg-round` it will return a string containing xml code.
##### `text`
Type: `String|Array`
@@ -591,7 +591,7 @@ Text to encode or a list of objects describing segments.
Default: `utf8`
Output format.
- Possible values are: `utf8`, `svg`, `terminal`.
+ Possible values are: `utf8`, `svg`, `svg-round`, `terminal`.
See [Options](#options) for other settings.
@@ -752,6 +752,12 @@ Default: `#ffffffff`
Color of light module. Value must be in hex format (RGBA).
+##### `rendererOpts.corners.topLeft`, `rendererOpts.corners.topRight`, `rendererOpts.corners.bottomLeft`, `rendererOpts.corners.bottomRight`
+Type: `Number`
+Default: `0`
+
+Radius of top-left, top-right, bottom-left and bottom-right corners respectively. Value should be between `0.0` - `1.0`, however larger values will be scaled down proportionally. Only works with `svg-round` format for now.
+
## GS1 QR Codes
diff --git a/lib/renderer/svg-round.js b/lib/renderer/svg-round.js
new file mode 100644
index 00000000..fd94a9dd
--- /dev/null
+++ b/lib/renderer/svg-round.js
@@ -0,0 +1,118 @@
+const Utils = require('./utils')
+
+function getColorAttrib (color, attrib) {
+ const alpha = color.a / 255
+ const str = attrib + '="' + color.hex + '"'
+
+ return alpha < 1
+ ? str + ' ' + attrib + '-opacity="' + alpha.toFixed(2).slice(1) + '"'
+ : str
+}
+
+/** Converts a number to string with leading and trailing zeros stripped @param {number} num */
+function svgNum(num) {
+ return num.toFixed(3).replace(/\.?0+$/, "").replace(/^0(?=\.)/, "");
+}
+
+/** @param {{ tl:number, tr:number, bl:number, br:number }} corners */
+function qrToPathCorners(data, size, margin, corners) {
+ let path = "";
+ let run = 0, i = 0;
+
+ // Calculate scale ratio and corner sizes
+ const s = Math.min(1, 1 / Math.max(corners.tl + corners.tr, corners.bl + corners.br));
+ const tl = Math.max(corners.tl, 0) * s, stl = svgNum(tl);
+ const tr = Math.max(corners.tr, 0) * s, str = svgNum(tr);
+ const bl = Math.max(corners.bl, 0) * s, sbl = svgNum(bl);
+ const br = Math.max(corners.br, 0) * s, sbr = svgNum(br);
+
+ for (let y = 0; y < size; ++y) {
+ for (let x = 0; x < size; ++x, ++i) {
+ if (!data[i]) { continue; }
+
+ const top = (y === 0) || !data[i - size];
+ const bot = (y === (size - 1)) || !data[i + size];
+
+ if (!run++) {
+ path += (bot && bl)
+ ? `M${svgNum(x + margin + bl)},${y + margin + 1}a${sbl},${sbl} 0 0 1 -${sbl},-${sbl}`
+ : `M${svgNum(x + margin)},${y + margin + 1}` + (bl ? `v-${sbl}` : "");
+
+ path += (top && tl)
+ ? `V${svgNum(y + margin + tl)}a${stl},${stl} 0 0 1 ${stl},-${stl}`
+ : `V${svgNum(y + margin)}`;
+ }
+
+ if ((x === (size - 1)) || !data[i + 1]) {
+ path += (top && tr)
+ ? `H${svgNum(x + margin + 1 - tr)}a${str},${str} 0 0 1 ${str},${str}`
+ : `H${svgNum(x + margin + 1)}` + (tr ? `v${str}` : "");
+
+ path += (bot && br)
+ ? `V${svgNum(y + margin + 1 - br)}a${sbr},${sbr} 0 0 1 -${sbr},${sbr}z`
+ : `V${svgNum(y + margin + 1)}z`;
+
+ run = 0;
+ }
+ }
+ }
+
+ return path;
+}
+
+exports.render = function render (qrData, options, cb) {
+ const opts = Utils.getOptions(options)
+ const size = qrData.modules.size
+ const data = qrData.modules.data
+ const qrcodesize = size + opts.margin * 2
+
+ const bg = !opts.color.light.a
+ ? ''
+ : ``
+
+ const path = ``;
+
+ const viewBox = `viewBox="0 0 ${qrcodesize} ${qrcodesize}"`;
+ const width = !opts.width ? '' : `width="${opts.width}" height="${opts.width}" `;
+ const svgTag = `\n`;
+
+ if (typeof cb === 'function') {
+ cb(null, svgTag)
+ }
+
+ return svgTag
+}
+
+exports.renderToDataURL = function renderToDataURL (qrData, options, cb) {
+ if (typeof cb === 'undefined') {
+ cb = options
+ options = undefined
+ }
+
+ const svgTag = exports.render(qrData, options);
+ const uriStr = `data:image/svg+xml;base64,${Buffer.from(svgTag).toString('base64')}`;
+
+ cb(null, uriStr)
+};
+
+exports.renderToFile = function renderToFile (path, qrData, options, cb) {
+ if (typeof cb === 'undefined') {
+ cb = options
+ options = undefined
+ }
+
+ const fs = require('fs')
+ const svgTag = exports.render(qrData, options)
+
+ const xmlStr = '' +
+ '' +
+ svgTag
+
+ fs.writeFile(path, xmlStr, cb)
+}
diff --git a/lib/server.js b/lib/server.js
index 619e398e..60a4302a 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -4,6 +4,7 @@ const PngRenderer = require('./renderer/png')
const Utf8Renderer = require('./renderer/utf8')
const TerminalRenderer = require('./renderer/terminal')
const SvgRenderer = require('./renderer/svg')
+const SvgRoundRenderer = require('./renderer/svg-round')
function checkParams (text, opts, cb) {
if (typeof text === 'undefined') {
@@ -39,6 +40,9 @@ function getRendererFromType (type) {
case 'svg':
return SvgRenderer
+ case 'svg-round':
+ return SvgRoundRenderer
+
case 'txt':
case 'utf8':
return Utf8Renderer
@@ -55,6 +59,9 @@ function getStringRendererFromType (type) {
case 'svg':
return SvgRenderer
+ case 'svg-round':
+ return SvgRoundRenderer
+
case 'terminal':
return TerminalRenderer