siyuan/kernel/api/icon.go

565 lines
21 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package api
import (
"fmt"
"math"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/util"
)
type ColorScheme struct {
Primary string
Secondary string
}
var colorSchemes = map[string]ColorScheme{
"red": {"#d13d51", "#ba2c3f"},
"blue": {"#3eb0ea", "#0097e6"},
"yellow": {"#eec468", "#d89b18"},
"green": {"#52E0B8", "#19b37a"},
"purple": {"#a36cda", "#8952d5"},
"pink": {"#f183aa", "#e05b8a"},
"orange": {"#f3865e", "#ef5e2a"},
"grey": {"#576574", "#374a60"},
}
func getColorScheme(color string) ColorScheme {
// 去除可能的空格
color = strings.TrimSpace(color)
// 检查是否是预定义的颜色
if scheme, ok := colorSchemes[strings.ToLower(color)]; ok {
return scheme
}
// 支持自定义颜色
// 如果颜色值不以#开头,自动添加#
if !strings.HasPrefix(color, "#") && len(color) > 0 {
color = "#" + color
}
// 检查是否是十六进制颜色值
hexColorPattern := `^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$`
if matched, _ := regexp.MatchString(hexColorPattern, color); matched {
// 确保颜色值有#前缀
if !strings.HasPrefix(color, "#") {
color = "#" + color
}
// 如果是3位十六进制转换为6位
if len(color) == 4 {
r := string(color[1])
g := string(color[2])
b := string(color[3])
color = "#" + r + r + g + g + b + b
}
// 生成次要颜色(将主色调变深)
secondary := darkenColor(color, 0.1)
return ColorScheme{
Primary: color,
Secondary: secondary,
}
}
// 如果既不是预定义颜色也不是有效的十六进制值,返回默认颜色
return colorSchemes["red"]
}
func darkenColor(hexColor string, factor float64) string {
// 去掉#号
hex := hexColor[1:]
// 将十六进制转换为RGB
r, _ := strconv.ParseInt(hex[0:2], 16, 64)
g, _ := strconv.ParseInt(hex[2:4], 16, 64)
b, _ := strconv.ParseInt(hex[4:6], 16, 64)
// 使颜色变深
r = int64(float64(r) * (1 - factor))
g = int64(float64(g) * (1 - factor))
b = int64(float64(b) * (1 - factor))
// 确保值在0-255范围内
r = int64(math.Max(0, float64(r)))
g = int64(math.Max(0, float64(g)))
b = int64(math.Max(0, float64(b)))
// 转回十六进制
return fmt.Sprintf("#%02X%02X%02X", r, g, b)
}
func getDynamicIcon(c *gin.Context) {
// Add internal kernel API `/api/icon/getDynamicIcon` https://github.com/siyuan-note/siyuan/pull/12939
iconType := c.DefaultQuery("type", "1")
color := c.Query("color") // 不要预设默认值不然type6返回星期就没法自动设置周末颜色了
date := c.Query("date")
lang := c.DefaultQuery("lang", util.Lang)
content := c.Query("content")
weekdayType := c.DefaultQuery("weekdayType", "1") // 设置星期几的格式zh_CH {1周日2周天 3星期日4星期天}, en_US {1: Mon, 2: MON3: Monday, 4. MONDAY,}
dateInfo := getDateInfo(date, lang, weekdayType)
var svg string
switch iconType {
case "1":
// Type 1: 显示年月日星期
svg = generateTypeOneSVG(color, lang, dateInfo)
case "2":
// Type 2: 显示年月日
svg = generateTypeTwoSVG(color, lang, dateInfo)
case "3":
// Type 3: 显示年月
svg = generateTypeThreeSVG(color, lang, dateInfo)
case "4":
// Type 4: 仅显示年
svg = generateTypeFourSVG(color, lang, dateInfo)
case "5":
// Type 5: 显示周数
svg = generateTypeFiveSVG(color, lang, dateInfo)
case "6":
// Type 6: 仅显示星期
svg = generateTypeSixSVG(color, lang, weekdayType, dateInfo)
case "7":
// Type 7: 倒数日
svg = generateTypeSevenSVG(color, lang, dateInfo)
case "8":
// Type 8: 文字图标
svg = generateTypeEightSVG(color, content)
default:
// 默认为Type 1
svg = generateTypeOneSVG(color, lang, dateInfo)
}
c.Header("Content-Type", "image/svg+xml")
c.Header("Cache-Control", "no-cache")
c.Header("Pragma", "no-cache")
c.String(http.StatusOK, svg)
}
func getDateInfo(dateStr string, lang string, weekdayType string) map[string]interface{} {
// 设置默认值
var date time.Time
var err error
if dateStr == "" {
date = time.Now()
} else {
date, err = time.Parse("2006-01-02", dateStr)
if err != nil {
date = time.Now()
}
}
// 获取年月日星期
year := date.Year()
month := date.Format("Jan")
day := date.Day()
var weekdayStr string
var weekdays []string
switch lang {
case "zh_CN":
month = date.Format("1月")
switch weekdayType {
case "1":
weekdays = []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}
case "2":
weekdays = []string{"周天", "周一", "周二", "周三", "周四", "周五", "周六"}
case "3":
weekdays = []string{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}
case "4":
weekdays = []string{"星期天", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}
default:
weekdays = []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}
}
weekdayStr = weekdays[date.Weekday()]
case "zh_CHT":
month = date.Format("1月")
switch weekdayType {
case "1":
weekdays = []string{"週日", "週一", "週二", "週三", "週四", "週五", "週六"}
case "2":
weekdays = []string{"週天", "週一", "週二", "週三", "週四", "週五", "週六"}
case "3":
weekdays = []string{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}
case "4":
weekdays = []string{"星期天", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}
default:
weekdays = []string{"週日", "週一", "週二", "週三", "週四", "週五", "週六"}
}
weekdayStr = weekdays[date.Weekday()]
default:
// 其他语言
switch weekdayType {
case "1":
weekdayStr = date.Format("Mon")
case "2":
weekdayStr = date.Format("Mon")
weekdayStr = strings.ToUpper(weekdayStr)
case "3":
weekdayStr = date.Format("Monday")
case "4":
weekdayStr = date.Format("Monday")
weekdayStr = strings.ToUpper(weekdayStr)
default:
weekdayStr = date.Format("Mon")
}
}
// Calculate week number
_, weekNum := date.ISOWeek()
weekNumStr := fmt.Sprintf("%dW", weekNum)
switch lang {
case "zh_CN":
weekNumStr = fmt.Sprintf("%d周", weekNum)
case "zh_CHT":
weekNumStr = fmt.Sprintf("%d週", weekNum)
}
// 判断是否是周末
isWeekend := date.Weekday() == time.Saturday || date.Weekday() == time.Sunday
// Calculate days until today
today := time.Now()
today = time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location())
date = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
// countDown := int(math.Floor(date.Sub(today).Hours() / 24)) // 注意最大返回106751天go的时间戳最大值
countDown := daysBetween(today, date)
return map[string]interface{}{
"year": year,
"month": month,
"day": day,
"date": fmt.Sprintf("%02d-%02d", date.Month(), date.Day()),
"weekday": weekdayStr,
"week": weekNumStr,
"countDown": countDown,
"isWeekend": isWeekend,
}
}
func daysBetween(date1, date2 time.Time) int {
// 将两个日期都调整到UTC时间的0点
date1 = time.Date(date1.Year(), date1.Month(), date1.Day(), 0, 0, 0, 0, time.UTC)
date2 = time.Date(date2.Year(), date2.Month(), date2.Day(), 0, 0, 0, 0, time.UTC)
// 确保date1不晚于date2
swap := false
if date1.After(date2) {
date1, date2 = date2, date1
swap = true
}
// 计算天数差
days := 0
for y := date1.Year(); y < date2.Year(); y++ {
if isLeapYear(y) {
days += 366
} else {
days += 365
}
}
// 加上最后一年的天数
days += int(date2.YearDay() - date1.YearDay())
// 如果原始的date1晚于date2返回负值
if swap {
return -days
}
return days
}
// 判断是否为闰年
func isLeapYear(year int) bool {
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}
// Type 1: 显示年月日星期
func generateTypeOneSVG(color string, lang string, dateInfo map[string]interface{}) string {
colorScheme := getColorScheme(color)
return fmt.Sprintf(`
<svg id="dynamic_icon_type1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M512,447.5c0,32-25,57-57,57H57c-32,0-57-25-57-57V120.5c0-31,25-57,57-57h398c32,0,57,26,57,57v327Z" style="fill: #ecf2f7;"/>
<path d="M39,0h434c21.52,0,39,17.48,39,39v146H0V39C0,17.48,17.48,0,39,0Z" style="fill: %s;"/>
<text transform="translate(22 146.5)" style="fill: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; font-size: 100px;">%s</text>
<text x="50%%" y="392.5" style="fill: #66757f; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; font-size: 240px; text-anchor: middle">%d</text>
<text x="50%%" y="472.5" style="fill: #66757f; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; font-size: 64px; text-anchor: middle">%s</text>
<text transform="translate(331.03 148.44)" style="fill: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; font-size: 71.18px;">%d</text>
</svg>
`, colorScheme.Primary, dateInfo["month"], dateInfo["day"], dateInfo["weekday"], dateInfo["year"])
}
// Type 2: 显示年月日
func generateTypeTwoSVG(color string, lang string, dateInfo map[string]interface{}) string {
colorScheme := getColorScheme(color)
return fmt.Sprintf(`
<svg id="dynamic_icon_type2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M512,447.5c0,32-25,57-57,57H57c-32,0-57-25-57-57V120.5c0-31,25-57,57-57h398c32,0,57,26,57,57v327Z" style="fill: #ecf2f7;"/>
<path d="M39,0h434c21.52,0,39,17.48,39,39v146H0V39C0,17.48,17.48,0,39,0Z" style="fill: %s;"/>
<text transform="translate(22 146.5)" style="fill: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; font-size: 100px;">%s</text>
<text x="50%%" y="420.5" style="fill: #66757f; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; font-size: 256px;text-anchor: middle">%d</text>
<text transform="translate(331.03 148.44)" style="fill: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; font-size: 71.18px;">%d</text>
</svg>
`, colorScheme.Primary, dateInfo["month"], dateInfo["day"], dateInfo["year"])
}
// Type 3: 显示年月
func generateTypeThreeSVG(color string, lang string, dateInfo map[string]interface{}) string {
colorScheme := getColorScheme(color)
return fmt.Sprintf(`
<svg id="dynamic_icon_type3" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 512">
<path class="cls-6" d="M512,447.5c0,32-25,57-57,57H57c-32,0-57-25-57-57V120.5c0-31,25-57,57-57h398c32,0,57,26,57,57v327Z" style="fill: #ecf2f7;"/>
<path class="cls-1" d="M39,0h434c21.5,0,39,17.5,39,39v146H0V39C0,17.5,17.5,0,39,0Z" style="fill: %s;"/>
<g style="fill: %s;">
<circle cx="468.5" cy="135" r="14"/>
<circle cx="468.5" cy="93" r="14"/>
<circle cx="425.5" cy="135" r="14"/>
<circle cx="425.5" cy="93" r="14"/>
<circle cx="382.5" cy="135" r="14"/>
<circle cx="382.5" cy="93" r="14"/>
</g>
<text transform="translate(22 146.5)" style="fill: #fff;font-size: 120px; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; ">%d</text>
<text x="50%%" y="410.5" style="fill: #66757f;font-size: 200px;text-anchor: middle;font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; ">%s</text>
</svg>
`, colorScheme.Primary, colorScheme.Secondary, dateInfo["year"], dateInfo["month"])
}
// Type 4: 仅显示年
func generateTypeFourSVG(color string, lang string, dateInfo map[string]interface{}) string {
colorScheme := getColorScheme(color)
return fmt.Sprintf(`
<svg id="dynamic_icon_type4" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 512">
<path class="cls-6" d="M512,447.5c0,32-25,57-57,57H57c-32,0-57-25-57-57V120.5c0-31,25-57,57-57h398c32,0,57,26,57,57v327Z" style="fill: #ecf2f7;"/>
<path class="cls-1" d="M39,0h434c21.5,0,39,17.5,39,39v146H0V39C0,17.5,17.5,0,39,0Z" style="fill: %s;"/>
<g style="fill: %s;">
<circle cx="468.5" cy="135" r="14"/>
<circle cx="468.5" cy="93" r="14"/>
<circle cx="425.5" cy="135" r="14"/>
<circle cx="425.5" cy="93" r="14"/>
<circle cx="382.5" cy="135" r="14"/>
<circle cx="382.5" cy="93" r="14"/>
</g>
<text x="50%%" y="410.5" style="fill: #66757f;font-size: 180px;text-anchor: middle;font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; ">%d</text>
</svg>
`, colorScheme.Primary, colorScheme.Secondary, dateInfo["year"])
}
// Type 5:: 显示周数
func generateTypeFiveSVG(color string, lang string, dateInfo map[string]interface{}) string {
colorScheme := getColorScheme(color)
return fmt.Sprintf(`
<svg id="dynamic_icon_type5" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 512">
<path class="cls-6" d="M512,447.5c0,32-25,57-57,57H57c-32,0-57-25-57-57V120.5c0-31,25-57,57-57h398c32,0,57,26,57,57v327Z" style="fill: #ecf2f7;"/>
<path class="cls-1" d="M39,0h434c21.5,0,39,17.5,39,39v146H0V39C0,17.5,17.5,0,39,0Z" style="fill: %s;"/>
<g style="fill: %s;">
<circle cx="468.5" cy="135" r="14"/>
<circle cx="468.5" cy="93" r="14"/>
<circle cx="425.5" cy="135" r="14"/>
<circle cx="425.5" cy="93" r="14"/>
<circle cx="382.5" cy="135" r="14"/>
<circle cx="382.5" cy="93" r="14"/>
</g>
<text transform="translate(22 146.5)" style="fill: #fff;font-size: 120px; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; ">%d</text>
<text x="50%%" y="410.5" style="fill: #66757f;font-size: 200px;text-anchor: middle;font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; ">%s</text>
</svg>
`, colorScheme.Primary, colorScheme.Secondary, dateInfo["year"], dateInfo["week"])
}
// Type 6: 仅显示星期
func generateTypeSixSVG(color string, lang string, weekdayType string, dateInfo map[string]interface{}) string {
weekday := dateInfo["weekday"].(string)
isWeekend := dateInfo["isWeekend"].(bool)
// 如果不设置颜色,周末默认使用蓝色,工作日默认使用红色
var colorScheme ColorScheme
if color == "" {
if isWeekend {
colorScheme = colorSchemes["blue"]
} else {
colorScheme = colorSchemes["red"]
}
} else {
colorScheme = getColorScheme(color)
}
// 动态变化字体大小
var fontSize float64
switch lang {
case "zh_CN", "zh_CHT":
fontSize = 460 / float64(len([]rune(weekday)))
default:
switch weekdayType {
case "1":
fontSize = 690 / float64(len([]rune(weekday)))
case "2":
fontSize = 600 / float64(len([]rune(weekday)))
case "3":
fontSize = 720 / float64(len([]rune(weekday)))
case "4":
fontSize = 630 / float64(len([]rune(weekday)))
default:
fontSize = 750 / float64(len([]rune(weekday)))
}
}
return fmt.Sprintf(`
<svg id="dynamic_icon_type6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path id="center" d="M512,447.5c0,32-25,57-57,57H57c-32,0-57-25-57-57V120.5c0-31,25-57,57-57h398c32,0,57,26,57,57v327Z" style="fill: #ecf2f7;"/>
<path id="top" d="M39,0h434c21.5,0,39,14,39,31.2v116.8H0V31.2C0,14,17.5,0,39,0Z" style="fill: %s;"/>
<g id="cirle" style="fill: %s;">
<circle cx="468.5" cy="113.5" r="14"/>
<circle cx="468.5" cy="71.5" r="14"/>
<circle cx="425.5" cy="113.5" r="14"/>
<circle cx="425.5" cy="71.5" r="14"/>
<circle cx="382.5" cy="113.5" r="14"/>
<circle cx="382.5" cy="71.5" r="14"/>
</g>
<text id="weekday" x="50%%" y="65%%" style="fill: %s; font-size: %.2fpx; text-anchor: middle; dominant-baseline:middle; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei';">%s</text>
</svg>`, colorScheme.Primary, colorScheme.Secondary, colorScheme.Primary, fontSize, weekday)
}
// Type7: 倒数日
func generateTypeSevenSVG(color string, lang string, dateInfo map[string]interface{}) string {
colorScheme := getColorScheme(color)
diffDays := dateInfo["countDown"].(int)
var tipText, diffDaysText string
// 设置输出字符
switch {
case diffDays == 0:
switch lang {
case "zh_CN", "zh_CHT":
tipText = "今天"
default:
tipText = "Today"
}
diffDaysText = "--"
case diffDays > 0:
switch lang {
case "zh_CN":
tipText = "还有"
case "zh_CHT":
tipText = "還有"
default:
tipText = "Left"
}
diffDaysText = fmt.Sprintf("%d", diffDays)
default:
switch lang {
case "zh_CN":
tipText = "已过"
case "zh_CHT":
tipText = "已過"
default:
tipText = "Past"
}
absDiffDays := -diffDays
diffDaysText = fmt.Sprintf("%d", absDiffDays)
}
var dayStr string
switch lang {
case "zh_CN", "zh_CHT":
dayStr = "天"
default:
dayStr = "days"
}
// 动态变化字体大小
var fontSize float64
switch {
case len(diffDaysText) <= 3:
fontSize = 240
case len(diffDaysText) == 4:
fontSize = 190
case len(diffDaysText) == 5:
fontSize = 140
case len(diffDaysText) >= 6:
fontSize = 780 / float64(len(diffDaysText))
}
return fmt.Sprintf(`
<svg id="dynamic_icon_type7" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path id="bottom" d="M512,447.5c0,32-25,57-57,57H57c-32,0-57-25-57-57V120.5c0-31,25-57,57-57h398c32,0,57,26,57,57v327Z" style="fill: #ecf2f7;"/>
<path id="top" d="M39,0h434c21.52,0,39,17.48,39,39v146H0V39C0,17.48,17.48,0,39,0Z" style="fill: %s;"/>
<text id="year" transform="translate(46.1 78.92)" style="fill: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; font-size: 60px;">%d</text>
<text id="day" transform="translate(43.58 148.44)" style="fill: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; font-size: 60px;">%s</text>
<text id="passStr" transform="translate(400 148.44)" style="fill: #fff; text-anchor: middle;font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; font-size: 71.18px;">%s</text>
<text id="diffDays" x="50%%" y="65%%" style="font-size: %.0fpx; fill: #66757f; text-anchor: middle; dominant-baseline:middle;font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; ">%s</text>
<text id="dayStr" x="50%%" y="472.5" style="font-size: 64px; text-anchor: middle; fill: #66757f; font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei';">%s</text>
</svg>`, colorScheme.Primary, dateInfo["year"], dateInfo["date"], tipText, fontSize, diffDaysText, dayStr)
}
// Type 8: 文字图标
func generateTypeEightSVG(color, content string) string {
colorScheme := getColorScheme(color)
// 动态变化字体大小
isChinese := regexp.MustCompile(`[\p{Han}]`).MatchString(content)
var fontSize float64
if isChinese {
switch {
case len([]rune(content)) == 1:
fontSize = 320
default:
fontSize = 480 / float64(len([]rune(content)))
}
} else {
switch {
case len([]rune(content)) == 1:
fontSize = 480
case len([]rune(content)) == 2:
fontSize = 300
case len([]rune(content)) == 3:
fontSize = 240
default:
fontSize = 750 / float64(len([]rune(content)))
}
}
// 当内容为单个字符时,一些小写字母需要调整文字位置(暂时没法批量解决)
dy := "0%"
if len([]rune(content)) == 1 {
switch content {
case "g", "p", "y", "q":
dy = "-10%"
case "j":
dy = "-5%"
default:
dy = "0%"
}
}
return fmt.Sprintf(`
<svg id="dynamic_icon_type8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M39,0h434c20.97,0,38,17.03,38,38v412c0,33.11-26.89,60-60,60H60c-32.56,0-59-26.44-59-59V38C1,17.03,18.03,0,39,0Z" style="fill: %s;"/>
<text x="50%%" y="55%%" dy="%s" style="font-size: %.2fpx; fill: #fff; text-anchor: middle; dominant-baseline:middle;font-family: -apple-system, BlinkMacSystemFont, 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft YaHei'; ">%s</text>
</svg>
`, colorScheme.Primary, dy, fontSize, content)
}