225 lines
4.4 KiB
Go
225 lines
4.4 KiB
Go
package services
|
|
|
|
import (
|
|
"archive/zip"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/signintech/gopdf"
|
|
)
|
|
|
|
type WordService struct{}
|
|
|
|
func NewWordService() *WordService {
|
|
return &WordService{}
|
|
}
|
|
|
|
type WordInfo struct {
|
|
Title string `json:"title"`
|
|
Author string `json:"author"`
|
|
Pages int `json:"pages"`
|
|
Words int `json:"words"`
|
|
FilePath string `json:"filePath"`
|
|
Size int64 `json:"size"`
|
|
}
|
|
|
|
func (s *WordService) GetWordInfo(filePath string) (*WordInfo, error) {
|
|
fileInfo, err := os.Stat(filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取文件信息失败: %v", err)
|
|
}
|
|
|
|
text, _ := s.ExtractText(filePath)
|
|
words := len(strings.Fields(text))
|
|
|
|
return &WordInfo{
|
|
Title: filepath.Base(filePath),
|
|
Author: "",
|
|
Pages: 1,
|
|
Words: words,
|
|
FilePath: filePath,
|
|
Size: fileInfo.Size(),
|
|
}, nil
|
|
}
|
|
|
|
func (s *WordService) ConvertToPDF(inputPath, outputPath string) error {
|
|
if outputPath == "" {
|
|
outputPath = ChangeExtension(inputPath, ".pdf")
|
|
}
|
|
|
|
text, err := s.ExtractText(inputPath)
|
|
if err != nil {
|
|
return fmt.Errorf("提取Word文本失败: %v", err)
|
|
}
|
|
|
|
if strings.TrimSpace(text) == "" {
|
|
text = "(空文档)"
|
|
}
|
|
|
|
return createCJKPDF(outputPath, text)
|
|
}
|
|
|
|
func (s *WordService) ExtractText(inputPath string) (string, error) {
|
|
r, err := zip.OpenReader(inputPath)
|
|
if err != nil {
|
|
return extractPlainText(inputPath)
|
|
}
|
|
defer r.Close()
|
|
|
|
var docBody []byte
|
|
for _, f := range r.File {
|
|
if f.Name == "word/document.xml" {
|
|
rc, err := f.Open()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
docBody, err = readAll(rc)
|
|
rc.Close()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if docBody == nil {
|
|
return extractPlainText(inputPath)
|
|
}
|
|
|
|
text := extractXMLText(string(docBody))
|
|
return text, nil
|
|
}
|
|
|
|
func readAll(r interface{ Read([]byte) (int, error) }) ([]byte, error) {
|
|
buf := make([]byte, 0, 4096)
|
|
tmp := make([]byte, 1024)
|
|
for {
|
|
n, err := r.Read(tmp)
|
|
buf = append(buf, tmp[:n]...)
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
func extractPlainText(filePath string) (string, error) {
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func extractXMLText(xml string) string {
|
|
var result strings.Builder
|
|
inTag := false
|
|
|
|
for i := 0; i < len(xml); i++ {
|
|
if xml[i] == '<' {
|
|
inTag = true
|
|
if i+1 < len(xml) && xml[i+1] == '/' {
|
|
if result.Len() > 0 {
|
|
ch := result.String()
|
|
if !strings.HasSuffix(ch, "\n") && !strings.HasSuffix(ch, " ") {
|
|
result.WriteString(" ")
|
|
}
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if xml[i] == '>' {
|
|
inTag = false
|
|
continue
|
|
}
|
|
if !inTag {
|
|
result.WriteByte(xml[i])
|
|
}
|
|
}
|
|
|
|
text := result.String()
|
|
text = strings.ReplaceAll(text, "
", "\n")
|
|
text = strings.ReplaceAll(text, "&", "&")
|
|
text = strings.ReplaceAll(text, "<", "<")
|
|
text = strings.ReplaceAll(text, ">", ">")
|
|
text = strings.ReplaceAll(text, """, "\"")
|
|
text = strings.ReplaceAll(text, "'", "'")
|
|
text = strings.ReplaceAll(text, "\n ", "\n")
|
|
text = strings.TrimSpace(text)
|
|
|
|
var lines []string
|
|
for _, line := range strings.Split(text, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" {
|
|
lines = append(lines, line)
|
|
}
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
func createCJKPDF(outputPath, text string) error {
|
|
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",
|
|
"C:\\Windows\\Fonts\\msyhbd.ttc",
|
|
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
|
|
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
|
}
|
|
|
|
var fontLoaded bool
|
|
for _, fp := range fontPaths {
|
|
if _, err := os.Stat(fp); err == nil {
|
|
err = pdf.AddTTFFont("cjk", fp)
|
|
if err == nil {
|
|
fontLoaded = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
pdf.AddPage()
|
|
|
|
lines := strings.Split(text, "\n")
|
|
y := 50.0
|
|
pageHeight := 800.0
|
|
lineHeight := 16.0
|
|
marginLeft := 50.0
|
|
|
|
for _, line := range lines {
|
|
if y > pageHeight-30 {
|
|
pdf.AddPage()
|
|
y = 50.0
|
|
}
|
|
|
|
if fontLoaded {
|
|
pdf.SetFont("cjk", "", 11)
|
|
} else {
|
|
pdf.SetFont("helvetica", "", 11)
|
|
}
|
|
|
|
wrappedLines, err := pdf.SplitTextWithWordWrap(line, 500)
|
|
if err != nil {
|
|
wrappedLines = []string{line}
|
|
}
|
|
|
|
for _, wl := range wrappedLines {
|
|
if y > pageHeight-30 {
|
|
pdf.AddPage()
|
|
y = 50.0
|
|
}
|
|
pdf.SetXY(marginLeft, y)
|
|
pdf.Cell(nil, wl)
|
|
y += lineHeight
|
|
}
|
|
}
|
|
|
|
return pdf.WritePdf(outputPath)
|
|
}
|