Skip to content

Commit c4d68a2

Browse files
committed
Attempt to cache repeated images at the document, rather than the page, level (issue 11878)
1 parent 6ffcedc commit c4d68a2

File tree

6 files changed

+183
-8
lines changed

6 files changed

+183
-8
lines changed

src/core/document.js

+5
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class Page {
7474
ref,
7575
fontCache,
7676
builtInCMapCache,
77+
globalImageCache,
7778
pdfFunctionFactory,
7879
}) {
7980
this.pdfManager = pdfManager;
@@ -83,6 +84,7 @@ class Page {
8384
this.ref = ref;
8485
this.fontCache = fontCache;
8586
this.builtInCMapCache = builtInCMapCache;
87+
this.globalImageCache = globalImageCache;
8688
this.pdfFunctionFactory = pdfFunctionFactory;
8789
this.evaluatorOptions = pdfManager.evaluatorOptions;
8890
this.resourcesPromise = null;
@@ -261,6 +263,7 @@ class Page {
261263
idFactory: this.idFactory,
262264
fontCache: this.fontCache,
263265
builtInCMapCache: this.builtInCMapCache,
266+
globalImageCache: this.globalImageCache,
264267
options: this.evaluatorOptions,
265268
pdfFunctionFactory: this.pdfFunctionFactory,
266269
});
@@ -354,6 +357,7 @@ class Page {
354357
idFactory: this.idFactory,
355358
fontCache: this.fontCache,
356359
builtInCMapCache: this.builtInCMapCache,
360+
globalImageCache: this.globalImageCache,
357361
options: this.evaluatorOptions,
358362
pdfFunctionFactory: this.pdfFunctionFactory,
359363
});
@@ -816,6 +820,7 @@ class PDFDocument {
816820
ref,
817821
fontCache: catalog.fontCache,
818822
builtInCMapCache: catalog.builtInCMapCache,
823+
globalImageCache: catalog.globalImageCache,
819824
pdfFunctionFactory: this.pdfFunctionFactory,
820825
});
821826
}));

src/core/evaluator.js

+73-4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import {
7373
getSymbolsFonts,
7474
} from "./standard_fonts.js";
7575
import { getTilingPatternIR, Pattern } from "./pattern.js";
76+
import { GlobalImageCacheKind, NativeImageDecoder } from "./image_utils.js";
7677
import { Lexer, Parser } from "./parser.js";
7778
import { bidi } from "./bidi.js";
7879
import { ColorSpace } from "./colorspace.js";
@@ -82,7 +83,6 @@ import { getMetrics } from "./metrics.js";
8283
import { isPDFFunction } from "./function.js";
8384
import { JpegStream } from "./jpeg_stream.js";
8485
import { MurmurHash3_64 } from "./murmurhash3.js";
85-
import { NativeImageDecoder } from "./image_utils.js";
8686
import { OperatorList } from "./operator_list.js";
8787
import { PDFImage } from "./image.js";
8888

@@ -105,6 +105,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
105105
idFactory,
106106
fontCache,
107107
builtInCMapCache,
108+
globalImageCache,
108109
options = null,
109110
pdfFunctionFactory,
110111
}) {
@@ -114,6 +115,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
114115
this.idFactory = idFactory;
115116
this.fontCache = fontCache;
116117
this.builtInCMapCache = builtInCMapCache;
118+
this.globalImageCache = globalImageCache;
117119
this.options = options || DefaultPartialEvaluatorOptions;
118120
this.pdfFunctionFactory = pdfFunctionFactory;
119121
this.parsingType3Font = false;
@@ -446,11 +448,14 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
446448
image,
447449
isInline = false,
448450
operatorList,
451+
task,
449452
cacheKey,
450453
imageCache,
454+
cacheGlobally = false,
451455
forceDisableNativeImageDecoder = false,
452456
}) {
453457
var dict = image.dict;
458+
const imageRef = dict.objId;
454459
var w = dict.get("Width", "W");
455460
var h = dict.get("Height", "H");
456461

@@ -528,7 +533,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
528533
return undefined;
529534
}
530535

531-
const nativeImageDecoderSupport = forceDisableNativeImageDecoder
536+
let nativeImageDecoderSupport = forceDisableNativeImageDecoder
532537
? NativeImageDecoding.NONE
533538
: this.options.nativeImageDecoderSupport;
534539
// If there is no imageMask, create the PDFImage and a lot
@@ -542,6 +547,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
542547
);
543548

544549
objId = `${this.idFactory.getDocId()}_type3res_${objId}`;
550+
} else if (cacheGlobally) {
551+
nativeImageDecoderSupport = NativeImageDecoding.NONE;
552+
553+
objId = `${this.idFactory.getDocId()}_${objId}`;
545554
}
546555

