@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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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(
|
|
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,
|
|
317
|
-
|
|
432
|
+
const jbig2 = await parseJbig2ImageFromZip(zip, candidates);
|
|
433
|
+
if (jbig2) {
|
|
434
|
+
multiMediaResObj[item['@_ID']] = jbig2;
|
|
435
|
+
}
|
|
318
436
|
} else {
|
|
319
|
-
const
|
|
320
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
389
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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,
|
|
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++) {
|