Add basic support for transparency groups.
This commit is contained in:
parent
3f195f3308
commit
725cd5407f
105
src/canvas.js
105
src/canvas.js
@ -16,7 +16,7 @@
|
||||
*/
|
||||
/* globals ColorSpace, DeviceCmykCS, DeviceGrayCS, DeviceRgbCS, error,
|
||||
FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageData, isArray, isNum,
|
||||
isString, Pattern, TilingPattern, TODO, Util, warn */
|
||||
isString, Pattern, TilingPattern, TODO, Util, warn, assert */
|
||||
|
||||
'use strict';
|
||||
|
||||
@ -225,6 +225,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
||||
this.objs = objs;
|
||||
this.textLayer = textLayer;
|
||||
this.imageLayer = imageLayer;
|
||||
this.groupStack = [];
|
||||
if (canvasCtx) {
|
||||
addContextCurrentTransform(canvasCtx);
|
||||
}
|
||||
@ -357,6 +358,25 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
||||
return tmpCanvas;
|
||||
}
|
||||
|
||||
function copyCtxState(sourceCtx, destCtx) {
|
||||
var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha',
|
||||
'lineWidth', 'lineCap', 'lineJoin', 'miterLimit',
|
||||
'globalCompositeOperation', 'font'];
|
||||
for (var i = 0, ii = properties.length; i < ii; i++) {
|
||||
var property = properties[i];
|
||||
if (property in sourceCtx) {
|
||||
destCtx[property] = sourceCtx[property];
|
||||
}
|
||||
}
|
||||
if ('setLineDash' in sourceCtx) {
|
||||
destCtx.setLineDash(sourceCtx.getLineDash());
|
||||
destCtx.lineDashOffset = sourceCtx.lineDashOffset;
|
||||
} else if ('mozDash' in sourceCtx) {
|
||||
destCtx.mozDash = sourceCtx.mozDash;
|
||||
destCtx.mozDashOffset = sourceCtx.mozDashOffset;
|
||||
}
|
||||
}
|
||||
|
||||
var LINE_CAP_STYLES = ['butt', 'round', 'square'];
|
||||
var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
|
||||
var NORMAL_CLIP = {};
|
||||
@ -1347,6 +1367,89 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
||||
} while (this.current.paintFormXObjectDepth >= depth);
|
||||
},
|
||||
|
||||
beginGroup: function CanvasGraphics_beginGroup(group) {
|
||||
this.save();
|
||||
var currentCtx = this.ctx;
|
||||
// TODO non-isolated groups - according to Rik at adobe non-isolated
|
||||
// group results aren't usually that different and they even have tools
|
||||
// that ignore this setting. Notes from Rik on implmenting:
|
||||
// - When you encounter an transparency group, create a new canvas with
|
||||
// the dimensions of the bbox
|
||||
// - copy the content from the previous canvas to the new canvas
|
||||
// - draw as usual
|
||||
// - remove the backdrop alpha:
|
||||
// alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha
|
||||
// value of your transparency group and 'alphaBackdrop' the alpha of the
|
||||
// backdrop
|
||||
// - remove background color:
|
||||
// colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew)
|
||||
if (!group.isolated) {
|
||||
TODO('Support non-isolated groups.');
|
||||
}
|
||||
|
||||
// TODO knockout - supposedly possible with the clever use of compositing
|
||||
// modes.
|
||||
if (group.knockout) {
|
||||
TODO('Support knockout groups.');
|
||||
}
|
||||
|
||||
var currentTransform = currentCtx.mozCurrentTransform;
|
||||
if (group.matrix) {
|
||||
currentCtx.transform.apply(currentCtx, group.matrix);
|
||||
}
|
||||
assert(group.bbox, 'Bounding box is required.');
|
||||
|
||||
// Based on the current transform figure out how big the bounding box
|
||||
// will actually be.
|
||||
var bounds = Util.getAxialAlignedBoundingBox(
|
||||
group.bbox,
|
||||
currentCtx.mozCurrentTransform);
|
||||
// Use ceil in case we're between sizes so we don't create canvas that is
|
||||
// too small.
|
||||
var drawnWidth = Math.ceil(bounds[2] - bounds[0]);
|
||||
var drawnHeight = Math.ceil(bounds[3] - bounds[1]);
|
||||
var scratchCanvas = createScratchCanvas(drawnWidth, drawnHeight);
|
||||
var groupCtx = scratchCanvas.getContext('2d');
|
||||
addContextCurrentTransform(groupCtx);
|
||||
// Since we created a new canvas that is just the size of the bounding box
|
||||
// we have to translate the group ctx.
|
||||
var offsetX = bounds[0];
|
||||
var offsetY = bounds[1];
|
||||
groupCtx.translate(-offsetX, -offsetY);
|
||||
groupCtx.transform.apply(groupCtx, currentTransform);
|
||||
|
||||
// Setup the current ctx so when the group is popped we draw it the right
|
||||
// location.
|
||||
currentCtx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
currentCtx.translate(offsetX, offsetY);
|
||||
|
||||
// The transparency group inherits all off the current graphics state
|
||||
// except the blend mode, soft mask, and alpha constants.
|
||||
copyCtxState(currentCtx, groupCtx);
|
||||
this.ctx = groupCtx;
|
||||
this.setGState([
|
||||
['SMask', 'None'],
|
||||
['BM', 'Normal'],
|
||||
['ca', 1],
|
||||
['CA', 1]
|
||||
]);
|
||||
this.groupStack.push(currentCtx);
|
||||
},
|
||||
|
||||
endGroup: function CanvasGraphics_endGroup(group) {
|
||||
var groupCtx = this.ctx;
|
||||
this.ctx = this.groupStack.pop();
|
||||
// Turn off image smoothing to avoid sub pixel interpolation which can
|
||||
// look kind of blurry for some pdfs.
|
||||
if ('imageSmoothingEnabled' in this.ctx) {
|
||||
this.ctx.imageSmoothingEnabled = false;
|
||||
} else {
|
||||
this.ctx.mozImageSmoothingEnabled = false;
|
||||
}
|
||||
this.ctx.drawImage(groupCtx.canvas, 0, 0);
|
||||
this.restore();
|
||||
},
|
||||
|
||||
paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
|
||||
var domImage = this.objs.get(objId);
|
||||
if (!domImage) {
|
||||
|
@ -244,6 +244,60 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
return loadedName;
|
||||
}
|
||||
|
||||
function buildFormXObject(xobj, smask) {
|
||||
var matrix = xobj.dict.get('Matrix');
|
||||
var bbox = xobj.dict.get('BBox');
|
||||
var group = xobj.dict.get('Group');
|
||||
if (group) {
|
||||
var groupOptions = {
|
||||
matrix: matrix,
|
||||
bbox: bbox,
|
||||
smask: !!smask,
|
||||
isolated: false,
|
||||
knockout: false
|
||||
};
|
||||
|
||||
var groupSubtype = group.get('S');
|
||||
if (isName(groupSubtype) && groupSubtype.name === 'Transparency') {
|
||||
groupOptions.isolated = group.get('I') || false;
|
||||
groupOptions.knockout = group.get('K') || false;
|
||||
// There is also a group colorspace, but since we put everything in
|
||||
// RGB I'm not sure we need it.
|
||||
}
|
||||
fnArray.push('beginGroup');
|
||||
argsArray.push([groupOptions]);
|
||||
}
|
||||
|
||||
fnArray.push('paintFormXObjectBegin');
|
||||
argsArray.push([matrix, bbox]);
|
||||
|
||||
// This adds the operatorList of the xObj to the current queue.
|
||||
var depIdx = dependencyArray.length;
|
||||
|
||||
// Pass in the current `queue` object. That means the `fnArray`
|
||||
// and the `argsArray` in this scope is reused and new commands
|
||||
// are added to them.
|
||||
self.getOperatorList(xobj,
|
||||
xobj.dict.get('Resources') || resources,
|
||||
dependencyArray, queue);
|
||||
|
||||
self.getOperatorList(xobj,
|
||||
xobj.dict.get('Resources') || resources,
|
||||
dependencyArray, queue);
|
||||
|
||||
// Add the dependencies that are required to execute the
|
||||
// operatorList.
|
||||
insertDependency(dependencyArray.slice(depIdx));
|
||||
|
||||
fnArray.push('paintFormXObjectEnd');
|
||||
argsArray.push([]);
|
||||
|
||||
if (group) {
|
||||
fnArray.push('endGroup');
|
||||
argsArray.push([groupOptions]);
|
||||
}
|
||||
}
|
||||
|
||||
function buildPaintImageXObject(image, inline) {
|
||||
var dict = image.dict;
|
||||
var w = dict.get('Width', 'W');
|
||||
@ -417,28 +471,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
);
|
||||
|
||||
if ('Form' == type.name) {
|
||||
var matrix = xobj.dict.get('Matrix');
|
||||
var bbox = xobj.dict.get('BBox');
|
||||
|
||||
fnArray.push('paintFormXObjectBegin');
|
||||
argsArray.push([matrix, bbox]);
|
||||
|
||||
// This adds the operatorList of the xObj to the current queue.
|
||||
var depIdx = dependencyArray.length;
|
||||
|
||||
// Pass in the current `queue` object. That means the `fnArray`
|
||||
// and the `argsArray` in this scope is reused and new commands
|
||||
// are added to them.
|
||||
this.getOperatorList(xobj,
|
||||
xobj.dict.get('Resources') || resources,
|
||||
dependencyArray, queue);
|
||||
|
||||
// Add the dependencies that are required to execute the
|
||||
// operatorList.
|
||||
insertDependency(dependencyArray.slice(depIdx));
|
||||
|
||||
fn = 'paintFormXObjectEnd';
|
||||
buildFormXObject(xobj);
|
||||
args = [];
|
||||
continue;
|
||||
} else if ('Image' == type.name) {
|
||||
buildPaintImageXObject(xobj, false);
|
||||
} else {
|
||||
|
17
src/util.js
17
src/util.js
@ -237,6 +237,23 @@ var Util = PDFJS.Util = (function UtilClosure() {
|
||||
return [xt, yt];
|
||||
};
|
||||
|
||||
// Applies the transform to the rectangle and finds the minimum axially
|
||||
// aligned bounding box.
|
||||
Util.getAxialAlignedBoundingBox =
|
||||
function Util_getAxialAlignedBoundingBox(r, m) {
|
||||
|
||||
var p1 = Util.applyTransform(r, m);
|
||||
var p2 = Util.applyTransform(r.slice(2, 4), m);
|
||||
var p3 = Util.applyTransform([r[0], r[3]], m);
|
||||
var p4 = Util.applyTransform([r[2], r[1]], m);
|
||||
return [
|
||||
Math.min(p1[0], p2[0], p3[0], p4[0]),
|
||||
Math.min(p1[1], p2[1], p3[1], p4[1]),
|
||||
Math.max(p1[0], p2[0], p3[0], p4[0]),
|
||||
Math.max(p1[1], p2[1], p3[1], p4[1])
|
||||
];
|
||||
};
|
||||
|
||||
Util.inverseTransform = function Util_inverseTransform(m) {
|
||||
var d = m[0] * m[3] - m[1] * m[2];
|
||||
return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d,
|
||||
|
2134
test/pdfs/transparency_group.pdf
Normal file
2134
test/pdfs/transparency_group.pdf
Normal file
File diff suppressed because one or more lines are too long
@ -845,6 +845,13 @@
|
||||
"type": "eq",
|
||||
"about": "Every blend mode that PDF supports."
|
||||
},
|
||||
{ "id": "transparency_group",
|
||||
"file": "pdfs/transparency_group.pdf",
|
||||
"md5": "10391f76434128e5da70cff5fc485ff0",
|
||||
"rounds": 1,
|
||||
"type": "eq",
|
||||
"about": "Rotated transparency group with blend mode."
|
||||
},
|
||||
{ "id": "issue2462",
|
||||
"file": "pdfs/issue2462.pdf",
|
||||
"md5": "d4e3dddfdd35464c71cf0310bff29b42",
|
||||
|
@ -2145,10 +2145,6 @@ var PageView = function pageView(container, pdfPage, id, scale,
|
||||
// TODO(mack): use data attributes to store these
|
||||
ctx._scaleX = outputScale.sx;
|
||||
ctx._scaleY = outputScale.sy;
|
||||
ctx.save();
|
||||
ctx.fillStyle = 'rgb(255, 255, 255)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.restore();
|
||||
if (outputScale.scaled) {
|
||||
ctx.scale(outputScale.sx, outputScale.sy);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user