mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 22:58:07 +02:00
Merge pull request #19736 from Snuffleupagus/compileType3Glyph-worker
[api-minor] Move Type3-glyph compilation to the worker-thread
This commit is contained in:
commit
e5fbf52405
5 changed files with 197 additions and 201 deletions
|
@ -32,6 +32,7 @@ import {
|
|||
} from "../shared/util.js";
|
||||
import { CMapFactory, IdentityCMap } from "./cmap.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 {
|
||||
fetchBinaryData,
|
||||
|
@ -72,7 +73,6 @@ import { bidi } from "./bidi.js";
|
|||
import { ColorSpace } from "./colorspace.js";
|
||||
import { ColorSpaceUtils } from "./colorspace_utils.js";
|
||||
import { DecodeStream } from "./decode_stream.js";
|
||||
import { FontFlags } from "./fonts_utils.js";
|
||||
import { getFontSubstitution } from "./font_substitutions.js";
|
||||
import { getGlyphsUnicode } from "./glyphlist.js";
|
||||
import { getMetrics } from "./metrics.js";
|
||||
|
@ -611,6 +611,12 @@ class PartialEvaluator {
|
|||
const decode = dict.getArray("D", "Decode");
|
||||
|
||||
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({
|
||||
imgArray,
|
||||
width: w,
|
||||
|
@ -619,25 +625,21 @@ class PartialEvaluator {
|
|||
inverseDecode: decode?.[0] > 0,
|
||||
interpolate,
|
||||
});
|
||||
args = compileType3Glyph(imgData);
|
||||
|
||||
imgData.cached = !!cacheKey;
|
||||
|
||||
fn = OPS.paintImageMaskXObject;
|
||||
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
|
||||
);
|
||||
}
|
||||
if (args) {
|
||||
operatorList.addImageOps(OPS.constructPath, args, optionalContent);
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DrawOPS, info, OPS } from "../shared/util.js";
|
||||
import { getEncoding, StandardEncoding } from "./encodings.js";
|
||||
import { getGlyphsUnicode } from "./glyphlist.js";
|
||||
import { getLookupTableFactory } from "./core_utils.js";
|
||||
import { getUnicodeForGlyph } from "./unicode.js";
|
||||
import { info } from "../shared/util.js";
|
||||
|
||||
// Accented characters have issues on Windows and Linux. When this flag is
|
||||
// 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
|
||||
});
|
||||
|
||||
// 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 {
|
||||
compileType3Glyph,
|
||||
FontFlags,
|
||||
getVerticalPresentationForm,
|
||||
MacStandardGlyphOrdering,
|
||||
|
|
|
@ -770,7 +770,7 @@ class OperatorList {
|
|||
case OPS.paintInlineImageXObjectGroup:
|
||||
case OPS.paintImageMaskXObject:
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -54,9 +54,6 @@ const EXECUTION_TIME = 15; // ms
|
|||
// Defines the number of steps before checking the execution time.
|
||||
const EXECUTION_STEPS = 10;
|
||||
|
||||
// To disable Type3 compilation, set the value to `-1`.
|
||||
const MAX_SIZE_TO_COMPILE = 1000;
|
||||
|
||||
const FULL_CHUNK_HEIGHT = 16;
|
||||
|
||||
// Only used in rescaleAndStroke. The goal is to avoid
|
||||
|
@ -299,169 +296,6 @@ function drawImageAtIntegerCoords(
|
|||
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 {
|
||||
constructor(width, height) {
|
||||
// Are soft masks and alpha values shapes or opacities?
|
||||
|
@ -821,7 +655,6 @@ class CanvasGraphics {
|
|||
this.canvasFactory = canvasFactory;
|
||||
this.filterFactory = filterFactory;
|
||||
this.groupStack = [];
|
||||
this.processingType3 = null;
|
||||
// Patterns are painted relative to the initial page/form transform, see
|
||||
// PDF spec 8.7.2 NOTE 1.
|
||||
this.baseTransform = null;
|
||||
|
@ -1747,6 +1580,10 @@ class CanvasGraphics {
|
|||
this.consumePath(path);
|
||||
}
|
||||
|
||||
rawFillPath(path) {
|
||||
this.ctx.fill(path);
|
||||
}
|
||||
|
||||
// Clipping
|
||||
clip() {
|
||||
this.pendingClip = NORMAL_CLIP;
|
||||
|
@ -2267,7 +2104,6 @@ class CanvasGraphics {
|
|||
if (!operatorList) {
|
||||
warn(`Type3 character "${glyph.operatorListId}" is not available.`);
|
||||
} else if (this.contentVisible) {
|
||||
this.processingType3 = glyph;
|
||||
this.save();
|
||||
ctx.scale(fontSize, fontSize);
|
||||
ctx.transform(...fontMatrix);
|
||||
|
@ -2282,7 +2118,6 @@ class CanvasGraphics {
|
|||
current.x += width * textHScale;
|
||||
}
|
||||
ctx.restore();
|
||||
this.processingType3 = null;
|
||||
}
|
||||
|
||||
// Type3 fonts
|
||||
|
@ -2703,18 +2538,6 @@ class CanvasGraphics {
|
|||
img.count = count;
|
||||
|
||||
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 maskCanvas = mask.canvas;
|
||||
|
||||
|
|
|
@ -339,6 +339,7 @@ const OPS = {
|
|||
constructPath: 91,
|
||||
setStrokeTransparent: 92,
|
||||
setFillTransparent: 93,
|
||||
rawFillPath: 94,
|
||||
};
|
||||
|
||||
// In order to have a switch statement that is fast (i.e. which use a jump
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue