// 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 . 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: MON,3: 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(` %s %d %s %d `, 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(` %s %d %d `, 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(` %d %s `, 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(` %d `, 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(` %d %s `, 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(` %s `, 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(` %d %s %s %s %s `, 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(` %s `, colorScheme.Primary, dy, fontSize, content) }