Support (rare) Type3 fonts which contains image resources (issue 10717)
The Type3 font type is not commonly used in PDF documents, as can be seen from telemetry data such as: https://telemetry.mozilla.org/new-pipeline/dist.html#!cumulative=0&end_date=2019-04-09&include_spill=0&keys=__none__!__none__!__none__&max_channel_version=nightly%252F68&measure=PDF_VIEWER_FONT_TYPES&min_channel_version=nightly%252F57&processType=*&product=Firefox&sanitize=1&sort_by_value=0&sort_keys=submissions&start_date=2019-03-18&table=0&trim=1&use_submission_date=0 (see also https://github.com/mozilla/pdf.js/wiki/Enumeration-Assignments-for-the-Telemetry-Histograms#pdf_viewer_font_types). Type3 fonts containing image resources are *very* rare in practice, usually they only contain path rendering operators, but as the issue shows they unfortunately do exist. Currently these Type3-related image resources are not handled in any special way, and given that fonts are document rather than page specific rendering breaks since the image resources are thus not available to the *entire* document. Fortunately fixing this isn't too difficult, but it does require adding a couple of Type3-specific code-paths to the `PartialEvaluator`. In order to keep the implementation simple, particularily on the main-thread, these Type3 image resources are completely decoded on the worker-thread to avoid adding too many special cases. This should not cause any issues, only marginally less efficient code, but given how rare this kind of Type3 font is adding premature optimizations didn't seem at all warranted at this point.
This commit is contained in:
parent
17de90b88a
commit
be604bd195
@ -73,6 +73,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
this.builtInCMapCache = builtInCMapCache;
|
this.builtInCMapCache = builtInCMapCache;
|
||||||
this.options = options || DefaultPartialEvaluatorOptions;
|
this.options = options || DefaultPartialEvaluatorOptions;
|
||||||
this.pdfFunctionFactory = pdfFunctionFactory;
|
this.pdfFunctionFactory = pdfFunctionFactory;
|
||||||
|
this.parsingType3Font = false;
|
||||||
|
|
||||||
this.fetchBuiltInCMap = async (name) => {
|
this.fetchBuiltInCMap = async (name) => {
|
||||||
if (this.builtInCMapCache.has(name)) {
|
if (this.builtInCMapCache.has(name)) {
|
||||||
@ -293,21 +294,21 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
buildPaintImageXObject({ resources, image, isInline = false, operatorList,
|
async buildPaintImageXObject({ resources, image, isInline = false,
|
||||||
cacheKey, imageCache,
|
operatorList, cacheKey, imageCache,
|
||||||
forceDisableNativeImageDecoder = false, }) {
|
forceDisableNativeImageDecoder = false, }) {
|
||||||
var dict = image.dict;
|
var dict = image.dict;
|
||||||
var w = dict.get('Width', 'W');
|
var w = dict.get('Width', 'W');
|
||||||
var h = dict.get('Height', 'H');
|
var h = dict.get('Height', 'H');
|
||||||
|
|
||||||
if (!(w && isNum(w)) || !(h && isNum(h))) {
|
if (!(w && isNum(w)) || !(h && isNum(h))) {
|
||||||
warn('Image dimensions are missing, or not numbers.');
|
warn('Image dimensions are missing, or not numbers.');
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
var maxImageSize = this.options.maxImageSize;
|
var maxImageSize = this.options.maxImageSize;
|
||||||
if (maxImageSize !== -1 && w * h > maxImageSize) {
|
if (maxImageSize !== -1 && w * h > maxImageSize) {
|
||||||
warn('Image exceeded maximum allowed size and was removed.');
|
warn('Image exceeded maximum allowed size and was removed.');
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageMask = (dict.get('ImageMask', 'IM') || false);
|
var imageMask = (dict.get('ImageMask', 'IM') || false);
|
||||||
@ -343,7 +344,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
args,
|
args,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var softMask = (dict.get('SMask', 'SM') || false);
|
var softMask = (dict.get('SMask', 'SM') || false);
|
||||||
@ -364,14 +365,21 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
// any other kind.
|
// any other kind.
|
||||||
imgData = imageObj.createImageData(/* forceRGBA = */ true);
|
imgData = imageObj.createImageData(/* forceRGBA = */ true);
|
||||||
operatorList.addOp(OPS.paintInlineImageXObject, [imgData]);
|
operatorList.addOp(OPS.paintInlineImageXObject, [imgData]);
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nativeImageDecoderSupport = forceDisableNativeImageDecoder ?
|
const nativeImageDecoderSupport = forceDisableNativeImageDecoder ?
|
||||||
NativeImageDecoding.NONE : this.options.nativeImageDecoderSupport;
|
NativeImageDecoding.NONE : this.options.nativeImageDecoderSupport;
|
||||||
// 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.
|
||||||
var objId = 'img_' + this.idFactory.createObjId();
|
let objId = 'img_' + this.idFactory.createObjId();
|
||||||
|
|
||||||
|
if (this.parsingType3Font) {
|
||||||
|
assert(nativeImageDecoderSupport === NativeImageDecoding.NONE,
|
||||||
|
'Type3 image resources should be completely decoded in the worker.');
|
||||||
|
|
||||||
|
objId = `g_${this.pdfManager.docId}_type3res_${objId}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (nativeImageDecoderSupport !== NativeImageDecoding.NONE &&
|
if (nativeImageDecoderSupport !== NativeImageDecoding.NONE &&
|
||||||
!softMask && !mask && image instanceof JpegStream &&
|
!softMask && !mask && image instanceof JpegStream &&
|
||||||
@ -428,7 +436,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
operatorList.addDependency(objId);
|
operatorList.addDependency(objId);
|
||||||
args = [objId, w, h];
|
args = [objId, w, h];
|
||||||
|
|
||||||
PDFImage.buildImage({
|
const imgPromise = PDFImage.buildImage({
|
||||||
handler: this.handler,
|
handler: this.handler,
|
||||||
xref: this.xref,
|
xref: this.xref,
|
||||||
res: resources,
|
res: resources,
|
||||||
@ -438,13 +446,30 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
pdfFunctionFactory: this.pdfFunctionFactory,
|
pdfFunctionFactory: this.pdfFunctionFactory,
|
||||||
}).then((imageObj) => {
|
}).then((imageObj) => {
|
||||||
var imgData = imageObj.createImageData(/* forceRGBA = */ false);
|
var imgData = imageObj.createImageData(/* forceRGBA = */ false);
|
||||||
|
|
||||||
|
if (this.parsingType3Font) {
|
||||||
|
return this.handler.sendWithPromise('commonobj',
|
||||||
|
[objId, 'FontType3Res', imgData], [imgData.data.buffer]);
|
||||||
|
}
|
||||||
this.handler.send('obj', [objId, this.pageIndex, 'Image', imgData],
|
this.handler.send('obj', [objId, this.pageIndex, 'Image', imgData],
|
||||||
[imgData.data.buffer]);
|
[imgData.data.buffer]);
|
||||||
}).catch((reason) => {
|
}).catch((reason) => {
|
||||||
warn('Unable to decode image: ' + reason);
|
warn('Unable to decode image: ' + reason);
|
||||||
|
|
||||||
|
if (this.parsingType3Font) {
|
||||||
|
return this.handler.sendWithPromise('commonobj',
|
||||||
|
[objId, 'FontType3Res', null]);
|
||||||
|
}
|
||||||
this.handler.send('obj', [objId, this.pageIndex, 'Image', null]);
|
this.handler.send('obj', [objId, this.pageIndex, 'Image', null]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.parsingType3Font) {
|
||||||
|
// In the very rare case where a Type3 image resource is being parsed,
|
||||||
|
// wait for the image to be both decoded *and* sent to simplify the
|
||||||
|
// rendering code on the main-thread (see issue10717.pdf).
|
||||||
|
await imgPromise;
|
||||||
|
}
|
||||||
|
|
||||||
operatorList.addOp(OPS.paintImageXObject, args);
|
operatorList.addOp(OPS.paintImageXObject, args);
|
||||||
if (cacheKey) {
|
if (cacheKey) {
|
||||||
imageCache[cacheKey] = {
|
imageCache[cacheKey] = {
|
||||||
@ -452,7 +477,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
args,
|
args,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSMask: function PartialEvaluator_handleSmask(smask, resources,
|
handleSMask: function PartialEvaluator_handleSmask(smask, resources,
|
||||||
@ -2622,9 +2646,15 @@ var TranslatedFont = (function TranslatedFontClosure() {
|
|||||||
// When parsing Type3 glyphs, always ignore them if there are errors.
|
// When parsing Type3 glyphs, always ignore them if there are errors.
|
||||||
// Compared to the parsing of e.g. an entire page, it doesn't really
|
// Compared to the parsing of e.g. an entire page, it doesn't really
|
||||||
// make sense to only be able to render a Type3 glyph partially.
|
// make sense to only be able to render a Type3 glyph partially.
|
||||||
|
//
|
||||||
|
// Also, ensure that any Type3 image resources (which should be very rare
|
||||||
|
// in practice) are completely decoded on the worker-thread, to simplify
|
||||||
|
// the rendering code on the main-thread (see issue10717.pdf).
|
||||||
var type3Options = Object.create(evaluator.options);
|
var type3Options = Object.create(evaluator.options);
|
||||||
type3Options.ignoreErrors = false;
|
type3Options.ignoreErrors = false;
|
||||||
|
type3Options.nativeImageDecoderSupport = NativeImageDecoding.NONE;
|
||||||
var type3Evaluator = evaluator.clone(type3Options);
|
var type3Evaluator = evaluator.clone(type3Options);
|
||||||
|
type3Evaluator.parsingType3Font = true;
|
||||||
|
|
||||||
var translatedFont = this.font;
|
var translatedFont = this.font;
|
||||||
var loadCharProcsPromise = Promise.resolve();
|
var loadCharProcsPromise = Promise.resolve();
|
||||||
|
@ -2003,6 +2003,7 @@ class WorkerTransport {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'FontPath':
|
case 'FontPath':
|
||||||
|
case 'FontType3Res':
|
||||||
this.commonObjs.resolve(id, exportedData);
|
this.commonObjs.resolve(id, exportedData);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -805,11 +805,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
if (fnId !== OPS.dependency) {
|
if (fnId !== OPS.dependency) {
|
||||||
this[fnId].apply(this, argsArray[i]);
|
this[fnId].apply(this, argsArray[i]);
|
||||||
} else {
|
} else {
|
||||||
var deps = argsArray[i];
|
for (const depObjId of argsArray[i]) {
|
||||||
for (var n = 0, nn = deps.length; n < nn; n++) {
|
const objsPool = depObjId.startsWith('g_') ? commonObjs : objs;
|
||||||
var depObjId = deps[n];
|
|
||||||
var common = depObjId[0] === 'g' && depObjId[1] === '_';
|
|
||||||
var objsPool = common ? commonObjs : objs;
|
|
||||||
|
|
||||||
// If the promise isn't resolved yet, add the continueCallback
|
// If the promise isn't resolved yet, add the continueCallback
|
||||||
// to the promise and bail out.
|
// to the promise and bail out.
|
||||||
@ -1930,7 +1927,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
|
paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
|
||||||
var domImage = this.objs.get(objId);
|
const domImage = this.processingType3 ? this.commonObjs.get(objId) :
|
||||||
|
this.objs.get(objId);
|
||||||
if (!domImage) {
|
if (!domImage) {
|
||||||
warn('Dependent image isn\'t ready yet');
|
warn('Dependent image isn\'t ready yet');
|
||||||
return;
|
return;
|
||||||
@ -2067,7 +2065,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
|
paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
|
||||||
var imgData = this.objs.get(objId);
|
const imgData = this.processingType3 ? this.commonObjs.get(objId) :
|
||||||
|
this.objs.get(objId);
|
||||||
if (!imgData) {
|
if (!imgData) {
|
||||||
warn('Dependent image isn\'t ready yet');
|
warn('Dependent image isn\'t ready yet');
|
||||||
return;
|
return;
|
||||||
@ -2079,7 +2078,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
paintImageXObjectRepeat:
|
paintImageXObjectRepeat:
|
||||||
function CanvasGraphics_paintImageXObjectRepeat(objId, scaleX, scaleY,
|
function CanvasGraphics_paintImageXObjectRepeat(objId, scaleX, scaleY,
|
||||||
positions) {
|
positions) {
|
||||||
var imgData = this.objs.get(objId);
|
const imgData = this.processingType3 ? this.commonObjs.get(objId) :
|
||||||
|
this.objs.get(objId);
|
||||||
if (!imgData) {
|
if (!imgData) {
|
||||||
warn('Dependent image isn\'t ready yet');
|
warn('Dependent image isn\'t ready yet');
|
||||||
return;
|
return;
|
||||||
|
1
test/pdfs/issue10717.pdf.link
Normal file
1
test/pdfs/issue10717.pdf.link
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://github.com/mozilla/pdf.js/files/3057353/test.pdf
|
@ -1252,6 +1252,16 @@
|
|||||||
"rounds": 1,
|
"rounds": 1,
|
||||||
"type": "text"
|
"type": "text"
|
||||||
},
|
},
|
||||||
|
{ "id": "issue10717",
|
||||||
|
"file": "pdfs/issue10717.pdf",
|
||||||
|
"md5": "6d2ed03db798cc6beb3c7bdf103f5c1a",
|
||||||
|
"link": true,
|
||||||
|
"rounds": 1,
|
||||||
|
"firstPage": 1,
|
||||||
|
"lastPage": 2,
|
||||||
|
"type": "eq",
|
||||||
|
"about": "Type3 fonts with image resources; both pages need to be tested, otherwise the bug won't manifest."
|
||||||
|
},
|
||||||
{ "id": "close-path-bug",
|
{ "id": "close-path-bug",
|
||||||
"file": "pdfs/close-path-bug.pdf",
|
"file": "pdfs/close-path-bug.pdf",
|
||||||
"md5": "48dd17ef58393857d2d038d33699cac5",
|
"md5": "48dd17ef58393857d2d038d33699cac5",
|
||||||
|
Loading…
Reference in New Issue
Block a user