Merge branch 'v1.3-dev'
[websub-hub] / src / template / badge-svg.js
1 'use strict';
2
3 const th = require('./template-helper');
4
5
6 const ctxDefaults = {
7 charWidth: 7.2,
8 height: 20,
9 labelColor: '#444',
10 messageColor: '#f73',
11 color: '#fff',
12 fontFamily: 'DejaVu Sans,Verdana,Geneva,sans-serif',
13 };
14
15
16 /**
17 *
18 * @param {number} n number
19 * @param {number} p precision
20 * @returns {number} rounded
21 */
22 function fixedRound(n, p = 2) {
23 return Number(n.toFixed(p));
24 }
25
26
27 /**
28 * image/svg+xml;charset=utf-8 formatted badge with subscriber count for a topic
29 * @param {object} ctx - badge-specific context (not request context)
30 * @param {string} label label
31 * @param {string} message message
32 * @param {string} accessibleText accessible text
33 * @returns {string} svg element
34 */
35 module.exports = (ctx, label, message, accessibleText) => {
36
37 ctx = {
38 ...ctxDefaults,
39 ...ctx,
40 label,
41 message,
42 accessibleText,
43 };
44 ctx.verticalMargin = fixedRound(ctx.height * 0.69);
45 ctx.labelWidth = fixedRound(ctx.label.length * ctx.charWidth);
46 ctx.messageWidth = fixedRound(ctx.message.length * ctx.charWidth);
47 ctx.width = ctx.labelWidth + ctx.messageWidth;
48 ctx.halfCharWidth = fixedRound(ctx.charWidth * 0.5);
49
50 /*
51 * This SVG content mostly replicates the output of the 'Plastic' badge
52 * renderer from https://github.com/badges/shields/tree/master/badge-maker which
53 * is under the http://creativecommons.org/publicdomain/zero/1.0/ license.
54 */
55 return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${ctx.width}" height="${ctx.height}" role="img" aria-label="${th.xmlEscape(ctx.accessibleText)}">
56 <title>${th.xmlEscape(ctx.accessibleText)}</title>
57 <linearGradient id="s" x2="0" y2="100%">
58 <stop offset="0" stop-color="#fff" stop-opacity=".7"/>
59 <stop offset=".1" stop-color="#aaa" stop-opacity=".1"/>
60 <stop offset=".9" stop-color="#000" stop-opacity=".3"/>
61 <stop offset="1" stop-color="#000" stop-opacity=".5"/>
62 </linearGradient>
63 <clipPath id="r">
64 <rect width="${ctx.width}" height="${ctx.height}" rx="4" fill="#fff"/>
65 </clipPath>
66 <g clip-path="url(#r)">
67 <rect width="${ctx.labelWidth}" height="${ctx.height}" fill="${ctx.labelColor}"/>
68 <rect x="${ctx.labelWidth}" width="${ctx.messageWidth}" height="${ctx.height}" fill="${ctx.messageColor}"/>
69 <rect width="${ctx.width}" height="${ctx.height}" fill="url(#s)"/>
70 </g>
71 <g fill="${ctx.color}" text-anchor="left" font-family="${ctx.fontFamily}" text-rendering="geometricPrecision" font-size="11" font-weight="bold">
72 <text x="${ctx.halfCharWidth}" y="${ctx.verticalMargin}">${th.xmlEscape(ctx.label)}</text>
73 <text x="${fixedRound(ctx.halfCharWidth + ctx.labelWidth)}" y="${ctx.verticalMargin}">${th.xmlEscape(ctx.message)}</text>
74 </g>
75 </svg>`;
76 };