547556
if (
@@ -566,7 +575,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
566575
image.getIR(this.options.forceDataSchema),
567576
])
568577
.then(
569-
function () {
578+
() => {
570579
// Only add the dependency once we know that the native JPEG
571580
// decoding succeeded, to ensure that rendering will always
572581
// complete.
@@ -579,6 +588,16 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
579588
fn: OPS.paintJpegXObject,
580589
args,
581590
};
591+
592+
if (imageRef) {
593+
this.globalImageCache.set({
594+
ref: imageRef,
595+
task,
596+
objId,
597+
fn: OPS.paintJpegXObject,
598+
args,
599+
});
600+
}
582601
}
583602
},
584603
reason => {
@@ -639,6 +658,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
639658
[objId, "FontType3Res", imgData],
640659
[imgData.data.buffer]
641660
);
661+
} else if (cacheGlobally) {
662+
this.handler.send(
663+
"commonobj",
664+
[objId, "Image", imgData],
665+
[imgData.data.buffer]
666+
);
667+
return undefined;
642668
}
643669
this.handler.send(
644670
"obj",
@@ -656,6 +682,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
656682
"FontType3Res",
657683
null,
658684
]);
685+
} else if (cacheGlobally) {
686+
this.handler.send("commonobj", [objId, "Image", null]);
687+
return undefined;
659688
}
660689
this.handler.send("obj", [objId, this.pageIndex, "Image", null]);
661690
return undefined;
@@ -674,6 +703,16 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
674703
fn: OPS.paintImageXObject,
675704
args,
676705
};
706+
707+
if (imageRef) {
708+
this.globalImageCache.set({
709+
ref: imageRef,
710+
task,
711+
objId,
712+
fn: OPS.paintImageXObject,
713+
args,
714+
});
715+
}
677716
}
678717
return undefined;
679718
},
@@ -1322,7 +1361,35 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
13221361
);
13231362
}
13241363

1325-
const xobj = xobjs.get(name);
1364+
let xobj = xobjs.getRaw(name),
1365+
cacheGlobally = false;
1366+
if (xobj instanceof Ref) {
1367+
const cacheKind = self.globalImageCache.kind({
1368+
ref: xobj,
1369+
task,
1370+
});
1371+
1372+
switch (cacheKind) {
1373+
case GlobalImageCacheKind.IS_CACHED:
1374+
const globalImage = self.globalImageCache.get({
1375+
ref: xobj,
1376+
});
1377+
1378+
if (globalImage) {
1379+
operatorList.addDependency(globalImage.objId);
1380+
operatorList.addOp(globalImage.fn, globalImage.args);
1381+
1382+
resolveXObject();
1383+
return;
1384+
}
1385+
break;
1386+
case GlobalImageCacheKind.SHOULD_CACHE:
1387+
cacheGlobally = true;
1388+
break;
1389+
}
1390+
xobj = xref.fetch(xobj);
1391+
}
1392+
13261393
if (!xobj) {
13271394
operatorList.addOp(fn, args);
13281395
resolveXObject();
@@ -1359,8 +1426,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
13591426
resources,
13601427
image: xobj,
13611428
operatorList,
1429+
task,
13621430
cacheKey: name,
13631431
imageCache,
1432+
cacheGlobally,
13641433
})
13651434
.then(resolveXObject, rejectXObject);
13661435
return;

src/core/image_utils.js

+98-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
*/
1515
/* eslint no-var: error */
1616

17+
import { info, warn } from "../shared/util.js";
18+
import { Ref, RefSetCache } from "./primitives.js";
1719
import { ColorSpace } from "./colorspace.js";
1820
import { JpegStream } from "./jpeg_stream.js";
1921
import { Stream } from "./stream.js";
@@ -111,4 +113,99 @@ class NativeImageDecoder {
111113
}
112114
}
113115

