@file-viewer/renderer-ofd 2.1.7 → 2.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/renderer-ofd",
3
- "version": "2.1.7",
3
+ "version": "2.1.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone OFD renderer plugin for Flyfish File Viewer powered by DLTech21/ofd.js.",
@@ -53,7 +53,7 @@
53
53
  "LICENSE"
54
54
  ],
55
55
  "dependencies": {
56
- "@file-viewer/core": "^2.1.7",
56
+ "@file-viewer/core": "^2.1.9",
57
57
  "jszip": "^3.10.1",
58
58
  "ofd-xml-parser": "^0.0.6"
59
59
  },
@@ -32,6 +32,132 @@ const ensureBrowserGlobal = () => {
32
32
  globalThis.xmlParseFlag = 0;
33
33
  }
34
34
 
35
+ const asArray = function (value) {
36
+ if (!value) {
37
+ return [];
38
+ }
39
+ return Array.isArray(value) ? value : [value];
40
+ }
41
+
42
+ const collectGroupedItems = function (groupOrGroups, childKey) {
43
+ let items = [];
44
+ for (const group of asArray(groupOrGroups)) {
45
+ if (group) {
46
+ items = items.concat(asArray(group[childKey]));
47
+ }
48
+ }
49
+ return items;
50
+ }
51
+
52
+ const zipEntryMapCache = new WeakMap();
53
+
54
+ const normalizeZipPath = function (value) {
55
+ if (!value) {
56
+ return '';
57
+ }
58
+ let normalized = String(value).replace(/\\/g, '/').trim();
59
+ try {
60
+ normalized = decodeURIComponent(normalized);
61
+ } catch {
62
+ // Keep the original path when it is not URI-encoded.
63
+ }
64
+ normalized = normalized
65
+ .replace(/^\/+/, '')
66
+ .replace(/^\.\//, '')
67
+ .replace(/\/+/g, '/');
68
+ const segments = [];
69
+ for (const segment of normalized.split('/')) {
70
+ if (!segment || segment === '.') {
71
+ continue;
72
+ }
73
+ if (segment === '..') {
74
+ segments.pop();
75
+ continue;
76
+ }
77
+ segments.push(segment);
78
+ }
79
+ return segments.join('/');
80
+ }
81
+
82
+ const joinZipPath = function (...parts) {
83
+ return normalizeZipPath(parts.filter(Boolean).join('/'));
84
+ }
85
+
86
+ const startsWithZipRoot = function (path, root) {
87
+ const normalizedPath = normalizeZipPath(path);
88
+ const normalizedRoot = normalizeZipPath(root);
89
+ return normalizedPath === normalizedRoot || normalizedPath.startsWith(`${normalizedRoot}/`);
90
+ }
91
+
92
+ const getZipEntry = function (zip, candidates) {
93
+ const paths = asArray(candidates).map(normalizeZipPath).filter(Boolean);
94
+ for (const path of paths) {
95
+ if (zip.files[path]) {
96
+ return zip.files[path];
97
+ }
98
+ }
99
+ let lowerMap = zipEntryMapCache.get(zip);
100
+ if (!lowerMap) {
101
+ lowerMap = new Map();
102
+ for (const name of Object.keys(zip.files)) {
103
+ lowerMap.set(normalizeZipPath(name).toLowerCase(), zip.files[name]);
104
+ }
105
+ zipEntryMapCache.set(zip, lowerMap);
106
+ }
107
+ for (const path of paths) {
108
+ const entry = lowerMap.get(path.toLowerCase());
109
+ if (entry) {
110
+ return entry;
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+
116
+ const resolveDocumentPath = function (path, doc) {
117
+ const normalizedPath = normalizeZipPath(path);
118
+ if (!normalizedPath || startsWithZipRoot(normalizedPath, doc)) {
119
+ return normalizedPath;
120
+ }
121
+ return joinZipPath(doc, normalizedPath);
122
+ }
123
+
124
+ const resolveResourceCandidates = function (file, baseLoc, doc) {
125
+ const mediaFile = normalizeZipPath(file);
126
+ const base = normalizeZipPath(baseLoc);
127
+ const candidates = [mediaFile];
128
+
129
+ if (base && !startsWithZipRoot(mediaFile, base)) {
130
+ candidates.push(joinZipPath(base, mediaFile));
131
+ }
132
+
133
+ const docCandidates = [];
134
+ for (const candidate of candidates) {
135
+ docCandidates.push(candidate);
136
+ if (candidate && !startsWithZipRoot(candidate, doc)) {
137
+ docCandidates.push(joinZipPath(doc, candidate));
138
+ }
139
+ }
140
+
141
+ return [...new Set(docCandidates.filter(Boolean))];
142
+ }
143
+
144
+ const getImageMime = function (format) {
145
+ const normalized = format ? format.toLowerCase() : '';
146
+ if (normalized === 'jpg' || normalized === 'jpeg') {
147
+ return 'image/jpeg';
148
+ }
149
+ if (normalized === 'gif') {
150
+ return 'image/gif';
151
+ }
152
+ if (normalized === 'bmp') {
153
+ return 'image/bmp';
154
+ }
155
+ if (normalized === 'webp') {
156
+ return 'image/webp';
157
+ }
158
+ return 'image/png';
159
+ }
160
+
35
161
  export const unzipOfd = function (file) {
36
162
  return new Promise((resolve, reject) => {
37
163
  JsZip.loadAsync(file)
@@ -69,7 +195,7 @@ export const parseSingleDoc = async function ([zip, array]) {
69
195
 
70
196
  export const doGetDocRoot = async function (zip, docbody) {
71
197
  let docRoot = docbody['ofd:DocRoot'];
72
- docRoot = replaceFirstSlash(docRoot);
198
+ docRoot = normalizeZipPath(replaceFirstSlash(docRoot));
73
199
  const doc = docRoot.split('/')[0];
74
200
  const signatures = docbody['ofd:Signatures'];
75
201
  const stampAnnot = await getSignature(zip, signatures, doc);
@@ -119,7 +245,7 @@ export const getDocument = async function ([zip, doc, docRoot, stampAnnot]) {
119
245
  if (annotations.indexOf(doc) === -1) {
120
246
  annotations = `${doc}/${annotations}`;
121
247
  }
122
- if (zip.files[annotations]) {
248
+ if (getZipEntry(zip, annotations)) {
123
249
  annotations = await getJsonFromXmlContent(zip, annotations);
124
250
  array = array.concat(annotations['json']['ofd:Annotations']['ofd:Page']);
125
251
  }
@@ -136,7 +262,10 @@ const getAnnotations = async function (annoBase, annotations, doc, zip) {
136
262
  }
137
263
  const pageId = anno['@_PageID'];
138
264
  let fileLoc = anno['ofd:FileLoc'];
139
- fileLoc = replaceFirstSlash(fileLoc);
265
+ if (!fileLoc) {
266
+ continue
267
+ }
268
+ fileLoc = normalizeZipPath(replaceFirstSlash(fileLoc));
140
269
  if (annoBase && fileLoc.indexOf(annoBase) === -1) {
141
270
  fileLoc = `${annoBase}/${fileLoc}`;
142
271
  }
@@ -144,7 +273,7 @@ const getAnnotations = async function (annoBase, annotations, doc, zip) {
144
273
  fileLoc = `${doc}/${fileLoc}`;
145
274
  }
146
275
 
147
- if (zip.files[fileLoc]) {
276
+ if (getZipEntry(zip, fileLoc)) {
148
277
  const data = await getJsonFromXmlContent(zip, fileLoc);
149
278
 
150
279
  let array = [];
@@ -173,10 +302,8 @@ export const getDocumentRes = async function ([zip, doc, Document, stampAnnot, a
173
302
  let drawParamResObj = {};
174
303
  let multiMediaResObj = {};
175
304
  if (documentResPath) {
176
- if (documentResPath.indexOf(doc) == -1) {
177
- documentResPath = `${doc}/${documentResPath}`;
178
- }
179
- if (zip.files[documentResPath]) {
305
+ documentResPath = resolveDocumentPath(documentResPath, doc);
306
+ if (getZipEntry(zip, documentResPath)) {
180
307
  const data = await getJsonFromXmlContent(zip, documentResPath);
181
308
  const documentResObj = data['json']['ofd:Res'];
182
309
  fontResObj = await getFont(documentResObj);
@@ -190,10 +317,8 @@ export const getDocumentRes = async function ([zip, doc, Document, stampAnnot, a
190
317
  export const getPublicRes = async function ([zip, doc, Document, stampAnnot, annotationObjs, fontResObj, drawParamResObj, multiMediaResObj]) {
191
318
  let publicResPath = Document['ofd:CommonData']['ofd:PublicRes'];
192
319
  if (publicResPath) {
193
- if (publicResPath.indexOf(doc) == -1) {
194
- publicResPath = `${doc}/${publicResPath}`;
195
- }
196
- if (zip.files[publicResPath]) {
320
+ publicResPath = resolveDocumentPath(publicResPath, doc);
321
+ if (getZipEntry(zip, publicResPath)) {
197
322
  const data = await getJsonFromXmlContent(zip, publicResPath);
198
323
  const publicResObj = data['json']['ofd:Res'];
199
324
  let fontObj = await getFont(publicResObj);
@@ -257,8 +382,7 @@ const getFont = async function (res) {
257
382
  const fonts = res['ofd:Fonts'];
258
383
  let fontResObj = {};
259
384
  if (fonts) {
260
- let fontArray = [];
261
- fontArray = fontArray.concat(fonts['ofd:Font']);
385
+ let fontArray = collectGroupedItems(fonts, 'ofd:Font');
262
386
  for (const font of fontArray) {
263
387
  if (font) {
264
388
  if (font['@_FamilyName']) {
@@ -276,8 +400,7 @@ const getDrawParam = async function (res) {
276
400
  const drawParams = res['ofd:DrawParams'];
277
401
  let drawParamResObj = {};
278
402
  if (drawParams) {
279
- let array = [];
280
- array = array.concat(drawParams['ofd:DrawParam']);
403
+ let array = collectGroupedItems(drawParams, 'ofd:DrawParam');
281
404
  for (const item of array) {
282
405
  if (item) {
283
406
  drawParamResObj[item['@_ID']] = {
@@ -296,31 +419,29 @@ const getMultiMediaRes = async function (zip, res, doc) {
296
419
  const multiMedias = res['ofd:MultiMedias'];
297
420
  let multiMediaResObj = {};
298
421
  if (multiMedias) {
299
- let array = [];
300
- array = array.concat(multiMedias['ofd:MultiMedia']);
422
+ let array = collectGroupedItems(multiMedias, 'ofd:MultiMedia');
301
423
  for (const item of array) {
302
- if (item) {
303
- let file = item['ofd:MediaFile'];
304
- if (res['@_BaseLoc']) {
305
- if (file.indexOf(res['@_BaseLoc']) === -1) {
306
- file = `${res['@_BaseLoc']}/${file}`
307
- }
308
- }
309
- if (file.indexOf(doc) === -1) {
310
- file = `${doc}/${file}`
311
- }
312
- if (item['@_Type'].toLowerCase() === 'image') {
424
+ if (item && item['ofd:MediaFile']) {
425
+ const candidates = resolveResourceCandidates(item['ofd:MediaFile'], res['@_BaseLoc'], doc);
426
+ const file = getZipEntry(zip, candidates) ? candidates : null;
427
+ const type = item['@_Type'] ? item['@_Type'].toLowerCase() : '';
428
+ if (type === 'image') {
313
429
  const format = item['@_Format'];
314
- const ext = getExtensionByPath(file);
430
+ const ext = getExtensionByPath(candidates[0]);
315
431
  if ((format && (format.toLowerCase() === 'gbig2' || format.toLowerCase() === 'jb2')) || ext && (ext.toLowerCase() === 'jb2' || ext.toLowerCase() === 'gbig2')) {
316
- const jbig2 = await parseJbig2ImageFromZip(zip, file);
317
- multiMediaResObj[item['@_ID']] = jbig2;
432
+ const jbig2 = await parseJbig2ImageFromZip(zip, candidates);
433
+ if (jbig2) {
434
+ multiMediaResObj[item['@_ID']] = jbig2;
435
+ }
318
436
  } else {
319
- const img = await parseOtherImageFromZip(zip, file);
320
- multiMediaResObj[item['@_ID']] = {img, 'format': 'png'};
437
+ const imageFormat = format || ext || 'png';
438
+ const img = await parseOtherImageFromZip(zip, candidates, getImageMime(imageFormat));
439
+ if (img) {
440
+ multiMediaResObj[item['@_ID']] = {img, 'format': imageFormat.toLowerCase()};
441
+ }
321
442
  }
322
443
  } else {
323
- multiMediaResObj[item['@_ID']] = file;
444
+ multiMediaResObj[item['@_ID']] = file ? file[0] : candidates[0];
324
445
  }
325
446
  }
326
447
  }
@@ -330,9 +451,7 @@ const getMultiMediaRes = async function (zip, res, doc) {
330
451
 
331
452
  const parsePage = async function (zip, obj, doc) {
332
453
  let pagePath = obj['@_BaseLoc'];
333
- if (pagePath.indexOf(doc) == -1) {
334
- pagePath = `${doc}/${pagePath}`;
335
- }
454
+ pagePath = resolveDocumentPath(pagePath, doc);
336
455
  const data = await getJsonFromXmlContent(zip, pagePath);
337
456
  let pageObj = {};
338
457
  pageObj[obj['@_ID']] = {'json': data['json']['ofd:Page'], 'xml': data['xml']};
@@ -354,7 +473,12 @@ const getSealDocumentObj = function () {
354
473
 
355
474
  const getJsonFromXmlContent = async function (zip, xmlName) {
356
475
  return new Promise((resolve, reject) => {
357
- zip.files[xmlName].async('string').then(function (content) {
476
+ const entry = getZipEntry(zip, xmlName);
477
+ if (!entry) {
478
+ reject(new Error(`OFD XML resource not found: ${normalizeZipPath(xmlName)}`));
479
+ return;
480
+ }
481
+ entry.async('string').then(function (content) {
358
482
  ensureBrowserGlobal();
359
483
  let ops = {
360
484
  attributeNamePrefix: "@_",
@@ -373,7 +497,12 @@ const getJsonFromXmlContent = async function (zip, xmlName) {
373
497
 
374
498
  const parseJbig2ImageFromZip = async function (zip, name) {
375
499
  return new Promise((resolve, reject) => {
376
- zip.files[name].async('uint8array').then(function (bytes) {
500
+ const entry = getZipEntry(zip, name);
501
+ if (!entry) {
502
+ resolve(null);
503
+ return;
504
+ }
505
+ entry.async('uint8array').then(function (bytes) {
377
506
  let jbig2 = new Jbig2Image();
378
507
  const img = jbig2.parse(bytes);
379
508
  resolve({img, width: jbig2.width, height: jbig2.height, format: 'gbig2'});
@@ -383,10 +512,15 @@ const parseJbig2ImageFromZip = async function (zip, name) {
383
512
  });
384
513
  }
385
514
 
386
- const parseOtherImageFromZip = async function (zip, name) {
515
+ const parseOtherImageFromZip = async function (zip, name, mime = 'image/png') {
387
516
  return new Promise((resolve, reject) => {
388
- zip.files[name].async('base64').then(function (bytes) {
389
- const img = 'data:image/png;base64,' + bytes;
517
+ const entry = getZipEntry(zip, name);
518
+ if (!entry) {
519
+ resolve(null);
520
+ return;
521
+ }
522
+ entry.async('base64').then(function (bytes) {
523
+ const img = `data:${mime};base64,` + bytes;
390
524
  resolve(img);
391
525
  }, function error(e) {
392
526
  reject(e);
@@ -295,16 +295,26 @@ export const renderImageObject = function (pageWidth, pageHeight, multiMediaResO
295
295
  let boundary = parseStBox(imageObject['@_Boundary']);
296
296
  boundary = converterBox(boundary);
297
297
  const resId = imageObject['@_ResourceID'];
298
- if (multiMediaResObj[resId].format === 'gbig2') {
299
- const img = multiMediaResObj[resId].img;
300
- const width = multiMediaResObj[resId].width;
301
- const height = multiMediaResObj[resId].height;
298
+ const imageResource = multiMediaResObj ? multiMediaResObj[resId] : null;
299
+ if (!imageResource || typeof imageResource !== 'object') {
300
+ return renderMissingImageObject(boundary, imageObject['pfIndex']);
301
+ }
302
+ if (imageResource.format === 'gbig2') {
303
+ const img = imageResource.img;
304
+ const width = imageResource.width;
305
+ const height = imageResource.height;
302
306
  return renderImageOnCanvas(img, width, height, boundary, imageObject['pfIndex']);
303
307
  } else {
304
- return renderImageOnDiv(pageWidth, pageHeight, multiMediaResObj[resId].img, boundary, false, false, null, null, imageObject['pfIndex']);
308
+ return renderImageOnDiv(pageWidth, pageHeight, imageResource.img, boundary, false, false, null, null, imageObject['pfIndex']);
305
309
  }
306
310
  }
307
311
 
312
+ const renderMissingImageObject = function (boundary, oid){
313
+ let div = document.createElement('div');
314
+ div.setAttribute('style', `overflow: hidden; position: absolute; left: ${boundary.x}px; top: ${boundary.y}px; width: ${boundary.w}px; height: ${boundary.h}px;z-index: ${oid}`)
315
+ return div;
316
+ }
317
+
308
318
  const renderImageOnCanvas = function (img, imgWidth, imgHeight, boundary, oid){
309
319
  const arr = new Uint8ClampedArray(4 * imgWidth * imgHeight);
310
320
  for (var i = 0; i < img.length; i++) {