diff --git a/assets/webui/index.html b/assets/webui/index.html index ffbf2d7..130602a 100644 --- a/assets/webui/index.html +++ b/assets/webui/index.html @@ -33,15 +33,15 @@ - + - -
-
diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index 1678df3..ab021a3 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -18,7 +18,7 @@ const COMMON_FONT_NAMES = [ ]; const LOCAL_FONT_LOAD_LIMIT = 16; const COMPACT_LAYOUT = window.matchMedia("(max-width: 900px)"); -const DEFAULT_RENDER_DPI = 300; +const DEFAULT_IMAGE_DPI = 300; const STATUS = { ready: "选择 OFD 文件", opening: "正在打开 OFD", @@ -60,7 +60,6 @@ const state = { pageIndex: 0, scale: 1, fitMode: "width", - renderDPI: DEFAULT_RENDER_DPI, renderAnnotations: true, pageCache: new Map(), pageInFlight: new Map(), @@ -94,7 +93,7 @@ const el = { fitButton: document.querySelector("#fitButton"), fitHeightButton: document.querySelector("#fitHeightButton"), annotationButton: document.querySelector("#annotationButton"), - renderDPI: document.querySelector("#renderDPI"), + imageDPI: document.querySelector("#imageDPI"), pageExportFormat: document.querySelector("#pageExportFormat"), exportPageButton: document.querySelector("#exportPageButton"), exportButton: document.querySelector("#exportButton"), @@ -139,7 +138,7 @@ el.zoomInButton.addEventListener("click", () => setScale(state.scale + 0.1)); el.fitButton.addEventListener("click", fitWidth); el.fitHeightButton.addEventListener("click", fitHeight); el.annotationButton.addEventListener("click", toggleAnnotations); -el.renderDPI.addEventListener("change", changeDPI); +el.pageExportFormat.addEventListener("change", () => updateDPIControl()); el.exportPageButton.addEventListener("click", exportCurrentPage); el.exportButton.addEventListener("click", exportPDF); el.pageInput.addEventListener("change", () => { @@ -246,6 +245,7 @@ function loadExportFormats() { if (formats.length) { el.pageExportFormat.value = formats[0].value; } + updateDPIControl(); } async function ensureWASM() { @@ -740,19 +740,6 @@ async function toggleAnnotations() { }); } -async function changeDPI() { - state.renderDPI = currentRenderDPI(); - if (!state.ofdBytes || !state.doc) { - return; - } - await openDocument({ - pageIndex: state.pageIndex, - fitMode: state.fitMode, - scale: state.scale, - skipAutoFonts: true, - }); -} - function removeFont(id) { state.localFonts = state.localFonts.filter((font) => font.id !== id); state.userFonts = state.userFonts.filter((font) => font.id !== id); @@ -896,7 +883,7 @@ async function openDocument(options = {}) { if (openSeq !== state.openSeq) { return; } - const doc = callWASM("ofdgoOpen", state.ofdBytes, resetLocalFonts ? uploadedFonts() : allFonts(), state.renderAnnotations, state.renderDPI); + const doc = callWASM("ofdgoOpen", state.ofdBytes, resetLocalFonts ? uploadedFonts() : allFonts(), state.renderAnnotations); if (openSeq !== state.openSeq) { return; } @@ -1054,7 +1041,8 @@ async function exportCurrentPage() { if (openSeq !== state.openSeq) { return; } - const result = callWASM("ofdgoExportPage", state.pageIndex, format); + const dpi = exportFormatUsesDPI(format) ? currentImageDPI() : 0; + const result = callWASM("ofdgoExportPage", state.pageIndex, format, dpi); if (openSeq !== state.openSeq) { return; } @@ -2130,6 +2118,7 @@ function updateControls() { el.pageExportFormat.disabled = !hasDoc || !state.exportFormats.length; el.exportPageButton.disabled = !hasDoc || !state.exportFormats.length; el.exportButton.disabled = !hasDoc; + updateDPIControl(); } function updateAnnotationButton() { @@ -2142,6 +2131,7 @@ function setExportControlsDisabled(disabled) { el.exportButton.disabled = disabled; el.exportPageButton.disabled = disabled; el.pageExportFormat.disabled = disabled; + updateDPIControl(disabled); } function callWASM(name, ...args) { @@ -2259,8 +2249,16 @@ function exportFormatInfo(value) { return state.exportFormats.find((format) => format.value === value) || null; } -function currentRenderDPI() { - return Number.parseFloat(el.renderDPI.value) || DEFAULT_RENDER_DPI; +function exportFormatUsesDPI(value) { + return value === "png" || value === "jpg"; +} + +function updateDPIControl(disabled = false) { + el.imageDPI.disabled = disabled || !state.doc || !exportFormatUsesDPI(el.pageExportFormat.value); +} + +function currentImageDPI() { + return Number.parseFloat(el.imageDPI.value) || DEFAULT_IMAGE_DPI; } function formatBytes(size, fallback = "PDF") { diff --git a/internal/webui/wasm.go b/internal/webui/wasm.go index ac733be..237dbfb 100644 --- a/internal/webui/wasm.go +++ b/internal/webui/wasm.go @@ -79,7 +79,7 @@ func safeCall(fn func([]js.Value) (any, error), args []js.Value) (data any, err // 入参: args 浏览器参数 // 返回: any 文档信息, error 错误信息 func openDocument(args []js.Value) (any, error) { - if len(args) < 4 { + if len(args) < 3 { return nil, fmt.Errorf("missing open arguments") } data, err := bytesFromJS(args[0]) @@ -91,12 +91,11 @@ func openDocument(args []js.Value) (any, error) { return nil, err } renderAnnotations := args[2].Bool() - dpi := args[3].Float() if currentSession != nil { _ = currentSession.Close() currentSession = nil } - session, err := Open(data, OpenOptions{Fonts: fonts, DPI: dpi, RenderAnnotations: renderAnnotations}) + session, err := Open(data, OpenOptions{Fonts: fonts, RenderAnnotations: renderAnnotations}) if err != nil { return nil, err } @@ -131,10 +130,10 @@ func exportPage(args []js.Value) (any, error) { if currentSession == nil { return nil, fmt.Errorf("ofd document is not opened") } - if len(args) < 2 { + if len(args) < 3 { return nil, fmt.Errorf("missing export page arguments") } - data, format, err := currentSession.ExportPage(args[0].Int(), args[1].String()) + data, format, err := currentSession.ExportPage(args[0].Int(), args[1].String(), args[2].Float()) if err != nil { return nil, err } diff --git a/internal/webui/webui.go b/internal/webui/webui.go index bed492c..5732afe 100644 --- a/internal/webui/webui.go +++ b/internal/webui/webui.go @@ -30,7 +30,6 @@ import ( // OpenOptions 打开OFD文档选项 type OpenOptions struct { Fonts []FontFile - DPI float64 RenderAnnotations bool } @@ -187,9 +186,6 @@ func Open(data []byte, opts OpenOptions) (*Session, error) { return nil, err } var rendererOptions []ofdgo.RendererOption - if opts.DPI > 0 { - rendererOptions = append(rendererOptions, ofdgo.WithDPI(opts.DPI)) - } if len(opts.Fonts) > 0 { fontFS := ofdgo.NewFontFS(opts.Fonts) if fontFS.Len() == 0 { @@ -276,9 +272,9 @@ func (s *Session) RenderPageSVG(index int) (PageSVG, error) { } // ExportPage 导出单页 -// 入参: index 页面索引, value 导出格式 +// 入参: index 页面索引, value 导出格式, dpi 图片DPI // 返回: []byte 文件数据, ExportFormat 导出格式, error 错误信息 -func (s *Session) ExportPage(index int, value string) ([]byte, ExportFormat, error) { +func (s *Session) ExportPage(index int, value string, dpi float64) ([]byte, ExportFormat, error) { format, ok := exportFormat(value) if !ok { return nil, ExportFormat{}, fmt.Errorf("unsupported export format %s", value) @@ -296,13 +292,13 @@ func (s *Session) ExportPage(index int, value string) ([]byte, ExportFormat, err case "eps": err = s.Renderer.RenderToEPS(page, &buf) case "png": - img, renderErr := s.Renderer.RenderToImage(page) + img, renderErr := s.renderPageImage(page, dpi) if renderErr != nil { return nil, ExportFormat{}, renderErr } err = png.Encode(&buf, img) case "jpg": - img, renderErr := s.Renderer.RenderToImage(page) + img, renderErr := s.renderPageImage(page, dpi) if renderErr != nil { return nil, ExportFormat{}, renderErr } @@ -314,6 +310,21 @@ func (s *Session) ExportPage(index int, value string) ([]byte, ExportFormat, err return buf.Bytes(), format, nil } +// renderPageImage 渲染页面图片 +// 入参: page 页面内容, dpi 图片DPI +// 返回: image.Image 图像对象, error 错误信息 +func (s *Session) renderPageImage(page *ofdgo.PageContent, dpi float64) (image.Image, error) { + if dpi <= 0 { + return s.Renderer.RenderToImage(page) + } + oldDPI := s.Renderer.DPI + s.Renderer.DPI = dpi + defer func() { + s.Renderer.DPI = oldDPI + }() + return s.Renderer.RenderToImage(page) +} + // signatureInfos 获取签名验证信息 // 返回: []SignatureInfo 签名验证信息, error 错误信息 func (s *Session) signatureInfos() ([]SignatureInfo, error) {