diff --git a/assets/webui/index.html b/assets/webui/index.html index 2e7fe00..848ef63 100644 --- a/assets/webui/index.html +++ b/assets/webui/index.html @@ -43,14 +43,18 @@ +
+ + +
- - +
@@ -486,28 +490,6 @@ -
-
- -
- -
-
-
- 预览 -
-
- - -
- -
-
-
- 详情 -
显示
SVG
编辑
SVG
字体
几何
编译
图片
SVG
PDF
EPS
-
-
文档
diff --git a/assets/webui/ofdgo.css b/assets/webui/ofdgo.css index ff230e2..b53c347 100644 --- a/assets/webui/ofdgo.css +++ b/assets/webui/ofdgo.css @@ -607,8 +607,7 @@ button:not(:disabled):hover { } .format-select, -.order-select, -.render-panel select { +.order-select { width: 58px; height: var(--control-height); min-width: 0; @@ -619,8 +618,46 @@ button:not(:disabled):hover { color: #1f2428; } -.dpi-select { - width: 79px; +.toolbar-select { + position: relative; + display: inline-flex; +} + +.toolbar-select .format-select { + width: calc(2em + 22px); + border-color: var(--line-strong); + appearance: none; + color: transparent; +} + +.toolbar-select option { + color: #1f2428; +} + +.toolbar-select > span { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + padding-right: 12px; + pointer-events: none; +} + +.toolbar-select::after { + position: absolute; + right: 7px; + top: calc(50% - 2px); + border: 4px solid transparent; + border-bottom: 0; + border-top-color: currentColor; + pointer-events: none; + content: ""; +} + +.toolbar-select select:disabled + span, +.toolbar-select:has(select:disabled)::after { + opacity: 0.48; } .form-dialog { @@ -1014,99 +1051,6 @@ body[data-hide-meta] .meta-panel { font-size: 12px; } -.render-panel { - --render-controls-width: 166px; - --render-modes-width: 79px; - position: relative; - margin-bottom: 12px; - padding-bottom: 12px; - border-bottom: 1px solid var(--line); -} - -.render-panel select { - width: 100%; - height: 28px; - font-size: 12px; -} - -.render-mode-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - font-size: 12px; -} - -.render-panel .panel-row { - flex-wrap: nowrap; - margin-bottom: 6px; -} - -.render-controls { - display: grid; - grid-template-columns: var(--render-modes-width) minmax(0, 1fr); - align-items: center; - width: var(--render-controls-width); - gap: 8px; -} - -.render-modes { - display: flex; - flex: none; -} - -.render-modes .small-button { - width: 40px; - height: 28px; - padding: 0; -} - -.render-modes .small-button + .small-button { - margin-left: -1px; -} - -.render-modes .small-button[aria-pressed="true"] { - position: relative; - border-color: var(--accent); - background: var(--accent-soft); - color: var(--accent-strong); -} - -.render-support { - font-size: 12px; - color: var(--text-muted); -} - -.render-support summary { - position: absolute; - top: 0; - right: 0; - width: calc(var(--render-controls-width) - var(--render-modes-width) - 8px); - text-align: center; - line-height: 28px; - cursor: pointer; - color: var(--accent-strong); -} - -.render-support dl { - margin: 8px 0 0; -} - -.render-support dl > div { - display: flex; - justify-content: space-between; - gap: 16px; - margin-top: 6px; -} - -.render-support dd { - min-width: 0; - margin: 0; - text-align: right; - overflow-wrap: anywhere; - color: #1f2428; -} - .app-refresh { width: 26px; padding: 0; @@ -2660,12 +2604,17 @@ button.outline-link:hover { min-width: 34px; } - .format-select { - width: 52px; - padding: 0 3px; + .toolbar-select .format-select { + width: var(--control-height); + padding: 0; } - .dpi-select { - width: 79px; + .toolbar-select::after { + display: none; + } + + .toolbar-select > span { + padding: 0; + font-size: 11px; } } diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index 82be778..1b99071 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -88,8 +88,7 @@ const state = { renderAnnotations: true, renderBackend: "canvas", renderMode: "svg", - renderBackends: {}, - renderDPI: 150, + renderDPI: DEFAULT_IMAGE_DPI, pageCache: new Map(), svgFonts: new Map(), svgImages: new Map(), @@ -353,6 +352,8 @@ const el = { continuousButton: document.querySelector("#continuousButton"), annotationButton: document.querySelector("#annotationButton"), imageDPI: document.querySelector("#imageDPI"), + dpiValue: document.querySelector("#dpiValue"), + formatValue: document.querySelector("#formatValue"), exportFormat: document.querySelector("#exportFormat"), exportPageButton: document.querySelector("#exportPageButton"), exportButton: document.querySelector("#exportButton"), @@ -397,18 +398,8 @@ const el = { searchResults: document.querySelector("#searchResults"), metaPanel: document.querySelector(".meta-panel"), appPanel: document.querySelector("#appPanel"), - renderBackend: document.querySelector("#renderBackend"), - renderDPI: document.querySelector("#renderDPI"), renderVectorButton: document.querySelector("#renderVectorButton"), renderRasterButton: document.querySelector("#renderRasterButton"), - renderDisplay: document.querySelector("#renderDisplay"), - renderImages: document.querySelector("#renderImages"), - renderSVG: document.querySelector("#renderSVG"), - renderPDF: document.querySelector("#renderPDF"), - renderEPS: document.querySelector("#renderEPS"), - renderCompiler: document.querySelector("#renderCompiler"), - renderFonts: document.querySelector("#renderFonts"), - renderGeometry: document.querySelector("#renderGeometry"), offlineStatus: document.querySelector("#offlineStatus"), refreshAppButton: document.querySelector("#refreshAppButton"), metaFile: document.querySelector("#metaFile"), @@ -1099,10 +1090,9 @@ el.ofdInput.addEventListener("change", () => openOFD(el.ofdInput.files[0])); el.fontInput.addEventListener("change", openSelectedFonts); el.fontDirectoryInput.addEventListener("change", openSelectedFonts); el.prevButton.addEventListener("click", () => renderPage(state.pageIndex - 1)); -el.renderBackend.addEventListener("change", () => changeRenderBackend()); -el.renderDPI.addEventListener("change", () => changeRenderBackend()); -el.renderVectorButton.addEventListener("click", () => changeRenderBackend("svg")); -el.renderRasterButton.addEventListener("click", () => changeRenderBackend("raster")); +el.imageDPI.addEventListener("change", () => changeDisplayMode()); +el.renderVectorButton.addEventListener("click", () => changeDisplayMode("svg")); +el.renderRasterButton.addEventListener("click", () => changeDisplayMode("raster")); el.nextButton.addEventListener("click", () => renderPage(state.pageIndex + 1)); el.zoomOutButton.addEventListener("click", () => setScale(state.scale - 0.1)); el.zoomInButton.addEventListener("click", () => setScale(state.scale + 0.1)); @@ -1631,7 +1621,7 @@ async function toggleEditor() { renderOutlines(false); if (state.renderMode !== "svg") { try { - await refreshRenderBackend(anchor); + await refreshPageDisplay(anchor); } catch (err) { state.editing = !state.editing; updateControls(); @@ -2936,14 +2926,6 @@ async function loadWASM() { if (data.phase === "commit") el.importCancel.disabled = el.cancelExportButton.disabled = true; } } else if (data.type === "ready") { - state.renderBackends = data.backends; - el.renderBackend.replaceChildren(); - for (const name of Object.keys(data.backends)) { - const option = document.createElement("option"); - option.value = name; - option.textContent = renderBackendLabel(name); - el.renderBackend.append(option); - } state.ready = true; resolve(); } else if (data.type === "exit") { @@ -3338,34 +3320,17 @@ function displayMode() { return state.editing ? "svg" : state.renderMode; } -function renderBackendLabel(name) { - return { svg: "SVG", canvas: "Canvas", ofd: "OFD" }[name] || name || "无"; -} - -function updateRenderBackend() { - el.renderBackend.value = state.renderBackend; - el.renderDPI.value = String(state.renderDPI); - const disabled = !state.ready || state.exporting || document.body.hasAttribute("aria-busy"); - el.renderBackend.disabled = disabled || Object.keys(state.renderBackends).length < 2; - el.renderDPI.disabled = disabled || displayMode() === "svg"; - el.renderDPI.title = displayMode() === "svg" ? "矢量预览无需DPI" : "预览DPI(仅位图)"; +function updateDisplayControls() { + const disabled = !state.ready || !state.doc || state.exporting || document.body.hasAttribute("aria-busy"); el.renderVectorButton.disabled = el.renderRasterButton.disabled = disabled || state.editing; el.renderVectorButton.setAttribute("aria-pressed", String(displayMode() === "svg")); el.renderRasterButton.setAttribute("aria-pressed", String(displayMode() === "raster")); el.renderVectorButton.title = state.editing ? "编辑使用矢量显示" : "矢量显示"; el.renderRasterButton.title = state.editing ? "阅读时可用" : "位图显示"; - const backend = state.renderBackends[state.renderBackend]; - el.renderDisplay.textContent = displayMode() === "svg" ? "SVG" : renderBackendLabel(backend?.raster); - el.renderImages.textContent = renderBackendLabel(backend?.raster); - el.renderSVG.textContent = renderBackendLabel(backend?.svg); - el.renderPDF.textContent = renderBackendLabel(backend?.pdf); - el.renderEPS.textContent = renderBackendLabel(backend?.eps); - el.renderCompiler.textContent = renderBackendLabel(backend?.compiler); - el.renderFonts.textContent = renderBackendLabel(backend?.fonts); - el.renderGeometry.textContent = renderBackendLabel(backend?.geometry); + updateDPIControl(); } -async function refreshRenderBackend(anchor = scaleAnchor(0)) { +async function refreshPageDisplay(anchor = scaleAnchor(0)) { const selection = state.editing ? { index: canvasEditor.selected?.index ?? state.pageIndex, ids: canvasEditor.items().map(item => item.id) } : null; await openDocument({ pageIndex: state.pageIndex, fitMode: state.fitMode, scale: state.scale, @@ -3374,29 +3339,29 @@ async function refreshRenderBackend(anchor = scaleAnchor(0)) { }); } -async function changeRenderBackend(mode = state.renderMode) { - if (document.body.hasAttribute("aria-busy")) return; - const backend = el.renderBackend.value, dpi = Number(el.renderDPI.value); - if (state.renderBackend === backend && state.renderDPI === dpi && state.renderMode === mode) return; - if (state.editing && (!await canvasEditor.commitText() || !await canvasEditor.commitCrop())) { - updateRenderBackend(); +async function changeDisplayMode(mode = state.renderMode) { + if (document.body.hasAttribute("aria-busy")) { + el.imageDPI.value = String(state.renderDPI); + updateDPIControl(); return; } - const previous = { backend: state.renderBackend, dpi: state.renderDPI, mode: state.renderMode }; - state.renderBackend = backend; + if (state.editing) mode = state.renderMode; + const dpi = currentImageDPI(); + if (state.renderDPI === dpi && state.renderMode === mode) return; + const previous = { dpi: state.renderDPI, mode: state.renderMode }; state.renderDPI = dpi; state.renderMode = mode; - updateRenderBackend(); - if (!state.doc) return; + updateDisplayControls(); + if (!state.doc || state.editing || (previous.mode === mode && mode === "svg")) return; try { - await refreshRenderBackend(); + await refreshPageDisplay(); } catch (err) { - state.renderBackend = previous.backend; state.renderDPI = previous.dpi; state.renderMode = previous.mode; + el.imageDPI.value = String(previous.dpi); resetPageLoading(); state.pageCache.clear(); - updateRenderBackend(); + updateDisplayControls(); showError(err, false); } } @@ -6814,7 +6779,7 @@ function updateFitSpace() { function updateControls() { renderSecurity(); const hasDoc = Boolean(state.doc); - updateRenderBackend(); + updateDisplayControls(); updateEditorTools(); const pageCount = state.doc ? state.doc.pageCount : 0; el.prevButton.disabled = !hasDoc || state.pageIndex <= 0; @@ -7008,7 +6973,7 @@ function showError(err, empty = !state.doc) { function setBusy(busy, text = "", percent = 0, status = "") { document.body.toggleAttribute("aria-busy", busy); renderSecurity(); - updateRenderBackend(); + updateDisplayControls(); el.editButton.disabled = busy || !state.doc || !state.ready || state.exporting; el.createForm.inert = busy; el.insertForm.inert = busy; @@ -7107,8 +7072,12 @@ function exportFormatUsesDPI(value) { } function updateDPIControl() { - el.imageDPI.disabled = state.exporting || !state.doc || !exportFormatUsesDPI(el.exportFormat.value); - el.imageDPI.title = exportFormatUsesDPI(el.exportFormat.value) ? "导出DPI(仅PNG/JPG)" : "当前格式无需DPI"; + el.imageDPI.disabled = state.exporting || !state.doc || document.body.hasAttribute("aria-busy") || (displayMode() !== "raster" && !exportFormatUsesDPI(el.exportFormat.value)); + el.imageDPI.title = `DPI:${currentImageDPI()}`; + el.dpiValue.textContent = String(currentImageDPI()); + const format = exportFormatInfo(el.exportFormat.value); + el.formatValue.textContent = format?.label || ""; + el.exportFormat.title = format ? `导出格式:${format.label}` : "导出格式"; } function currentImageDPI() {