From 09f4f6455d34a176e1acc1f40ecb12dc07538bc2 Mon Sep 17 00:00:00 2001 From: helianwen Date: Thu, 20 Aug 2026 17:29:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(build):=20=E6=B7=BB=E5=8A=A0UMD=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E6=94=AF=E6=8C=81=E5=B9=B6=E6=94=B9=E8=BF=9BOFD?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增umd-entry.js入口文件,提供全局OFD对象用于网页预览 - 添加dist/和.history/到.gitignore忽略列表 - 在package.json中添加build:umd构建脚本 - 配置vue.config.js的externals选项以支持UMD模式 - 注释掉styles/index.css中的.gray样式类 - 在ofd_parser.js中增加空值检查和错误处理逻辑 - 在ofd_render.js中增强媒体资源存在性验证 --- .gitignore | 2 + package.json | 3 +- src/styles/index.css | 4 +- src/umd-entry.js | 108 ++++++++++++++++++++++++++++++++++++ src/utils/ofd/ofd_parser.js | 27 ++++++++- src/utils/ofd/ofd_render.js | 15 +++-- vue.config.js | 7 ++- 7 files changed, 155 insertions(+), 11 deletions(-) create mode 100644 src/umd-entry.js diff --git a/.gitignore b/.gitignore index c24208f..d86ffec 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /package-lock.json .DS_Store /.idea +dist/ +.history/ diff --git a/package.json b/package.json index 8b59ee8..2ed4ffc 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", - "lib": "vue-cli-service build --target lib --name ofd --dest lib ./src/utils/ofd/ofd.js" + "lib": "vue-cli-service build --target lib --name ofd --dest lib ./src/utils/ofd/ofd.js", + "build:umd": "vue-cli-service build --target lib --name OFD --dest dist ./src/umd-entry.js" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.30", diff --git a/src/styles/index.css b/src/styles/index.css index 8420556..8121a08 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -24,11 +24,11 @@ body { margin: 0; } src: url("../assets/simhei.ttf"); } -.gray { +/* .gray { -webkit-filter: grayscale(100%); -moz-filter: grayscale(100%); -ms-filter: grayscale(100%); -o-filter: grayscale(100%); filter: grayscale(100%); filter: gray; -} +} */ diff --git a/src/umd-entry.js b/src/umd-entry.js new file mode 100644 index 0000000..baee801 --- /dev/null +++ b/src/umd-entry.js @@ -0,0 +1,108 @@ +import { + parseOfdDocument, + renderOfd, + renderOfdByScale, + setPageScale, + getPageScale +} from "./utils/ofd/ofd"; + +/** + * OFD.js UMD 入口 + * 提供简单的 API 用于在任意网页中预览 OFD 文件 + * + * 用法: + * // 通过 CDN 引入后,全局变量为 OFD + * OFD.preview('https://example.com/file.ofd', document.getElementById('ofd-container')); + * + * // 或使用选择器字符串 + * OFD.preview('https://example.com/file.ofd', '#ofd-container'); + */ + /** + * 预览 OFD 文件并渲染到指定 DOM 节点 + * @param {string|File|ArrayBuffer} source - OFD 文件 URL、File 对象或 ArrayBuffer + * @param {HTMLElement|string} container - 渲染目标 DOM 节点或 CSS 选择器字符串 + * @param {Object} options - 配置项 + * @param {number} [options.width] - 容器宽度,默认使用容器实际宽度 + * @param {Function} [options.onSuccess] - 解析成功回调,参数:(ofdData, divArray) + * @param {Function} [options.onFail] - 解析失败回调,参数:(error) + */ + const preview=(source, container, options = {}) => { + const { width, onSuccess, onFail } = options; + + let targetContainer; + if (typeof container === "string") { + targetContainer = document.querySelector(container); + } else if (container instanceof HTMLElement) { + targetContainer = container; + } + + if (!targetContainer) { + console.error("OFD Preview: 目标容器未找到,请检查传入的容器参数"); + return; + } + + const screenWidth = + width || targetContainer.clientWidth || document.body.clientWidth; + + parseOfdDocument({ + ofd: source, + success(ofdArray) { + const ofd = ofdArray[0]; + const divs = renderOfd(screenWidth, ofd); + + targetContainer.innerHTML = ""; + for (const div of divs) { + targetContainer.appendChild(div); + } + + if (onSuccess) { + onSuccess(ofd, divs); + } + }, + fail(err) { + console.error("OFD Preview 加载失败:", err); + if (onFail) { + onFail(err); + } + }, + }); + } + /** + * 设置页面缩放比例 + * @param {number} scale - 缩放比例 + */ + const setScale=(scale)=> { + setPageScale(scale); + } + + /** + * 获取当前页面缩放比例 + * @returns {number} 当前缩放比例 + */ + const getScale=() =>{ + return getPageScale(); + } +const OFD = { + +preview, +setScale, +getScale, + // 暴露底层 API,便于高级用户自定义渲染逻辑 + parseOfdDocument, + renderOfd, + renderOfdByScale, + setPageScale, + getPageScale, +}; + +export default OFD; +export { + preview, +setScale, +getScale, + parseOfdDocument, + renderOfd, + renderOfdByScale, + setPageScale, + getPageScale, +}; diff --git a/src/utils/ofd/ofd_parser.js b/src/utils/ofd/ofd_parser.js index ab1a66b..778ccf2 100644 --- a/src/utils/ofd/ofd_parser.js +++ b/src/utils/ofd/ofd_parser.js @@ -302,15 +302,23 @@ const getMultiMediaRes = async function (zip, res, doc) { if (file.indexOf(doc) === -1) { file = `${doc}/${file}` } + if (!file) { + console.warn(`Empty MediaFile for item ${item['@_ID']}, skipping.`); + continue; + } if (item['@_Type'].toLowerCase() === 'image') { const format = item['@_Format']; const ext = getExtensionByPath(file); if ((format && (format.toLowerCase() === 'gbig2' || format.toLowerCase() === 'jb2')) || ext && (ext.toLowerCase() === 'jb2' || ext.toLowerCase() === 'gbig2')) { const jbig2 = await parseJbig2ImageFromZip(zip, file); - multiMediaResObj[item['@_ID']] = jbig2; + if (jbig2) { + multiMediaResObj[item['@_ID']] = jbig2; + } } else { const img = await parseOtherImageFromZip(zip, file); - multiMediaResObj[item['@_ID']] = {img, 'format': 'png'}; + if (img) { + multiMediaResObj[item['@_ID']] = {img, 'format': 'png'}; + } } } else { multiMediaResObj[item['@_ID']] = file; @@ -364,6 +372,10 @@ const getSignature = async function (zip, signatures, doc) { } const getFileData = async function (zip, name){ + if (!zip.files[name]) { + console.warn(`File not found in zip: ${name}`); + return null; + } return zip.files[name].async('uint8array'); } @@ -415,6 +427,9 @@ const getSealDocumentObj = function (stampAnnot) { const getJsonFromXmlContent = async function (zip, xmlName) { return new Promise((resolve, reject) => { + if (!zip.files[xmlName]) { + return reject(new Error(`File not found in zip: ${xmlName}`)); + } zip.files[xmlName].async('string').then(function (content) { let ops = { attributeNamePrefix: "@_", @@ -433,6 +448,10 @@ const getJsonFromXmlContent = async function (zip, xmlName) { const parseJbig2ImageFromZip = async function (zip, name) { return new Promise((resolve, reject) => { + if (!zip.files[name]) { + console.warn(`Image not found in zip: ${name}`); + return resolve(null); + } zip.files[name].async('uint8array').then(function (bytes) { let jbig2 = new Jbig2Image(); const img = jbig2.parse(bytes); @@ -445,6 +464,10 @@ const parseJbig2ImageFromZip = async function (zip, name) { const parseOtherImageFromZip = async function (zip, name) { return new Promise((resolve, reject) => { + if (!zip.files[name]) { + console.warn(`Image not found in zip: ${name}`); + return resolve(null); + } zip.files[name].async('base64').then(function (bytes) { const img = 'data:image/png;base64,' + bytes; resolve(img); diff --git a/src/utils/ofd/ofd_render.js b/src/utils/ofd/ofd_render.js index 9a434fe..a20a68d 100644 --- a/src/utils/ofd/ofd_render.js +++ b/src/utils/ofd/ofd_render.js @@ -295,13 +295,18 @@ export const renderImageObject = function (pageWidth, pageHeight, multiMediaResO let boundary = parseStBox(imageObject['@_Boundary']); boundary = converterBox(boundary); const resId = imageObject['@_ResourceID']; - if (multiMediaResObj[resId].format === 'gbig2') { - const img = multiMediaResObj[resId].img; - const width = multiMediaResObj[resId].width; - const height = multiMediaResObj[resId].height; + const mediaRes = multiMediaResObj[resId]; + if (!mediaRes) { + console.warn(`Image resource not found for ID: ${resId}`); + return document.createElement('div'); + } + if (mediaRes.format === 'gbig2') { + const img = mediaRes.img; + const width = mediaRes.width; + const height = mediaRes.height; return renderImageOnCanvas(img, width, height, boundary, imageObject['pfIndex']); } else { - return renderImageOnDiv(pageWidth, pageHeight, multiMediaResObj[resId].img, boundary, false, false, null, null, imageObject['pfIndex']); + return renderImageOnDiv(pageWidth, pageHeight, mediaRes.img, boundary, false, false, null, null, imageObject['pfIndex']); } } diff --git a/vue.config.js b/vue.config.js index b77eda5..b91d39e 100644 --- a/vue.config.js +++ b/vue.config.js @@ -1,4 +1,9 @@ module.exports = { publicPath: '/ofd', - lintOnSave: false + lintOnSave: false, + configureWebpack: { + externals: { + // UMD 模式下不需要排除依赖,打包成完整单文件 + } + } }