2026-06-11 15:46:19 +08:00
|
|
|
|
package services
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
2026-06-23 15:33:22 +08:00
|
|
|
|
"image"
|
|
|
|
|
|
"image/jpeg"
|
|
|
|
|
|
"image/png"
|
2026-06-11 15:46:19 +08:00
|
|
|
|
"os"
|
|
|
|
|
|
"path/filepath"
|
2026-06-17 08:05:20 +08:00
|
|
|
|
"strings"
|
2026-06-23 15:33:22 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/pdfcpu/pdfcpu/pkg/api"
|
|
|
|
|
|
"github.com/signintech/gopdf"
|
2026-06-11 15:46:19 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type PDFService struct{}
|
|
|
|
|
|
|
|
|
|
|
|
func NewPDFService() *PDFService {
|
|
|
|
|
|
return &PDFService{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type PDFInfo struct {
|
|
|
|
|
|
Pages int `json:"pages"`
|
|
|
|
|
|
Title string `json:"title"`
|
|
|
|
|
|
Author string `json:"author"`
|
|
|
|
|
|
FilePath string `json:"filePath"`
|
|
|
|
|
|
Size int64 `json:"size"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *PDFService) GetPDFInfo(filePath string) (*PDFInfo, error) {
|
|
|
|
|
|
fileInfo, err := os.Stat(filePath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("获取文件信息失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
pages, err := api.PageCountFile(filePath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
pages = 1
|
2026-06-17 08:05:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-11 15:46:19 +08:00
|
|
|
|
return &PDFInfo{
|
2026-06-17 08:05:20 +08:00
|
|
|
|
Pages: pages,
|
|
|
|
|
|
Title: filepath.Base(filePath),
|
|
|
|
|
|
Author: "",
|
2026-06-11 15:46:19 +08:00
|
|
|
|
FilePath: filePath,
|
|
|
|
|
|
Size: fileInfo.Size(),
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func safeFileOp(inputPath, outputPath string, op func(tmpPath string) error) error {
|
|
|
|
|
|
tmpDir := os.TempDir()
|
|
|
|
|
|
tmpFile := filepath.Join(tmpDir, "xk_pdf_"+fmt.Sprintf("%d", time.Now().UnixNano())+".pdf")
|
|
|
|
|
|
defer os.Remove(tmpFile)
|
2026-06-11 15:46:19 +08:00
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
if err := op(tmpFile); err != nil {
|
|
|
|
|
|
return err
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
data, err := os.ReadFile(tmpFile)
|
2026-06-11 15:46:19 +08:00
|
|
|
|
if err != nil {
|
2026-06-23 15:33:22 +08:00
|
|
|
|
return fmt.Errorf("read temp file: %v", err)
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
if err := os.WriteFile(outputPath, data, 0644); err != nil {
|
|
|
|
|
|
return fmt.Errorf("write output file (may be locked by another process): %v", err)
|
|
|
|
|
|
}
|
2026-06-11 15:46:19 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func (s *PDFService) OptimizePDF(inputPath, outputPath, level string) error {
|
|
|
|
|
|
if outputPath == "" {
|
|
|
|
|
|
outputPath = GetOutputPath(inputPath, "_optimized")
|
|
|
|
|
|
}
|
2026-06-30 12:30:49 +08:00
|
|
|
|
|
|
|
|
|
|
// 记录原始文件大小
|
|
|
|
|
|
originalInfo, err := os.Stat(inputPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("获取原文件信息失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
originalSize := originalInfo.Size()
|
|
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
conf := api.LoadConfiguration()
|
2026-06-30 12:30:49 +08:00
|
|
|
|
|
|
|
|
|
|
err = safeFileOp(inputPath, outputPath, func(tmpPath string) error {
|
2026-06-23 15:33:22 +08:00
|
|
|
|
return api.OptimizeFile(inputPath, tmpPath, conf)
|
|
|
|
|
|
})
|
2026-06-30 12:30:49 +08:00
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查优化后的文件大小
|
|
|
|
|
|
optimizedInfo, err := os.Stat(outputPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("获取优化文件信息失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
optimizedSize := optimizedInfo.Size()
|
|
|
|
|
|
|
|
|
|
|
|
// 计算减少的百分比
|
|
|
|
|
|
reduction := float64(originalSize-optimizedSize) / float64(originalSize) * 100
|
|
|
|
|
|
|
|
|
|
|
|
// 如果优化后文件变大或没有减少,删除输出文件并返回提示
|
|
|
|
|
|
if optimizedSize >= originalSize {
|
|
|
|
|
|
os.Remove(outputPath)
|
|
|
|
|
|
return fmt.Errorf("PDF已是最优状态,无法进一步压缩。原始大小: %s, 优化后: %s",
|
|
|
|
|
|
formatSize(originalSize), formatSize(optimizedSize))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录优化效果
|
|
|
|
|
|
fmt.Printf("PDF优化: %s -> %s (%.1f%% 减少)\n",
|
|
|
|
|
|
formatSize(originalSize),
|
|
|
|
|
|
formatSize(optimizedSize),
|
|
|
|
|
|
reduction)
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
2026-06-23 15:33:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *PDFService) SplitPDF(inputPath, outputDir string, pageRanges string) ([]string, error) {
|
2026-06-11 15:46:19 +08:00
|
|
|
|
if outputDir == "" {
|
|
|
|
|
|
outputDir = filepath.Dir(inputPath)
|
|
|
|
|
|
}
|
2026-06-23 15:33:22 +08:00
|
|
|
|
conf := api.LoadConfiguration()
|
2026-06-11 15:46:19 +08:00
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
if pageRanges != "" {
|
|
|
|
|
|
pageSelection, err := api.ParsePageSelection(pageRanges)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("解析页码范围失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
pageCount, err := api.PageCountFile(inputPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("获取页数失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
pageNrs, err := api.PagesForPageCollection(pageCount, pageSelection)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("解析页码失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
baseName := GetBaseName(inputPath)
|
|
|
|
|
|
err = api.SplitByPageNrFile(inputPath, outputDir, pageNrs, conf)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("分割PDF失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
var results []string
|
|
|
|
|
|
entries, _ := os.ReadDir(outputDir)
|
|
|
|
|
|
for _, e := range entries {
|
|
|
|
|
|
if !e.IsDir() && strings.HasSuffix(e.Name(), ".pdf") && strings.Contains(e.Name(), baseName) {
|
|
|
|
|
|
results = append(results, filepath.Join(outputDir, e.Name()))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return results, nil
|
2026-06-17 08:05:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
baseName := GetBaseName(inputPath)
|
|
|
|
|
|
err := api.SplitFile(inputPath, outputDir, 1, conf)
|
2026-06-11 15:46:19 +08:00
|
|
|
|
if err != nil {
|
2026-06-23 15:33:22 +08:00
|
|
|
|
return nil, fmt.Errorf("分割PDF失败: %v", err)
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
2026-06-23 15:33:22 +08:00
|
|
|
|
var results []string
|
|
|
|
|
|
entries, _ := os.ReadDir(outputDir)
|
|
|
|
|
|
for _, e := range entries {
|
|
|
|
|
|
if !e.IsDir() && strings.HasSuffix(e.Name(), ".pdf") && strings.HasPrefix(e.Name(), baseName) {
|
|
|
|
|
|
results = append(results, filepath.Join(outputDir, e.Name()))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return results, nil
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *PDFService) MergePDFs(inputPaths []string, outputPath string) error {
|
|
|
|
|
|
if outputPath == "" {
|
|
|
|
|
|
outputPath = filepath.Join(filepath.Dir(inputPaths[0]), "merged.pdf")
|
|
|
|
|
|
}
|
2026-06-23 15:33:22 +08:00
|
|
|
|
conf := api.LoadConfiguration()
|
|
|
|
|
|
return api.MergeCreateFile(inputPaths, outputPath, false, conf)
|
|
|
|
|
|
}
|
2026-06-11 15:46:19 +08:00
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func (s *PDFService) RotatePDF(inputPath, outputPath string, rotation int) error {
|
|
|
|
|
|
if outputPath == "" {
|
|
|
|
|
|
outputPath = GetOutputPath(inputPath, "_rotated")
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
2026-06-23 15:33:22 +08:00
|
|
|
|
conf := api.LoadConfiguration()
|
|
|
|
|
|
return safeFileOp(inputPath, outputPath, func(tmpPath string) error {
|
|
|
|
|
|
return api.RotateFile(inputPath, tmpPath, rotation, nil, conf)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-06-11 15:46:19 +08:00
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func (s *PDFService) AddWatermark(inputPath, outputPath, text string) error {
|
|
|
|
|
|
if outputPath == "" {
|
|
|
|
|
|
outputPath = GetOutputPath(inputPath, "_watermarked")
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
2026-06-23 15:33:22 +08:00
|
|
|
|
conf := api.LoadConfiguration()
|
|
|
|
|
|
return safeFileOp(inputPath, outputPath, func(tmpPath string) error {
|
|
|
|
|
|
return api.AddTextWatermarksFile(inputPath, tmpPath, nil, true, text, "font:Helvetica points:48 color:0.8,0.8,0.8 rotation:45", conf)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-06-11 15:46:19 +08:00
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func (s *PDFService) ExtractText(inputPath string) (string, error) {
|
2026-06-30 12:30:49 +08:00
|
|
|
|
// 检查是否应使用nofitz模式
|
|
|
|
|
|
if os.Getenv("XK_NO_FITZ") == "1" {
|
|
|
|
|
|
return extractTextNoFitz(inputPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试fitz,失败则降级
|
|
|
|
|
|
text, err := extractTextWithFitz(inputPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 如果是MuPDF相关错误,切换到nofitz模式
|
|
|
|
|
|
errMsg := err.Error()
|
|
|
|
|
|
if contains(errMsg, []string{"MuPDF", "DLL", "libmupdf", "not found"}) {
|
|
|
|
|
|
os.Setenv("XK_NO_FITZ", "1")
|
|
|
|
|
|
return extractTextNoFitz(inputPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
return text, nil
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func (s *PDFService) PDFToImages(inputPath, outputDir, format string, dpi float64) ([]string, error) {
|
2026-06-30 12:30:49 +08:00
|
|
|
|
// 检查是否应使用nofitz模式
|
|
|
|
|
|
if os.Getenv("XK_NO_FITZ") == "1" {
|
|
|
|
|
|
return pdfToImagesNoFitz(inputPath, outputDir, format, dpi)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
images, err := pdfToImagesWithFitz(inputPath, outputDir, format, dpi)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 如果是MuPDF相关错误,切换到nofitz模式
|
|
|
|
|
|
errMsg := err.Error()
|
|
|
|
|
|
if contains(errMsg, []string{"MuPDF", "DLL", "libmupdf", "not found"}) {
|
|
|
|
|
|
os.Setenv("XK_NO_FITZ", "1")
|
|
|
|
|
|
return pdfToImagesNoFitz(inputPath, outputDir, format, dpi)
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return images, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// contains 检查字符串是否包含任意一个子串
|
|
|
|
|
|
func contains(s string, substrs []string) bool {
|
|
|
|
|
|
for _, substr := range substrs {
|
|
|
|
|
|
if len(s) >= len(substr) {
|
|
|
|
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
|
|
|
|
if s[i:i+len(substr)] == substr {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
2026-06-23 15:33:22 +08:00
|
|
|
|
}
|
2026-06-11 15:46:19 +08:00
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func savePNG(img image.Image, path string) error {
|
|
|
|
|
|
f, err := os.Create(path)
|
2026-06-11 15:46:19 +08:00
|
|
|
|
if err != nil {
|
2026-06-23 15:33:22 +08:00
|
|
|
|
return err
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
2026-06-23 15:33:22 +08:00
|
|
|
|
defer f.Close()
|
|
|
|
|
|
return png.Encode(f, img)
|
|
|
|
|
|
}
|
2026-06-11 15:46:19 +08:00
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func saveJPEG(img image.Image, path string, quality int) error {
|
|
|
|
|
|
f, err := os.Create(path)
|
2026-06-11 15:46:19 +08:00
|
|
|
|
if err != nil {
|
2026-06-23 15:33:22 +08:00
|
|
|
|
return err
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
2026-06-23 15:33:22 +08:00
|
|
|
|
defer f.Close()
|
|
|
|
|
|
return jpeg.Encode(f, img, &jpeg.Options{Quality: quality})
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func (s *PDFService) TextToPDF(inputPath, outputPath string) error {
|
2026-06-11 15:46:19 +08:00
|
|
|
|
if outputPath == "" {
|
2026-06-23 15:33:22 +08:00
|
|
|
|
outputPath = ChangeExtension(inputPath, ".pdf")
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
2026-06-23 15:33:22 +08:00
|
|
|
|
data, err := os.ReadFile(inputPath)
|
2026-06-11 15:46:19 +08:00
|
|
|
|
if err != nil {
|
2026-06-23 15:33:22 +08:00
|
|
|
|
return fmt.Errorf("读取文本文件失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
text := string(data)
|
|
|
|
|
|
if strings.TrimSpace(text) == "" {
|
|
|
|
|
|
text = "(空文档)"
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
2026-06-23 15:33:22 +08:00
|
|
|
|
return createCJKPDF(outputPath, text)
|
|
|
|
|
|
}
|
2026-06-11 15:46:19 +08:00
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func (s *PDFService) MarkdownToPDF(inputPath, outputPath string) error {
|
|
|
|
|
|
if outputPath == "" {
|
|
|
|
|
|
outputPath = ChangeExtension(inputPath, ".pdf")
|
|
|
|
|
|
}
|
|
|
|
|
|
data, err := os.ReadFile(inputPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("读取Markdown文件失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
lines := strings.Split(string(data), "\n")
|
|
|
|
|
|
pdf := gopdf.GoPdf{}
|
|
|
|
|
|
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
|
|
|
|
|
|
fontPaths := []string{
|
|
|
|
|
|
"C:\\Windows\\Fonts\\msyh.ttc",
|
|
|
|
|
|
"C:\\Windows\\Fonts\\simhei.ttf",
|
|
|
|
|
|
"C:\\Windows\\Fonts\\simsun.ttc",
|
|
|
|
|
|
}
|
|
|
|
|
|
var fontLoaded bool
|
|
|
|
|
|
for _, fp := range fontPaths {
|
|
|
|
|
|
if _, err := os.Stat(fp); err == nil {
|
|
|
|
|
|
if err := pdf.AddTTFFont("cjk", fp); err == nil {
|
|
|
|
|
|
fontLoaded = true
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
pdf.AddPage()
|
|
|
|
|
|
y := 50.0
|
|
|
|
|
|
pageHeight := 800.0
|
|
|
|
|
|
marginLeft := 50.0
|
|
|
|
|
|
for _, line := range lines {
|
|
|
|
|
|
if y > pageHeight-30 {
|
|
|
|
|
|
pdf.AddPage()
|
|
|
|
|
|
y = 50.0
|
|
|
|
|
|
}
|
|
|
|
|
|
trimmed := strings.TrimSpace(line)
|
|
|
|
|
|
if strings.HasPrefix(trimmed, "# ") {
|
|
|
|
|
|
if fontLoaded {
|
|
|
|
|
|
pdf.SetFont("cjk", "", 20)
|
|
|
|
|
|
}
|
|
|
|
|
|
y += 4
|
|
|
|
|
|
} else if strings.HasPrefix(trimmed, "## ") {
|
|
|
|
|
|
if fontLoaded {
|
|
|
|
|
|
pdf.SetFont("cjk", "", 16)
|
|
|
|
|
|
}
|
|
|
|
|
|
y += 3
|
|
|
|
|
|
} else if strings.HasPrefix(trimmed, "### ") {
|
|
|
|
|
|
if fontLoaded {
|
|
|
|
|
|
pdf.SetFont("cjk", "", 14)
|
|
|
|
|
|
}
|
|
|
|
|
|
y += 2
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if fontLoaded {
|
|
|
|
|
|
pdf.SetFont("cjk", "", 11)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
displayLine := trimmed
|
|
|
|
|
|
displayLine = strings.ReplaceAll(displayLine, "**", "")
|
|
|
|
|
|
displayLine = strings.ReplaceAll(displayLine, "*", "")
|
|
|
|
|
|
displayLine = strings.ReplaceAll(displayLine, "`", "")
|
|
|
|
|
|
displayLine = strings.TrimLeft(displayLine, "# ")
|
|
|
|
|
|
if displayLine == "" {
|
|
|
|
|
|
y += 8
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
wrappedLines, err := pdf.SplitTextWithWordWrap(displayLine, 500)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
wrappedLines = []string{displayLine}
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, wl := range wrappedLines {
|
|
|
|
|
|
if y > pageHeight-30 {
|
|
|
|
|
|
pdf.AddPage()
|
|
|
|
|
|
y = 50.0
|
|
|
|
|
|
}
|
|
|
|
|
|
pdf.SetXY(marginLeft, y)
|
|
|
|
|
|
pdf.Cell(nil, wl)
|
|
|
|
|
|
y += 16
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return pdf.WritePdf(outputPath)
|
|
|
|
|
|
}
|
2026-06-11 15:46:19 +08:00
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func (s *PDFService) HTMLToPDF(inputPath, outputPath string) error {
|
|
|
|
|
|
if outputPath == "" {
|
|
|
|
|
|
outputPath = ChangeExtension(inputPath, ".pdf")
|
|
|
|
|
|
}
|
|
|
|
|
|
data, err := os.ReadFile(inputPath)
|
2026-06-11 15:46:19 +08:00
|
|
|
|
if err != nil {
|
2026-06-23 15:33:22 +08:00
|
|
|
|
return fmt.Errorf("读取HTML文件失败: %v", err)
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|
2026-06-23 15:33:22 +08:00
|
|
|
|
text := stripHTML(string(data))
|
|
|
|
|
|
if strings.TrimSpace(text) == "" {
|
|
|
|
|
|
text = "(空文档)"
|
|
|
|
|
|
}
|
|
|
|
|
|
return createCJKPDF(outputPath, text)
|
|
|
|
|
|
}
|
2026-06-11 15:46:19 +08:00
|
|
|
|
|
2026-06-23 15:33:22 +08:00
|
|
|
|
func stripHTML(html string) string {
|
|
|
|
|
|
var result strings.Builder
|
|
|
|
|
|
inTag := false
|
|
|
|
|
|
for i := 0; i < len(html); i++ {
|
|
|
|
|
|
if html[i] == '<' {
|
|
|
|
|
|
inTag = true
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if html[i] == '>' {
|
|
|
|
|
|
inTag = false
|
|
|
|
|
|
if i+1 < len(html) && html[i+1] != '\n' {
|
|
|
|
|
|
result.WriteByte('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if !inTag {
|
|
|
|
|
|
result.WriteByte(html[i])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
text := result.String()
|
|
|
|
|
|
lines := strings.Split(text, "\n")
|
|
|
|
|
|
var cleaned []string
|
|
|
|
|
|
for _, line := range lines {
|
|
|
|
|
|
line = strings.TrimSpace(line)
|
|
|
|
|
|
if line != "" {
|
|
|
|
|
|
cleaned = append(cleaned, line)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return strings.Join(cleaned, "\n")
|
2026-06-11 15:46:19 +08:00
|
|
|
|
}
|