Skip to content

Commit e5fbf52

Browse files
Merge pull request #19736 from Snuffleupagus/compileType3Glyph-worker
[api-minor] Move Type3-glyph compilation to the worker-thread
2 parents b33522a + 9cd5a96 commit e5fbf52

5 files changed

Lines changed: 197 additions & 201 deletions

File tree

src/core/evaluator.js

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
} from "../shared/util.js";
3333
import { CMapFactory, IdentityCMap } from "./cmap.js";
3434
import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js";
35+
import { compileType3Glyph, FontFlags } from "./fonts_utils.js";
3536
import { ErrorFont, Font } from "./fonts.js";
3637
import {
3738
fetchBinaryData,
@@ -72,7 +73,6 @@ import { bidi } from "./bidi.js";
7273
import { ColorSpace } from "./colorspace.js";
7374
import { ColorSpaceUtils } from "./colorspace_utils.js";
7475
import { DecodeStream } from "./decode_stream.js";
75-
import { FontFlags } from "./fonts_utils.js";
7676
import { getFontSubstitution } from "./font_substitutions.js";
7777
import { getGlyphsUnicode } from "./glyphlist.js";
7878
import { getMetrics } from "./metrics.js";
@@ -611,6 +611,12 @@ class PartialEvaluator {
611611
const decode = dict.getArray("D", "Decode");
612612

613613
if (this.parsingType3Font) {
614+
// NOTE: Compared to other image resources we don't bother caching
615+
// Type3-glyph image masks, since we've not come across any cases
616+
// where that actually helps.
617+
// In Type3-glyphs image masks are "always" inline resources,
618+
// they're usually fairly small and aren't being re-used either.
619+
614620
imgData = PDFImage.createRawMask({
615621
imgArray,
616622
width: w,
@@ -619,25 +625,21 @@ class PartialEvaluator {
619625
inverseDecode: decode?.[0] > 0,
620626
interpolate,
621627
});
628+
args = compileType3Glyph(imgData);
622629

623-
imgData.cached = !!cacheKey;
624-
625-
fn = OPS.paintImageMaskXObject;
626-
args = [imgData];
627-
operatorList.addImageOps(fn, args, optionalContent);
628-
629-
if (cacheKey) {
630-
const cacheData = { fn, args, optionalContent };
631-
localImageCache.set(cacheKey, imageRef, cacheData);
632-
633-
if (imageRef) {
634-
this._regionalImageCache.set(
635-
/* name = */ null,
636-
imageRef,
637-
cacheData
638-
);
639-
}
630+
if (args) {
631+
operatorList.addImageOps(OPS.constructPath, args, optionalContent);
632+
return;
640633
}
634+
warn("Cannot compile Type3 glyph.");
635+
636+
// If compilation failed, or was disabled, fallback to using an inline
637+
// image mask; this case should be extremely rare.
638+
operatorList.addImageOps(
639+
OPS.paintImageMaskXObject,
640+
[imgData],
641+
optionalContent
642+
);
641643
return;
642644
}
643645

src/core/fonts_utils.js

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
* limitations under the License.
1414
*/
1515

16+
import { DrawOPS, info, OPS } from "../shared/util.js";
1617
import { getEncoding, StandardEncoding } from "./encodings.js";
1718
import { getGlyphsUnicode } from "./glyphlist.js";
1819
import { getLookupTableFactory } from "./core_utils.js";
1920
import { getUnicodeForGlyph } from "./unicode.js";
20-
import { info } from "../shared/util.js";
2121

2222
// Accented characters have issues on Windows and Linux. When this flag is
2323
// enabled glyphs that use seac and seac style endchar operators are truncated
@@ -207,7 +207,177 @@ const getVerticalPresentationForm = getLookupTableFactory(t => {
207207
t[0xff5d] = 0xfe38; // FULLWIDTH RIGHT CURLY BRACKET
208208
});
209209

210+
// To disable Type3 compilation, set the value to `-1`.
211+
const MAX_SIZE_TO_COMPILE = 1000;
212+
213+
function compileType3Glyph({ data: img, width, height }) {
214+
if (width > MAX_SIZE_TO_COMPILE || height > MAX_SIZE_TO_COMPILE) {
215+
return null;
216+
}
217+
218+
const POINT_TO_PROCESS_LIMIT = 1000;
219+
const POINT_TYPES = new Uint8Array([
220+
0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0,
221+
]);
222+
223+
const width1 = width + 1;
224+
const points = new Uint8Array(width1 * (height + 1));
225+
let i, j, j0;
226+
227+
// decodes bit-packed mask data
228+
const lineSize = (width + 7) & ~7;
229+
const data = new Uint8Array(lineSize * height);
230+
let pos = 0;
231+
for (const elem of img) {
232+
let mask = 128;
233+
while (mask > 0) {
234+
data[pos++] = elem & mask ? 0 : 255;
235+
mask >>= 1;
236+
}
237+
}
238+
239+
// finding interesting points: every point is located between mask pixels,
240+
// so there will be points of the (width + 1)x(height + 1) grid. Every point
241+
// will have flags assigned based on neighboring mask pixels:
242+
// 4 | 8
243+
// --P--
244+
// 2 | 1
245+
// We are interested only in points with the flags:
246+
// - outside corners: 1, 2, 4, 8;
247+
// - inside corners: 7, 11, 13, 14;
248+
// - and, intersections: 5, 10.
249+
let count = 0;
250+
pos = 0;
251+
if (data[pos] !== 0) {
252+
points[0] = 1;
253+
++count;
254+
}
255+
for (j = 1; j < width; j++) {
256+
if (data[pos] !== data[pos + 1]) {
257+
points[j] = data[pos] ? 2 : 1;
258+
++count;
259+
}
260+
pos++;
261+
}
262+
if (data[pos] !== 0) {
263+
points[j] = 2;
264+
++count;
265+
}
266+
for (i = 1; i < height; i++) {
267+
pos = i * lineSize;
268+
j0 = i * width1;
269+
if (data[pos - lineSize] !== data[pos]) {
270+
points[j0] = data[pos] ? 1 : 8;
271+
++count;
272+
}
273+
// 'sum' is the position of the current pixel configuration in the 'TYPES'
274+
// array (in order 8-1-2-4, so we can use '>>2' to shift the column).
275+
let sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
276+
for (j = 1; j < width; j++) {
277+
sum =
278+
(sum >> 2) +
279+
(data[pos + 1] ? 4 : 0) +
280+
(data[pos - lineSize + 1] ? 8 : 0);
281+
if (POINT_TYPES[sum]) {
282+
points[j0 + j] = POINT_TYPES[sum];
283+
++count;
284+
}
285+
pos++;
286+
}
287+
if (data[pos - lineSize] !== data[pos]) {
288+
points[j0 + j] = data[pos] ? 2 : 4;
289+
++count;
290+
}
291+
292+
if (count > POINT_TO_PROCESS_LIMIT) {
293+
return null;
294+
}
295+
}
296+
297+
pos = lineSize * (height - 1);
298+
j0 = i * width1;
299+
if (data[pos] !== 0) {
300+
points[j0] = 8;
301+
++count;
302+
}
303+
for (j = 1; j < width; j++) {
304+
if (data[pos] !== data[pos + 1]) {
305+
points[j0 + j] = data[pos] ? 4 : 8;
306+
++count;
307+
}
308+
pos++;
309+
}
310+
if (data[pos] !== 0) {
311+
points[j0 + j] = 4;
312+
++count;
313+
}
314+
if (count > POINT_TO_PROCESS_LIMIT) {
315+
return null;
316+
}
317+
318+
// building outlines
319+
const steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
320+
const pathBuf = [];
321+
322+
// the path shall be painted in [0..1]x[0..1] space
323+
const { a, b, c, d, e, f } = new DOMMatrix()
324+
.scaleSelf(1 / width, -1 / height)
325+
.translateSelf(0, -height);
326+
327+
for (i = 0; count && i <= height; i++) {
328+
let p = i * width1;
329+
const end = p + width;
330+
while (p < end && !points[p]) {
331+
p++;
332+
}
333+
if (p === end) {
334+
continue;
335+
}
336+
let x = p % width1;
337+
let y = i;
338+
pathBuf.push(DrawOPS.moveTo, a * x + c * y + e, b * x + d * y + f);
339+
340+
const p0 = p;
341+
let type = points[p];
342+
do {
343+
const step = steps[type];
344+
do {
345+
p += step;
346+
} while (!points[p]);
347+
348+
const pp = points[p];
349+
if (pp !== 5 && pp !== 10) {
350+
// set new direction
351+
type = pp;
352+
// delete mark
353+
points[p] = 0;
354+
} else {
355+
// type is 5 or 10, ie, a crossing
356+
// set new direction
357+
type = pp & ((0x33 * type) >> 4);
358+
// set new type for "future hit"
359+
points[p] &= (type >> 2) | (type << 2);
360+
}
361+
x = p % width1;
362+
y = (p / width1) | 0;
363+
pathBuf.push(DrawOPS.lineTo, a * x + c * y + e, b * x + d * y + f);
364+
365+
if (!points[p]) {
366+
--count;
367+
}
368+
} while (p0 !== p);
369+
--i;
370+
}
371+
372+
return [
373+
OPS.rawFillPath,
374+
[new Float32Array(pathBuf)],
375+
new Float32Array([0, 0, width, height]),
376+
];
377+
}
378+
210379
export {
380+
compileType3Glyph,
211381
FontFlags,
212382
getVerticalPresentationForm,
213383
MacStandardGlyphOrdering,

src/core/operator_list.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,7 @@ class OperatorList {
770770
case OPS.paintInlineImageXObjectGroup:
771771
case OPS.paintImageMaskXObject:
772772
const arg = argsArray[i][0]; // First parameter in imgData.
773-
if (!arg.cached && arg.data?.buffer instanceof ArrayBuffer) {
773+
if (arg.data?.buffer instanceof ArrayBuffer) {
774774
transfers.push(arg.data.buffer);
775775
}
776776
break;

0 commit comments

Comments
 (0)