From 6220e863e31c23ee73b8812f4977243f2a68c9e5 Mon Sep 17 00:00:00 2001 From: ywlmac Date: Wed, 13 May 2026 15:17:43 +0800 Subject: [PATCH] fix: support windows certificate fonts --- SDK_USAGE.md | 3 ++ pkg/zxchainsdk/certificate.go | 48 ++++++++++++++++++++++-------- pkg/zxchainsdk/certificate_flow.go | 4 ++- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/SDK_USAGE.md b/SDK_USAGE.md index d64216f..f7e0dd9 100644 --- a/SDK_USAGE.md +++ b/SDK_USAGE.md @@ -26,6 +26,7 @@ if err != nil { - `COS_PUBLIC_BASE_URL`:证书公开访问基础地址。 - `COS_SECRET_ID`:COS 访问密钥 ID。 - `COS_SECRET_KEY`:COS 访问密钥。 +- `ZXCHAIN_CERTIFICATE_FONT_PATH`:可选。证书绘制使用的中文字体路径;Windows 可用 `C:/Windows/Fonts/msyh.ttc`。 ## 存证和生成证书并一起返回 @@ -58,6 +59,7 @@ result, err := client.CreateSimpleHashAttestationCertificate(context.Background( CompletedAt: "2026.5.11 15:20:01", WorkPreviewURL: "https://your-cdn/path/work-preview.jpg", EvidenceHash: "作品hash", //可选参数,可以不传 + FontPath: "C:/Windows/Fonts/msyh.ttc", // 可选;Windows 推荐微软雅黑,或配置 ZXCHAIN_CERTIFICATE_FONT_PATH CertificateStorage: &zxchainsdk.COSCertificateStorage{ //可选参数,可以不传 BucketURL: os.Getenv("COS_BUCKET_URL"), @@ -88,6 +90,7 @@ fmt.Println("zxChainCertURL:", result.ZXChainCertURL) - `CompletedAt`:作品完成时间。 - `WorkPreviewURL`:作品图片 URL。 - `EvidenceHash`:可选。作品文件哈希;不传时 SDK 会从 `WorkPreviewURL` 下载内容并自行计算hash值。 +- `FontPath`:可选。中文字体路径;不传时 SDK 会自动查找 Windows/macOS/Linux 常见中文字体,也可以用 `ZXCHAIN_CERTIFICATE_FONT_PATH` 环境变量指定。Windows 常见可用路径:`C:/Windows/Fonts/msyh.ttc`、`C:/Windows/Fonts/simhei.ttf`、`C:/Windows/Fonts/simsun.ttc`。 - `CertificateStorage`:可选。COS 上传配置。 返回字段: diff --git a/pkg/zxchainsdk/certificate.go b/pkg/zxchainsdk/certificate.go index e5de0b1..a6d469b 100644 --- a/pkg/zxchainsdk/certificate.go +++ b/pkg/zxchainsdk/certificate.go @@ -393,16 +393,39 @@ func containImage(src image.Image, boxWidth, boxHeight int) image.Image { func resolveCertificateFont(explicitPath string) (string, error) { if explicitPath != "" { - if _, err := os.Stat(explicitPath); err != nil { - return "", err - } - if _, err := gg.LoadFontFace(explicitPath, 25); err != nil { - return "", fmt.Errorf("load certificate font %s: %w", explicitPath, err) + return validateCertificateFont(explicitPath) + } + + if envPath := os.Getenv("ZXCHAIN_CERTIFICATE_FONT_PATH"); envPath != "" { + return validateCertificateFont(envPath) + } + + candidates := certificateFontCandidates() + for _, candidate := range candidates { + if fontPath, err := validateCertificateFont(candidate); err == nil { + return fontPath, nil } - return explicitPath, nil } + return "", errors.New("missing certificate font, set HashCertificateRequest.FontPath or ZXCHAIN_CERTIFICATE_FONT_PATH to a Chinese TTF/TTC/OTF font") +} + +func validateCertificateFont(fontPath string) (string, error) { + if _, err := os.Stat(fontPath); err != nil { + return "", err + } + if _, err := gg.LoadFontFace(fontPath, 25); err != nil { + return "", fmt.Errorf("load certificate font %s: %w", fontPath, err) + } + return fontPath, nil +} +func certificateFontCandidates() []string { candidates := []string{ + "C:/Windows/Fonts/msyh.ttc", + "C:/Windows/Fonts/msyhbd.ttc", + "C:/Windows/Fonts/simhei.ttf", + "C:/Windows/Fonts/simsun.ttc", + "C:/Windows/Fonts/simkai.ttf", "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", "/System/Library/Fonts/Supplemental/Arial.ttf", "/System/Library/Fonts/Supplemental/Microsoft Sans Serif.ttf", @@ -414,15 +437,14 @@ func resolveCertificateFont(explicitPath string) (string, error) { "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc", } - for _, candidate := range candidates { - if _, err := os.Stat(candidate); err != nil { - continue - } - if _, err := gg.LoadFontFace(candidate, 25); err == nil { - return candidate, nil + + if windowsDir := firstNonEmpty(os.Getenv("WINDIR"), os.Getenv("SystemRoot")); windowsDir != "" { + windowsDir = strings.TrimRight(windowsDir, `\/`) + for _, name := range []string{"msyh.ttc", "msyhbd.ttc", "simhei.ttf", "simsun.ttc", "simkai.ttf"} { + candidates = append(candidates, windowsDir+"/Fonts/"+name) } } - return "", errors.New("missing certificate font, set HashCertificateRequest.FontPath to a Chinese TTF/TTC font") + return candidates } func certificateImageObjectKey(evidenceID, certNo string) string { diff --git a/pkg/zxchainsdk/certificate_flow.go b/pkg/zxchainsdk/certificate_flow.go index 1ab2674..a269139 100644 --- a/pkg/zxchainsdk/certificate_flow.go +++ b/pkg/zxchainsdk/certificate_flow.go @@ -62,6 +62,7 @@ type SimpleHashAttestationCertificateRequest struct { CompletedAt string `json:"completed_at"` // CompletedAt 表示作品完成时间 WorkPreviewURL string `json:"work_preview_url"` // WorkPreviewURL 表示作品预览图 URL EvidenceHash string `json:"evidence_hash,omitempty"` // EvidenceHash 表示作品文件哈希,可选。如果不传,SDK 将从 WorkPreviewURL 下载并计算哈希 + FontPath string `json:"font_path,omitempty"` // FontPath 表示用于绘制证书的中文字体路径,可选 // 可选:如果不传,将使用内置默认值 CertificateStorage *COSCertificateStorage `json:"certificate_storage,omitempty"` // CertificateStorage 表示 COS 上传配置 @@ -204,6 +205,7 @@ func (c *SDKClient) CreateSimpleHashAttestationCertificate(ctx context.Context, CompletedAt: req.CompletedAt, WorkPreviewURL: req.WorkPreviewURL, EvidenceHash: evidenceHash, + FontPath: req.FontPath, CertificateStorage: req.CertificateStorage, } @@ -372,7 +374,7 @@ func UploadCertificatePNGToCOS(ctx context.Context, pngBytes []byte, defaultObje _, err = client.Object.Put(ctx, objectKey, bytes.NewReader(pngBytes), &cos.ObjectPutOptions{ ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ ContentType: "image/png", - ContentLength: int64(len(pngBytes)), + ContentLength: len(pngBytes), CacheControl: "public, max-age=31536000, immutable", }, })