1
0
mirror of https://github.com/badges/shields.git synced 2025-04-18 19:44:04 +03:00

feat(logos): support auto-sizing mode (#9191)

Co-authored-by: chris48s <git@chris-shaw.dev>
This commit is contained in:
LitoMore 2024-04-22 19:46:00 +08:00 committed by GitHub
parent e8671be7f2
commit 915ab742d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 160 additions and 18 deletions

View File

@ -0,0 +1,5 @@
const DEFAULT_LOGO_HEIGHT = 14
module.exports = {
DEFAULT_LOGO_HEIGHT,
}

View File

@ -3,6 +3,7 @@
const { normalizeColor, toSvgColor } = require('./color')
const badgeRenderers = require('./badge-renderers')
const { stripXmlWhitespace } = require('./xml')
const { DEFAULT_LOGO_HEIGHT } = require('./constants')
/*
note: makeBadge() is fairly thinly wrapped so if we are making changes here
@ -17,6 +18,7 @@ module.exports = function makeBadge({
labelColor,
logo,
logoPosition,
logoSize,
logoWidth,
links = ['', ''],
}) {
@ -45,7 +47,7 @@ module.exports = function makeBadge({
throw new Error(`Unknown badge style: '${style}'`)
}
logoWidth = +logoWidth || (logo ? 14 : 0)
logoWidth = +logoWidth || (logo ? DEFAULT_LOGO_HEIGHT : 0)
return stripXmlWhitespace(
render({
@ -55,6 +57,7 @@ module.exports = function makeBadge({
logo,
logoPosition,
logoWidth,
logoSize,
logoPadding: logo && label.length ? 3 : 0,
color: toSvgColor(color),
labelColor: toSvgColor(labelColor),

View File

@ -377,6 +377,7 @@ describe('BaseService', function () {
namedLogo: undefined,
logo: undefined,
logoWidth: undefined,
logoSize: undefined,
logoPosition: undefined,
links: [],
labelColor: undefined,

View File

@ -2,7 +2,8 @@ import {
decodeDataUrlFromQueryParam,
prepareNamedLogo,
} from '../../lib/logos.js'
import { svg2base64 } from '../../lib/svg-helpers.js'
import { svg2base64, getIconSize } from '../../lib/svg-helpers.js'
import { DEFAULT_LOGO_HEIGHT } from '../../badge-maker/lib/constants.js'
import coalesce from './coalesce.js'
import toArray from './to-array.js'
@ -56,6 +57,7 @@ export default function coalesceBadge(
let {
logoWidth: overrideLogoWidth,
logoPosition: overrideLogoPosition,
logoSize: overrideLogoSize,
color: overrideColor,
labelColor: overrideLabelColor,
} = overrides
@ -87,6 +89,7 @@ export default function coalesceBadge(
logoSvg: serviceLogoSvg,
namedLogo: serviceNamedLogo,
logoColor: serviceLogoColor,
logoSize: serviceLogoSize,
logoWidth: serviceLogoWidth,
logoPosition: serviceLogoPosition,
link: serviceLink,
@ -119,7 +122,12 @@ export default function coalesceBadge(
style = 'flat'
}
let namedLogo, namedLogoColor, logoWidth, logoPosition, logoSvgBase64
let namedLogo,
namedLogoColor,
logoSize,
logoWidth,
logoPosition,
logoSvgBase64
if (overrideLogo) {
// `?logo=` could be a named logo or encoded svg.
const overrideLogoSvgBase64 = decodeDataUrlFromQueryParam(overrideLogo)
@ -133,6 +141,7 @@ export default function coalesceBadge(
}
// If the logo has been overridden it does not make sense to inherit the
// original width or position.
logoSize = overrideLogoSize
logoWidth = overrideLogoWidth
logoPosition = overrideLogoPosition
} else {
@ -145,13 +154,21 @@ export default function coalesceBadge(
)
namedLogoColor = coalesce(overrideLogoColor, serviceLogoColor)
}
logoSize = coalesce(overrideLogoSize, serviceLogoSize)
logoWidth = coalesce(overrideLogoWidth, serviceLogoWidth)
logoPosition = coalesce(overrideLogoPosition, serviceLogoPosition)
}
if (namedLogo) {
const iconSize = getIconSize(String(namedLogo).toLowerCase())
if (!logoWidth && iconSize && logoSize === 'auto') {
logoWidth = (iconSize.width / iconSize.height) * DEFAULT_LOGO_HEIGHT
}
logoSvgBase64 = prepareNamedLogo({
name: namedLogo,
color: namedLogoColor,
size: logoSize,
style,
})
}
@ -179,6 +196,7 @@ export default function coalesceBadge(
logo: logoSvgBase64,
logoWidth,
logoPosition,
logoSize,
links: toArray(overrideLink || serviceLink),
cacheLengthSeconds: coalesce(serviceCacheSeconds, defaultCacheSeconds),
}

View File

@ -260,6 +260,20 @@ describe('coalesceBadge', function () {
})
})
describe('Logo size', function () {
it('overrides the logoSize', function () {
expect(coalesceBadge({ logoSize: 'auto' }, {}, {})).to.include({
logoSize: 'auto',
})
})
it('applies the logo size', function () {
expect(
coalesceBadge({}, { namedLogo: 'npm', logoSize: 'auto' }, {}),
).to.include({ logoSize: 'auto' })
})
})
describe('Logo width', function () {
it('overrides the logoWidth', function () {
expect(coalesceBadge({ logoWidth: 20 }, {}, {})).to.include({

View File

@ -9,6 +9,7 @@ const globalParamRefs = [
{ $ref: '#/components/parameters/style' },
{ $ref: '#/components/parameters/logo' },
{ $ref: '#/components/parameters/logoColor' },
{ $ref: '#/components/parameters/logoSize' },
{ $ref: '#/components/parameters/label' },
{ $ref: '#/components/parameters/labelColor' },
{ $ref: '#/components/parameters/color' },
@ -140,6 +141,17 @@ function category2openapi({ category, services, sort = false }) {
},
example: 'violet',
},
logoSize: {
name: 'logoSize',
in: 'query',
required: false,
description:
'Make icons adaptively resize by setting `auto`. Useful for some wider logos like `amd` and `amg`. Supported for simple-icons logos only.',
schema: {
type: 'string',
},
example: 'auto',
},
label: {
name: 'label',
in: 'query',

View File

@ -93,6 +93,17 @@ const expected = {
schema: { type: 'string' },
example: 'violet',
},
logoSize: {
name: 'logoSize',
in: 'query',
required: false,
description:
'Make icons adaptively resize by setting `auto`. Useful for some wider logos like `amd` and `amg`. Supported for simple-icons logos only.',
schema: {
type: 'string',
},
example: 'auto',
},
label: {
name: 'label',
in: 'query',
@ -158,6 +169,7 @@ const expected = {
{ $ref: '#/components/parameters/style' },
{ $ref: '#/components/parameters/logo' },
{ $ref: '#/components/parameters/logoColor' },
{ $ref: '#/components/parameters/logoSize' },
{ $ref: '#/components/parameters/label' },
{ $ref: '#/components/parameters/labelColor' },
{ $ref: '#/components/parameters/color' },
@ -213,6 +225,7 @@ const expected = {
{ $ref: '#/components/parameters/style' },
{ $ref: '#/components/parameters/logo' },
{ $ref: '#/components/parameters/logoColor' },
{ $ref: '#/components/parameters/logoSize' },
{ $ref: '#/components/parameters/label' },
{ $ref: '#/components/parameters/labelColor' },
{ $ref: '#/components/parameters/color' },

View File

@ -1,5 +1,4 @@
import * as originalSimpleIcons from 'simple-icons/icons'
import { svg2base64 } from './svg-helpers.js'
function loadSimpleIcons() {
const simpleIcons = {}
@ -16,10 +15,10 @@ function loadSimpleIcons() {
const icon = originalSimpleIcons[key]
const { title, slug, hex } = icon
icon.base64 = {
default: svg2base64(icon.svg.replace('<svg', `<svg fill="#${hex}"`)),
light: svg2base64(icon.svg.replace('<svg', '<svg fill="whitesmoke"')),
dark: svg2base64(icon.svg.replace('<svg', '<svg fill="#333"')),
icon.styles = {
default: icon.svg.replace('<svg', `<svg fill="#${hex}"`),
light: icon.svg.replace('<svg', '<svg fill="whitesmoke"'),
dark: icon.svg.replace('<svg', '<svg fill="#333"'),
}
// There are a few instances where multiple icons have the same title

View File

@ -8,7 +8,7 @@ describe('loadSimpleIcons', function () {
})
it('prepares three color themes', function () {
expect(simpleIcons.sentry.base64).to.have.all.keys(
expect(simpleIcons.sentry.styles).to.have.all.keys(
'default',
'light',
'dark',

View File

@ -5,7 +5,7 @@ import {
normalizeColor,
} from '../badge-maker/lib/color.js'
import coalesce from '../core/base-service/coalesce.js'
import { svg2base64 } from './svg-helpers.js'
import { svg2base64, getIconSize, resetIconPosition } from './svg-helpers.js'
import loadLogos from './load-logos.js'
import loadSimpleIcons from './load-simple-icons.js'
const logos = loadLogos()
@ -88,25 +88,42 @@ function getSimpleIconStyle({ icon, style }) {
return 'default'
}
function getSimpleIcon({ name, color, style }) {
// The `size` parameter is used to determine whether the icon should be resized
// to fit the badge. If `size` is 'auto', the icon will be resized to fit the
// badge. If `size` is not 'auto', the icon will be displayed at its original.
// https://github.com/badges/shields/pull/9191
function getSimpleIcon({ name, color, style, size }) {
const key = name === 'travis' ? 'travis-ci' : name.replace(/ /g, '-')
if (!(key in simpleIcons)) {
return undefined
}
let iconSvg
const svgColor = toSvgColor(color)
if (svgColor) {
return svg2base64(
simpleIcons[key].svg.replace('<svg', `<svg fill="${svgColor}"`),
)
iconSvg = simpleIcons[key].svg.replace('<svg', `<svg fill="${svgColor}"`)
} else {
const iconStyle = getSimpleIconStyle({ icon: simpleIcons[key], style })
return simpleIcons[key].base64[iconStyle]
iconSvg = simpleIcons[key].styles[iconStyle]
}
if (size === 'auto') {
const { width: iconWidth, height: iconHeight } = getIconSize(key)
if (iconWidth > iconHeight) {
const path = resetIconPosition(simpleIcons[key].path)
iconSvg = iconSvg
.replace('viewBox="0 0 24 24"', `viewBox="0 0 24 ${iconHeight}"`)
.replace(/<path d=".*"\/>/, `<path d="${path}"/>`)
}
}
return svg2base64(iconSvg)
}
function prepareNamedLogo({ name, color, style }) {
function prepareNamedLogo({ name, color, style, size }) {
if (typeof name !== 'string') {
return undefined
}
@ -118,7 +135,8 @@ function prepareNamedLogo({ name, color, style }) {
}
return (
getShieldsIcon({ name, color }) || getSimpleIcon({ name, color, style })
getShieldsIcon({ name, color }) ||
getSimpleIcon({ name, color, style, size })
)
}
@ -131,6 +149,7 @@ function makeLogo(defaultNamedLogo, overrides) {
name: coalesce(overrides.logo, defaultNamedLogo),
color: overrides.logoColor,
style: overrides.style,
size: overrides.logoSize,
})
}
}

View File

@ -57,6 +57,14 @@ describe('Logo helpers', function () {
given({ name: 'github', color: 'red' }).expect(
'data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjZTA1ZDQ0IiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R2l0SHViPC90aXRsZT48cGF0aCBkPSJNMTIgLjI5N2MtNi42MyAwLTEyIDUuMzczLTEyIDEyIDAgNS4zMDMgMy40MzggOS44IDguMjA1IDExLjM4NS42LjExMy44Mi0uMjU4LjgyLS41NzcgMC0uMjg1LS4wMS0xLjA0LS4wMTUtMi4wNC0zLjMzOC43MjQtNC4wNDItMS42MS00LjA0Mi0xLjYxQzQuNDIyIDE4LjA3IDMuNjMzIDE3LjcgMy42MzMgMTcuN2MtMS4wODctLjc0NC4wODQtLjcyOS4wODQtLjcyOSAxLjIwNS4wODQgMS44MzggMS4yMzYgMS44MzggMS4yMzYgMS4wNyAxLjgzNSAyLjgwOSAxLjMwNSAzLjQ5NS45OTguMTA4LS43NzYuNDE3LTEuMzA1Ljc2LTEuNjA1LTIuNjY1LS4zLTUuNDY2LTEuMzMyLTUuNDY2LTUuOTMgMC0xLjMxLjQ2NS0yLjM4IDEuMjM1LTMuMjItLjEzNS0uMzAzLS41NC0xLjUyMy4xMDUtMy4xNzYgMCAwIDEuMDA1LS4zMjIgMy4zIDEuMjMuOTYtLjI2NyAxLjk4LS4zOTkgMy0uNDA1IDEuMDIuMDA2IDIuMDQuMTM4IDMgLjQwNSAyLjI4LTEuNTUyIDMuMjg1LTEuMjMgMy4yODUtMS4yMy42NDUgMS42NTMuMjQgMi44NzMuMTIgMy4xNzYuNzY1Ljg0IDEuMjMgMS45MSAxLjIzIDMuMjIgMCA0LjYxLTIuODA1IDUuNjI1LTUuNDc1IDUuOTIuNDIuMzYuODEgMS4wOTYuODEgMi4yMiAwIDEuNjA2LS4wMTUgMi44OTYtLjAxNSAzLjI4NiAwIC4zMTUuMjEuNjkuODI1LjU3QzIwLjU2NSAyMi4wOTIgMjQgMTcuNTkyIDI0IDEyLjI5N2MwLTYuNjI3LTUuMzczLTEyLTEyLTEyIi8+PC9zdmc+',
)
// use simple icon with auto logo size
given({ name: 'amd', size: 'auto' }).expect(
'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJ3aGl0ZXNtb2tlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCA1LjcyNTk5OTk5OTk5OTk5OSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+QU1EPC90aXRsZT48cGF0aCBkPSJNMTguMzI0IDBMMTkuODgzIDEuNTZIMjIuNDM5VjQuMTE3TDI0IDUuNjc3VjBaTTIgMC4zODNMMCA1LjM0M0gxLjMwOUwxLjY3OSA0LjM2MUgzLjlMNC4zMDggNS4zNDNINS42NDZMMy40MzIgMC4zODNaTTYuMjA5IDAuMzgzVjUuMzM4SDcuNDQ3VjIuMjQ2TDguNzg1IDMuODA4SDguOTczTDEwLjMxMSAyLjI1MlY1LjM0M0gxMS41NDlWMC4zODNIMTAuNDdMOC44NzggMi4yMjhMNy4yODcgMC4zODNaTTEyLjQ5MiAwLjM4M1Y1LjM0M0gxNC41NDlDMTYuNTI4IDUuMzQzIDE3LjQyOSA0LjI5NyAxNy40MjkgMi44NzFDMTcuNDI5IDEuNTExIDE2LjQ5MiAwLjM4MyAxNC42ODIgMC4zODNaTTEzLjcyOSAxLjI5M0gxNC41MjFDMTUuNjkxIDEuMjkzIDE2LjE1MSAyLjAwNCAxNi4xNTEgMi44NjNDMTYuMTUxIDMuNTkxIDE1Ljc3OSA0LjQzNSAxNC41MzUgNC40MzVIMTMuNzI5Wk0yLjc0NCAxLjU2NkwzLjUzNSAzLjQ5OEgyLjAwOFpNMTkuODgxIDEuODczTDE4LjI3NyAzLjQ3NlY1LjcyNkgyMC41MjNMMjIuMTI3IDQuMTE5SDE5Ljg4MVoiLz48L3N2Zz4=',
)
// use simple icon with color & auto logo size
given({ name: 'amd', color: 'white', size: 'auto' }).expect(
'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJ3aGl0ZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgMjQgNS43MjU5OTk5OTk5OTk5OTkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHRpdGxlPkFNRDwvdGl0bGU+PHBhdGggZD0iTTE4LjMyNCAwTDE5Ljg4MyAxLjU2SDIyLjQzOVY0LjExN0wyNCA1LjY3N1YwWk0yIDAuMzgzTDAgNS4zNDNIMS4zMDlMMS42NzkgNC4zNjFIMy45TDQuMzA4IDUuMzQzSDUuNjQ2TDMuNDMyIDAuMzgzWk02LjIwOSAwLjM4M1Y1LjMzOEg3LjQ0N1YyLjI0Nkw4Ljc4NSAzLjgwOEg4Ljk3M0wxMC4zMTEgMi4yNTJWNS4zNDNIMTEuNTQ5VjAuMzgzSDEwLjQ3TDguODc4IDIuMjI4TDcuMjg3IDAuMzgzWk0xMi40OTIgMC4zODNWNS4zNDNIMTQuNTQ5QzE2LjUyOCA1LjM0MyAxNy40MjkgNC4yOTcgMTcuNDI5IDIuODcxQzE3LjQyOSAxLjUxMSAxNi40OTIgMC4zODMgMTQuNjgyIDAuMzgzWk0xMy43MjkgMS4yOTNIMTQuNTIxQzE1LjY5MSAxLjI5MyAxNi4xNTEgMi4wMDQgMTYuMTUxIDIuODYzQzE2LjE1MSAzLjU5MSAxNS43NzkgNC40MzUgMTQuNTM1IDQuNDM1SDEzLjcyOVpNMi43NDQgMS41NjZMMy41MzUgMy40OThIMi4wMDhaTTE5Ljg4MSAxLjg3M0wxOC4yNzcgMy40NzZWNS43MjZIMjAuNTIzTDIyLjEyNyA0LjExOUgxOS44ODFaIi8+PC9zdmc+',
)
// use travis shield icon
given({ name: 'travis' }).expect(

View File

@ -1,7 +1,38 @@
import SVGPathCommander from 'svg-path-commander'
import loadSimpleIcons from './load-simple-icons.js'
function svg2base64(svg) {
return `data:image/svg+xml;base64,${Buffer.from(svg.trim()).toString(
'base64',
)}`
}
export { svg2base64 }
function getIconSize(iconKey) {
const simpleIcons = loadSimpleIcons()
if (!(iconKey in simpleIcons)) {
return undefined
}
const {
width,
height,
x: x0,
y: y0,
x2: x1,
y2: y1,
} = SVGPathCommander.getPathBBox(simpleIcons[iconKey].path)
return { width, height, x0, y0, x1, y1 }
}
function resetIconPosition(path) {
const { x: offsetX, y: offsetY } = SVGPathCommander.getPathBBox(path)
const pathReset = new SVGPathCommander(path)
.transform({ translate: [-offsetX, -offsetY] })
.toString()
return pathReset
}
export { svg2base64, getIconSize, resetIconPosition }

18
package-lock.json generated
View File

@ -53,6 +53,7 @@
"semver": "~7.6.0",
"simple-icons": "11.12.0",
"smol-toml": "1.1.4",
"svg-path-commander": "^2.0.9",
"webextension-store-meta": "^1.2.1",
"xpath": "~0.0.34"
},
@ -4893,6 +4894,11 @@
"node": ">=14.16"
}
},
"node_modules/@thednp/dommatrix": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@thednp/dommatrix/-/dommatrix-2.0.6.tgz",
"integrity": "sha512-DXQq4Rs/akYzeXYGkNy3KiJ4JoD8+SYr1QRWTXtAGoZ0+vJcyBt0aeqA1K4CxPaBaIfKdOTE+Te1HV9sAQ4I4A=="
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
@ -27281,6 +27287,18 @@
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
"dev": true
},
"node_modules/svg-path-commander": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/svg-path-commander/-/svg-path-commander-2.0.9.tgz",
"integrity": "sha512-VfRLznHewlpQvuahtBK0MT/PlWAapbTx8RSytqgaVwD3US2keKcc3WYYlBBk4vIOR+jB3nQu/NAVlWHKlo0Fjw==",
"dependencies": {
"@thednp/dommatrix": "^2.0.6"
},
"engines": {
"node": ">=16",
"pnpm": ">=8.6.0"
}
},
"node_modules/svgo": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz",

View File

@ -65,6 +65,7 @@
"semver": "~7.6.0",
"simple-icons": "11.12.0",
"smol-toml": "1.1.4",
"svg-path-commander": "^2.0.9",
"webextension-store-meta": "^1.2.1",
"xpath": "~0.0.34"
},