114-
export { NativeImageDecoder };
116+
const NUM_TASKS_THRESHOLD = 2;
117+
const MAX_IMAGES_TO_CACHE = 10;
118+
119+
const GlobalImageCacheKind = {
120+
NOT_CACHED: 1,
121+
SHOULD_CACHE: 2,
122+
IS_CACHED: 3,
123+
};
124+
125+
class GlobalImageCache {
126+
constructor() {
127+
this._refCache = new RefSetCache();
128+
this._imageCache = new RefSetCache();
129+
this._numImages = 0;
130+
}
131+
132+
kind({ ref, task }) {
133+
if (!(ref instanceof Ref || typeof ref === "string")) {
134+
warn('GlobalImageCache.kind - invalid "ref" argument.');
135+
return GlobalImageCacheKind.NOT_CACHED;
136+
}
137+
138+
if (!this._refCache.has(ref)) {
139+
return GlobalImageCacheKind.NOT_CACHED;
140+
}
141+
const taskSet = this._refCache.get(ref);
142+
taskSet.add(task.name);
143+
144+
if (taskSet.size < NUM_TASKS_THRESHOLD) {
145+
info(
146+
'GlobalImageCache.kind - ignoring image below "NUM_TASKS_THRESHOLD".'
147+
);
148+
return GlobalImageCacheKind.NOT_CACHED;
149+
}
150+
if (taskSet.size === NUM_TASKS_THRESHOLD) {
151+
if (++this._numImages > MAX_IMAGES_TO_CACHE) {
152+
warn(
153+
'GlobalImageCache.kind - ignoring image above "MAX_IMAGES_TO_CACHE".'
154+
);
155+
return GlobalImageCacheKind.NOT_CACHED;
156+
}
157+
return GlobalImageCacheKind.SHOULD_CACHE;
158+
}
159+
return GlobalImageCacheKind.IS_CACHED;
160+
}
161+
162+
get({ ref }) {
163+
if (!(ref instanceof Ref || typeof ref === "string")) {
164+
warn('GlobalImageCache.get - invalid "ref" argument.');
165+
return null;
166+
}
167+
168+
if (!this._refCache.has(ref)) {
169+
return null;
170+
}
171+
const taskSet = this._refCache.get(ref);
172+
173+
if (taskSet.size < NUM_TASKS_THRESHOLD) {
174+
info(
175+
'GlobalImageCache.get - ignoring image below "NUM_TASKS_THRESHOLD".'
176+
);
177+
return null;
178+
}
179+
return this._imageCache.get(ref) || null;
180+
}
181+
182+
set({ ref, task, objId, fn, args }) {
183+
if (!(ref instanceof Ref || typeof ref === "string")) {
184+
warn('GlobalImageCache.set - invalid "ref" argument.');
185+
return;
186+
}
187+
188+
let taskSet = this._refCache.get(ref);
189+
if (!taskSet) {
190+
taskSet = new Set();
191+
this._refCache.put(ref, taskSet);
192+
}
193+
taskSet.add(task.name);
194+
195+
if (taskSet.size < NUM_TASKS_THRESHOLD) {
196+
info(
197+
'GlobalImageCache.set - ignoring image below "NUM_TASKS_THRESHOLD".'
198+
);
199+
return;
200+
}
201+
this._imageCache.put(ref, { objId, fn, args });
202+
}
203+
204+
clear() {
205+
this._refCache.clear();
206+
this._imageCache.clear();
207+
this._numImages = 0;
208+
}
209+
}
210+
211+
export { NativeImageDecoder, GlobalImageCache, GlobalImageCacheKind };

src/core/obj.js

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
} from "./core_utils.js";
5555
import { CipherTransformFactory } from "./crypto.js";
5656
import { ColorSpace } from "./colorspace.js";
57+
import { GlobalImageCache } from "./image_utils.js";
5758

5859
function fetchDestination(dest) {
5960
return isDict(dest) ? dest.get("D") : dest;
@@ -71,6 +72,7 @@ class Catalog {
7172

7273
this.fontCache = new RefSetCache();
7374
this.builtInCMapCache = new Map();
75+
this.globalImageCache = new GlobalImageCache();
7476
this.pageKidsCountCache = new RefSetCache();
7577
}
7678

@@ -716,6 +718,7 @@ class Catalog {
716718

717719
cleanup() {
718720
clearPrimitiveCaches();
721+
this.globalImageCache.clear();
719722
this.pageKidsCountCache.clear();
720723

721724
const promises = [];

src/display/api.js

+1
Original file line numberDiff line numberDiff line change
@@ -2275,6 +2275,7 @@ class WorkerTransport {
22752275
break;
22762276
case "FontPath":
22772277
case "FontType3Res":
2278+
case "Image":
22782279
this.commonObjs.resolve(id, exportedData);
22792280
break;
22802281
default:

src/display/canvas.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -2114,7 +2114,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
21142114
},
21152115

21162116
paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
2117-
const domImage = this.processingType3
2117+
const domImage = objId.startsWith("g_")
21182118
? this.commonObjs.get(objId)
21192119
: this.objs.get(objId);
21202120
if (!domImage) {
@@ -2277,7 +2277,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
22772277
},
22782278

22792279
paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
2280-
const imgData = this.processingType3
2280+
const imgData = objId.startsWith("g_")
22812281
? this.commonObjs.get(objId)
22822282
: this.objs.get(objId);
22832283
if (!imgData) {
@@ -2294,7 +2294,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
22942294
scaleY,
22952295
positions
22962296
) {
2297-
const imgData = this.processingType3
2297+
const imgData = objId.startsWith("g_")
22982298
? this.commonObjs.get(objId)
22992299
: this.objs.get(objId);
23002300
if (!imgData) {

0 commit comments

Comments
 (0)