diff --git a/badge-maker/CHANGELOG.md b/badge-maker/CHANGELOG.md
index 2d99050baff6c..3cf6c3c37f06f 100644
--- a/badge-maker/CHANGELOG.md
+++ b/badge-maker/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## 4.0.0 [WIP]
+
+- Drop compatibility with Node 10
+
## 3.3.1
- Improve font measuring in for-the-badge and social styles
diff --git a/badge-maker/lib/badge-renderers.js b/badge-maker/lib/badge-renderers.js
index 6aba74624ac16..9d841f228d9fd 100644
--- a/badge-maker/lib/badge-renderers.js
+++ b/badge-maker/lib/badge-renderers.js
@@ -2,23 +2,22 @@
const anafanafo = require('anafanafo')
const { brightness } = require('./color')
-const { XmlElement, escapeXml } = require('./xml')
+const { XmlElement, ElementList } = require('./xml')
// https://github.com/badges/shields/pull/1132
const FONT_SCALE_UP_FACTOR = 10
const FONT_SCALE_DOWN_VALUE = 'scale(.1)'
const FONT_FAMILY = 'Verdana,Geneva,DejaVu Sans,sans-serif'
-const fontFamily = `font-family="${FONT_FAMILY}"`
-const socialFontFamily =
- 'font-family="Helvetica Neue,Helvetica,Arial,sans-serif"'
-const brightnessThreshold = 0.69
+const WIDTH_FONT = '11px Verdana'
+const SOCIAL_FONT_FAMILY = 'Helvetica Neue,Helvetica,Arial,sans-serif'
function capitalize(s) {
return `${s.charAt(0).toUpperCase()}${s.slice(1)}`
}
function colorsForBackground(color) {
+ const brightnessThreshold = 0.69
if (brightness(color) <= brightnessThreshold) {
return { textColor: '#fff', shadowColor: '#010101' }
} else {
@@ -53,127 +52,61 @@ function shouldWrapBodyWithLink({ links }) {
return hasLeftLink && !hasRightLink
}
-function renderAriaAttributes({ accessibleText, links }) {
- const { hasLink } = hasLinks({ links })
- return hasLink ? '' : `role="img" aria-label="${escapeXml(accessibleText)}"`
+function getLogoElement({ logo, horizPadding, badgeHeight, logoWidth }) {
+ const logoHeight = 14
+ if (!logo) return ''
+ return new XmlElement({
+ name: 'image',
+ attrs: {
+ x: horizPadding,
+ y: 0.5 * (badgeHeight - logoHeight),
+ width: logoWidth,
+ height: logoHeight,
+ 'xlink:href': logo,
+ },
+ })
}
-function renderTitle({ accessibleText, links }) {
+function renderBadge(
+ { links, leftWidth, rightWidth, height, accessibleText },
+ content
+) {
+ const width = leftWidth + rightWidth
+ const leftLink = links[0]
const { hasLink } = hasLinks({ links })
- return hasLink ? '' : `
${escapeXml(accessibleText)}`
-}
-function renderLogo({
- logo,
- badgeHeight,
- horizPadding,
- logoWidth = 14,
- logoPadding = 0,
-}) {
- if (logo) {
- const logoHeight = 14
- const y = (badgeHeight - logoHeight) / 2
- const x = horizPadding
- return {
- hasLogo: true,
- totalLogoWidth: logoWidth + logoPadding,
- renderedLogo: ``,
- }
- } else {
- return { hasLogo: false, totalLogoWidth: 0, renderedLogo: '' }
- }
-}
+ const title = hasLink
+ ? ''
+ : new XmlElement({ name: 'title', content: [accessibleText] })
-function renderLink({
- link,
- height,
- textLength,
- horizPadding,
- leftMargin,
- renderedText,
-}) {
- const rectHeight = height
- const rectWidth = textLength + horizPadding * 2
- const rectX = leftMargin > 1 ? leftMargin + 1 : 0
- return `
-
- ${renderedText}
- `
-}
+ const body = shouldWrapBodyWithLink({ links })
+ ? new XmlElement({
+ name: 'a',
+ content,
+ attrs: { target: '_blank', 'xlink:href': leftLink },
+ })
+ : new ElementList({ content })
-function renderText({
- leftMargin,
- horizPadding = 0,
- content,
- link,
- height,
- verticalMargin = 0,
- shadow = false,
- color,
-}) {
- if (!content.length) {
- return { renderedText: '', width: 0 }
+ const svgAttrs = {
+ xmlns: 'http://www.w3.org/2000/svg',
+ 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
+ width,
+ height,
}
-
- const textLength = preferredWidthOf(content, { font: '11px Verdana' })
- const escapedContent = escapeXml(content)
-
- const shadowMargin = 150 + verticalMargin
- const textMargin = 140 + verticalMargin
-
- const outTextLength = 10 * textLength
- const x = 10 * (leftMargin + 0.5 * textLength + horizPadding)
-
- let renderedText = ''
- const { textColor, shadowColor } = colorsForBackground(color)
- if (shadow) {
- renderedText = `${escapedContent}`
+ if (!hasLink) {
+ svgAttrs.role = 'img'
+ svgAttrs['aria-label'] = accessibleText
}
- renderedText += `${escapedContent}`
- return {
- renderedText: link
- ? renderLink({
- link,
- height,
- textLength,
- horizPadding,
- leftMargin,
- renderedText,
- })
- : renderedText,
- width: textLength,
- }
-}
-
-function renderBadge(
- { links, leftWidth, rightWidth, height, accessibleText },
- main
-) {
- const width = leftWidth + rightWidth
- const leftLink = escapeXml(links[0])
-
- return `
- `
+ const svg = new XmlElement({
+ name: 'svg',
+ content: [title, body],
+ attrs: svgAttrs,
+ })
+ return svg.render()
}
class Badge {
- static get fontFamily() {
- throw new Error('Not implemented')
- }
-
static get height() {
throw new Error('Not implemented')
}
@@ -197,41 +130,25 @@ class Badge {
labelColor,
}) {
const horizPadding = 5
- const { hasLogo, totalLogoWidth, renderedLogo } = renderLogo({
- logo,
- badgeHeight: this.constructor.height,
- horizPadding,
- logoWidth,
- logoPadding,
- })
+ const hasLogo = !!logo
+ const totalLogoWidth = logoWidth + logoPadding
+ const accessibleText = createAccessibleText({ label, message })
+
const hasLabel = label.length || labelColor
if (labelColor == null) {
labelColor = '#555'
}
-
- const [leftLink, rightLink] = links
-
labelColor = hasLabel || hasLogo ? labelColor : color
- labelColor = escapeXml(labelColor)
- color = escapeXml(color)
const labelMargin = totalLogoWidth + 1
-
- const { renderedText: renderedLabel, width: labelWidth } = renderText({
- leftMargin: labelMargin,
- horizPadding,
- content: label,
- link: !shouldWrapBodyWithLink({ links }) && leftLink,
- height: this.constructor.height,
- verticalMargin: this.constructor.verticalMargin,
- shadow: this.constructor.shadow,
- color: labelColor,
- })
-
+ const labelWidth = label.length
+ ? preferredWidthOf(label, { font: WIDTH_FONT })
+ : 0
const leftWidth = hasLabel
? labelWidth + 2 * horizPadding + totalLogoWidth
: 0
+ const messageWidth = preferredWidthOf(message, { font: WIDTH_FONT })
let messageMargin = leftWidth - (message.length ? 1 : 0)
if (!hasLabel) {
if (hasLogo) {
@@ -240,18 +157,6 @@ class Badge {
messageMargin = messageMargin + 1
}
}
-
- const { renderedText: renderedMessage, width: messageWidth } = renderText({
- leftMargin: messageMargin,
- horizPadding,
- content: message,
- link: rightLink,
- height: this.constructor.height,
- verticalMargin: this.constructor.verticalMargin,
- shadow: this.constructor.shadow,
- color,
- })
-
let rightWidth = messageWidth + 2 * horizPadding
if (hasLogo && !hasLabel) {
rightWidth += totalLogoWidth + horizPadding - 1
@@ -259,9 +164,12 @@ class Badge {
const width = leftWidth + rightWidth
- const accessibleText = createAccessibleText({ label, message })
-
+ this.horizPadding = horizPadding
+ this.labelMargin = labelMargin
+ this.messageMargin = messageMargin
this.links = links
+ this.labelWidth = labelWidth
+ this.messageWidth = messageWidth
this.leftWidth = leftWidth
this.rightWidth = rightWidth
this.width = width
@@ -270,25 +178,174 @@ class Badge {
this.label = label
this.message = message
this.accessibleText = accessibleText
- this.renderedLogo = renderedLogo
- this.renderedLabel = renderedLabel
- this.renderedMessage = renderedMessage
+
+ this.logoElement = getLogoElement({
+ logo,
+ horizPadding,
+ badgeHeight: this.constructor.height,
+ logoWidth,
+ })
+ this.foregroundGroupElement = this.getForegroundGroupElement()
}
static render(params) {
return new this(params).render()
}
+ getTextElement({ leftMargin, content, link, color, textWidth, linkWidth }) {
+ if (!content.length) return ''
+
+ const { textColor, shadowColor } = colorsForBackground(color)
+ const x =
+ FONT_SCALE_UP_FACTOR * (leftMargin + 0.5 * textWidth + this.horizPadding)
+
+ const text = new XmlElement({
+ name: 'text',
+ content: [content],
+ attrs: {
+ x,
+ y: 140 + this.constructor.verticalMargin,
+ transform: FONT_SCALE_DOWN_VALUE,
+ fill: textColor,
+ textLength: FONT_SCALE_UP_FACTOR * textWidth,
+ },
+ })
+
+ const shadowText = new XmlElement({
+ name: 'text',
+ content: [content],
+ attrs: {
+ 'aria-hidden': 'true',
+ x,
+ y: 150 + this.constructor.verticalMargin,
+ fill: shadowColor,
+ 'fill-opacity': '.3',
+ transform: FONT_SCALE_DOWN_VALUE,
+ textLength: FONT_SCALE_UP_FACTOR * textWidth,
+ },
+ })
+ const shadow = this.constructor.shadow ? shadowText : ''
+
+ if (!link) {
+ return new ElementList({ content: [shadow, text] })
+ }
+
+ const rect = new XmlElement({
+ name: 'rect',
+ attrs: {
+ width: linkWidth,
+ x: leftMargin > 1 ? leftMargin + 1 : 0,
+ height: this.constructor.height,
+ fill: 'rgba(0,0,0,0)',
+ },
+ })
+ return new XmlElement({
+ name: 'a',
+ content: [rect, shadow, text],
+ attrs: { target: '_blank', 'xlink:href': link },
+ })
+ }
+
+ getLabelElement() {
+ const leftLink = this.links[0]
+ return this.getTextElement({
+ leftMargin: this.labelMargin,
+ content: this.label,
+ link: !shouldWrapBodyWithLink({ links: this.links })
+ ? leftLink
+ : undefined,
+ color: this.labelColor,
+ textWidth: this.labelWidth,
+ linkWidth: this.leftWidth,
+ })
+ }
+
+ getMessageElement() {
+ const rightLink = this.links[1]
+ return this.getTextElement({
+ leftMargin: this.messageMargin,
+ content: this.message,
+ link: rightLink,
+ color: this.messageColor,
+ textWidth: this.messageWidth,
+ linkWidth: this.rightWidth,
+ })
+ }
+
+ getClipPathElement(rx) {
+ return new XmlElement({
+ name: 'clipPath',
+ content: [
+ new XmlElement({
+ name: 'rect',
+ attrs: {
+ width: this.width,
+ height: this.constructor.height,
+ rx,
+ fill: '#fff',
+ },
+ }),
+ ],
+ attrs: { id: 'r' },
+ })
+ }
+
+ getBackgroundGroupElement({ withGradient, attrs }) {
+ const leftRect = new XmlElement({
+ name: 'rect',
+ attrs: {
+ width: this.leftWidth,
+ height: this.constructor.height,
+ fill: this.labelColor,
+ },
+ })
+ const rightRect = new XmlElement({
+ name: 'rect',
+ attrs: {
+ x: this.leftWidth,
+ width: this.rightWidth,
+ height: this.constructor.height,
+ fill: this.color,
+ },
+ })
+ const gradient = new XmlElement({
+ name: 'rect',
+ attrs: {
+ width: this.width,
+ height: this.constructor.height,
+ fill: 'url(#s)',
+ },
+ })
+ const content = withGradient
+ ? [leftRect, rightRect, gradient]
+ : [leftRect, rightRect]
+ return new XmlElement({ name: 'g', content, attrs })
+ }
+
+ getForegroundGroupElement() {
+ return new XmlElement({
+ name: 'g',
+ content: [
+ this.logoElement,
+ this.getLabelElement(),
+ this.getMessageElement(),
+ ],
+ attrs: {
+ fill: '#fff',
+ 'text-anchor': 'middle',
+ 'font-family': FONT_FAMILY,
+ 'text-rendering': 'geometricPrecision',
+ 'font-size': 110,
+ },
+ })
+ }
+
render() {
throw new Error('Not implemented')
}
}
class Plastic extends Badge {
- static get fontFamily() {
- return fontFamily
- }
-
static get height() {
return 18
}
@@ -302,6 +359,36 @@ class Plastic extends Badge {
}
render() {
+ const gradient = new XmlElement({
+ name: 'linearGradient',
+ content: [
+ new XmlElement({
+ name: 'stop',
+ attrs: { offset: 0, 'stop-color': '#fff', 'stop-opacity': '.7' },
+ }),
+ new XmlElement({
+ name: 'stop',
+ attrs: { offset: '.1', 'stop-color': '#aaa', 'stop-opacity': '.1' },
+ }),
+ new XmlElement({
+ name: 'stop',
+ attrs: { offset: '.9', 'stop-color': '#000', 'stop-opacity': '.3' },
+ }),
+ new XmlElement({
+ name: 'stop',
+ attrs: { offset: 1, 'stop-color': '#000', 'stop-opacity': '.5' },
+ }),
+ ],
+ attrs: { id: 's', x2: 0, y2: '100%' },
+ })
+
+ const clipPath = this.getClipPathElement(4)
+
+ const backgroundGroup = this.getBackgroundGroupElement({
+ withGradient: true,
+ attrs: { 'clip-path': 'url(#r)' },
+ })
+
return renderBadge(
{
links: this.links,
@@ -310,38 +397,12 @@ class Plastic extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
- `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${this.renderedLogo}
- ${this.renderedLabel}
- ${this.renderedMessage}
- `
+ [gradient, clipPath, backgroundGroup, this.foregroundGroupElement]
)
}
}
class Flat extends Badge {
- static get fontFamily() {
- return fontFamily
- }
-
static get height() {
return 20
}
@@ -355,6 +416,28 @@ class Flat extends Badge {
}
render() {
+ const gradient = new XmlElement({
+ name: 'linearGradient',
+ content: [
+ new XmlElement({
+ name: 'stop',
+ attrs: { offset: 0, 'stop-color': '#bbb', 'stop-opacity': '.1' },
+ }),
+ new XmlElement({
+ name: 'stop',
+ attrs: { offset: 1, 'stop-opacity': '.1' },
+ }),
+ ],
+ attrs: { id: 's', x2: 0, y2: '100%' },
+ })
+
+ const clipPath = this.getClipPathElement(3)
+
+ const backgroundGroup = this.getBackgroundGroupElement({
+ withGradient: true,
+ attrs: { 'clip-path': 'url(#r)' },
+ })
+
return renderBadge(
{
links: this.links,
@@ -363,36 +446,12 @@ class Flat extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
- `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${this.renderedLogo}
- ${this.renderedLabel}
- ${this.renderedMessage}
- `
+ [gradient, clipPath, backgroundGroup, this.foregroundGroupElement]
)
}
}
class FlatSquare extends Badge {
- static get fontFamily() {
- return fontFamily
- }
-
static get height() {
return 20
}
@@ -406,6 +465,11 @@ class FlatSquare extends Badge {
}
render() {
+ const backgroundGroup = this.getBackgroundGroupElement({
+ withGradient: false,
+ attrs: { 'shape-rendering': 'crispEdges' },
+ })
+
return renderBadge(
{
links: this.links,
@@ -414,17 +478,7 @@ class FlatSquare extends Badge {
accessibleText: this.accessibleText,
height: this.constructor.height,
},
- `
-
-
-
-
-
-
- ${this.renderedLogo}
- ${this.renderedLabel}
- ${this.renderedMessage}
- `
+ [backgroundGroup, this.foregroundGroupElement]
)
}
}
@@ -448,13 +502,7 @@ function social({
const labelHorizPadding = 5
const messageHorizPadding = 4
const horizGutter = 6
- const { totalLogoWidth, renderedLogo } = renderLogo({
- logo,
- badgeHeight: externalHeight,
- horizPadding: labelHorizPadding,
- logoWidth,
- logoPadding,
- })
+ const totalLogoWidth = logoWidth + logoPadding
const hasMessage = message.length
const font = 'bold 11px Helvetica'
@@ -463,75 +511,235 @@ function social({
const labelRectWidth = labelTextWidth + totalLogoWidth + 2 * labelHorizPadding
const messageRectWidth = messageTextWidth + 2 * messageHorizPadding
- let [leftLink, rightLink] = links
- leftLink = escapeXml(leftLink)
- rightLink = escapeXml(rightLink)
+ const [leftLink, rightLink] = links
const { hasLeftLink, hasRightLink, hasLink } = hasLinks({ links })
const accessibleText = createAccessibleText({ label, message })
- function renderMessageBubble() {
+ function getMessageBubble() {
+ if (!hasMessage) return ''
+
const messageBubbleMainX = labelRectWidth + horizGutter + 0.5
const messageBubbleNotchX = labelRectWidth + horizGutter
- return `
-
-
-
- `
+ const content = [
+ new XmlElement({
+ name: 'rect',
+ attrs: {
+ x: messageBubbleMainX,
+ y: 0.5,
+ width: messageRectWidth,
+ height: internalHeight,
+ rx: 2,
+ fill: '#fafafa',
+ },
+ }),
+ new XmlElement({
+ name: 'rect',
+ attrs: {
+ x: messageBubbleNotchX,
+ y: 7.5,
+ width: 0.5,
+ height: 5,
+ stroke: '#fafafa',
+ },
+ }),
+ new XmlElement({
+ name: 'path',
+ attrs: {
+ d: `M${messageBubbleMainX} 6.5 l-3 3v1 l3 3`,
+ stroke: 'd5d5d5',
+ fill: '#fafafa',
+ },
+ }),
+ ]
+ return new ElementList({ content })
}
- function renderLabelText() {
+ function getLabelText() {
const labelTextX =
- 10 * (totalLogoWidth + labelTextWidth / 2 + labelHorizPadding)
- const labelTextLength = 10 * labelTextWidth
- const escapedLabel = escapeXml(label)
+ FONT_SCALE_UP_FACTOR *
+ (totalLogoWidth + labelTextWidth / 2 + labelHorizPadding)
+ const labelTextLength = FONT_SCALE_UP_FACTOR * labelTextWidth
const shouldWrapWithLink = hasLeftLink && !shouldWrapBodyWithLink({ links })
- const rect = ``
- const shadow = `${escapedLabel}`
- const text = `${escapedLabel}`
+ const rect = new XmlElement({
+ name: 'rect',
+ attrs: {
+ id: 'llink',
+ stroke: '#d5d5d5',
+ fill: 'url(#a)',
+ x: '.5',
+ y: '.5',
+ width: labelRectWidth,
+ height: internalHeight,
+ rx: 2,
+ },
+ })
+ const shadow = new XmlElement({
+ name: 'text',
+ content: [label],
+ attrs: {
+ 'aria-hidden': 'true',
+ x: labelTextX,
+ y: 150,
+ fill: '#fff',
+ transform: FONT_SCALE_DOWN_VALUE,
+ textLength: labelTextLength,
+ },
+ })
+ const text = new XmlElement({
+ name: 'text',
+ content: [label],
+ attrs: {
+ x: labelTextX,
+ y: 140,
+ transform: FONT_SCALE_DOWN_VALUE,
+ textLength: labelTextLength,
+ },
+ })
return shouldWrapWithLink
- ? `
-
- ${shadow}
- ${text}
- ${rect}
-
- `
- : `
- ${rect}
- ${shadow}
- ${text}
- `
- }
-
- function renderMessageText() {
+ ? new XmlElement({
+ name: 'a',
+ content: [shadow, text, rect],
+ attrs: { target: '_blank', 'xlink:href': leftLink },
+ })
+ : new ElementList({ content: [rect, shadow, text] })
+ }
+
+ function getMessageText() {
+ if (!hasMessage) return ''
+
const messageTextX =
- 10 * (labelRectWidth + horizGutter + messageRectWidth / 2)
- const messageTextLength = 10 * messageTextWidth
- const escapedMessage = escapeXml(message)
+ FONT_SCALE_UP_FACTOR *
+ (labelRectWidth + horizGutter + messageRectWidth / 2)
+ const messageTextLength = FONT_SCALE_UP_FACTOR * messageTextWidth
- const rect = ``
- const shadow = `${escapedMessage}`
- const text = `${escapedMessage}`
+ const rect = new XmlElement({
+ name: 'rect',
+ attrs: {
+ width: messageRectWidth + 1,
+ x: labelRectWidth + horizGutter,
+ height: internalHeight + 1,
+ fill: 'rgba(0,0,0,0)',
+ },
+ })
+ const shadow = new XmlElement({
+ name: 'text',
+ content: [message],
+ attrs: {
+ 'aria-hidden': 'true',
+ x: messageTextX,
+ y: 150,
+ fill: '#fff',
+ transform: FONT_SCALE_DOWN_VALUE,
+ textLength: messageTextLength,
+ },
+ })
+ const text = new XmlElement({
+ name: 'text',
+ content: [message],
+ attrs: {
+ id: 'rlink',
+ x: messageTextX,
+ y: 140,
+ transform: FONT_SCALE_DOWN_VALUE,
+ textLength: messageTextLength,
+ },
+ })
return hasRightLink
- ? `
-
- ${rect}
- ${shadow}
- ${text}
-
- `
- : `
- ${shadow}
- ${text}
- `
+ ? new XmlElement({
+ name: 'a',
+ content: [rect, shadow, text],
+ attrs: { target: '_blank', 'xlink:href': rightLink },
+ })
+ : new ElementList({ content: [shadow, text] })
}
+ const style = new XmlElement({
+ name: 'style',
+ content: [
+ 'a:hover #llink{fill:url(#b);stroke:#ccc}a:hover #rlink{fill:#4183c4}',
+ ],
+ })
+ const gradients = new ElementList({
+ content: [
+ new XmlElement({
+ name: 'linearGradient',
+ content: [
+ new XmlElement({
+ name: 'stop',
+ attrs: {
+ offset: 0,
+ 'stop-color': '#fcfcfc',
+ 'stop-opacity': 0,
+ },
+ }),
+ new XmlElement({
+ name: 'stop',
+ attrs: { offset: 1, 'stop-opacity': '.1' },
+ }),
+ ],
+ attrs: { id: 'a', x2: 0, y2: '100%' },
+ }),
+ new XmlElement({
+ name: 'linearGradient',
+ content: [
+ new XmlElement({
+ name: 'stop',
+ attrs: { offset: 0, 'stop-color': '#ccc', 'stop-opacity': '.1' },
+ }),
+ new XmlElement({
+ name: 'stop',
+ attrs: { offset: 1, 'stop-opacity': '.1' },
+ }),
+ ],
+ attrs: { id: 'b', x2: 0, y2: '100%' },
+ }),
+ ],
+ })
+ const labelRect = new XmlElement({
+ name: 'rect',
+ attrs: {
+ stroke: 'none',
+ fill: '#fcfcfc',
+ x: 0.5,
+ y: 0.5,
+ width: labelRectWidth,
+ height: internalHeight,
+ rx: 2,
+ },
+ })
+ const messageBubble = getMessageBubble()
+ const labelText = getLabelText()
+ const messageText = getMessageText()
+ const backgroundGroup = new XmlElement({
+ name: 'g',
+ content: [labelRect, messageBubble],
+ attrs: { stroke: '#d5d5d5' },
+ })
+ const foregroundGroup = new XmlElement({
+ name: 'g',
+ content: [labelText, messageText],
+ attrs: {
+ 'aria-hidden': `${!hasLink}`,
+ fill: '#333',
+ 'text-anchor': 'middle',
+ 'font-family': SOCIAL_FONT_FAMILY,
+ 'text-rendering': 'geometricPrecision',
+ 'font-weight': 700,
+ 'font-size': '110px',
+ 'line-height': '14px',
+ },
+ })
+ const logoElement = getLogoElement({
+ logo,
+ horizPadding: labelHorizPadding,
+ badgeHeight: externalHeight,
+ logoWidth,
+ })
+
return renderBadge(
{
links,
@@ -540,26 +748,7 @@ function social({
accessibleText,
height: externalHeight,
},
- `
-
-
-
-
-
-
-
-
-
-
-
- ${hasMessage ? renderMessageBubble() : ''}
-
- ${renderedLogo}
-
- ${renderLabelText()}
- ${hasMessage ? renderMessageText() : ''}
-
- `
+ [style, gradients, backgroundGroup, logoElement, foregroundGroup]
)
}
@@ -574,7 +763,6 @@ function forTheBadge({
}) {
const FONT_SIZE = 10
const BADGE_HEIGHT = 28
- const LOGO_HEIGHT = 14
const TEXT_MARGIN = 12
const LOGO_MARGIN = 9
const LOGO_TEXT_GUTTER = 6
@@ -641,15 +829,11 @@ function forTheBadge({
}
}
- const logoElement = new XmlElement({
- name: 'image',
- attrs: {
- x: logoMinX,
- y: 0.5 * (BADGE_HEIGHT - LOGO_HEIGHT),
- width: logoWidth,
- height: LOGO_HEIGHT,
- 'xlink:href': logo,
- },
+ const logoElement = getLogoElement({
+ logo,
+ horizPadding: logoMinX,
+ badgeHeight: BADGE_HEIGHT,
+ logoWidth,
})
function getLabelElement() {
@@ -772,7 +956,7 @@ function forTheBadge({
const foregroundGroup = new XmlElement({
name: 'g',
content: [
- logo ? logoElement : '',
+ logoElement,
hasLabel ? getLabelElement() : '',
getMessageElement(),
],
@@ -794,7 +978,7 @@ function forTheBadge({
accessibleText: createAccessibleText({ label, message }),
height: BADGE_HEIGHT,
},
- [backgroundGroup.render(), foregroundGroup.render()].join('')
+ [backgroundGroup, foregroundGroup]
)
}
diff --git a/badge-maker/lib/xml.js b/badge-maker/lib/xml.js
index 1d30fa501ea67..1916113c47317 100644
--- a/badge-maker/lib/xml.js
+++ b/badge-maker/lib/xml.js
@@ -58,7 +58,7 @@ class XmlElement {
if (this.content.length > 0) {
const content = this.content
.map(function (el) {
- if (el instanceof XmlElement) {
+ if (typeof el.render === 'function') {
return el.render()
} else {
return escapeXml(el)
@@ -73,4 +73,24 @@ class XmlElement {
}
}
-module.exports = { escapeXml, stripXmlWhitespace, XmlElement }
+/**
+ * Convenience class. Sometimes it is useful to return an object that behaves
+ * like an XmlElement but renders multiple XML tags (not wrapped in a ).
+ */
+class ElementList {
+ constructor({ content = [] }) {
+ this.content = content
+ }
+
+ render() {
+ return this.content.reduce(
+ (acc, el) =>
+ typeof el.render === 'function'
+ ? acc + el.render()
+ : acc + escapeXml(el),
+ ''
+ )
+ }
+}
+
+module.exports = { escapeXml, stripXmlWhitespace, XmlElement, ElementList }