mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
feat(正式发布): 实现了OFD渲染和导出为PDF、图片、矢量图等
This commit is contained in:
+299
@@ -0,0 +1,299 @@
|
||||
// Copyright 2025 肖其顿 (XIAO QI DUN)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package ofdgo 首款原生、全平台兼容的纯 Go 语言 OFD 渲染库
|
||||
package ofdgo
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Reader OFD文件阅读器
|
||||
type Reader struct {
|
||||
Path string
|
||||
Zip *zip.Reader
|
||||
Closer io.Closer
|
||||
OFD *OFD
|
||||
RootDir string
|
||||
ResMap map[string]string
|
||||
fontCache map[string]*Font
|
||||
drawParamCache map[string]*DrawParam
|
||||
doc *Document
|
||||
}
|
||||
|
||||
// Close 关闭阅读器
|
||||
// 返回: error 错误信息
|
||||
func (r *Reader) Close() error {
|
||||
if r.Closer != nil {
|
||||
return r.Closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// initRoot 读取根节点信息
|
||||
// 返回: error 错误信息
|
||||
func (r *Reader) initRoot() error {
|
||||
data, err := r.readFile("OFD.xml")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read ofd.xml: %w", err)
|
||||
}
|
||||
var ofd OFD
|
||||
if err := xml.Unmarshal(data, &ofd); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal ofd.xml: %w", err)
|
||||
}
|
||||
r.OFD = &ofd
|
||||
return nil
|
||||
}
|
||||
|
||||
// readFile 读取压缩包内的文件
|
||||
// 入参: name 文件名
|
||||
// 返回: []byte 文件内容, error 错误信息
|
||||
func (r *Reader) readFile(name string) ([]byte, error) {
|
||||
name = strings.ReplaceAll(name, "\\", "/")
|
||||
for _, f := range r.Zip.File {
|
||||
if f.Name == name {
|
||||
return readZipFile(f)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("file not found: %s", name)
|
||||
}
|
||||
|
||||
// readZipFile 读取zip文件内容
|
||||
// 入参: f zip文件对象
|
||||
// 返回: []byte 文件内容, error 错误信息
|
||||
func readZipFile(f *zip.File) ([]byte, error) {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
return io.ReadAll(rc)
|
||||
}
|
||||
|
||||
// Doc 获取主文档结构
|
||||
// 返回: *Document 文档结构, error 错误信息
|
||||
func (r *Reader) Doc() (*Document, error) {
|
||||
if r.doc != nil {
|
||||
return r.doc, nil
|
||||
}
|
||||
if r.OFD == nil || len(r.OFD.DocBody) == 0 {
|
||||
return nil, fmt.Errorf("no docbody found")
|
||||
}
|
||||
docAttr := r.OFD.DocBody[0]
|
||||
docRootPath := docAttr.DocRoot
|
||||
r.RootDir = path.Dir(docRootPath)
|
||||
data, err := r.readFile(docRootPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var doc Document
|
||||
if err := xml.Unmarshal(data, &doc); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal document.xml: %w", err)
|
||||
}
|
||||
r.OFD.DocBody[0].DocInfo.DocID = "loaded"
|
||||
r.ResMap = make(map[string]string)
|
||||
r.fontCache = make(map[string]*Font)
|
||||
r.drawParamCache = make(map[string]*DrawParam)
|
||||
if doc.CommonData.DocumentRes != "" {
|
||||
r.loadRes(doc.CommonData.DocumentRes)
|
||||
}
|
||||
if doc.CommonData.PublicRes != "" {
|
||||
r.loadRes(doc.CommonData.PublicRes)
|
||||
}
|
||||
r.doc = &doc
|
||||
return &doc, nil
|
||||
}
|
||||
|
||||
// loadRes 加载资源文件
|
||||
// 入参: resPath 资源路径
|
||||
func (r *Reader) loadRes(resPath string) {
|
||||
if resPath == "" {
|
||||
return
|
||||
}
|
||||
fullPath := path.Join(r.RootDir, resPath)
|
||||
data, err := r.readFile(fullPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var res Res
|
||||
if err := xml.Unmarshal(data, &res); err != nil {
|
||||
return
|
||||
}
|
||||
baseLoc := res.BaseLoc
|
||||
for _, mm := range res.MultiMedias.MultiMedia {
|
||||
if mm.MediaFile != "" {
|
||||
p := strings.TrimSpace(mm.MediaFile)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
dir := path.Dir(resPath)
|
||||
if baseLoc != "" {
|
||||
if dir != baseLoc {
|
||||
dir = path.Join(dir, baseLoc)
|
||||
}
|
||||
}
|
||||
finalPath := path.Join(dir, p)
|
||||
r.ResMap[mm.ID] = finalPath
|
||||
}
|
||||
}
|
||||
for i := range res.Fonts.Font {
|
||||
f := &res.Fonts.Font[i]
|
||||
if f.FontFile != "" {
|
||||
dir := path.Dir(resPath)
|
||||
if baseLoc != "" {
|
||||
if dir != baseLoc {
|
||||
dir = path.Join(dir, baseLoc)
|
||||
}
|
||||
}
|
||||
f.FontFile = path.Join(dir, f.FontFile)
|
||||
}
|
||||
r.fontCache[f.ID] = f
|
||||
}
|
||||
for i := range res.DrawParams.DrawParam {
|
||||
dp := &res.DrawParams.DrawParam[i]
|
||||
r.drawParamCache[dp.ID] = dp
|
||||
}
|
||||
}
|
||||
|
||||
// PageContent 获取页面内容
|
||||
// 入参: page 页面对象
|
||||
// 返回: *PageContent 页面内容, error 错误信息
|
||||
func (r *Reader) PageContent(page Page) (*PageContent, error) {
|
||||
fullPath := path.Join(r.RootDir, page.BaseLoc)
|
||||
data, err := r.readFile(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var content PageContent
|
||||
if err := xml.Unmarshal(data, &content); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal page content: %w", err)
|
||||
}
|
||||
return &content, nil
|
||||
}
|
||||
|
||||
// ResPath 获取资源的完整路径
|
||||
// 入参: resLink 资源链接
|
||||
// 返回: string 完整路径
|
||||
func (r *Reader) ResPath(resLink string) string {
|
||||
if resLink == "" {
|
||||
return ""
|
||||
}
|
||||
return path.Join(r.RootDir, resLink)
|
||||
}
|
||||
|
||||
// ResData 获取资源文件数据
|
||||
// 入参: resLink 资源链接
|
||||
// 返回: []byte 资源数据, error 错误信息
|
||||
func (r *Reader) ResData(resLink string) ([]byte, error) {
|
||||
fullPath := r.ResPath(resLink)
|
||||
return r.readFile(fullPath)
|
||||
}
|
||||
|
||||
// DocRoots 获取所有文档根路径
|
||||
// 返回: []string 路径列表
|
||||
func (r *Reader) DocRoots() []string {
|
||||
var roots []string
|
||||
if r.OFD == nil {
|
||||
return roots
|
||||
}
|
||||
for _, body := range r.OFD.DocBody {
|
||||
roots = append(roots, body.DocRoot)
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
// Version 获取OFD版本号
|
||||
// 返回: string 版本号
|
||||
func (r *Reader) Version() string {
|
||||
if r.OFD == nil {
|
||||
return ""
|
||||
}
|
||||
return r.OFD.Version
|
||||
}
|
||||
|
||||
// DocType 获取文档类型
|
||||
// 返回: string 文档类型
|
||||
func (r *Reader) DocType() string {
|
||||
if r.OFD == nil {
|
||||
return ""
|
||||
}
|
||||
return r.OFD.DocType
|
||||
}
|
||||
|
||||
// DocInfo 获取文档元数据
|
||||
// 返回: *DocInfo 元数据, error 错误信息
|
||||
func (r *Reader) DocInfo() (*DocInfo, error) {
|
||||
if r.OFD == nil || len(r.OFD.DocBody) == 0 {
|
||||
return nil, fmt.Errorf("no docbody found")
|
||||
}
|
||||
return &r.OFD.DocBody[0].DocInfo, nil
|
||||
}
|
||||
|
||||
// Permissions 获取文档权限信息
|
||||
// 返回: *Permissions 权限信息, error 错误信息
|
||||
func (r *Reader) Permissions() (*Permissions, error) {
|
||||
doc, err := r.Doc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &doc.Permissions, nil
|
||||
}
|
||||
|
||||
// Outlines 获取文档大纲
|
||||
// 返回: []OutlineElem 大纲列表, error 错误信息
|
||||
func (r *Reader) Outlines() ([]OutlineElem, error) {
|
||||
doc, err := r.Doc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return doc.Outlines.OutlineElem, nil
|
||||
}
|
||||
|
||||
// Attachments 获取附件列表
|
||||
// 返回: []Attachment 附件列表, error 错误信息
|
||||
func (r *Reader) Attachments() ([]Attachment, error) {
|
||||
doc, err := r.Doc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return doc.Attachments.Attachment, nil
|
||||
}
|
||||
|
||||
// CustomDatas 获取自定义数据
|
||||
// 返回: []CustomData 自定义数据列表, error 错误信息
|
||||
func (r *Reader) CustomDatas() ([]CustomData, error) {
|
||||
info, err := r.DocInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.CustomDatas == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return info.CustomDatas.CustomData, nil
|
||||
}
|
||||
|
||||
// Extensions 获取分列扩展项
|
||||
// 返回: []Extension 扩展项列表, error 错误信息
|
||||
func (r *Reader) Extensions() ([]Extension, error) {
|
||||
doc, err := r.Doc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return doc.Extensions.Extension, nil
|
||||
}
|
||||
Reference in New Issue
Block a user