mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
This commit is contained in:
1 parent
ca8f22dba1
commit
d3904521b9
5 files changed
+244
-99
No files matched your search
+77
-15
@@ -17,11 +17,43 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
// exportWriter 分块传递导出数据
|
||||
type exportWriter struct {
|
||||
write js.Value
|
||||
}
|
||||
|
||||
// Write 将数据块交给浏览器保存
|
||||
// 入参: data 导出数据
|
||||
// 返回: int 写入长度, error 错误信息
|
||||
func (w exportWriter) Write(data []byte) (int, error) {
|
||||
done := make(chan error)
|
||||
callback := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
var err error
|
||||
if len(args) > 0 {
|
||||
err = fmt.Errorf("%s", args[0].String())
|
||||
}
|
||||
done <- err
|
||||
return nil
|
||||
})
|
||||
defer callback.Release()
|
||||
size := len(data)
|
||||
for len(data) > 0 {
|
||||
n := min(len(data), 1<<20)
|
||||
w.write.Invoke(bytesToJS(data[:n]), callback)
|
||||
if err := <-done; err != nil {
|
||||
return size - len(data), err
|
||||
}
|
||||
data = data[n:]
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// currentSession 当前WebUI文档会话
|
||||
var currentSession *Session
|
||||
|
||||
@@ -42,8 +74,8 @@ func RunWASM() {
|
||||
registerCallback("ofdgoSVGFontData", svgFontData)
|
||||
registerCallback("ofdgoSearchPage", searchPage)
|
||||
registerCallback("ofdgoExportFormats", exportFormats)
|
||||
registerCallback("ofdgoExportPage", exportPage)
|
||||
registerCallback("ofdgoExportDocument", exportDocument)
|
||||
registerExportCallback("ofdgoExportPage", exportPage)
|
||||
registerExportCallback("ofdgoExportDocument", exportDocument)
|
||||
registerCallback("ofdgoMatchFontFiles", matchFontFiles)
|
||||
select {}
|
||||
}
|
||||
@@ -52,18 +84,42 @@ func RunWASM() {
|
||||
// 入参: name 回调名称, fn 回调函数
|
||||
func registerCallback(name string, fn func([]js.Value) (any, error)) {
|
||||
cb := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
data, err := safeCall(fn, args)
|
||||
if err != nil {
|
||||
return encodeResult(apiResult{OK: false, Error: err.Error()})
|
||||
}
|
||||
if result, ok := data.(js.Value); ok {
|
||||
return result
|
||||
}
|
||||
return encodeResult(apiResult{OK: true, Data: data})
|
||||
return callbackResult(fn, args)
|
||||
})
|
||||
js.Global().Set(name, cb)
|
||||
}
|
||||
|
||||
// registerExportCallback 异步导出,为浏览器处理数据块让出事件循环
|
||||
// 入参: name 回调名称, fn 回调函数
|
||||
func registerExportCallback(name string, fn func([]js.Value) (any, error)) {
|
||||
cb := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
executor := js.FuncOf(func(this js.Value, callbacks []js.Value) any {
|
||||
resolve := callbacks[0]
|
||||
go func() {
|
||||
resolve.Invoke(callbackResult(fn, args))
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
defer executor.Release()
|
||||
return js.Global().Get("Promise").New(executor)
|
||||
})
|
||||
js.Global().Set(name, cb)
|
||||
}
|
||||
|
||||
// callbackResult 转换浏览器接口结果
|
||||
// 入参: fn 回调函数, args 回调参数
|
||||
// 返回: any 浏览器结果
|
||||
func callbackResult(fn func([]js.Value) (any, error), args []js.Value) any {
|
||||
data, err := safeCall(fn, args)
|
||||
if err != nil {
|
||||
return encodeResult(apiResult{OK: false, Error: err.Error()})
|
||||
}
|
||||
if result, ok := data.(js.Value); ok {
|
||||
return result
|
||||
}
|
||||
return encodeResult(apiResult{OK: true, Data: data})
|
||||
}
|
||||
|
||||
// safeCall 调用浏览器回调并转换异常
|
||||
// 入参: fn 回调函数, args 回调参数
|
||||
// 返回: any 回调结果, error 错误信息
|
||||
@@ -238,15 +294,18 @@ func exportPage(args []js.Value) (any, error) {
|
||||
if currentSession == nil {
|
||||
return nil, fmt.Errorf("ofd document is not opened")
|
||||
}
|
||||
if len(args) < 3 {
|
||||
if len(args) < 4 {
|
||||
return nil, fmt.Errorf("missing export page arguments")
|
||||
}
|
||||
data, format, err := currentSession.ExportPage(args[0].Int(), args[1].String(), args[2].Float())
|
||||
writer := bufio.NewWriterSize(exportWriter{write: args[3]}, 1<<20)
|
||||
format, err := currentSession.ExportPage(args[0].Int(), args[1].String(), args[2].Float(), writer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writer.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return successResult(map[string]any{
|
||||
"bytes": bytesToJS(data),
|
||||
"label": format.Label,
|
||||
"mime": format.MIME,
|
||||
}), nil
|
||||
@@ -259,12 +318,15 @@ func exportDocument(args []js.Value) (any, error) {
|
||||
if currentSession == nil {
|
||||
return nil, fmt.Errorf("ofd document is not opened")
|
||||
}
|
||||
data, format, err := currentSession.ExportDocument(args[0].String(), args[1].Float())
|
||||
writer := bufio.NewWriterSize(exportWriter{write: args[2]}, 1<<20)
|
||||
format, err := currentSession.ExportDocument(args[0].String(), args[1].Float(), writer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writer.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return successResult(map[string]any{
|
||||
"bytes": bytesToJS(data),
|
||||
"label": format.Label,
|
||||
"mime": format.MIME,
|
||||
}), nil
|
||||
|
||||
+24
-71
@@ -15,12 +15,10 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -450,38 +448,22 @@ func (s *Session) SVGFontData(name string) ([]byte, error) {
|
||||
}
|
||||
|
||||
// ExportPage 导出单页
|
||||
// 入参: index 页面索引, value 导出格式, dpi 图片DPI
|
||||
// 返回: []byte 文件数据, ExportFormat 导出格式, error 错误信息
|
||||
func (s *Session) ExportPage(index int, value string, dpi float64) ([]byte, ExportFormat, error) {
|
||||
// 入参: index 页面索引, value 导出格式, dpi 图片DPI, writer 输出流
|
||||
// 返回: ExportFormat 导出格式, error 错误信息
|
||||
func (s *Session) ExportPage(index int, value string, dpi float64, writer io.Writer) (ExportFormat, error) {
|
||||
format, ok := exportFormat(value)
|
||||
if !ok {
|
||||
return nil, ExportFormat{}, fmt.Errorf("unsupported export format %s", value)
|
||||
return ExportFormat{}, fmt.Errorf("unsupported export format %s", value)
|
||||
}
|
||||
page, err := s.pageContent(index)
|
||||
if err != nil {
|
||||
return nil, ExportFormat{}, err
|
||||
return ExportFormat{}, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
renderer := *s.Renderer
|
||||
if dpi > 0 && (format.Value == "png" || format.Value == "jpg") {
|
||||
renderer.DPI = dpi
|
||||
}
|
||||
switch format.Value {
|
||||
case "svg":
|
||||
err = renderer.RenderToSVG(page, &buf)
|
||||
case "pdf":
|
||||
err = renderer.RenderToPDF(page, &buf)
|
||||
case "eps":
|
||||
err = renderer.RenderToEPS(page, &buf)
|
||||
case "png":
|
||||
err = renderer.RenderToPNG(page, &buf)
|
||||
case "jpg":
|
||||
err = renderer.RenderToJPEG(page, &buf, &jpeg.Options{Quality: 95})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, ExportFormat{}, err
|
||||
}
|
||||
return buf.Bytes(), format, nil
|
||||
return format, renderer.RenderTo(page, writer, format.Value)
|
||||
}
|
||||
|
||||
// signatureInfos 获取签名验证信息
|
||||
@@ -632,72 +614,43 @@ func signatureStampInfos(positions []ofdgo.SignatureStampPosition) []SignatureSt
|
||||
}
|
||||
|
||||
// ExportDocument 导出文档为PDF或逐页打包ZIP
|
||||
// 入参: value 导出格式, dpi 图片DPI
|
||||
// 返回: []byte 文件数据, ExportFormat 导出格式, error 错误信息
|
||||
func (s *Session) ExportDocument(value string, dpi float64) ([]byte, ExportFormat, error) {
|
||||
// 入参: value 导出格式, dpi 图片DPI, writer 输出流
|
||||
// 返回: ExportFormat 导出格式, error 错误信息
|
||||
func (s *Session) ExportDocument(value string, dpi float64, writer io.Writer) (ExportFormat, error) {
|
||||
format, ok := exportFormat(value)
|
||||
if !ok {
|
||||
return nil, ExportFormat{}, fmt.Errorf("unsupported export format %s", value)
|
||||
return ExportFormat{}, fmt.Errorf("unsupported export format %s", value)
|
||||
}
|
||||
if format.Value == "pdf" {
|
||||
data, err := s.ExportPDF()
|
||||
return data, format, err
|
||||
return format, s.ExportPDF(writer)
|
||||
}
|
||||
if s == nil || s.Reader == nil || s.Renderer == nil || s.doc == nil {
|
||||
return nil, ExportFormat{}, fmt.Errorf("ofd document is not opened")
|
||||
return ExportFormat{}, fmt.Errorf("ofd document is not opened")
|
||||
}
|
||||
count := len(s.doc.Pages.Page)
|
||||
if count == 0 {
|
||||
return nil, ExportFormat{}, fmt.Errorf("no pages found")
|
||||
renderer := *s.Renderer
|
||||
if dpi > 0 && (format.Value == "png" || format.Value == "jpg") {
|
||||
renderer.DPI = dpi
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
archive := zip.NewWriter(&buf)
|
||||
method := zip.Deflate
|
||||
if format.Value == "png" || format.Value == "jpg" {
|
||||
method = zip.Store
|
||||
}
|
||||
width := len(strconv.Itoa(count))
|
||||
for index := range count {
|
||||
data, _, err := s.ExportPage(index, format.Value, dpi)
|
||||
if err != nil {
|
||||
return nil, ExportFormat{}, fmt.Errorf("failed to export page %d: %w", index+1, err)
|
||||
}
|
||||
entry, err := archive.CreateHeader(&zip.FileHeader{
|
||||
Name: fmt.Sprintf("%0*d.%s", width, index+1, format.Extension),
|
||||
Method: method,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, ExportFormat{}, err
|
||||
}
|
||||
if _, err := entry.Write(data); err != nil {
|
||||
return nil, ExportFormat{}, err
|
||||
}
|
||||
}
|
||||
if err := archive.Close(); err != nil {
|
||||
return nil, ExportFormat{}, err
|
||||
}
|
||||
return buf.Bytes(), ExportFormat{Value: "zip", Label: "ZIP", Extension: "zip", MIME: "application/zip"}, nil
|
||||
err := renderer.RenderToZIP(writer, format.Value)
|
||||
return ExportFormat{Value: "zip", Label: "ZIP", Extension: "zip", MIME: "application/zip"}, err
|
||||
}
|
||||
|
||||
// ExportPDF 导出文档为PDF
|
||||
// 返回: []byte PDF文件数据, error 错误信息
|
||||
func (s *Session) ExportPDF() ([]byte, error) {
|
||||
// 入参: writer 输出流
|
||||
// 返回: error 错误信息
|
||||
func (s *Session) ExportPDF(writer io.Writer) error {
|
||||
if s == nil || s.Reader == nil || s.Renderer == nil || s.doc == nil {
|
||||
return nil, fmt.Errorf("ofd document is not opened")
|
||||
return fmt.Errorf("ofd document is not opened")
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
pages := make([]*ofdgo.PageContent, len(s.doc.Pages.Page))
|
||||
for i := range pages {
|
||||
page, err := s.pageContent(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
pages[i] = page
|
||||
}
|
||||
if err := s.Renderer.RenderPagesToPDF(pages, &buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
return s.Renderer.RenderPagesToPDF(pages, writer)
|
||||
}
|
||||
|
||||
// pageContent 获取页面内容
|
||||
|
||||
Reference in new issue
Block a user