mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-02 20:37:21 +02:00
Compare commits
7 Commits
a4950c0b71
...
e5fbf52405
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5fbf52405 | ||
|
|
b33522a208 | ||
|
|
25693dc0ee | ||
|
|
9cd5a9658a | ||
|
|
213830f44f | ||
|
|
e0e59eaf01 | ||
|
|
6146e5fee7 |
@ -32,6 +32,7 @@ import {
|
|||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
import { CMapFactory, IdentityCMap } from "./cmap.js";
|
import { CMapFactory, IdentityCMap } from "./cmap.js";
|
||||||
import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js";
|
import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js";
|
||||||
|
import { compileType3Glyph, FontFlags } from "./fonts_utils.js";
|
||||||
import { ErrorFont, Font } from "./fonts.js";
|
import { ErrorFont, Font } from "./fonts.js";
|
||||||
import {
|
import {
|
||||||
fetchBinaryData,
|
fetchBinaryData,
|
||||||
@ -72,7 +73,6 @@ import { bidi } from "./bidi.js";
|
|||||||
import { ColorSpace } from "./colorspace.js";
|
import { ColorSpace } from "./colorspace.js";
|
||||||
import { ColorSpaceUtils } from "./colorspace_utils.js";
|
import { ColorSpaceUtils } from "./colorspace_utils.js";
|
||||||
import { DecodeStream } from "./decode_stream.js";
|
import { DecodeStream } from "./decode_stream.js";
|
||||||
import { FontFlags } from "./fonts_utils.js";
|
|
||||||
import { getFontSubstitution } from "./font_substitutions.js";
|
import { getFontSubstitution } from "./font_substitutions.js";
|
||||||
import { getGlyphsUnicode } from "./glyphlist.js";
|
import { getGlyphsUnicode } from "./glyphlist.js";
|
||||||
import { getMetrics } from "./metrics.js";
|
import { getMetrics } from "./metrics.js";
|
||||||
@ -180,14 +180,17 @@ function normalizeBlendMode(value, parsingArray = false) {
|
|||||||
return "source-over";
|
return "source-over";
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLocallyCachedImageOps(opList, data) {
|
function addCachedImageOps(
|
||||||
if (data.objId) {
|
opList,
|
||||||
opList.addDependency(data.objId);
|
{ objId, fn, args, optionalContent, hasMask }
|
||||||
|
) {
|
||||||
|
if (objId) {
|
||||||
|
opList.addDependency(objId);
|
||||||
}
|
}
|
||||||
opList.addImageOps(data.fn, data.args, data.optionalContent, data.hasMask);
|
opList.addImageOps(fn, args, optionalContent, hasMask);
|
||||||
|
|
||||||
if (data.fn === OPS.paintImageMaskXObject && data.args[0]?.count > 0) {
|
if (fn === OPS.paintImageMaskXObject && args[0]?.count > 0) {
|
||||||
data.args[0].count++;
|
args[0].count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -608,6 +611,12 @@ class PartialEvaluator {
|
|||||||
const decode = dict.getArray("D", "Decode");
|
const decode = dict.getArray("D", "Decode");
|
||||||
|
|
||||||
if (this.parsingType3Font) {
|
if (this.parsingType3Font) {
|
||||||
|
// NOTE: Compared to other image resources we don't bother caching
|
||||||
|
// Type3-glyph image masks, since we've not come across any cases
|
||||||
|
// where that actually helps.
|
||||||
|
// In Type3-glyphs image masks are "always" inline resources,
|
||||||
|
// they're usually fairly small and aren't being re-used either.
|
||||||
|
|
||||||
imgData = PDFImage.createRawMask({
|
imgData = PDFImage.createRawMask({
|
||||||
imgArray,
|
imgArray,
|
||||||
width: w,
|
width: w,
|
||||||
@ -616,25 +625,21 @@ class PartialEvaluator {
|
|||||||
inverseDecode: decode?.[0] > 0,
|
inverseDecode: decode?.[0] > 0,
|
||||||
interpolate,
|
interpolate,
|
||||||
});
|
});
|
||||||
|
args = compileType3Glyph(imgData);
|
||||||
|
|
||||||
imgData.cached = !!cacheKey;
|
if (args) {
|
||||||
|
operatorList.addImageOps(OPS.constructPath, args, optionalContent);
|
||||||
fn = OPS.paintImageMaskXObject;
|
return;
|
||||||
args = [imgData];
|
|
||||||
operatorList.addImageOps(fn, args, optionalContent);
|
|
||||||
|
|
||||||
if (cacheKey) {
|
|
||||||
const cacheData = { fn, args, optionalContent };
|
|
||||||
localImageCache.set(cacheKey, imageRef, cacheData);
|
|
||||||
|
|
||||||
if (imageRef) {
|
|
||||||
this._regionalImageCache.set(
|
|
||||||
/* name = */ null,
|
|
||||||
imageRef,
|
|
||||||
cacheData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
warn("Cannot compile Type3 glyph.");
|
||||||
|
|
||||||
|
// If compilation failed, or was disabled, fallback to using an inline
|
||||||
|
// image mask; this case should be extremely rare.
|
||||||
|
operatorList.addImageOps(
|
||||||
|
OPS.paintImageMaskXObject,
|
||||||
|
[imgData],
|
||||||
|
optionalContent
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -742,7 +747,8 @@ class PartialEvaluator {
|
|||||||
// If there is no imageMask, create the PDFImage and a lot
|
// If there is no imageMask, create the PDFImage and a lot
|
||||||
// of image processing can be done here.
|
// of image processing can be done here.
|
||||||
let objId = `img_${this.idFactory.createObjId()}`,
|
let objId = `img_${this.idFactory.createObjId()}`,
|
||||||
cacheGlobally = false;
|
cacheGlobally = false,
|
||||||
|
globalCacheData = null;
|
||||||
|
|
||||||
if (this.parsingType3Font) {
|
if (this.parsingType3Font) {
|
||||||
objId = `${this.idFactory.getDocId()}_type3_${objId}`;
|
objId = `${this.idFactory.getDocId()}_type3_${objId}`;
|
||||||
@ -767,15 +773,17 @@ class PartialEvaluator {
|
|||||||
operatorList.addImageOps(fn, args, optionalContent, hasMask);
|
operatorList.addImageOps(fn, args, optionalContent, hasMask);
|
||||||
|
|
||||||
if (cacheGlobally) {
|
if (cacheGlobally) {
|
||||||
|
globalCacheData = {
|
||||||
|
objId,
|
||||||
|
fn,
|
||||||
|
args,
|
||||||
|
optionalContent,
|
||||||
|
hasMask,
|
||||||
|
byteSize: 0, // Temporary entry, to avoid `setData` returning early.
|
||||||
|
};
|
||||||
|
|
||||||
if (this.globalImageCache.hasDecodeFailed(imageRef)) {
|
if (this.globalImageCache.hasDecodeFailed(imageRef)) {
|
||||||
this.globalImageCache.setData(imageRef, {
|
this.globalImageCache.setData(imageRef, globalCacheData);
|
||||||
objId,
|
|
||||||
fn,
|
|
||||||
args,
|
|
||||||
optionalContent,
|
|
||||||
hasMask,
|
|
||||||
byteSize: 0, // Data is `null`, since decoding failed previously.
|
|
||||||
});
|
|
||||||
|
|
||||||
this._sendImgData(objId, /* imgData = */ null, cacheGlobally);
|
this._sendImgData(objId, /* imgData = */ null, cacheGlobally);
|
||||||
return;
|
return;
|
||||||
@ -792,14 +800,7 @@ class PartialEvaluator {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (localLength) {
|
if (localLength) {
|
||||||
this.globalImageCache.setData(imageRef, {
|
this.globalImageCache.setData(imageRef, globalCacheData);
|
||||||
objId,
|
|
||||||
fn,
|
|
||||||
args,
|
|
||||||
optionalContent,
|
|
||||||
hasMask,
|
|
||||||
byteSize: 0, // Temporary entry, to avoid `setData` returning early.
|
|
||||||
});
|
|
||||||
this.globalImageCache.addByteSize(imageRef, localLength);
|
this.globalImageCache.addByteSize(imageRef, localLength);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -848,14 +849,8 @@ class PartialEvaluator {
|
|||||||
this._regionalImageCache.set(/* name = */ null, imageRef, cacheData);
|
this._regionalImageCache.set(/* name = */ null, imageRef, cacheData);
|
||||||
|
|
||||||
if (cacheGlobally) {
|
if (cacheGlobally) {
|
||||||
this.globalImageCache.setData(imageRef, {
|
assert(globalCacheData, "The global cache-data must be available.");
|
||||||
objId,
|
this.globalImageCache.setData(imageRef, globalCacheData);
|
||||||
fn,
|
|
||||||
args,
|
|
||||||
optionalContent,
|
|
||||||
hasMask,
|
|
||||||
byteSize: 0, // Temporary entry, note `addByteSize` above.
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1779,7 +1774,7 @@ class PartialEvaluator {
|
|||||||
if (isValidName) {
|
if (isValidName) {
|
||||||
const localImage = localImageCache.getByName(name);
|
const localImage = localImageCache.getByName(name);
|
||||||
if (localImage) {
|
if (localImage) {
|
||||||
addLocallyCachedImageOps(operatorList, localImage);
|
addCachedImageOps(operatorList, localImage);
|
||||||
args = null;
|
args = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -1793,28 +1788,12 @@ class PartialEvaluator {
|
|||||||
|
|
||||||
let xobj = xobjs.getRaw(name);
|
let xobj = xobjs.getRaw(name);
|
||||||
if (xobj instanceof Ref) {
|
if (xobj instanceof Ref) {
|
||||||
const localImage =
|
const cachedImage =
|
||||||
localImageCache.getByRef(xobj) ||
|
localImageCache.getByRef(xobj) ||
|
||||||
self._regionalImageCache.getByRef(xobj);
|
self._regionalImageCache.getByRef(xobj) ||
|
||||||
if (localImage) {
|
self.globalImageCache.getData(xobj, self.pageIndex);
|
||||||
addLocallyCachedImageOps(operatorList, localImage);
|
if (cachedImage) {
|
||||||
resolveXObject();
|
addCachedImageOps(operatorList, cachedImage);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const globalImage = self.globalImageCache.getData(
|
|
||||||
xobj,
|
|
||||||
self.pageIndex
|
|
||||||
);
|
|
||||||
if (globalImage) {
|
|
||||||
operatorList.addDependency(globalImage.objId);
|
|
||||||
operatorList.addImageOps(
|
|
||||||
globalImage.fn,
|
|
||||||
globalImage.args,
|
|
||||||
globalImage.optionalContent,
|
|
||||||
globalImage.hasMask
|
|
||||||
);
|
|
||||||
|
|
||||||
resolveXObject();
|
resolveXObject();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1907,7 +1886,7 @@ class PartialEvaluator {
|
|||||||
if (cacheKey) {
|
if (cacheKey) {
|
||||||
const localImage = localImageCache.getByName(cacheKey);
|
const localImage = localImageCache.getByName(cacheKey);
|
||||||
if (localImage) {
|
if (localImage) {
|
||||||
addLocallyCachedImageOps(operatorList, localImage);
|
addCachedImageOps(operatorList, localImage);
|
||||||
args = null;
|
args = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,11 +13,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { DrawOPS, info, OPS } from "../shared/util.js";
|
||||||
import { getEncoding, StandardEncoding } from "./encodings.js";
|
import { getEncoding, StandardEncoding } from "./encodings.js";
|
||||||
import { getGlyphsUnicode } from "./glyphlist.js";
|
import { getGlyphsUnicode } from "./glyphlist.js";
|
||||||
import { getLookupTableFactory } from "./core_utils.js";
|
import { getLookupTableFactory } from "./core_utils.js";
|
||||||
import { getUnicodeForGlyph } from "./unicode.js";
|
import { getUnicodeForGlyph } from "./unicode.js";
|
||||||
import { info } from "../shared/util.js";
|
|
||||||
|
|
||||||
// Accented characters have issues on Windows and Linux. When this flag is
|
// Accented characters have issues on Windows and Linux. When this flag is
|
||||||
// enabled glyphs that use seac and seac style endchar operators are truncated
|
// enabled glyphs that use seac and seac style endchar operators are truncated
|
||||||
@ -207,7 +207,177 @@ const getVerticalPresentationForm = getLookupTableFactory(t => {
|
|||||||
t[0xff5d] = 0xfe38; // FULLWIDTH RIGHT CURLY BRACKET
|
t[0xff5d] = 0xfe38; // FULLWIDTH RIGHT CURLY BRACKET
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// To disable Type3 compilation, set the value to `-1`.
|
||||||
|
const MAX_SIZE_TO_COMPILE = 1000;
|
||||||
|
|
||||||
|
function compileType3Glyph({ data: img, width, height }) {
|
||||||
|
if (width > MAX_SIZE_TO_COMPILE || height > MAX_SIZE_TO_COMPILE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const POINT_TO_PROCESS_LIMIT = 1000;
|
||||||
|
const POINT_TYPES = new Uint8Array([
|
||||||
|
0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const width1 = width + 1;
|
||||||
|
const points = new Uint8Array(width1 * (height + 1));
|
||||||
|
let i, j, j0;
|
||||||
|
|
||||||
|
// decodes bit-packed mask data
|
||||||
|
const lineSize = (width + 7) & ~7;
|
||||||
|
const data = new Uint8Array(lineSize * height);
|
||||||
|
let pos = 0;
|
||||||
|
for (const elem of img) {
|
||||||
|
let mask = 128;
|
||||||
|
while (mask > 0) {
|
||||||
|
data[pos++] = elem & mask ? 0 : 255;
|
||||||
|
mask >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding interesting points: every point is located between mask pixels,
|
||||||
|
// so there will be points of the (width + 1)x(height + 1) grid. Every point
|
||||||
|
// will have flags assigned based on neighboring mask pixels:
|
||||||
|
// 4 | 8
|
||||||
|
// --P--
|
||||||
|
// 2 | 1
|
||||||
|
// We are interested only in points with the flags:
|
||||||
|
// - outside corners: 1, 2, 4, 8;
|
||||||
|
// - inside corners: 7, 11, 13, 14;
|
||||||
|
// - and, intersections: 5, 10.
|
||||||
|
let count = 0;
|
||||||
|
pos = 0;
|
||||||
|
if (data[pos] !== 0) {
|
||||||
|
points[0] = 1;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
for (j = 1; j < width; j++) {
|
||||||
|
if (data[pos] !== data[pos + 1]) {
|
||||||
|
points[j] = data[pos] ? 2 : 1;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (data[pos] !== 0) {
|
||||||
|
points[j] = 2;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
for (i = 1; i < height; i++) {
|
||||||
|
pos = i * lineSize;
|
||||||
|
j0 = i * width1;
|
||||||
|
if (data[pos - lineSize] !== data[pos]) {
|
||||||
|
points[j0] = data[pos] ? 1 : 8;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
// 'sum' is the position of the current pixel configuration in the 'TYPES'
|
||||||
|
// array (in order 8-1-2-4, so we can use '>>2' to shift the column).
|
||||||
|
let sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
|
||||||
|
for (j = 1; j < width; j++) {
|
||||||
|
sum =
|
||||||
|
(sum >> 2) +
|
||||||
|
(data[pos + 1] ? 4 : 0) +
|
||||||
|
(data[pos - lineSize + 1] ? 8 : 0);
|
||||||
|
if (POINT_TYPES[sum]) {
|
||||||
|
points[j0 + j] = POINT_TYPES[sum];
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (data[pos - lineSize] !== data[pos]) {
|
||||||
|
points[j0 + j] = data[pos] ? 2 : 4;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > POINT_TO_PROCESS_LIMIT) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = lineSize * (height - 1);
|
||||||
|
j0 = i * width1;
|
||||||
|
if (data[pos] !== 0) {
|
||||||
|
points[j0] = 8;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
for (j = 1; j < width; j++) {
|
||||||
|
if (data[pos] !== data[pos + 1]) {
|
||||||
|
points[j0 + j] = data[pos] ? 4 : 8;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (data[pos] !== 0) {
|
||||||
|
points[j0 + j] = 4;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
if (count > POINT_TO_PROCESS_LIMIT) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// building outlines
|
||||||
|
const steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
|
||||||
|
const pathBuf = [];
|
||||||
|
|
||||||
|
// the path shall be painted in [0..1]x[0..1] space
|
||||||
|
const { a, b, c, d, e, f } = new DOMMatrix()
|
||||||
|
.scaleSelf(1 / width, -1 / height)
|
||||||
|
.translateSelf(0, -height);
|
||||||
|
|
||||||
|
for (i = 0; count && i <= height; i++) {
|
||||||
|
let p = i * width1;
|
||||||
|
const end = p + width;
|
||||||
|
while (p < end && !points[p]) {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (p === end) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let x = p % width1;
|
||||||
|
let y = i;
|
||||||
|
pathBuf.push(DrawOPS.moveTo, a * x + c * y + e, b * x + d * y + f);
|
||||||
|
|
||||||
|
const p0 = p;
|
||||||
|
let type = points[p];
|
||||||
|
do {
|
||||||
|
const step = steps[type];
|
||||||
|
do {
|
||||||
|
p += step;
|
||||||
|
} while (!points[p]);
|
||||||
|
|
||||||
|
const pp = points[p];
|
||||||
|
if (pp !== 5 && pp !== 10) {
|
||||||
|
// set new direction
|
||||||
|
type = pp;
|
||||||
|
// delete mark
|
||||||
|
points[p] = 0;
|
||||||
|
} else {
|
||||||
|
// type is 5 or 10, ie, a crossing
|
||||||
|
// set new direction
|
||||||
|
type = pp & ((0x33 * type) >> 4);
|
||||||
|
// set new type for "future hit"
|
||||||
|
points[p] &= (type >> 2) | (type << 2);
|
||||||
|
}
|
||||||
|
x = p % width1;
|
||||||
|
y = (p / width1) | 0;
|
||||||
|
pathBuf.push(DrawOPS.lineTo, a * x + c * y + e, b * x + d * y + f);
|
||||||
|
|
||||||
|
if (!points[p]) {
|
||||||
|
--count;
|
||||||
|
}
|
||||||
|
} while (p0 !== p);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
OPS.rawFillPath,
|
||||||
|
[new Float32Array(pathBuf)],
|
||||||
|
new Float32Array([0, 0, width, height]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
compileType3Glyph,
|
||||||
FontFlags,
|
FontFlags,
|
||||||
getVerticalPresentationForm,
|
getVerticalPresentationForm,
|
||||||
MacStandardGlyphOrdering,
|
MacStandardGlyphOrdering,
|
||||||
|
|||||||
@ -13,7 +13,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ImageKind, OPS, RenderingIntentFlag, warn } from "../shared/util.js";
|
import {
|
||||||
|
DrawOPS,
|
||||||
|
ImageKind,
|
||||||
|
OPS,
|
||||||
|
RenderingIntentFlag,
|
||||||
|
Util,
|
||||||
|
warn,
|
||||||
|
} from "../shared/util.js";
|
||||||
|
|
||||||
function addState(parentState, pattern, checkFn, iterateFn, processFn) {
|
function addState(parentState, pattern, checkFn, iterateFn, processFn) {
|
||||||
let state = parentState;
|
let state = parentState;
|
||||||
@ -470,6 +477,70 @@ addState(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// This replaces (save, transform, constructPath, restore)
|
||||||
|
// sequences with |constructPath| operation.
|
||||||
|
addState(
|
||||||
|
InitialState,
|
||||||
|
[OPS.save, OPS.transform, OPS.constructPath, OPS.restore],
|
||||||
|
context => {
|
||||||
|
const argsArray = context.argsArray;
|
||||||
|
const iFirstConstructPath = context.iCurr - 1;
|
||||||
|
const op = argsArray[iFirstConstructPath][0];
|
||||||
|
|
||||||
|
// When stroking the transform has to be applied to the line width too.
|
||||||
|
// So we can only optimize if the transform is an identity.
|
||||||
|
if (
|
||||||
|
op !== OPS.stroke &&
|
||||||
|
op !== OPS.closeStroke &&
|
||||||
|
op !== OPS.fillStroke &&
|
||||||
|
op !== OPS.eoFillStroke &&
|
||||||
|
op !== OPS.closeFillStroke &&
|
||||||
|
op !== OPS.closeEOFillStroke
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const iFirstTransform = context.iCurr - 2;
|
||||||
|
const transform = argsArray[iFirstTransform];
|
||||||
|
return (
|
||||||
|
transform[0] === 1 &&
|
||||||
|
transform[1] === 0 &&
|
||||||
|
transform[2] === 0 &&
|
||||||
|
transform[3] === 1
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => false,
|
||||||
|
(context, i) => {
|
||||||
|
const { fnArray, argsArray } = context;
|
||||||
|
const curr = context.iCurr;
|
||||||
|
const iFirstSave = curr - 3;
|
||||||
|
const iFirstTransform = curr - 2;
|
||||||
|
const iFirstConstructPath = curr - 1;
|
||||||
|
const args = argsArray[iFirstConstructPath];
|
||||||
|
const transform = argsArray[iFirstTransform];
|
||||||
|
const [, [buffer], minMax] = args;
|
||||||
|
|
||||||
|
Util.scaleMinMax(transform, minMax);
|
||||||
|
for (let k = 0, kk = buffer.length; k < kk; ) {
|
||||||
|
switch (buffer[k++]) {
|
||||||
|
case DrawOPS.moveTo:
|
||||||
|
case DrawOPS.lineTo:
|
||||||
|
Util.applyTransformInPlace(buffer.subarray(k), transform);
|
||||||
|
k += 2;
|
||||||
|
break;
|
||||||
|
case DrawOPS.curveTo:
|
||||||
|
Util.applyTransformToBezierInPlace(buffer.subarray(k), transform);
|
||||||
|
k += 6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Replace queue items.
|
||||||
|
fnArray.splice(iFirstSave, 4, OPS.constructPath);
|
||||||
|
argsArray.splice(iFirstSave, 4, args);
|
||||||
|
|
||||||
|
return iFirstSave + 1;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
class NullOptimizer {
|
class NullOptimizer {
|
||||||
constructor(queue) {
|
constructor(queue) {
|
||||||
this.queue = queue;
|
this.queue = queue;
|
||||||
@ -699,7 +770,7 @@ class OperatorList {
|
|||||||
case OPS.paintInlineImageXObjectGroup:
|
case OPS.paintInlineImageXObjectGroup:
|
||||||
case OPS.paintImageMaskXObject:
|
case OPS.paintImageMaskXObject:
|
||||||
const arg = argsArray[i][0]; // First parameter in imgData.
|
const arg = argsArray[i][0]; // First parameter in imgData.
|
||||||
if (!arg.cached && arg.data?.buffer instanceof ArrayBuffer) {
|
if (arg.data?.buffer instanceof ArrayBuffer) {
|
||||||
transfers.push(arg.data.buffer);
|
transfers.push(arg.data.buffer);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -54,9 +54,6 @@ const EXECUTION_TIME = 15; // ms
|
|||||||
// Defines the number of steps before checking the execution time.
|
// Defines the number of steps before checking the execution time.
|
||||||
const EXECUTION_STEPS = 10;
|
const EXECUTION_STEPS = 10;
|
||||||
|
|
||||||
// To disable Type3 compilation, set the value to `-1`.
|
|
||||||
const MAX_SIZE_TO_COMPILE = 1000;
|
|
||||||
|
|
||||||
const FULL_CHUNK_HEIGHT = 16;
|
const FULL_CHUNK_HEIGHT = 16;
|
||||||
|
|
||||||
// Only used in rescaleAndStroke. The goal is to avoid
|
// Only used in rescaleAndStroke. The goal is to avoid
|
||||||
@ -299,169 +296,6 @@ function drawImageAtIntegerCoords(
|
|||||||
return [scaleX * destW, scaleY * destH];
|
return [scaleX * destW, scaleY * destH];
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileType3Glyph(imgData) {
|
|
||||||
const { width, height } = imgData;
|
|
||||||
if (width > MAX_SIZE_TO_COMPILE || height > MAX_SIZE_TO_COMPILE) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const POINT_TO_PROCESS_LIMIT = 1000;
|
|
||||||
const POINT_TYPES = new Uint8Array([
|
|
||||||
0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const width1 = width + 1;
|
|
||||||
const points = new Uint8Array(width1 * (height + 1));
|
|
||||||
let i, j, j0;
|
|
||||||
|
|
||||||
// decodes bit-packed mask data
|
|
||||||
const lineSize = (width + 7) & ~7;
|
|
||||||
const data = new Uint8Array(lineSize * height);
|
|
||||||
let pos = 0;
|
|
||||||
for (const elem of imgData.data) {
|
|
||||||
let mask = 128;
|
|
||||||
while (mask > 0) {
|
|
||||||
data[pos++] = elem & mask ? 0 : 255;
|
|
||||||
mask >>= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// finding interesting points: every point is located between mask pixels,
|
|
||||||
// so there will be points of the (width + 1)x(height + 1) grid. Every point
|
|
||||||
// will have flags assigned based on neighboring mask pixels:
|
|
||||||
// 4 | 8
|
|
||||||
// --P--
|
|
||||||
// 2 | 1
|
|
||||||
// We are interested only in points with the flags:
|
|
||||||
// - outside corners: 1, 2, 4, 8;
|
|
||||||
// - inside corners: 7, 11, 13, 14;
|
|
||||||
// - and, intersections: 5, 10.
|
|
||||||
let count = 0;
|
|
||||||
pos = 0;
|
|
||||||
if (data[pos] !== 0) {
|
|
||||||
points[0] = 1;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
for (j = 1; j < width; j++) {
|
|
||||||
if (data[pos] !== data[pos + 1]) {
|
|
||||||
points[j] = data[pos] ? 2 : 1;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
if (data[pos] !== 0) {
|
|
||||||
points[j] = 2;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
for (i = 1; i < height; i++) {
|
|
||||||
pos = i * lineSize;
|
|
||||||
j0 = i * width1;
|
|
||||||
if (data[pos - lineSize] !== data[pos]) {
|
|
||||||
points[j0] = data[pos] ? 1 : 8;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
// 'sum' is the position of the current pixel configuration in the 'TYPES'
|
|
||||||
// array (in order 8-1-2-4, so we can use '>>2' to shift the column).
|
|
||||||
let sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
|
|
||||||
for (j = 1; j < width; j++) {
|
|
||||||
sum =
|
|
||||||
(sum >> 2) +
|
|
||||||
(data[pos + 1] ? 4 : 0) +
|
|
||||||
(data[pos - lineSize + 1] ? 8 : 0);
|
|
||||||
if (POINT_TYPES[sum]) {
|
|
||||||
points[j0 + j] = POINT_TYPES[sum];
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
if (data[pos - lineSize] !== data[pos]) {
|
|
||||||
points[j0 + j] = data[pos] ? 2 : 4;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count > POINT_TO_PROCESS_LIMIT) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = lineSize * (height - 1);
|
|
||||||
j0 = i * width1;
|
|
||||||
if (data[pos] !== 0) {
|
|
||||||
points[j0] = 8;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
for (j = 1; j < width; j++) {
|
|
||||||
if (data[pos] !== data[pos + 1]) {
|
|
||||||
points[j0 + j] = data[pos] ? 4 : 8;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
if (data[pos] !== 0) {
|
|
||||||
points[j0 + j] = 4;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
if (count > POINT_TO_PROCESS_LIMIT) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// building outlines
|
|
||||||
const steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
|
|
||||||
const path = new Path2D();
|
|
||||||
|
|
||||||
// the path shall be painted in [0..1]x[0..1] space
|
|
||||||
const { a, b, c, d, e, f } = new DOMMatrix()
|
|
||||||
.scaleSelf(1 / width, -1 / height)
|
|
||||||
.translateSelf(0, -height);
|
|
||||||
|
|
||||||
for (i = 0; count && i <= height; i++) {
|
|
||||||
let p = i * width1;
|
|
||||||
const end = p + width;
|
|
||||||
while (p < end && !points[p]) {
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
if (p === end) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let x = p % width1;
|
|
||||||
let y = i;
|
|
||||||
path.moveTo(a * x + c * y + e, b * x + d * y + f);
|
|
||||||
|
|
||||||
const p0 = p;
|
|
||||||
let type = points[p];
|
|
||||||
do {
|
|
||||||
const step = steps[type];
|
|
||||||
do {
|
|
||||||
p += step;
|
|
||||||
} while (!points[p]);
|
|
||||||
|
|
||||||
const pp = points[p];
|
|
||||||
if (pp !== 5 && pp !== 10) {
|
|
||||||
// set new direction
|
|
||||||
type = pp;
|
|
||||||
// delete mark
|
|
||||||
points[p] = 0;
|
|
||||||
} else {
|
|
||||||
// type is 5 or 10, ie, a crossing
|
|
||||||
// set new direction
|
|
||||||
type = pp & ((0x33 * type) >> 4);
|
|
||||||
// set new type for "future hit"
|
|
||||||
points[p] &= (type >> 2) | (type << 2);
|
|
||||||
}
|
|
||||||
x = p % width1;
|
|
||||||
y = (p / width1) | 0;
|
|
||||||
path.lineTo(a * x + c * y + e, b * x + d * y + f);
|
|
||||||
|
|
||||||
if (!points[p]) {
|
|
||||||
--count;
|
|
||||||
}
|
|
||||||
} while (p0 !== p);
|
|
||||||
--i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CanvasExtraState {
|
class CanvasExtraState {
|
||||||
constructor(width, height) {
|
constructor(width, height) {
|
||||||
// Are soft masks and alpha values shapes or opacities?
|
// Are soft masks and alpha values shapes or opacities?
|
||||||
@ -821,7 +655,6 @@ class CanvasGraphics {
|
|||||||
this.canvasFactory = canvasFactory;
|
this.canvasFactory = canvasFactory;
|
||||||
this.filterFactory = filterFactory;
|
this.filterFactory = filterFactory;
|
||||||
this.groupStack = [];
|
this.groupStack = [];
|
||||||
this.processingType3 = null;
|
|
||||||
// Patterns are painted relative to the initial page/form transform, see
|
// Patterns are painted relative to the initial page/form transform, see
|
||||||
// PDF spec 8.7.2 NOTE 1.
|
// PDF spec 8.7.2 NOTE 1.
|
||||||
this.baseTransform = null;
|
this.baseTransform = null;
|
||||||
@ -1747,6 +1580,10 @@ class CanvasGraphics {
|
|||||||
this.consumePath(path);
|
this.consumePath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rawFillPath(path) {
|
||||||
|
this.ctx.fill(path);
|
||||||
|
}
|
||||||
|
|
||||||
// Clipping
|
// Clipping
|
||||||
clip() {
|
clip() {
|
||||||
this.pendingClip = NORMAL_CLIP;
|
this.pendingClip = NORMAL_CLIP;
|
||||||
@ -2267,7 +2104,6 @@ class CanvasGraphics {
|
|||||||
if (!operatorList) {
|
if (!operatorList) {
|
||||||
warn(`Type3 character "${glyph.operatorListId}" is not available.`);
|
warn(`Type3 character "${glyph.operatorListId}" is not available.`);
|
||||||
} else if (this.contentVisible) {
|
} else if (this.contentVisible) {
|
||||||
this.processingType3 = glyph;
|
|
||||||
this.save();
|
this.save();
|
||||||
ctx.scale(fontSize, fontSize);
|
ctx.scale(fontSize, fontSize);
|
||||||
ctx.transform(...fontMatrix);
|
ctx.transform(...fontMatrix);
|
||||||
@ -2282,7 +2118,6 @@ class CanvasGraphics {
|
|||||||
current.x += width * textHScale;
|
current.x += width * textHScale;
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
this.processingType3 = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type3 fonts
|
// Type3 fonts
|
||||||
@ -2703,18 +2538,6 @@ class CanvasGraphics {
|
|||||||
img.count = count;
|
img.count = count;
|
||||||
|
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
const glyph = this.processingType3;
|
|
||||||
|
|
||||||
if (glyph) {
|
|
||||||
if (glyph.compiled === undefined) {
|
|
||||||
glyph.compiled = compileType3Glyph(img);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (glyph.compiled) {
|
|
||||||
ctx.fill(glyph.compiled);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const mask = this._createMaskCanvas(img);
|
const mask = this._createMaskCanvas(img);
|
||||||
const maskCanvas = mask.canvas;
|
const maskCanvas = mask.canvas;
|
||||||
|
|
||||||
|
|||||||
@ -339,6 +339,7 @@ const OPS = {
|
|||||||
constructPath: 91,
|
constructPath: 91,
|
||||||
setStrokeTransparent: 92,
|
setStrokeTransparent: 92,
|
||||||
setFillTransparent: 93,
|
setFillTransparent: 93,
|
||||||
|
rawFillPath: 94,
|
||||||
};
|
};
|
||||||
|
|
||||||
// In order to have a switch statement that is fast (i.e. which use a jump
|
// In order to have a switch statement that is fast (i.e. which use a jump
|
||||||
@ -676,6 +677,57 @@ class Util {
|
|||||||
return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`;
|
return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply a scaling matrix to some min/max values.
|
||||||
|
// If a scaling factor is negative then min and max must be
|
||||||
|
// swapped.
|
||||||
|
static scaleMinMax(transform, minMax) {
|
||||||
|
let temp;
|
||||||
|
if (transform[0]) {
|
||||||
|
if (transform[0] < 0) {
|
||||||
|
temp = minMax[0];
|
||||||
|
minMax[0] = minMax[2];
|
||||||
|
minMax[2] = temp;
|
||||||
|
}
|
||||||
|
minMax[0] *= transform[0];
|
||||||
|
minMax[2] *= transform[0];
|
||||||
|
|
||||||
|
if (transform[3] < 0) {
|
||||||
|
temp = minMax[1];
|
||||||
|
minMax[1] = minMax[3];
|
||||||
|
minMax[3] = temp;
|
||||||
|
}
|
||||||
|
minMax[1] *= transform[3];
|
||||||
|
minMax[3] *= transform[3];
|
||||||
|
} else {
|
||||||
|
temp = minMax[0];
|
||||||
|
minMax[0] = minMax[1];
|
||||||
|
minMax[1] = temp;
|
||||||
|
temp = minMax[2];
|
||||||
|
minMax[2] = minMax[3];
|
||||||
|
minMax[3] = temp;
|
||||||
|
|
||||||
|
if (transform[1] < 0) {
|
||||||
|
temp = minMax[1];
|
||||||
|
minMax[1] = minMax[3];
|
||||||
|
minMax[3] = temp;
|
||||||
|
}
|
||||||
|
minMax[1] *= transform[1];
|
||||||
|
minMax[3] *= transform[1];
|
||||||
|
|
||||||
|
if (transform[2] < 0) {
|
||||||
|
temp = minMax[0];
|
||||||
|
minMax[0] = minMax[2];
|
||||||
|
minMax[2] = temp;
|
||||||
|
}
|
||||||
|
minMax[0] *= transform[2];
|
||||||
|
minMax[2] *= transform[2];
|
||||||
|
}
|
||||||
|
minMax[0] += transform[4];
|
||||||
|
minMax[1] += transform[5];
|
||||||
|
minMax[2] += transform[4];
|
||||||
|
minMax[3] += transform[5];
|
||||||
|
}
|
||||||
|
|
||||||
// Concatenates two transformation matrices together and returns the result.
|
// Concatenates two transformation matrices together and returns the result.
|
||||||
static transform(m1, m2) {
|
static transform(m1, m2) {
|
||||||
return [
|
return [
|
||||||
@ -695,6 +747,22 @@ class Util {
|
|||||||
return [xt, yt];
|
return [xt, yt];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static applyTransformInPlace(p, m) {
|
||||||
|
const [p0, p1] = p;
|
||||||
|
p[0] = p0 * m[0] + p1 * m[2] + m[4];
|
||||||
|
p[1] = p0 * m[1] + p1 * m[3] + m[5];
|
||||||
|
}
|
||||||
|
|
||||||
|
// For 2d affine transforms
|
||||||
|
static applyTransformToBezierInPlace(p, [m0, m1, m2, m3, m4, m5]) {
|
||||||
|
for (let i = 0; i < 6; i += 2) {
|
||||||
|
const pI = p[i];
|
||||||
|
const pI1 = p[i + 1];
|
||||||
|
p[i] = pI * m0 + pI1 * m2 + m4;
|
||||||
|
p[i + 1] = pI * m1 + pI1 * m3 + m5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static applyInverseTransform(p, m) {
|
static applyInverseTransform(p, m) {
|
||||||
const d = m[0] * m[3] - m[1] * m[2];
|
const d = m[0] * m[3] - m[1] * m[2];
|
||||||
const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
|
const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user