@file-viewer/renderer-ofd 2.1.24 → 2.1.25
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.25",
|
|
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.25",
|
|
57
57
|
"jszip": "^3.10.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
@@ -64,6 +64,8 @@
|
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsc -b tsconfig.json",
|
|
66
66
|
"type-check": "tsc -b tsconfig.json",
|
|
67
|
-
"verify:github-94": "node scripts/verify-github-94.mjs"
|
|
67
|
+
"verify:github-94": "node scripts/verify-github-94.mjs",
|
|
68
|
+
"verify:pageblock-render": "node scripts/verify-pageblock-render.mjs",
|
|
69
|
+
"verify:render-fidelity": "node scripts/verify-render-fidelity.mjs"
|
|
68
70
|
}
|
|
69
71
|
}
|
|
@@ -43,7 +43,7 @@ const appendChildValue = function (target, key, value) {
|
|
|
43
43
|
target[key] = value;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const parseXmlElement = function (element, options) {
|
|
46
|
+
const parseXmlElement = function (element, options, order) {
|
|
47
47
|
const attrPrefix = options.attributeNamePrefix ?? '@_';
|
|
48
48
|
const result = {};
|
|
49
49
|
if (options.ignoreAttributes !== true) {
|
|
@@ -55,7 +55,16 @@ const parseXmlElement = function (element, options) {
|
|
|
55
55
|
const textParts = [];
|
|
56
56
|
for (const child of Array.from(element.childNodes || [])) {
|
|
57
57
|
if (child.nodeType === 1) {
|
|
58
|
-
|
|
58
|
+
// 渲染层按 pfIndex 做绘制顺序(CSS z-index),这里用前序遍历给每个元素编号,
|
|
59
|
+
// 还原 Image/Path/Text/PageBlock 在原始文档里交错出现的先后关系。
|
|
60
|
+
// 若不记录,解析结果会把同名标签分组成数组,渲染时只能按“类型”整批绘制
|
|
61
|
+
// (所有图片一起、所有路径一起……),导致本该压在图片下方的装饰路径盖住图片。
|
|
62
|
+
const pfIndex = order ? order.next++ : undefined;
|
|
63
|
+
const parsedChild = parseXmlElement(child, options, order);
|
|
64
|
+
if (parsedChild && typeof parsedChild === 'object' && pfIndex !== undefined) {
|
|
65
|
+
parsedChild.pfIndex = pfIndex;
|
|
66
|
+
}
|
|
67
|
+
appendChildValue(result, child.nodeName, parsedChild);
|
|
59
68
|
continue;
|
|
60
69
|
}
|
|
61
70
|
if (child.nodeType === 3 || child.nodeType === 4) {
|
|
@@ -94,7 +103,7 @@ const parseXmlToJson = function (xmlData, options = {}) {
|
|
|
94
103
|
return {};
|
|
95
104
|
}
|
|
96
105
|
return {
|
|
97
|
-
[root.nodeName]: parseXmlElement(root, options)
|
|
106
|
+
[root.nodeName]: parseXmlElement(root, options, { next: 0 })
|
|
98
107
|
};
|
|
99
108
|
}
|
|
100
109
|
|
|
@@ -125,6 +125,43 @@ export const calPageBoxScale = function (document, page) {
|
|
|
125
125
|
return box;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
// OFD 的 PathObject/TextObject/Layer 通常通过 DrawParam 引用共享颜色/线宽,
|
|
129
|
+
// 而不是在对象上直接写 ofd:FillColor / ofd:StrokeColor;DrawParam 之间还能通过
|
|
130
|
+
// Relative 相互继承。三处(Layer、PathObject、TextObject)都需要同样的解析规则,
|
|
131
|
+
// 否则引用 DrawParam 取色的对象会因为拿不到颜色而被当成透明,完全不可见。
|
|
132
|
+
const resolveDrawParamStyle = function (drawParamResObj, drawParamId, base) {
|
|
133
|
+
let fillColor = base?.fillColor ?? null;
|
|
134
|
+
let strokeColor = base?.strokeColor ?? null;
|
|
135
|
+
let lineWith = base?.lineWith;
|
|
136
|
+
if (!drawParamId || !drawParamResObj || !drawParamResObj[drawParamId]) {
|
|
137
|
+
return { fillColor, strokeColor, lineWith };
|
|
138
|
+
}
|
|
139
|
+
let drawParam = drawParamId;
|
|
140
|
+
if (drawParamResObj[drawParam]['relative']) {
|
|
141
|
+
drawParam = drawParamResObj[drawParam]['relative'];
|
|
142
|
+
if (drawParamResObj[drawParam] && drawParamResObj[drawParam]['FillColor']) {
|
|
143
|
+
fillColor = parseColor(drawParamResObj[drawParam]['FillColor']);
|
|
144
|
+
}
|
|
145
|
+
if (drawParamResObj[drawParam] && drawParamResObj[drawParam]['StrokeColor']) {
|
|
146
|
+
strokeColor = parseColor(drawParamResObj[drawParam]['StrokeColor']);
|
|
147
|
+
}
|
|
148
|
+
if (drawParamResObj[drawParam] && drawParamResObj[drawParam]['LineWidth']) {
|
|
149
|
+
lineWith = converterDpi(drawParamResObj[drawParam]['LineWidth']);
|
|
150
|
+
}
|
|
151
|
+
drawParam = drawParamId;
|
|
152
|
+
}
|
|
153
|
+
if (drawParamResObj[drawParam]['FillColor']) {
|
|
154
|
+
fillColor = parseColor(drawParamResObj[drawParam]['FillColor']);
|
|
155
|
+
}
|
|
156
|
+
if (drawParamResObj[drawParam]['StrokeColor']) {
|
|
157
|
+
strokeColor = parseColor(drawParamResObj[drawParam]['StrokeColor']);
|
|
158
|
+
}
|
|
159
|
+
if (drawParamResObj[drawParam]['LineWidth']) {
|
|
160
|
+
lineWith = converterDpi(drawParamResObj[drawParam]['LineWidth']);
|
|
161
|
+
}
|
|
162
|
+
return { fillColor, strokeColor, lineWith };
|
|
163
|
+
}
|
|
164
|
+
|
|
128
165
|
// 根据模板来渲染层节点
|
|
129
166
|
const renderLayerFromTemplate = function (tpls, template, pageDiv, fontResObj, drawParamResObj, multiMediaResObj) {
|
|
130
167
|
let array = [];
|
|
@@ -235,33 +272,14 @@ const renderSealPage = function (pageDiv, pages, tpls, isStampAnnot, stampAnnot,
|
|
|
235
272
|
}
|
|
236
273
|
|
|
237
274
|
const renderLayer = function (pageDiv, fontResObj, drawParamResObj, multiMediaResObj, layer, isStampAnnot) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
fillColor = parseColor(drawParamResObj[drawParam]['FillColor']);
|
|
247
|
-
}
|
|
248
|
-
if (drawParamResObj[drawParam]['StrokeColor']) {
|
|
249
|
-
strokeColor = parseColor(drawParamResObj[drawParam]['StrokeColor']);
|
|
250
|
-
}
|
|
251
|
-
if (drawParamResObj[drawParam]['LineWidth']) {
|
|
252
|
-
lineWith = converterDpi(drawParamResObj[drawParam]['LineWidth']);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (drawParamResObj[drawParam]['FillColor']) {
|
|
256
|
-
fillColor = parseColor(drawParamResObj[drawParam]['FillColor']);
|
|
257
|
-
}
|
|
258
|
-
if (drawParamResObj[drawParam]['StrokeColor']) {
|
|
259
|
-
strokeColor = parseColor(drawParamResObj[drawParam]['StrokeColor']);
|
|
260
|
-
}
|
|
261
|
-
if (drawParamResObj[drawParam]['LineWidth']) {
|
|
262
|
-
lineWith = converterDpi(drawParamResObj[drawParam]['LineWidth']);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
275
|
+
const layerStyle = resolveDrawParamStyle(drawParamResObj, layer?.['@_DrawParam'], {
|
|
276
|
+
fillColor: null,
|
|
277
|
+
strokeColor: null,
|
|
278
|
+
lineWith: converterDpi(0.353),
|
|
279
|
+
});
|
|
280
|
+
let fillColor = layerStyle.fillColor;
|
|
281
|
+
let strokeColor = layerStyle.strokeColor;
|
|
282
|
+
let lineWith = layerStyle.lineWith;
|
|
265
283
|
const imageObjects = layer?.['ofd:ImageObject'];
|
|
266
284
|
let imageObjectArray = [];
|
|
267
285
|
imageObjectArray = imageObjectArray.concat(imageObjects);
|
|
@@ -285,15 +303,55 @@ const renderLayer = function (pageDiv, fontResObj, drawParamResObj, multiMediaRe
|
|
|
285
303
|
textObjectArray = textObjectArray.concat(textObjects);
|
|
286
304
|
for (const textObject of textObjectArray) {
|
|
287
305
|
if (textObject) {
|
|
288
|
-
let svg = renderTextObject(fontResObj, textObject, fillColor, strokeColor);
|
|
306
|
+
let svg = renderTextObject(drawParamResObj, fontResObj, textObject, fillColor, strokeColor);
|
|
289
307
|
pageDiv.appendChild(svg);
|
|
290
308
|
}
|
|
291
309
|
}
|
|
310
|
+
const pageBlocks = layer?.['ofd:PageBlock'];
|
|
311
|
+
let pageBlockArray = [];
|
|
312
|
+
pageBlockArray = pageBlockArray.concat(pageBlocks);
|
|
313
|
+
for (const pageBlock of pageBlockArray) {
|
|
314
|
+
if (pageBlock) {
|
|
315
|
+
// 部分 OFD(如 OFD R&W 导出的版式)把正文对象包在 PageBlock 里,需递归渲染。
|
|
316
|
+
renderLayer(pageDiv, fontResObj, drawParamResObj, multiMediaResObj, pageBlock, isStampAnnot);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 部分 OFD 导出工具(如 OFD R&W)会让同一图层内所有图像共享同一个整页 Boundary,
|
|
322
|
+
// 图像的真实位置和尺寸完全由 CTM 决定;此时必须优先按 CTM 计算矩形,否则所有图像会被拉伸铺满整页并互相重叠。
|
|
323
|
+
const parseImageCtm = function (ctm) {
|
|
324
|
+
if (!ctm) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
const values = String(ctm).trim().split(/\s+/).map(Number);
|
|
328
|
+
return values.length === 6 && values.every(value => Number.isFinite(value)) ? values : null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const resolveImageGeometry = function (imageObject) {
|
|
332
|
+
const boundary = converterBox(parseStBox(imageObject['@_Boundary']));
|
|
333
|
+
const ctmValues = parseImageCtm(imageObject['@_CTM']);
|
|
334
|
+
if (!ctmValues) {
|
|
335
|
+
return { boundary, matrix: null };
|
|
336
|
+
}
|
|
337
|
+
const [a, b, c, d, e, f] = ctmValues;
|
|
338
|
+
const isAxisAligned = Math.abs(b) < 1e-6 && Math.abs(c) < 1e-6;
|
|
339
|
+
if (isAxisAligned) {
|
|
340
|
+
// CTM 把单位正方形映射到页面坐标系,a/d 为宽高、e/f 为左上角,单位与 Boundary 一致(mm)。
|
|
341
|
+
const ctmBoundary = converterBox({
|
|
342
|
+
x: Math.min(e, e + a),
|
|
343
|
+
y: Math.min(f, f + d),
|
|
344
|
+
w: Math.abs(a),
|
|
345
|
+
h: Math.abs(d),
|
|
346
|
+
});
|
|
347
|
+
return { boundary: ctmBoundary, matrix: null };
|
|
348
|
+
}
|
|
349
|
+
// 存在旋转/斜切时,退化为整页 Boundary 无法给出正确外框,改用矩阵直接变换单位方块。
|
|
350
|
+
return { boundary, matrix: ctmValues };
|
|
292
351
|
}
|
|
293
352
|
|
|
294
353
|
export const renderImageObject = function (pageWidth, pageHeight, multiMediaResObj, imageObject){
|
|
295
|
-
|
|
296
|
-
boundary = converterBox(boundary);
|
|
354
|
+
const { boundary, matrix } = resolveImageGeometry(imageObject);
|
|
297
355
|
const resId = imageObject['@_ResourceID'];
|
|
298
356
|
const imageResource = multiMediaResObj ? multiMediaResObj[resId] : null;
|
|
299
357
|
if (!imageResource || typeof imageResource !== 'object') {
|
|
@@ -304,11 +362,24 @@ export const renderImageObject = function (pageWidth, pageHeight, multiMediaResO
|
|
|
304
362
|
const width = imageResource.width;
|
|
305
363
|
const height = imageResource.height;
|
|
306
364
|
return renderImageOnCanvas(img, width, height, boundary, imageObject['pfIndex']);
|
|
365
|
+
} else if (matrix) {
|
|
366
|
+
return renderImageWithMatrix(imageResource.img, matrix, imageObject['pfIndex']);
|
|
307
367
|
} else {
|
|
308
368
|
return renderImageOnDiv(pageWidth, pageHeight, imageResource.img, boundary, false, false, null, null, imageObject['pfIndex']);
|
|
309
369
|
}
|
|
310
370
|
}
|
|
311
371
|
|
|
372
|
+
const renderImageWithMatrix = function (imgSrc, ctmValues, oid) {
|
|
373
|
+
const [a, b, c, d, e, f] = ctmValues;
|
|
374
|
+
const unit = converterDpi(1);
|
|
375
|
+
let img = document.createElement('img');
|
|
376
|
+
img.src = imgSrc;
|
|
377
|
+
img.setAttribute('width', `${unit}`);
|
|
378
|
+
img.setAttribute('height', `${unit}`);
|
|
379
|
+
img.setAttribute('style', `position:absolute;left:0;top:0;transform-origin:0 0;transform:matrix(${a}, ${b}, ${c}, ${d}, ${converterDpi(e)}, ${converterDpi(f)});z-index:${oid}`);
|
|
380
|
+
return img;
|
|
381
|
+
}
|
|
382
|
+
|
|
312
383
|
const renderMissingImageObject = function (boundary, oid){
|
|
313
384
|
let div = document.createElement('div');
|
|
314
385
|
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}`)
|
|
@@ -360,7 +431,7 @@ export const renderImageOnDiv = function (pageWidth, pageHeight, imgSrc, boundar
|
|
|
360
431
|
return div;
|
|
361
432
|
}
|
|
362
433
|
|
|
363
|
-
export const renderTextObject = function (fontResObj, textObject, defaultFillColor, defaultStrokeColor) {
|
|
434
|
+
export const renderTextObject = function (drawParamResObj, fontResObj, textObject, defaultFillColor, defaultStrokeColor) {
|
|
364
435
|
let defaultFillOpacity = 1;
|
|
365
436
|
let boundary = parseStBox(textObject['@_Boundary']);
|
|
366
437
|
boundary = converterBox(boundary);
|
|
@@ -374,6 +445,12 @@ export const renderTextObject = function (fontResObj, textObject, defaultFillCol
|
|
|
374
445
|
const textCodePointList = calTextPoint(array);
|
|
375
446
|
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
376
447
|
svg.setAttribute('version', '1.1');
|
|
448
|
+
const objectStyle = resolveDrawParamStyle(drawParamResObj, textObject['@_DrawParam'], {
|
|
449
|
+
fillColor: defaultFillColor,
|
|
450
|
+
strokeColor: defaultStrokeColor,
|
|
451
|
+
});
|
|
452
|
+
defaultFillColor = objectStyle.fillColor;
|
|
453
|
+
defaultStrokeColor = objectStyle.strokeColor;
|
|
377
454
|
const fillColor = textObject['ofd:FillColor'];
|
|
378
455
|
if (fillColor) {
|
|
379
456
|
defaultFillColor = parseColor(fillColor['@_Value']);
|
|
@@ -412,6 +489,83 @@ export const renderTextObject = function (fontResObj, textObject, defaultFillCol
|
|
|
412
489
|
return svg;
|
|
413
490
|
}
|
|
414
491
|
|
|
492
|
+
const buildSvgPathData = function (points) {
|
|
493
|
+
let d = '';
|
|
494
|
+
for (const point of points) {
|
|
495
|
+
if (point.type === 'M') {
|
|
496
|
+
d += `M${point.x} ${point.y} `;
|
|
497
|
+
} else if (point.type === 'L') {
|
|
498
|
+
d += `L${point.x} ${point.y} `;
|
|
499
|
+
} else if (point.type === 'Q') {
|
|
500
|
+
d += `Q${point.x1} ${point.y1} ${point.x2} ${point.y2} `;
|
|
501
|
+
} else if (point.type === 'B') {
|
|
502
|
+
d += `C${point.x1} ${point.y1} ${point.x2} ${point.y2} ${point.x3} ${point.y3} `;
|
|
503
|
+
} else if (point.type === 'C') {
|
|
504
|
+
d += `Z`;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return d;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// OFD 的装饰性 PathObject(如色带、圆角背景)经常共用一个近乎整页的 Boundary,
|
|
511
|
+
// 真正可见的形状由 Clips 裁剪出来;不处理 Clips 会让这些路径以整块矩形铺满并
|
|
512
|
+
// 盖住下层图片/文字。Clips 下可以有多个 Clip(依次相交),每个 Clip 下可以有
|
|
513
|
+
// 多个 Area(在该 Clip 内取并集),语义上等价于 SVG 里逐层嵌套 clip-path。
|
|
514
|
+
const appendClipAreaPath = function (clipPathEl, area) {
|
|
515
|
+
const clipShape = area?.['ofd:Path'];
|
|
516
|
+
const abbreviatedData = clipShape?.['ofd:AbbreviatedData'];
|
|
517
|
+
if (!clipShape || !abbreviatedData) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const clipPoints = calPathPoint(convertPathAbbreviatedDatatoPoint(abbreviatedData));
|
|
521
|
+
const clipPathShape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
522
|
+
clipPathShape.setAttribute('d', buildSvgPathData(clipPoints));
|
|
523
|
+
const clipCtm = clipShape['@_CTM'];
|
|
524
|
+
if (clipCtm) {
|
|
525
|
+
const ctms = parseCtm(clipCtm);
|
|
526
|
+
clipPathShape.setAttribute('transform', `matrix(${ctms[0]} ${ctms[1]} ${ctms[2]} ${ctms[3]} ${converterDpi(ctms[4])} ${converterDpi(ctms[5])})`);
|
|
527
|
+
}
|
|
528
|
+
clipPathEl.appendChild(clipPathShape);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const applyClips = function (svgRoot, contentEl, clips) {
|
|
532
|
+
const clipList = clips?.['ofd:Clip'];
|
|
533
|
+
if (!clipList) {
|
|
534
|
+
return contentEl;
|
|
535
|
+
}
|
|
536
|
+
let clipArray = [];
|
|
537
|
+
clipArray = clipArray.concat(clipList);
|
|
538
|
+
let defs = null;
|
|
539
|
+
let current = contentEl;
|
|
540
|
+
let clipIndex = 0;
|
|
541
|
+
for (const clip of clipArray) {
|
|
542
|
+
const areas = clip?.['ofd:Area'];
|
|
543
|
+
if (!clip || !areas) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (!defs) {
|
|
547
|
+
defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
548
|
+
svgRoot.appendChild(defs);
|
|
549
|
+
}
|
|
550
|
+
const clipPathEl = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
|
|
551
|
+
const clipId = `ofd-clip-${clipIndex++}-${Math.random().toString(36).slice(2, 8)}`;
|
|
552
|
+
clipPathEl.setAttribute('id', clipId);
|
|
553
|
+
let areaArray = [];
|
|
554
|
+
areaArray = areaArray.concat(areas);
|
|
555
|
+
for (const area of areaArray) {
|
|
556
|
+
if (area) {
|
|
557
|
+
appendClipAreaPath(clipPathEl, area);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
defs.appendChild(clipPathEl);
|
|
561
|
+
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
562
|
+
group.setAttribute('clip-path', `url(#${clipId})`);
|
|
563
|
+
group.appendChild(current);
|
|
564
|
+
current = group;
|
|
565
|
+
}
|
|
566
|
+
return current;
|
|
567
|
+
}
|
|
568
|
+
|
|
415
569
|
export const renderPathObject = function (drawParamResObj, pathObject, defaultFillColor, defaultStrokeColor, defaultLineWith, isStampAnnot) {
|
|
416
570
|
let boundary = parseStBox(pathObject['@_Boundary']);
|
|
417
571
|
boundary = converterBox(boundary);
|
|
@@ -427,11 +581,20 @@ export const renderPathObject = function (drawParamResObj, pathObject, defaultFi
|
|
|
427
581
|
}
|
|
428
582
|
const drawParam = pathObject['@_DrawParam'];
|
|
429
583
|
if (drawParam) {
|
|
430
|
-
lineWidth = drawParamResObj[drawParam]
|
|
584
|
+
lineWidth = drawParamResObj[drawParam]?.LineWidth;
|
|
431
585
|
if (lineWidth) {
|
|
432
586
|
defaultLineWith = converterDpi(lineWidth);
|
|
433
587
|
}
|
|
434
588
|
}
|
|
589
|
+
// PathObject 自身没有内联 ofd:FillColor/StrokeColor 时,颜色通常来自其引用的 DrawParam;
|
|
590
|
+
// 先按 DrawParam 解析出默认色,再让内联颜色(若存在)覆盖,否则文字/图形轮廓会因为
|
|
591
|
+
// 拿不到颜色而按 fill:none 处理,变成不可见。
|
|
592
|
+
const objectStyle = resolveDrawParamStyle(drawParamResObj, drawParam, {
|
|
593
|
+
fillColor: defaultFillColor,
|
|
594
|
+
strokeColor: defaultStrokeColor,
|
|
595
|
+
});
|
|
596
|
+
defaultFillColor = objectStyle.fillColor;
|
|
597
|
+
defaultStrokeColor = objectStyle.strokeColor;
|
|
435
598
|
if (ctm) {
|
|
436
599
|
const ctms = parseCtm(ctm);
|
|
437
600
|
path.setAttribute('transform', `matrix(${ctms[0]} ${ctms[1]} ${ctms[2]} ${ctms[3]} ${converterDpi(ctms[4])} ${converterDpi(ctms[5])})`)
|
|
@@ -460,20 +623,8 @@ export const renderPathObject = function (drawParamResObj, pathObject, defaultFi
|
|
|
460
623
|
fillStyle = `fill:${isStampAnnot ? 'none' : defaultFillColor ? defaultFillColor : 'none'};`;
|
|
461
624
|
}
|
|
462
625
|
path.setAttribute('style', `${strokeStyle};${fillStyle}`)
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if (point.type === 'M') {
|
|
466
|
-
d += `M${point.x} ${point.y} `;
|
|
467
|
-
} else if (point.type === 'L') {
|
|
468
|
-
d += `L${point.x} ${point.y} `;
|
|
469
|
-
} else if (point.type === 'B') {
|
|
470
|
-
d += `C${point.x1} ${point.y1} ${point.x2} ${point.y2} ${point.x3} ${point.y3} `;
|
|
471
|
-
} else if (point.type === 'C') {
|
|
472
|
-
d += `Z`;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
path.setAttribute('d', d);
|
|
476
|
-
svg.appendChild(path);
|
|
626
|
+
path.setAttribute('d', buildSvgPathData(points));
|
|
627
|
+
svg.appendChild(applyClips(svg, path, pathObject['ofd:Clips']));
|
|
477
628
|
let width = isStampAnnot ? boundary.w : Math.ceil(boundary.w);
|
|
478
629
|
let height = isStampAnnot ? boundary.h : Math.ceil(boundary.h);
|
|
479
630
|
let left = boundary.x;
|
|
@@ -61,6 +61,19 @@ export const convertPathAbbreviatedDatatoPoint = abbreviatedData => {
|
|
|
61
61
|
}
|
|
62
62
|
i = i + 7;
|
|
63
63
|
pointList.push(point);
|
|
64
|
+
} else if (array[i] === 'Q') {
|
|
65
|
+
// Q x1 y1 x2 y2:以(x1,y1)为控制点画二次贝塞尔到(x2,y2)。
|
|
66
|
+
// 大量中文字形轮廓(本身是路径而非 TextObject)都靠 Q 描述笔画弧线,
|
|
67
|
+
// 之前完全未识别,会导致后续 token 错位、字形整体丢失。
|
|
68
|
+
let point = {
|
|
69
|
+
'type': 'Q',
|
|
70
|
+
'x1': parseFloat(array[i + 1]),
|
|
71
|
+
'y1': parseFloat(array[i + 2]),
|
|
72
|
+
'x2': parseFloat(array[i + 3]),
|
|
73
|
+
'y2': parseFloat(array[i + 4])
|
|
74
|
+
}
|
|
75
|
+
i = i + 5;
|
|
76
|
+
pointList.push(point);
|
|
64
77
|
} else {
|
|
65
78
|
i++;
|
|
66
79
|
}
|
|
@@ -90,6 +103,13 @@ export const calPathPoint = function (abbreviatedPoint) {
|
|
|
90
103
|
'x3': converterDpi(x3), 'y3': converterDpi(y3)
|
|
91
104
|
}
|
|
92
105
|
pointList.push(realPoint);
|
|
106
|
+
} else if (point.type === 'Q') {
|
|
107
|
+
let realPoint = {
|
|
108
|
+
'type': 'Q',
|
|
109
|
+
'x1': converterDpi(point.x1), 'y1': converterDpi(point.y1),
|
|
110
|
+
'x2': converterDpi(point.x2), 'y2': converterDpi(point.y2)
|
|
111
|
+
}
|
|
112
|
+
pointList.push(realPoint);
|
|
93
113
|
}
|
|
94
114
|
}
|
|
95
115
|
return pointList;
|