2012-09-01 07:48:21 +09:00
|
|
|
/* Copyright 2012 Mozilla Foundation
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
2021-09-11 18:11:32 +09:00
|
|
|
|
2017-04-02 21:25:33 +09:00
|
|
|
import {
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
FONT_IDENTITY_MATRIX,
|
|
|
|
IDENTITY_MATRIX,
|
|
|
|
ImageKind,
|
|
|
|
info,
|
2020-02-10 17:38:33 +09:00
|
|
|
IsLittleEndianCached,
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
isNum,
|
|
|
|
OPS,
|
|
|
|
shadow,
|
|
|
|
TextRenderingMode,
|
|
|
|
unreachable,
|
|
|
|
Util,
|
|
|
|
warn,
|
2020-01-02 20:00:16 +09:00
|
|
|
} from "../shared/util.js";
|
2021-07-01 07:09:07 +09:00
|
|
|
import { getShadingPattern, TilingPattern } from "./pattern_helper.js";
|
2021-09-11 18:11:32 +09:00
|
|
|
import { PixelsPerInch } from "./display_utils.js";
|
2015-11-22 01:32:47 +09:00
|
|
|
|
2011-10-28 03:51:10 +09:00
|
|
|
// <canvas> contexts store most of the state we need natively.
|
|
|
|
// However, PDF needs a bit more state, which we store here.
|
2012-02-05 05:42:07 +09:00
|
|
|
// Minimal font size that would be used during canvas fillText operations.
|
2020-10-02 20:51:21 +09:00
|
|
|
const MIN_FONT_SIZE = 16;
|
2014-10-25 10:48:31 +09:00
|
|
|
// Maximum font size that would be used during canvas fillText operations.
|
2020-10-02 20:51:21 +09:00
|
|
|
const MAX_FONT_SIZE = 100;
|
|
|
|
const MAX_GROUP_SIZE = 4096;
|
2012-02-05 03:45:18 +09:00
|
|
|
|
2021-07-28 11:58:06 +09:00
|
|
|
// This value comes from sampling a few PDFs that re-use patterns, there doesn't
|
|
|
|
// seem to be any that benefit from caching more than 2 patterns.
|
|
|
|
const MAX_CACHED_CANVAS_PATTERNS = 2;
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Defines the time the `executeOperatorList`-method is going to be executing
|
|
|
|
// before it stops and shedules a continue of execution.
|
|
|
|
const EXECUTION_TIME = 15; // ms
|
|
|
|
// Defines the number of steps before checking the execution time.
|
|
|
|
const EXECUTION_STEPS = 10;
|
|
|
|
|
2020-10-02 20:51:21 +09:00
|
|
|
const COMPILE_TYPE3_GLYPHS = true;
|
|
|
|
const MAX_SIZE_TO_COMPILE = 1000;
|
2014-10-27 02:20:04 +09:00
|
|
|
|
2020-10-02 20:51:21 +09:00
|
|
|
const FULL_CHUNK_HEIGHT = 16;
|
2013-05-11 12:50:14 +09:00
|
|
|
|
2021-01-26 03:25:05 +09:00
|
|
|
// Because of https://bugs.chromium.org/p/chromium/issues/detail?id=1170396
|
|
|
|
// some curves aren't rendered correctly.
|
|
|
|
// Multiplying lineWidth by this factor should help to have "correct"
|
|
|
|
// rendering with no artifacts.
|
|
|
|
// Once the bug is fixed upstream, we can remove this constant and its use.
|
|
|
|
const LINEWIDTH_SCALE_FACTOR = 1.000001;
|
|
|
|
|
2021-10-20 12:14:48 +09:00
|
|
|
/**
|
|
|
|
* Overrides certain methods on a 2d ctx so that when they are called they
|
|
|
|
* will also call the same method on the destCtx. The methods that are
|
|
|
|
* overridden are all the transformation state modifiers, path creation, and
|
|
|
|
* save/restore. We only forward these specific methods because they are the
|
|
|
|
* only state modifiers that we cannot copy over when we switch contexts.
|
|
|
|
*
|
|
|
|
* To remove mirroring call `ctx._removeMirroring()`.
|
|
|
|
*
|
|
|
|
* @param {Object} ctx - The 2d canvas context that will duplicate its calls on
|
|
|
|
* the destCtx.
|
|
|
|
* @param {Object} destCtx - The 2d canvas context that will receive the
|
|
|
|
* forwarded calls.
|
|
|
|
*/
|
|
|
|
function mirrorContextOperations(ctx, destCtx) {
|
|
|
|
if (ctx._removeMirroring) {
|
|
|
|
throw new Error("Context is already forwarding operations.");
|
|
|
|
}
|
|
|
|
ctx.__originalSave = ctx.save;
|
|
|
|
ctx.__originalRestore = ctx.restore;
|
|
|
|
ctx.__originalRotate = ctx.rotate;
|
|
|
|
ctx.__originalScale = ctx.scale;
|
|
|
|
ctx.__originalTranslate = ctx.translate;
|
|
|
|
ctx.__originalTransform = ctx.transform;
|
|
|
|
ctx.__originalSetTransform = ctx.setTransform;
|
|
|
|
ctx.__originalResetTransform = ctx.resetTransform;
|
|
|
|
ctx.__originalClip = ctx.clip;
|
|
|
|
ctx.__originalMoveTo = ctx.moveTo;
|
|
|
|
ctx.__originalLineTo = ctx.lineTo;
|
|
|
|
ctx.__originalBezierCurveTo = ctx.bezierCurveTo;
|
|
|
|
ctx.__originalRect = ctx.rect;
|
|
|
|
ctx.__originalClosePath = ctx.closePath;
|
|
|
|
ctx.__originalBeginPath = ctx.beginPath;
|
|
|
|
|
|
|
|
ctx._removeMirroring = () => {
|
|
|
|
ctx.save = ctx.__originalSave;
|
|
|
|
ctx.restore = ctx.__originalRestore;
|
|
|
|
ctx.rotate = ctx.__originalRotate;
|
|
|
|
ctx.scale = ctx.__originalScale;
|
|
|
|
ctx.translate = ctx.__originalTranslate;
|
|
|
|
ctx.transform = ctx.__originalTransform;
|
|
|
|
ctx.setTransform = ctx.__originalSetTransform;
|
|
|
|
ctx.resetTransform = ctx.__originalResetTransform;
|
|
|
|
|
|
|
|
ctx.clip = ctx.__originalClip;
|
|
|
|
ctx.moveTo = ctx.__originalMoveTo;
|
|
|
|
ctx.lineTo = ctx.__originalLineTo;
|
|
|
|
ctx.bezierCurveTo = ctx.__originalBezierCurveTo;
|
|
|
|
ctx.rect = ctx.__originalRect;
|
|
|
|
ctx.closePath = ctx.__originalClosePath;
|
|
|
|
ctx.beginPath = ctx.__originalBeginPath;
|
|
|
|
delete ctx._removeMirroring;
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.save = function ctxSave() {
|
|
|
|
destCtx.save();
|
|
|
|
this.__originalSave();
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.restore = function ctxRestore() {
|
|
|
|
destCtx.restore();
|
|
|
|
this.__originalRestore();
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.translate = function ctxTranslate(x, y) {
|
|
|
|
destCtx.translate(x, y);
|
|
|
|
this.__originalTranslate(x, y);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.scale = function ctxScale(x, y) {
|
|
|
|
destCtx.scale(x, y);
|
|
|
|
this.__originalScale(x, y);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.transform = function ctxTransform(a, b, c, d, e, f) {
|
|
|
|
destCtx.transform(a, b, c, d, e, f);
|
|
|
|
this.__originalTransform(a, b, c, d, e, f);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
|
|
|
|
destCtx.setTransform(a, b, c, d, e, f);
|
|
|
|
this.__originalSetTransform(a, b, c, d, e, f);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.resetTransform = function ctxResetTransform() {
|
|
|
|
destCtx.resetTransform();
|
|
|
|
this.__originalResetTransform();
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.rotate = function ctxRotate(angle) {
|
|
|
|
destCtx.rotate(angle);
|
|
|
|
this.__originalRotate(angle);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.clip = function ctxRotate(rule) {
|
|
|
|
destCtx.clip(rule);
|
|
|
|
this.__originalClip(rule);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.moveTo = function (x, y) {
|
|
|
|
destCtx.moveTo(x, y);
|
|
|
|
this.__originalMoveTo(x, y);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.lineTo = function (x, y) {
|
|
|
|
destCtx.lineTo(x, y);
|
|
|
|
this.__originalLineTo(x, y);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) {
|
|
|
|
destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
|
|
|
this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.rect = function (x, y, width, height) {
|
|
|
|
destCtx.rect(x, y, width, height);
|
|
|
|
this.__originalRect(x, y, width, height);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.closePath = function () {
|
|
|
|
destCtx.closePath();
|
|
|
|
this.__originalClosePath();
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.beginPath = function () {
|
|
|
|
destCtx.beginPath();
|
|
|
|
this.__originalBeginPath();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2011-11-27 17:22:08 +09:00
|
|
|
function addContextCurrentTransform(ctx) {
|
2015-03-12 04:55:26 +09:00
|
|
|
// If the context doesn't expose a `mozCurrentTransform`, add a JS based one.
|
2021-04-27 04:42:11 +09:00
|
|
|
if (ctx.mozCurrentTransform) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ctx._originalSave = ctx.save;
|
|
|
|
ctx._originalRestore = ctx.restore;
|
|
|
|
ctx._originalRotate = ctx.rotate;
|
|
|
|
ctx._originalScale = ctx.scale;
|
|
|
|
ctx._originalTranslate = ctx.translate;
|
|
|
|
ctx._originalTransform = ctx.transform;
|
|
|
|
ctx._originalSetTransform = ctx.setTransform;
|
|
|
|
ctx._originalResetTransform = ctx.resetTransform;
|
|
|
|
|
|
|
|
ctx._transformMatrix = ctx._transformMatrix || [1, 0, 0, 1, 0, 0];
|
|
|
|
ctx._transformStack = [];
|
|
|
|
|
|
|
|
try {
|
|
|
|
// The call to getOwnPropertyDescriptor throws an exception in Node.js:
|
|
|
|
// "TypeError: Method lineWidth called on incompatible receiver
|
|
|
|
// #<CanvasRenderingContext2D>".
|
|
|
|
const desc = Object.getOwnPropertyDescriptor(
|
|
|
|
Object.getPrototypeOf(ctx),
|
|
|
|
"lineWidth"
|
|
|
|
);
|
2021-01-26 03:25:05 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
ctx._setLineWidth = desc.set;
|
|
|
|
ctx._getLineWidth = desc.get;
|
2021-01-26 03:25:05 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
Object.defineProperty(ctx, "lineWidth", {
|
|
|
|
set: function setLineWidth(width) {
|
|
|
|
this._setLineWidth(width * LINEWIDTH_SCALE_FACTOR);
|
Fix inconsistent spacing and trailing commas in objects in remaining `src/` files, so we can enable the `comma-dangle` and `object-curly-spacing` ESLint rules later on
http://eslint.org/docs/rules/comma-dangle
http://eslint.org/docs/rules/object-curly-spacing
Given that we currently have quite inconsistent object formatting, fixing this in *one* big patch probably wouldn't be feasible (since I cannot imagine anyone wanting to review that); hence I've opted to try and do this piecewise instead.
Please note: This patch was created automatically, using the ESLint `--fix` command line option. In a couple of places this caused lines to become too long, and I've fixed those manually; please refer to the interdiff below for the only hand-edits in this patch.
```diff
diff --git a/src/display/canvas.js b/src/display/canvas.js
index 5739f6f2..4216b2d2 100644
--- a/src/display/canvas.js
+++ b/src/display/canvas.js
@@ -2071,7 +2071,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var map = [];
for (var i = 0, ii = positions.length; i < ii; i += 2) {
map.push({ transform: [scaleX, 0, 0, scaleY, positions[i],
- positions[i + 1]], x: 0, y: 0, w: width, h: height, });
+ positions[i + 1]], x: 0, y: 0, w: width, h: height, });
}
this.paintInlineImageXObjectGroup(imgData, map);
},
diff --git a/src/display/svg.js b/src/display/svg.js
index 9eb05dfa..2aa21482 100644
--- a/src/display/svg.js
+++ b/src/display/svg.js
@@ -458,7 +458,11 @@ SVGGraphics = (function SVGGraphicsClosure() {
for (var x = 0; x < fnArrayLen; x++) {
var fnId = fnArray[x];
- opList.push({ 'fnId': fnId, 'fn': REVOPS[fnId], 'args': argsArray[x], });
+ opList.push({
+ 'fnId': fnId,
+ 'fn': REVOPS[fnId],
+ 'args': argsArray[x],
+ });
}
return opListToTree(opList);
},
```
2017-06-02 18:26:37 +09:00
|
|
|
},
|
2021-04-27 04:42:11 +09:00
|
|
|
get: function getLineWidth() {
|
|
|
|
return this._getLineWidth();
|
Fix inconsistent spacing and trailing commas in objects in remaining `src/` files, so we can enable the `comma-dangle` and `object-curly-spacing` ESLint rules later on
http://eslint.org/docs/rules/comma-dangle
http://eslint.org/docs/rules/object-curly-spacing
Given that we currently have quite inconsistent object formatting, fixing this in *one* big patch probably wouldn't be feasible (since I cannot imagine anyone wanting to review that); hence I've opted to try and do this piecewise instead.
Please note: This patch was created automatically, using the ESLint `--fix` command line option. In a couple of places this caused lines to become too long, and I've fixed those manually; please refer to the interdiff below for the only hand-edits in this patch.
```diff
diff --git a/src/display/canvas.js b/src/display/canvas.js
index 5739f6f2..4216b2d2 100644
--- a/src/display/canvas.js
+++ b/src/display/canvas.js
@@ -2071,7 +2071,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var map = [];
for (var i = 0, ii = positions.length; i < ii; i += 2) {
map.push({ transform: [scaleX, 0, 0, scaleY, positions[i],
- positions[i + 1]], x: 0, y: 0, w: width, h: height, });
+ positions[i + 1]], x: 0, y: 0, w: width, h: height, });
}
this.paintInlineImageXObjectGroup(imgData, map);
},
diff --git a/src/display/svg.js b/src/display/svg.js
index 9eb05dfa..2aa21482 100644
--- a/src/display/svg.js
+++ b/src/display/svg.js
@@ -458,7 +458,11 @@ SVGGraphics = (function SVGGraphicsClosure() {
for (var x = 0; x < fnArrayLen; x++) {
var fnId = fnArray[x];
- opList.push({ 'fnId': fnId, 'fn': REVOPS[fnId], 'args': argsArray[x], });
+ opList.push({
+ 'fnId': fnId,
+ 'fn': REVOPS[fnId],
+ 'args': argsArray[x],
+ });
}
return opListToTree(opList);
},
```
2017-06-02 18:26:37 +09:00
|
|
|
},
|
2011-11-27 17:22:08 +09:00
|
|
|
});
|
2021-04-27 04:42:11 +09:00
|
|
|
} catch (_) {}
|
|
|
|
|
|
|
|
Object.defineProperty(ctx, "mozCurrentTransform", {
|
|
|
|
get: function getCurrentTransform() {
|
|
|
|
return this._transformMatrix;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(ctx, "mozCurrentTransformInverse", {
|
|
|
|
get: function getCurrentTransformInverse() {
|
|
|
|
// Calculation done using WolframAlpha:
|
|
|
|
// http://www.wolframalpha.com/input/?
|
|
|
|
// i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}}
|
|
|
|
|
|
|
|
const [a, b, c, d, e, f] = this._transformMatrix;
|
|
|
|
const ad_bc = a * d - b * c;
|
|
|
|
const bc_ad = b * c - a * d;
|
2011-11-23 05:00:04 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
return [
|
|
|
|
d / ad_bc,
|
|
|
|
b / bc_ad,
|
|
|
|
c / bc_ad,
|
|
|
|
a / ad_bc,
|
|
|
|
(d * e - c * f) / bc_ad,
|
|
|
|
(b * e - a * f) / ad_bc,
|
2011-11-23 05:00:04 +09:00
|
|
|
];
|
2021-04-27 04:42:11 +09:00
|
|
|
},
|
|
|
|
});
|
2011-11-23 05:00:04 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
ctx.save = function ctxSave() {
|
|
|
|
const old = this._transformMatrix;
|
|
|
|
this._transformStack.push(old);
|
|
|
|
this._transformMatrix = old.slice(0, 6);
|
2011-11-23 05:00:04 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
this._originalSave();
|
|
|
|
};
|
2013-05-01 02:01:01 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
ctx.restore = function ctxRestore() {
|
|
|
|
const prev = this._transformStack.pop();
|
|
|
|
if (prev) {
|
|
|
|
this._transformMatrix = prev;
|
|
|
|
this._originalRestore();
|
|
|
|
}
|
|
|
|
};
|
2013-05-01 02:01:01 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
ctx.translate = function ctxTranslate(x, y) {
|
|
|
|
const m = this._transformMatrix;
|
|
|
|
m[4] = m[0] * x + m[2] * y + m[4];
|
|
|
|
m[5] = m[1] * x + m[3] * y + m[5];
|
2021-01-26 03:25:05 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
this._originalTranslate(x, y);
|
|
|
|
};
|
2021-01-26 03:25:05 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
ctx.scale = function ctxScale(x, y) {
|
|
|
|
const m = this._transformMatrix;
|
2021-07-04 18:51:11 +09:00
|
|
|
m[0] *= x;
|
|
|
|
m[1] *= x;
|
|
|
|
m[2] *= y;
|
|
|
|
m[3] *= y;
|
2011-11-23 05:00:04 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
this._originalScale(x, y);
|
|
|
|
};
|
2011-11-23 05:00:04 +09:00
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
ctx.transform = function ctxTransform(a, b, c, d, e, f) {
|
|
|
|
const m = this._transformMatrix;
|
|
|
|
this._transformMatrix = [
|
|
|
|
m[0] * a + m[2] * b,
|
|
|
|
m[1] * a + m[3] * b,
|
|
|
|
m[0] * c + m[2] * d,
|
|
|
|
m[1] * c + m[3] * d,
|
|
|
|
m[0] * e + m[2] * f + m[4],
|
|
|
|
m[1] * e + m[3] * f + m[5],
|
|
|
|
];
|
|
|
|
|
|
|
|
ctx._originalTransform(a, b, c, d, e, f);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
|
|
|
|
this._transformMatrix = [a, b, c, d, e, f];
|
|
|
|
|
|
|
|
ctx._originalSetTransform(a, b, c, d, e, f);
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.resetTransform = function ctxResetTransform() {
|
|
|
|
this._transformMatrix = [1, 0, 0, 1, 0, 0];
|
|
|
|
|
|
|
|
ctx._originalResetTransform();
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.rotate = function ctxRotate(angle) {
|
|
|
|
const cosValue = Math.cos(angle);
|
|
|
|
const sinValue = Math.sin(angle);
|
|
|
|
|
|
|
|
const m = this._transformMatrix;
|
|
|
|
this._transformMatrix = [
|
|
|
|
m[0] * cosValue + m[2] * sinValue,
|
|
|
|
m[1] * cosValue + m[3] * sinValue,
|
|
|
|
m[0] * -sinValue + m[2] * cosValue,
|
|
|
|
m[1] * -sinValue + m[3] * cosValue,
|
|
|
|
m[4],
|
|
|
|
m[5],
|
|
|
|
];
|
|
|
|
|
|
|
|
this._originalRotate(angle);
|
|
|
|
};
|
2011-11-23 05:00:04 +09:00
|
|
|
}
|
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
class CachedCanvases {
|
|
|
|
constructor(canvasFactory) {
|
2017-01-28 02:58:39 +09:00
|
|
|
this.canvasFactory = canvasFactory;
|
2015-11-17 01:50:02 +09:00
|
|
|
this.cache = Object.create(null);
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
|
|
|
getCanvas(id, width, height, trackTransform) {
|
|
|
|
let canvasEntry;
|
|
|
|
if (this.cache[id] !== undefined) {
|
|
|
|
canvasEntry = this.cache[id];
|
|
|
|
this.canvasFactory.reset(canvasEntry, width, height);
|
|
|
|
// reset canvas transform for emulated mozCurrentTransform, if needed
|
|
|
|
canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
|
} else {
|
|
|
|
canvasEntry = this.canvasFactory.create(width, height);
|
|
|
|
this.cache[id] = canvasEntry;
|
|
|
|
}
|
|
|
|
if (trackTransform) {
|
|
|
|
addContextCurrentTransform(canvasEntry.context);
|
|
|
|
}
|
|
|
|
return canvasEntry;
|
|
|
|
}
|
|
|
|
|
|
|
|
clear() {
|
|
|
|
for (const id in this.cache) {
|
|
|
|
const canvasEntry = this.cache[id];
|
|
|
|
this.canvasFactory.destroy(canvasEntry);
|
|
|
|
delete this.cache[id];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-05-31 09:42:26 +09:00
|
|
|
|
2021-07-28 11:58:06 +09:00
|
|
|
/**
|
|
|
|
* Least recently used cache implemented with a JS Map. JS Map keys are ordered
|
|
|
|
* by last insertion.
|
|
|
|
*/
|
|
|
|
class LRUCache {
|
|
|
|
constructor(maxSize = 0) {
|
|
|
|
this._cache = new Map();
|
|
|
|
this._maxSize = maxSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
has(key) {
|
|
|
|
return this._cache.has(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
get(key) {
|
|
|
|
if (this._cache.has(key)) {
|
|
|
|
// Delete and set the value so it's moved to the end of the map iteration.
|
|
|
|
const value = this._cache.get(key);
|
|
|
|
this._cache.delete(key);
|
|
|
|
this._cache.set(key, value);
|
|
|
|
}
|
|
|
|
return this._cache.get(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
set(key, value) {
|
|
|
|
if (this._maxSize <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (this._cache.size + 1 > this._maxSize) {
|
|
|
|
// Delete the least recently used.
|
|
|
|
this._cache.delete(this._cache.keys().next().value);
|
|
|
|
}
|
|
|
|
this._cache.set(key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
clear() {
|
|
|
|
this._cache.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-11 12:50:14 +09:00
|
|
|
function compileType3Glyph(imgData) {
|
2020-10-02 20:51:21 +09:00
|
|
|
const POINT_TO_PROCESS_LIMIT = 1000;
|
2021-05-19 18:24:38 +09:00
|
|
|
const POINT_TYPES = new Uint8Array([
|
|
|
|
0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0,
|
|
|
|
]);
|
2013-05-11 12:50:14 +09:00
|
|
|
|
2020-10-02 20:51:21 +09:00
|
|
|
const width = imgData.width,
|
2020-10-02 20:54:44 +09:00
|
|
|
height = imgData.height,
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
width1 = width + 1;
|
2020-10-02 20:54:44 +09:00
|
|
|
let i, ii, j, j0;
|
2020-10-02 20:51:21 +09:00
|
|
|
const points = new Uint8Array(width1 * (height + 1));
|
2014-01-14 12:21:03 +09:00
|
|
|
|
|
|
|
// decodes bit-packed mask data
|
2020-10-02 20:51:21 +09:00
|
|
|
const lineSize = (width + 7) & ~7,
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
data0 = imgData.data;
|
2020-10-02 20:54:44 +09:00
|
|
|
const data = new Uint8Array(lineSize * height);
|
|
|
|
let pos = 0;
|
2014-01-14 12:21:03 +09:00
|
|
|
for (i = 0, ii = data0.length; i < ii; i++) {
|
2020-10-02 20:54:44 +09:00
|
|
|
const elem = data0[i];
|
|
|
|
let mask = 128;
|
2014-01-14 12:21:03 +09:00
|
|
|
while (mask > 0) {
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
data[pos++] = elem & mask ? 0 : 255;
|
2014-01-14 12:21:03 +09:00
|
|
|
mask >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-02 06:20:41 +09:00
|
|
|
// finding interesting points: every point is located between mask pixels,
|
2013-05-11 12:50:14 +09:00
|
|
|
// 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.
|
2020-10-02 20:51:21 +09:00
|
|
|
let count = 0;
|
2014-01-14 12:21:03 +09:00
|
|
|
pos = 0;
|
|
|
|
if (data[pos] !== 0) {
|
2013-06-11 22:40:26 +09:00
|
|
|
points[0] = 1;
|
2013-05-11 12:50:14 +09:00
|
|
|
++count;
|
|
|
|
}
|
|
|
|
for (j = 1; j < width; j++) {
|
2014-01-14 12:21:03 +09:00
|
|
|
if (data[pos] !== data[pos + 1]) {
|
2013-06-11 22:40:26 +09:00
|
|
|
points[j] = data[pos] ? 2 : 1;
|
2013-05-11 12:50:14 +09:00
|
|
|
++count;
|
|
|
|
}
|
2014-01-14 12:21:03 +09:00
|
|
|
pos++;
|
2013-05-11 12:50:14 +09:00
|
|
|
}
|
|
|
|
if (data[pos] !== 0) {
|
2013-06-11 22:40:26 +09:00
|
|
|
points[j] = 2;
|
2013-05-11 12:50:14 +09:00
|
|
|
++count;
|
|
|
|
}
|
|
|
|
for (i = 1; i < height; i++) {
|
2014-01-14 12:21:03 +09:00
|
|
|
pos = i * lineSize;
|
2013-06-12 04:01:10 +09:00
|
|
|
j0 = i * width1;
|
2013-05-11 12:50:14 +09:00
|
|
|
if (data[pos - lineSize] !== data[pos]) {
|
2013-06-11 22:40:26 +09:00
|
|
|
points[j0] = data[pos] ? 1 : 8;
|
2013-05-11 12:50:14 +09:00
|
|
|
++count;
|
|
|
|
}
|
2013-06-11 22:40:26 +09:00
|
|
|
// '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).
|
2020-10-02 20:51:21 +09:00
|
|
|
let sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
|
2013-05-11 12:50:14 +09:00
|
|
|
for (j = 1; j < width; j++) {
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
sum =
|
|
|
|
(sum >> 2) +
|
|
|
|
(data[pos + 1] ? 4 : 0) +
|
|
|
|
(data[pos - lineSize + 1] ? 8 : 0);
|
2013-07-02 01:25:46 +09:00
|
|
|
if (POINT_TYPES[sum]) {
|
2013-06-12 04:01:10 +09:00
|
|
|
points[j0 + j] = POINT_TYPES[sum];
|
2013-05-11 12:50:14 +09:00
|
|
|
++count;
|
|
|
|
}
|
2014-01-14 12:21:03 +09:00
|
|
|
pos++;
|
2013-05-11 12:50:14 +09:00
|
|
|
}
|
|
|
|
if (data[pos - lineSize] !== data[pos]) {
|
2013-06-11 22:40:26 +09:00
|
|
|
points[j0 + j] = data[pos] ? 2 : 4;
|
2013-05-11 12:50:14 +09:00
|
|
|
++count;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count > POINT_TO_PROCESS_LIMIT) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2013-06-12 04:01:10 +09:00
|
|
|
|
2014-01-14 12:21:03 +09:00
|
|
|
pos = lineSize * (height - 1);
|
2013-06-12 04:01:10 +09:00
|
|
|
j0 = i * width1;
|
2013-05-11 12:50:14 +09:00
|
|
|
if (data[pos] !== 0) {
|
2013-06-12 04:01:10 +09:00
|
|
|
points[j0] = 8;
|
2013-05-11 12:50:14 +09:00
|
|
|
++count;
|
|
|
|
}
|
|
|
|
for (j = 1; j < width; j++) {
|
2014-01-14 12:21:03 +09:00
|
|
|
if (data[pos] !== data[pos + 1]) {
|
2013-06-12 04:01:10 +09:00
|
|
|
points[j0 + j] = data[pos] ? 4 : 8;
|
2013-05-11 12:50:14 +09:00
|
|
|
++count;
|
|
|
|
}
|
2014-01-14 12:21:03 +09:00
|
|
|
pos++;
|
2013-05-11 12:50:14 +09:00
|
|
|
}
|
|
|
|
if (data[pos] !== 0) {
|
2013-06-12 04:01:10 +09:00
|
|
|
points[j0 + j] = 4;
|
2013-05-11 12:50:14 +09:00
|
|
|
++count;
|
|
|
|
}
|
|
|
|
if (count > POINT_TO_PROCESS_LIMIT) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// building outlines
|
2020-10-02 20:51:21 +09:00
|
|
|
const steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
|
|
|
|
const outlines = [];
|
2013-06-11 22:40:26 +09:00
|
|
|
for (i = 0; count && i <= height; i++) {
|
2020-10-02 20:51:21 +09:00
|
|
|
let p = i * width1;
|
|
|
|
const end = p + width;
|
2013-06-11 22:40:26 +09:00
|
|
|
while (p < end && !points[p]) {
|
|
|
|
p++;
|
2013-05-11 12:50:14 +09:00
|
|
|
}
|
2013-06-11 22:40:26 +09:00
|
|
|
if (p === end) {
|
2013-05-11 12:50:14 +09:00
|
|
|
continue;
|
|
|
|
}
|
2020-10-02 20:51:21 +09:00
|
|
|
const coords = [p % width1, i];
|
2013-05-11 12:50:14 +09:00
|
|
|
|
2020-10-02 20:54:44 +09:00
|
|
|
const p0 = p;
|
|
|
|
let type = points[p];
|
2013-05-11 12:50:14 +09:00
|
|
|
do {
|
2020-10-02 20:51:21 +09:00
|
|
|
const step = steps[type];
|
2014-03-14 21:11:11 +09:00
|
|
|
do {
|
|
|
|
p += step;
|
|
|
|
} while (!points[p]);
|
2013-07-02 01:25:46 +09:00
|
|
|
|
2020-10-02 20:54:44 +09:00
|
|
|
const pp = points[p];
|
2013-06-12 04:01:10 +09:00
|
|
|
if (pp !== 5 && pp !== 10) {
|
2013-06-11 22:40:26 +09:00
|
|
|
// set new direction
|
2013-07-02 01:25:46 +09:00
|
|
|
type = pp;
|
2013-06-11 22:40:26 +09:00
|
|
|
// delete mark
|
2013-07-02 01:25:46 +09:00
|
|
|
points[p] = 0;
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
} else {
|
|
|
|
// type is 5 or 10, ie, a crossing
|
2013-06-11 22:40:26 +09:00
|
|
|
// set new direction
|
2013-07-02 01:25:46 +09:00
|
|
|
type = pp & ((0x33 * type) >> 4);
|
2013-06-11 22:40:26 +09:00
|
|
|
// set new type for "future hit"
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
points[p] &= (type >> 2) | (type << 2);
|
2013-05-11 12:50:14 +09:00
|
|
|
}
|
|
|
|
|
2021-05-24 20:20:19 +09:00
|
|
|
coords.push(p % width1, (p / width1) | 0);
|
2019-02-22 00:25:34 +09:00
|
|
|
|
|
|
|
if (!points[p]) {
|
|
|
|
--count;
|
|
|
|
}
|
2013-06-11 22:40:26 +09:00
|
|
|
} while (p0 !== p);
|
2013-06-12 04:01:10 +09:00
|
|
|
outlines.push(coords);
|
2013-05-11 12:50:14 +09:00
|
|
|
--i;
|
|
|
|
}
|
|
|
|
|
2020-10-02 20:51:21 +09:00
|
|
|
const drawOutline = function (c) {
|
2013-06-11 22:40:26 +09:00
|
|
|
c.save();
|
|
|
|
// the path shall be painted in [0..1]x[0..1] space
|
|
|
|
c.scale(1 / width, -1 / height);
|
|
|
|
c.translate(0, -height);
|
|
|
|
c.beginPath();
|
2020-03-21 07:00:36 +09:00
|
|
|
for (let k = 0, kk = outlines.length; k < kk; k++) {
|
2020-10-02 20:51:21 +09:00
|
|
|
const o = outlines[k];
|
2013-06-12 04:01:10 +09:00
|
|
|
c.moveTo(o[0], o[1]);
|
2020-03-21 07:00:36 +09:00
|
|
|
for (let l = 2, ll = o.length; l < ll; l += 2) {
|
|
|
|
c.lineTo(o[l], o[l + 1]);
|
2013-06-11 22:40:26 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
c.fill();
|
|
|
|
c.beginPath();
|
|
|
|
c.restore();
|
|
|
|
};
|
2013-07-02 01:25:46 +09:00
|
|
|
|
2013-06-11 22:40:26 +09:00
|
|
|
return drawOutline;
|
2013-05-11 12:50:14 +09:00
|
|
|
}
|
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
class CanvasExtraState {
|
|
|
|
constructor() {
|
2012-04-05 05:43:26 +09:00
|
|
|
// Are soft masks and alpha values shapes or opacities?
|
|
|
|
this.alphaIsShape = false;
|
|
|
|
this.fontSize = 0;
|
|
|
|
this.fontSizeScale = 1;
|
|
|
|
this.textMatrix = IDENTITY_MATRIX;
|
2014-05-24 03:36:54 +09:00
|
|
|
this.textMatrixScale = 1;
|
2013-01-04 09:39:06 +09:00
|
|
|
this.fontMatrix = FONT_IDENTITY_MATRIX;
|
2012-04-05 05:43:26 +09:00
|
|
|
this.leading = 0;
|
|
|
|
// Current point (in user coordinates)
|
|
|
|
this.x = 0;
|
|
|
|
this.y = 0;
|
|
|
|
// Start of text line (in text coordinates)
|
|
|
|
this.lineX = 0;
|
|
|
|
this.lineY = 0;
|
|
|
|
// Character and word spacing
|
|
|
|
this.charSpacing = 0;
|
|
|
|
this.wordSpacing = 0;
|
|
|
|
this.textHScale = 1;
|
|
|
|
this.textRenderingMode = TextRenderingMode.FILL;
|
2012-08-02 05:10:48 +09:00
|
|
|
this.textRise = 0;
|
2012-04-05 05:43:26 +09:00
|
|
|
// Default fore and background colors
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
this.fillColor = "#000000";
|
|
|
|
this.strokeColor = "#000000";
|
2014-10-27 02:20:04 +09:00
|
|
|
this.patternFill = false;
|
2012-04-05 05:43:26 +09:00
|
|
|
// Note: fill alpha applies to all non-stroking operations
|
|
|
|
this.fillAlpha = 1;
|
|
|
|
this.strokeAlpha = 1;
|
|
|
|
this.lineWidth = 1;
|
2016-04-10 08:46:15 +09:00
|
|
|
this.activeSMask = null;
|
2020-08-17 15:49:19 +09:00
|
|
|
this.transferMaps = null;
|
2012-04-05 05:43:26 +09:00
|
|
|
}
|
|
|
|
|
2021-04-27 04:42:11 +09:00
|
|
|
clone() {
|
|
|
|
return Object.create(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentPoint(x, y) {
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
}
|
|
|
|
}
|
2012-04-05 05:43:26 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
function putBinaryImageData(ctx, imgData, transferMaps = null) {
|
|
|
|
if (typeof ImageData !== "undefined" && imgData instanceof ImageData) {
|
|
|
|
ctx.putImageData(imgData, 0, 0);
|
|
|
|
return;
|
|
|
|
}
|
2012-12-08 03:19:43 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Put the image data to the canvas in chunks, rather than putting the
|
|
|
|
// whole image at once. This saves JS memory, because the ImageData object
|
|
|
|
// is smaller. It also possibly saves C++ memory within the implementation
|
|
|
|
// of putImageData(). (E.g. in Firefox we make two short-lived copies of
|
|
|
|
// the data passed to putImageData()). |n| shouldn't be too small, however,
|
|
|
|
// because too many putImageData() calls will slow things down.
|
|
|
|
//
|
|
|
|
// Note: as written, if the last chunk is partial, the putImageData() call
|
|
|
|
// will (conceptually) put pixels past the bounds of the canvas. But
|
|
|
|
// that's ok; any such pixels are ignored.
|
|
|
|
|
|
|
|
const height = imgData.height,
|
|
|
|
width = imgData.width;
|
|
|
|
const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
|
|
|
|
const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
|
|
|
|
const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
|
|
|
|
|
|
|
|
const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
|
|
|
|
let srcPos = 0,
|
|
|
|
destPos;
|
|
|
|
const src = imgData.data;
|
|
|
|
const dest = chunkImgData.data;
|
|
|
|
let i, j, thisChunkHeight, elemsInThisChunk;
|
|
|
|
|
|
|
|
let transferMapRed, transferMapGreen, transferMapBlue, transferMapGray;
|
|
|
|
if (transferMaps) {
|
|
|
|
switch (transferMaps.length) {
|
|
|
|
case 1:
|
|
|
|
transferMapRed = transferMaps[0];
|
|
|
|
transferMapGreen = transferMaps[0];
|
|
|
|
transferMapBlue = transferMaps[0];
|
|
|
|
transferMapGray = transferMaps[0];
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
transferMapRed = transferMaps[0];
|
|
|
|
transferMapGreen = transferMaps[1];
|
|
|
|
transferMapBlue = transferMaps[2];
|
|
|
|
transferMapGray = transferMaps[3];
|
|
|
|
break;
|
2020-08-17 15:49:19 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2020-08-17 15:49:19 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// There are multiple forms in which the pixel data can be passed, and
|
|
|
|
// imgData.kind tells us which one this is.
|
|
|
|
if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
|
|
|
|
// Grayscale, 1 bit per pixel (i.e. black-and-white).
|
|
|
|
const srcLength = src.byteLength;
|
|
|
|
const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2);
|
|
|
|
const dest32DataLength = dest32.length;
|
|
|
|
const fullSrcDiff = (width + 7) >> 3;
|
|
|
|
let white = 0xffffffff;
|
|
|
|
let black = IsLittleEndianCached.value ? 0xff000000 : 0x000000ff;
|
|
|
|
|
|
|
|
if (transferMapGray) {
|
|
|
|
if (transferMapGray[0] === 0xff && transferMapGray[0xff] === 0) {
|
|
|
|
[white, black] = [black, white];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < totalChunks; i++) {
|
|
|
|
thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
|
|
|
|
destPos = 0;
|
|
|
|
for (j = 0; j < thisChunkHeight; j++) {
|
|
|
|
const srcDiff = srcLength - srcPos;
|
|
|
|
let k = 0;
|
|
|
|
const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
|
|
|
|
const kEndUnrolled = kEnd & ~7;
|
|
|
|
let mask = 0;
|
|
|
|
let srcByte = 0;
|
|
|
|
for (; k < kEndUnrolled; k += 8) {
|
|
|
|
srcByte = src[srcPos++];
|
|
|
|
dest32[destPos++] = srcByte & 128 ? white : black;
|
|
|
|
dest32[destPos++] = srcByte & 64 ? white : black;
|
|
|
|
dest32[destPos++] = srcByte & 32 ? white : black;
|
|
|
|
dest32[destPos++] = srcByte & 16 ? white : black;
|
|
|
|
dest32[destPos++] = srcByte & 8 ? white : black;
|
|
|
|
dest32[destPos++] = srcByte & 4 ? white : black;
|
|
|
|
dest32[destPos++] = srcByte & 2 ? white : black;
|
|
|
|
dest32[destPos++] = srcByte & 1 ? white : black;
|
2020-08-17 15:49:19 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
for (; k < kEnd; k++) {
|
|
|
|
if (mask === 0) {
|
2014-03-21 00:57:11 +09:00
|
|
|
srcByte = src[srcPos++];
|
2021-07-25 19:55:51 +09:00
|
|
|
mask = 128;
|
2014-03-21 00:57:11 +09:00
|
|
|
}
|
2014-01-23 09:47:51 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
dest32[destPos++] = srcByte & mask ? white : black;
|
|
|
|
mask >>= 1;
|
2014-01-30 04:10:34 +09:00
|
|
|
}
|
2014-01-17 09:59:22 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
// We ran out of input. Make all remaining pixels transparent.
|
|
|
|
while (destPos < dest32DataLength) {
|
|
|
|
dest32[destPos++] = 0;
|
|
|
|
}
|
2014-01-23 09:47:51 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
|
|
|
|
}
|
|
|
|
} else if (imgData.kind === ImageKind.RGBA_32BPP) {
|
|
|
|
// RGBA, 32-bits per pixel.
|
|
|
|
const hasTransferMaps = !!(
|
|
|
|
transferMapRed ||
|
|
|
|
transferMapGreen ||
|
|
|
|
transferMapBlue
|
|
|
|
);
|
2014-03-11 12:18:37 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
j = 0;
|
|
|
|
elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
|
|
|
|
for (i = 0; i < fullChunks; i++) {
|
|
|
|
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
|
|
|
|
srcPos += elemsInThisChunk;
|
2020-08-17 15:49:19 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (hasTransferMaps) {
|
|
|
|
for (let k = 0; k < elemsInThisChunk; k += 4) {
|
|
|
|
if (transferMapRed) {
|
|
|
|
dest[k + 0] = transferMapRed[dest[k + 0]];
|
|
|
|
}
|
|
|
|
if (transferMapGreen) {
|
|
|
|
dest[k + 1] = transferMapGreen[dest[k + 1]];
|
|
|
|
}
|
|
|
|
if (transferMapBlue) {
|
|
|
|
dest[k + 2] = transferMapBlue[dest[k + 2]];
|
2020-08-17 15:49:19 +09:00
|
|
|
}
|
|
|
|
}
|
2014-01-17 09:59:22 +09:00
|
|
|
}
|
2020-08-17 15:49:19 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.putImageData(chunkImgData, 0, j);
|
|
|
|
j += FULL_CHUNK_HEIGHT;
|
|
|
|
}
|
|
|
|
if (i < totalChunks) {
|
|
|
|
elemsInThisChunk = width * partialChunkHeight * 4;
|
|
|
|
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
|
2020-08-17 15:49:19 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (hasTransferMaps) {
|
|
|
|
for (let k = 0; k < elemsInThisChunk; k += 4) {
|
|
|
|
if (transferMapRed) {
|
|
|
|
dest[k + 0] = transferMapRed[dest[k + 0]];
|
|
|
|
}
|
|
|
|
if (transferMapGreen) {
|
|
|
|
dest[k + 1] = transferMapGreen[dest[k + 1]];
|
|
|
|
}
|
|
|
|
if (transferMapBlue) {
|
|
|
|
dest[k + 2] = transferMapBlue[dest[k + 2]];
|
2020-08-17 15:49:19 +09:00
|
|
|
}
|
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx.putImageData(chunkImgData, 0, j);
|
|
|
|
}
|
|
|
|
} else if (imgData.kind === ImageKind.RGB_24BPP) {
|
|
|
|
// RGB, 24-bits per pixel.
|
|
|
|
const hasTransferMaps = !!(
|
|
|
|
transferMapRed ||
|
|
|
|
transferMapGreen ||
|
|
|
|
transferMapBlue
|
|
|
|
);
|
2020-08-17 15:49:19 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
thisChunkHeight = FULL_CHUNK_HEIGHT;
|
|
|
|
elemsInThisChunk = width * thisChunkHeight;
|
|
|
|
for (i = 0; i < totalChunks; i++) {
|
|
|
|
if (i >= fullChunks) {
|
|
|
|
thisChunkHeight = partialChunkHeight;
|
|
|
|
elemsInThisChunk = width * thisChunkHeight;
|
2014-02-25 12:37:19 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
|
|
|
|
destPos = 0;
|
|
|
|
for (j = elemsInThisChunk; j--; ) {
|
|
|
|
dest[destPos++] = src[srcPos++];
|
|
|
|
dest[destPos++] = src[srcPos++];
|
|
|
|
dest[destPos++] = src[srcPos++];
|
|
|
|
dest[destPos++] = 255;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasTransferMaps) {
|
|
|
|
for (let k = 0; k < destPos; k += 4) {
|
|
|
|
if (transferMapRed) {
|
|
|
|
dest[k + 0] = transferMapRed[dest[k + 0]];
|
|
|
|
}
|
|
|
|
if (transferMapGreen) {
|
|
|
|
dest[k + 1] = transferMapGreen[dest[k + 1]];
|
|
|
|
}
|
|
|
|
if (transferMapBlue) {
|
|
|
|
dest[k + 2] = transferMapBlue[dest[k + 2]];
|
2014-02-28 14:15:38 +09:00
|
|
|
}
|
2014-01-14 11:08:39 +09:00
|
|
|
}
|
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
|
2014-10-27 02:20:04 +09:00
|
|
|
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
|
2014-01-14 11:08:39 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
} else {
|
|
|
|
throw new Error(`bad image kind: ${imgData.kind}`);
|
2014-01-14 11:08:39 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2014-01-14 11:08:39 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
function putBinaryImageMask(ctx, imgData) {
|
|
|
|
const height = imgData.height,
|
|
|
|
width = imgData.width;
|
|
|
|
const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
|
|
|
|
const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
|
|
|
|
const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
|
|
|
|
|
|
|
|
const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
|
|
|
|
let srcPos = 0;
|
|
|
|
const src = imgData.data;
|
|
|
|
const dest = chunkImgData.data;
|
|
|
|
|
|
|
|
for (let i = 0; i < totalChunks; i++) {
|
|
|
|
const thisChunkHeight =
|
|
|
|
i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
|
|
|
|
|
|
|
|
// Expand the mask so it can be used by the canvas. Any required
|
|
|
|
// inversion has already been handled.
|
|
|
|
let destPos = 3; // alpha component offset
|
|
|
|
for (let j = 0; j < thisChunkHeight; j++) {
|
|
|
|
let elem,
|
|
|
|
mask = 0;
|
|
|
|
for (let k = 0; k < width; k++) {
|
|
|
|
if (!mask) {
|
|
|
|
elem = src[srcPos++];
|
|
|
|
mask = 128;
|
|
|
|
}
|
|
|
|
dest[destPos] = elem & mask ? 0 : 255;
|
|
|
|
destPos += 4;
|
|
|
|
mask >>= 1;
|
2013-03-13 09:20:38 +09:00
|
|
|
}
|
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
|
2013-03-13 09:20:38 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
function copyCtxState(sourceCtx, destCtx) {
|
|
|
|
const properties = [
|
|
|
|
"strokeStyle",
|
|
|
|
"fillStyle",
|
|
|
|
"fillRule",
|
|
|
|
"globalAlpha",
|
|
|
|
"lineWidth",
|
|
|
|
"lineCap",
|
|
|
|
"lineJoin",
|
|
|
|
"miterLimit",
|
|
|
|
"globalCompositeOperation",
|
|
|
|
"font",
|
|
|
|
];
|
|
|
|
for (let i = 0, ii = properties.length; i < ii; i++) {
|
|
|
|
const property = properties[i];
|
|
|
|
if (sourceCtx[property] !== undefined) {
|
|
|
|
destCtx[property] = sourceCtx[property];
|
2014-06-02 21:54:40 +09:00
|
|
|
}
|
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
if (sourceCtx.setLineDash !== undefined) {
|
|
|
|
destCtx.setLineDash(sourceCtx.getLineDash());
|
|
|
|
destCtx.lineDashOffset = sourceCtx.lineDashOffset;
|
2014-06-02 21:54:40 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2014-06-02 21:54:40 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
function resetCtxToDefault(ctx) {
|
|
|
|
ctx.strokeStyle = "#000000";
|
|
|
|
ctx.fillStyle = "#000000";
|
|
|
|
ctx.fillRule = "nonzero";
|
|
|
|
ctx.globalAlpha = 1;
|
|
|
|
ctx.lineWidth = 1;
|
|
|
|
ctx.lineCap = "butt";
|
|
|
|
ctx.lineJoin = "miter";
|
|
|
|
ctx.miterLimit = 10;
|
|
|
|
ctx.globalCompositeOperation = "source-over";
|
|
|
|
ctx.font = "10px sans-serif";
|
|
|
|
if (ctx.setLineDash !== undefined) {
|
|
|
|
ctx.setLineDash([]);
|
|
|
|
ctx.lineDashOffset = 0;
|
2014-06-02 21:54:40 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2014-06-02 21:54:40 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
function composeSMaskBackdrop(bytes, r0, g0, b0) {
|
|
|
|
const length = bytes.length;
|
|
|
|
for (let i = 3; i < length; i += 4) {
|
|
|
|
const alpha = bytes[i];
|
|
|
|
if (alpha === 0) {
|
|
|
|
bytes[i - 3] = r0;
|
|
|
|
bytes[i - 2] = g0;
|
|
|
|
bytes[i - 1] = b0;
|
|
|
|
} else if (alpha < 255) {
|
|
|
|
const alpha_ = 255 - alpha;
|
|
|
|
bytes[i - 3] = (bytes[i - 3] * alpha + r0 * alpha_) >> 8;
|
|
|
|
bytes[i - 2] = (bytes[i - 2] * alpha + g0 * alpha_) >> 8;
|
|
|
|
bytes[i - 1] = (bytes[i - 1] * alpha + b0 * alpha_) >> 8;
|
2014-01-24 02:13:32 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
|
|
|
}
|
2014-01-24 02:13:32 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
function composeSMaskAlpha(maskData, layerData, transferMap) {
|
|
|
|
const length = maskData.length;
|
|
|
|
const scale = 1 / 255;
|
|
|
|
for (let i = 3; i < length; i += 4) {
|
|
|
|
const alpha = transferMap ? transferMap[maskData[i]] : maskData[i];
|
|
|
|
layerData[i] = (layerData[i] * alpha * scale) | 0;
|
|
|
|
}
|
|
|
|
}
|
2014-01-24 02:13:32 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
function composeSMaskLuminosity(maskData, layerData, transferMap) {
|
|
|
|
const length = maskData.length;
|
|
|
|
for (let i = 3; i < length; i += 4) {
|
|
|
|
const y =
|
|
|
|
maskData[i - 3] * 77 + // * 0.3 / 255 * 0x10000
|
|
|
|
maskData[i - 2] * 152 + // * 0.59 ....
|
|
|
|
maskData[i - 1] * 28; // * 0.11 ....
|
|
|
|
layerData[i] = transferMap
|
|
|
|
? (layerData[i] * transferMap[y >> 8]) >> 8
|
|
|
|
: (layerData[i] * y) >> 16;
|
2014-02-13 23:44:58 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2014-02-13 23:44:58 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
function genericComposeSMask(
|
|
|
|
maskCtx,
|
|
|
|
layerCtx,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
subtype,
|
|
|
|
backdrop,
|
2021-10-20 12:14:48 +09:00
|
|
|
transferMap,
|
|
|
|
layerOffsetX,
|
|
|
|
layerOffsetY,
|
|
|
|
maskOffsetX,
|
|
|
|
maskOffsetY
|
2021-07-25 19:55:51 +09:00
|
|
|
) {
|
|
|
|
const hasBackdrop = !!backdrop;
|
|
|
|
const r0 = hasBackdrop ? backdrop[0] : 0;
|
|
|
|
const g0 = hasBackdrop ? backdrop[1] : 0;
|
|
|
|
const b0 = hasBackdrop ? backdrop[2] : 0;
|
|
|
|
|
|
|
|
let composeFn;
|
|
|
|
if (subtype === "Luminosity") {
|
|
|
|
composeFn = composeSMaskLuminosity;
|
|
|
|
} else {
|
|
|
|
composeFn = composeSMaskAlpha;
|
|
|
|
}
|
2014-01-24 02:13:32 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// processing image in chunks to save memory
|
|
|
|
const PIXELS_TO_PROCESS = 1048576;
|
|
|
|
const chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
|
|
|
|
for (let row = 0; row < height; row += chunkSize) {
|
|
|
|
const chunkHeight = Math.min(chunkSize, height - row);
|
2021-10-20 12:14:48 +09:00
|
|
|
const maskData = maskCtx.getImageData(
|
|
|
|
layerOffsetX - maskOffsetX,
|
|
|
|
row + (layerOffsetY - maskOffsetY),
|
|
|
|
width,
|
|
|
|
chunkHeight
|
|
|
|
);
|
|
|
|
const layerData = layerCtx.getImageData(
|
|
|
|
layerOffsetX,
|
|
|
|
row + layerOffsetY,
|
|
|
|
width,
|
|
|
|
chunkHeight
|
|
|
|
);
|
2014-02-13 23:44:58 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (hasBackdrop) {
|
|
|
|
composeSMaskBackdrop(maskData.data, r0, g0, b0);
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
composeFn(maskData.data, layerData.data, transferMap);
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-10-20 12:14:48 +09:00
|
|
|
layerCtx.putImageData(layerData, layerOffsetX, row + layerOffsetY);
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
|
|
|
}
|
2015-11-17 01:50:02 +09:00
|
|
|
|
2021-10-20 12:14:48 +09:00
|
|
|
function composeSMask(ctx, smask, layerCtx, layerBox) {
|
|
|
|
const layerOffsetX = layerBox[0];
|
|
|
|
const layerOffsetY = layerBox[1];
|
|
|
|
const layerWidth = layerBox[2] - layerOffsetX;
|
|
|
|
const layerHeight = layerBox[3] - layerOffsetY;
|
2021-07-25 19:55:51 +09:00
|
|
|
genericComposeSMask(
|
2021-10-20 12:14:48 +09:00
|
|
|
smask.context,
|
2021-07-25 19:55:51 +09:00
|
|
|
layerCtx,
|
2021-10-20 12:14:48 +09:00
|
|
|
layerWidth,
|
|
|
|
layerHeight,
|
2021-07-25 19:55:51 +09:00
|
|
|
smask.subtype,
|
|
|
|
smask.backdrop,
|
2021-10-20 12:14:48 +09:00
|
|
|
smask.transferMap,
|
|
|
|
layerOffsetX,
|
|
|
|
layerOffsetY,
|
|
|
|
smask.offsetX,
|
|
|
|
smask.offsetY
|
2021-07-25 19:55:51 +09:00
|
|
|
);
|
2021-10-20 12:14:48 +09:00
|
|
|
ctx.save();
|
|
|
|
ctx.globalAlpha = 1;
|
|
|
|
ctx.globalCompositeOperation = "source-over";
|
|
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
|
ctx.drawImage(layerCtx.canvas, 0, 0);
|
|
|
|
ctx.restore();
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:31:10 +09:00
|
|
|
function getImageSmoothingEnabled(transform, interpolate) {
|
|
|
|
const scale = Util.singularValueDecompose2dScale(transform);
|
|
|
|
// Round to a 32bit float so that `<=` check below will pass for numbers that
|
|
|
|
// are very close, but not exactly the same 64bit floats.
|
|
|
|
scale[0] = Math.fround(scale[0]);
|
|
|
|
scale[1] = Math.fround(scale[1]);
|
|
|
|
const actualScale = Math.fround(
|
2021-09-20 17:10:57 +09:00
|
|
|
(globalThis.devicePixelRatio || 1) * PixelsPerInch.PDF_TO_CSS_UNITS
|
2021-09-09 09:31:10 +09:00
|
|
|
);
|
|
|
|
if (interpolate !== undefined) {
|
|
|
|
// If the value is explicitly set use it.
|
|
|
|
return interpolate;
|
|
|
|
} else if (scale[0] <= actualScale || scale[1] <= actualScale) {
|
|
|
|
// Smooth when downscaling.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// Don't smooth when upscaling.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const LINE_CAP_STYLES = ["butt", "round", "square"];
|
|
|
|
const LINE_JOIN_STYLES = ["miter", "round", "bevel"];
|
|
|
|
const NORMAL_CLIP = {};
|
|
|
|
const EO_CLIP = {};
|
|
|
|
|
|
|
|
class CanvasGraphics {
|
|
|
|
constructor(
|
|
|
|
canvasCtx,
|
|
|
|
commonObjs,
|
|
|
|
objs,
|
|
|
|
canvasFactory,
|
|
|
|
imageLayer,
|
|
|
|
optionalContentConfig
|
|
|
|
) {
|
|
|
|
this.ctx = canvasCtx;
|
|
|
|
this.current = new CanvasExtraState();
|
|
|
|
this.stateStack = [];
|
|
|
|
this.pendingClip = null;
|
|
|
|
this.pendingEOFill = false;
|
|
|
|
this.res = null;
|
|
|
|
this.xobjs = null;
|
|
|
|
this.commonObjs = commonObjs;
|
|
|
|
this.objs = objs;
|
|
|
|
this.canvasFactory = canvasFactory;
|
|
|
|
this.imageLayer = imageLayer;
|
|
|
|
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;
|
|
|
|
this.baseTransformStack = [];
|
|
|
|
this.groupLevel = 0;
|
|
|
|
this.smaskStack = [];
|
|
|
|
this.smaskCounter = 0;
|
|
|
|
this.tempSMask = null;
|
2021-10-20 12:14:48 +09:00
|
|
|
this.suspendedCtx = null;
|
2021-07-25 19:55:51 +09:00
|
|
|
this.contentVisible = true;
|
|
|
|
this.markedContentStack = [];
|
|
|
|
this.optionalContentConfig = optionalContentConfig;
|
|
|
|
this.cachedCanvases = new CachedCanvases(this.canvasFactory);
|
2021-07-28 11:58:06 +09:00
|
|
|
this.cachedCanvasPatterns = new LRUCache(MAX_CACHED_CANVAS_PATTERNS);
|
2021-07-25 19:55:51 +09:00
|
|
|
this.cachedPatterns = new Map();
|
|
|
|
if (canvasCtx) {
|
|
|
|
// NOTE: if mozCurrentTransform is polyfilled, then the current state of
|
|
|
|
// the transformation must already be set in canvasCtx._transformMatrix.
|
|
|
|
addContextCurrentTransform(canvasCtx);
|
|
|
|
}
|
|
|
|
this._cachedGetSinglePixelWidth = null;
|
|
|
|
}
|
2011-12-19 03:53:30 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
beginDrawing({
|
|
|
|
transform,
|
|
|
|
viewport,
|
|
|
|
transparency = false,
|
|
|
|
background = null,
|
|
|
|
}) {
|
|
|
|
// For pdfs that use blend modes we have to clear the canvas else certain
|
|
|
|
// blend modes can look wrong since we'd be blending with a white
|
|
|
|
// backdrop. The problem with a transparent backdrop though is we then
|
|
|
|
// don't get sub pixel anti aliasing on text, creating temporary
|
|
|
|
// transparent canvas when we have blend modes.
|
|
|
|
const width = this.ctx.canvas.width;
|
|
|
|
const height = this.ctx.canvas.height;
|
|
|
|
|
|
|
|
this.ctx.save();
|
|
|
|
this.ctx.fillStyle = background || "rgb(255, 255, 255)";
|
|
|
|
this.ctx.fillRect(0, 0, width, height);
|
|
|
|
this.ctx.restore();
|
|
|
|
|
|
|
|
if (transparency) {
|
|
|
|
const transparentCanvas = this.cachedCanvases.getCanvas(
|
|
|
|
"transparent",
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
true
|
2021-01-16 22:05:37 +09:00
|
|
|
);
|
2021-07-25 19:55:51 +09:00
|
|
|
this.compositeCtx = this.ctx;
|
|
|
|
this.transparentCanvas = transparentCanvas.canvas;
|
|
|
|
this.ctx = transparentCanvas.context;
|
|
|
|
this.ctx.save();
|
|
|
|
// The transform can be applied before rendering, transferring it to
|
|
|
|
// the new canvas.
|
|
|
|
this.ctx.transform.apply(this.ctx, this.compositeCtx.mozCurrentTransform);
|
|
|
|
}
|
2014-01-27 22:17:14 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.ctx.save();
|
|
|
|
resetCtxToDefault(this.ctx);
|
|
|
|
if (transform) {
|
|
|
|
this.ctx.transform.apply(this.ctx, transform);
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
this.ctx.transform.apply(this.ctx, viewport.transform);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.baseTransform = this.ctx.mozCurrentTransform.slice();
|
|
|
|
this._combinedScaleFactor = Math.hypot(
|
|
|
|
this.baseTransform[0],
|
|
|
|
this.baseTransform[2]
|
|
|
|
);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (this.imageLayer) {
|
|
|
|
this.imageLayer.beginLayout();
|
|
|
|
}
|
|
|
|
}
|
2011-11-28 04:54:25 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
executeOperatorList(
|
|
|
|
operatorList,
|
|
|
|
executionStartIdx,
|
|
|
|
continueCallback,
|
|
|
|
stepper
|
|
|
|
) {
|
|
|
|
const argsArray = operatorList.argsArray;
|
|
|
|
const fnArray = operatorList.fnArray;
|
|
|
|
let i = executionStartIdx || 0;
|
|
|
|
const argsArrayLen = argsArray.length;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Sometimes the OperatorList to execute is empty.
|
|
|
|
if (argsArrayLen === i) {
|
|
|
|
return i;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const chunkOperations =
|
|
|
|
argsArrayLen - i > EXECUTION_STEPS &&
|
|
|
|
typeof continueCallback === "function";
|
|
|
|
const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
|
|
|
|
let steps = 0;
|
2012-02-14 10:35:58 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const commonObjs = this.commonObjs;
|
|
|
|
const objs = this.objs;
|
|
|
|
let fnId;
|
2011-12-01 08:02:30 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
while (true) {
|
|
|
|
if (stepper !== undefined && i === stepper.nextBreakPoint) {
|
|
|
|
stepper.breakIt(i, continueCallback);
|
|
|
|
return i;
|
|
|
|
}
|
2011-12-01 08:02:30 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
fnId = fnArray[i];
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (fnId !== OPS.dependency) {
|
|
|
|
this[fnId].apply(this, argsArray[i]);
|
|
|
|
} else {
|
|
|
|
for (const depObjId of argsArray[i]) {
|
|
|
|
const objsPool = depObjId.startsWith("g_") ? commonObjs : objs;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// If the promise isn't resolved yet, add the continueCallback
|
|
|
|
// to the promise and bail out.
|
|
|
|
if (!objsPool.has(depObjId)) {
|
|
|
|
objsPool.get(depObjId, continueCallback);
|
2014-06-02 21:40:33 +09:00
|
|
|
return i;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
i++;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// If the entire operatorList was executed, stop as were done.
|
|
|
|
if (i === argsArrayLen) {
|
|
|
|
return i;
|
2011-11-28 04:54:25 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// If the execution took longer then a certain amount of time and
|
|
|
|
// `continueCallback` is specified, interrupt the execution.
|
|
|
|
if (chunkOperations && ++steps > EXECUTION_STEPS) {
|
|
|
|
if (Date.now() > endTime) {
|
|
|
|
continueCallback();
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
steps = 0;
|
2016-04-10 08:50:11 +09:00
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// If the operatorList isn't executed completely yet OR the execution
|
|
|
|
// time was short enough, do another execution round.
|
|
|
|
}
|
|
|
|
}
|
2015-11-17 01:50:02 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
endDrawing() {
|
|
|
|
// Finishing all opened operations such as SMask group painting.
|
|
|
|
while (this.stateStack.length || this.current.activeSMask !== null) {
|
|
|
|
this.restore();
|
|
|
|
}
|
2015-11-17 01:50:02 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.ctx.restore();
|
2011-10-29 06:37:55 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (this.transparentCanvas) {
|
|
|
|
this.ctx = this.compositeCtx;
|
|
|
|
this.ctx.save();
|
|
|
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0); // Avoid apply transform twice
|
|
|
|
this.ctx.drawImage(this.transparentCanvas, 0, 0);
|
|
|
|
this.ctx.restore();
|
|
|
|
this.transparentCanvas = null;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.cachedCanvases.clear();
|
2021-07-28 11:58:06 +09:00
|
|
|
this.cachedCanvasPatterns.clear();
|
2021-07-25 19:55:51 +09:00
|
|
|
this.cachedPatterns.clear();
|
2021-07-01 07:09:07 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (this.imageLayer) {
|
|
|
|
this.imageLayer.endLayout();
|
2021-07-01 07:09:07 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2021-07-01 07:09:07 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
_scaleImage(img, inverseTransform) {
|
|
|
|
// Vertical or horizontal scaling shall not be more than 2 to not lose the
|
|
|
|
// pixels during drawImage operation, painting on the temporary canvas(es)
|
|
|
|
// that are twice smaller in size.
|
|
|
|
const width = img.width;
|
|
|
|
const height = img.height;
|
|
|
|
let widthScale = Math.max(
|
|
|
|
Math.hypot(inverseTransform[0], inverseTransform[1]),
|
|
|
|
1
|
|
|
|
);
|
|
|
|
let heightScale = Math.max(
|
|
|
|
Math.hypot(inverseTransform[2], inverseTransform[3]),
|
|
|
|
1
|
|
|
|
);
|
|
|
|
|
|
|
|
let paintWidth = width,
|
|
|
|
paintHeight = height;
|
|
|
|
let tmpCanvasId = "prescale1";
|
|
|
|
let tmpCanvas, tmpCtx;
|
|
|
|
while (
|
|
|
|
(widthScale > 2 && paintWidth > 1) ||
|
|
|
|
(heightScale > 2 && paintHeight > 1)
|
|
|
|
) {
|
|
|
|
let newWidth = paintWidth,
|
|
|
|
newHeight = paintHeight;
|
|
|
|
if (widthScale > 2 && paintWidth > 1) {
|
|
|
|
newWidth = Math.ceil(paintWidth / 2);
|
|
|
|
widthScale /= paintWidth / newWidth;
|
|
|
|
}
|
|
|
|
if (heightScale > 2 && paintHeight > 1) {
|
|
|
|
newHeight = Math.ceil(paintHeight / 2);
|
|
|
|
heightScale /= paintHeight / newHeight;
|
|
|
|
}
|
|
|
|
tmpCanvas = this.cachedCanvases.getCanvas(
|
|
|
|
tmpCanvasId,
|
|
|
|
newWidth,
|
|
|
|
newHeight
|
2021-07-01 07:09:07 +09:00
|
|
|
);
|
2021-07-25 19:55:51 +09:00
|
|
|
tmpCtx = tmpCanvas.context;
|
|
|
|
tmpCtx.clearRect(0, 0, newWidth, newHeight);
|
|
|
|
tmpCtx.drawImage(
|
|
|
|
img,
|
2021-07-01 07:09:07 +09:00
|
|
|
0,
|
|
|
|
0,
|
2021-07-25 19:55:51 +09:00
|
|
|
paintWidth,
|
|
|
|
paintHeight,
|
2021-07-01 07:09:07 +09:00
|
|
|
0,
|
|
|
|
0,
|
2021-07-25 19:55:51 +09:00
|
|
|
newWidth,
|
|
|
|
newHeight
|
2021-07-01 07:09:07 +09:00
|
|
|
);
|
2021-07-25 19:55:51 +09:00
|
|
|
img = tmpCanvas.canvas;
|
|
|
|
paintWidth = newWidth;
|
|
|
|
paintHeight = newHeight;
|
|
|
|
tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1";
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
img,
|
|
|
|
paintWidth,
|
|
|
|
paintHeight,
|
|
|
|
};
|
|
|
|
}
|
2021-07-01 07:09:07 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
_createMaskCanvas(img) {
|
|
|
|
const ctx = this.ctx;
|
|
|
|
const width = img.width,
|
|
|
|
height = img.height;
|
|
|
|
const fillColor = this.current.fillColor;
|
|
|
|
const isPatternFill = this.current.patternFill;
|
|
|
|
const maskCanvas = this.cachedCanvases.getCanvas(
|
|
|
|
"maskCanvas",
|
|
|
|
width,
|
|
|
|
height
|
|
|
|
);
|
|
|
|
const maskCtx = maskCanvas.context;
|
|
|
|
putBinaryImageMask(maskCtx, img);
|
|
|
|
|
|
|
|
// Create the mask canvas at the size it will be drawn at and also set
|
|
|
|
// its transform to match the current transform so if there are any
|
|
|
|
// patterns applied they will be applied relative to the correct
|
|
|
|
// transform.
|
|
|
|
const objToCanvas = ctx.mozCurrentTransform;
|
|
|
|
let maskToCanvas = Util.transform(objToCanvas, [
|
|
|
|
1 / width,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
-1 / height,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
]);
|
|
|
|
maskToCanvas = Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]);
|
|
|
|
const cord1 = Util.applyTransform([0, 0], maskToCanvas);
|
|
|
|
const cord2 = Util.applyTransform([width, height], maskToCanvas);
|
|
|
|
const rect = Util.normalizeRect([cord1[0], cord1[1], cord2[0], cord2[1]]);
|
|
|
|
const drawnWidth = Math.ceil(rect[2] - rect[0]);
|
|
|
|
const drawnHeight = Math.ceil(rect[3] - rect[1]);
|
|
|
|
const fillCanvas = this.cachedCanvases.getCanvas(
|
|
|
|
"fillCanvas",
|
|
|
|
drawnWidth,
|
|
|
|
drawnHeight,
|
|
|
|
true
|
|
|
|
);
|
|
|
|
const fillCtx = fillCanvas.context;
|
|
|
|
// The offset will be the top-left cordinate mask.
|
|
|
|
const offsetX = Math.min(cord1[0], cord2[0]);
|
|
|
|
const offsetY = Math.min(cord1[1], cord2[1]);
|
|
|
|
fillCtx.translate(-offsetX, -offsetY);
|
|
|
|
fillCtx.transform.apply(fillCtx, maskToCanvas);
|
|
|
|
// Pre-scale if needed to improve image smoothing.
|
|
|
|
const scaled = this._scaleImage(
|
|
|
|
maskCanvas.canvas,
|
|
|
|
fillCtx.mozCurrentTransformInverse
|
|
|
|
);
|
2021-09-09 09:31:10 +09:00
|
|
|
fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(
|
|
|
|
fillCtx.mozCurrentTransform,
|
|
|
|
img.interpolate
|
|
|
|
);
|
2021-07-25 19:55:51 +09:00
|
|
|
fillCtx.drawImage(
|
|
|
|
scaled.img,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
scaled.img.width,
|
|
|
|
scaled.img.height,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
width,
|
|
|
|
height
|
|
|
|
);
|
|
|
|
fillCtx.globalCompositeOperation = "source-in";
|
2021-07-01 07:09:07 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const inverse = Util.transform(fillCtx.mozCurrentTransformInverse, [
|
|
|
|
1,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
-offsetX,
|
|
|
|
-offsetY,
|
|
|
|
]);
|
|
|
|
fillCtx.fillStyle = isPatternFill
|
|
|
|
? fillColor.getPattern(ctx, this, inverse, false)
|
|
|
|
: fillColor;
|
|
|
|
|
|
|
|
fillCtx.fillRect(0, 0, width, height);
|
|
|
|
|
|
|
|
// Round the offsets to avoid drawing fractional pixels.
|
|
|
|
return {
|
|
|
|
canvas: fillCanvas.canvas,
|
|
|
|
offsetX: Math.round(offsetX),
|
|
|
|
offsetY: Math.round(offsetY),
|
|
|
|
};
|
|
|
|
}
|
2021-07-01 07:09:07 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Graphics state
|
|
|
|
setLineWidth(width) {
|
|
|
|
this.current.lineWidth = width;
|
|
|
|
this.ctx.lineWidth = width;
|
|
|
|
}
|
2021-07-01 07:09:07 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setLineCap(style) {
|
|
|
|
this.ctx.lineCap = LINE_CAP_STYLES[style];
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setLineJoin(style) {
|
|
|
|
this.ctx.lineJoin = LINE_JOIN_STYLES[style];
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setMiterLimit(limit) {
|
|
|
|
this.ctx.miterLimit = limit;
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setDash(dashArray, dashPhase) {
|
|
|
|
const ctx = this.ctx;
|
|
|
|
if (ctx.setLineDash !== undefined) {
|
|
|
|
ctx.setLineDash(dashArray);
|
|
|
|
ctx.lineDashOffset = dashPhase;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setRenderingIntent(intent) {
|
|
|
|
// This operation is ignored since we haven't found a use case for it yet.
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setFlatness(flatness) {
|
|
|
|
// This operation is ignored since we haven't found a use case for it yet.
|
|
|
|
}
|
|
|
|
|
|
|
|
setGState(states) {
|
|
|
|
for (let i = 0, ii = states.length; i < ii; i++) {
|
|
|
|
const state = states[i];
|
|
|
|
const key = state[0];
|
|
|
|
const value = state[1];
|
|
|
|
|
|
|
|
switch (key) {
|
|
|
|
case "LW":
|
|
|
|
this.setLineWidth(value);
|
|
|
|
break;
|
|
|
|
case "LC":
|
|
|
|
this.setLineCap(value);
|
|
|
|
break;
|
|
|
|
case "LJ":
|
|
|
|
this.setLineJoin(value);
|
|
|
|
break;
|
|
|
|
case "ML":
|
|
|
|
this.setMiterLimit(value);
|
|
|
|
break;
|
|
|
|
case "D":
|
|
|
|
this.setDash(value[0], value[1]);
|
|
|
|
break;
|
|
|
|
case "RI":
|
|
|
|
this.setRenderingIntent(value);
|
|
|
|
break;
|
|
|
|
case "FL":
|
|
|
|
this.setFlatness(value);
|
|
|
|
break;
|
|
|
|
case "Font":
|
|
|
|
this.setFont(value[0], value[1]);
|
|
|
|
break;
|
|
|
|
case "CA":
|
|
|
|
this.current.strokeAlpha = state[1];
|
|
|
|
break;
|
|
|
|
case "ca":
|
|
|
|
this.current.fillAlpha = state[1];
|
|
|
|
this.ctx.globalAlpha = state[1];
|
|
|
|
break;
|
|
|
|
case "BM":
|
|
|
|
this.ctx.globalCompositeOperation = value;
|
|
|
|
break;
|
|
|
|
case "SMask":
|
|
|
|
this.current.activeSMask = value ? this.tempSMask : null;
|
|
|
|
this.tempSMask = null;
|
2021-10-20 12:14:48 +09:00
|
|
|
this.checkSMaskState();
|
2021-07-25 19:55:51 +09:00
|
|
|
break;
|
|
|
|
case "TR":
|
|
|
|
this.current.transferMaps = value;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-10-20 12:14:48 +09:00
|
|
|
checkSMaskState() {
|
|
|
|
const inSMaskMode = !!this.suspendedCtx;
|
|
|
|
if (this.current.activeSMask && !inSMaskMode) {
|
|
|
|
this.beginSMaskMode();
|
|
|
|
} else if (!this.current.activeSMask && inSMaskMode) {
|
|
|
|
this.endSMaskMode();
|
|
|
|
}
|
|
|
|
// Else, the state is okay and nothing needs to be done.
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Soft mask mode takes the current main drawing canvas and replaces it with
|
|
|
|
* a temporary canvas. Any drawing operations that happen on the temporary
|
|
|
|
* canvas need to be composed with the main canvas that was suspended (see
|
|
|
|
* `compose()`). The temporary canvas also duplicates many of its operations
|
|
|
|
* on the suspended canvas to keep them in sync, so that when the soft mask
|
|
|
|
* mode ends any clipping paths or transformations will still be active and in
|
|
|
|
* the right order on the canvas' graphics state stack.
|
|
|
|
*/
|
|
|
|
beginSMaskMode() {
|
|
|
|
if (this.suspendedCtx) {
|
|
|
|
throw new Error("beginSMaskMode called while already in smask mode");
|
|
|
|
}
|
|
|
|
const drawnWidth = this.ctx.canvas.width;
|
|
|
|
const drawnHeight = this.ctx.canvas.height;
|
2021-07-25 19:55:51 +09:00
|
|
|
const cacheId = "smaskGroupAt" + this.groupLevel;
|
|
|
|
const scratchCanvas = this.cachedCanvases.getCanvas(
|
|
|
|
cacheId,
|
|
|
|
drawnWidth,
|
|
|
|
drawnHeight,
|
|
|
|
true
|
|
|
|
);
|
2021-10-20 12:14:48 +09:00
|
|
|
this.suspendedCtx = this.ctx;
|
|
|
|
this.ctx = scratchCanvas.context;
|
|
|
|
const ctx = this.ctx;
|
|
|
|
ctx.setTransform.apply(ctx, this.suspendedCtx.mozCurrentTransform);
|
|
|
|
copyCtxState(this.suspendedCtx, ctx);
|
|
|
|
mirrorContextOperations(ctx, this.suspendedCtx);
|
2014-01-24 02:13:32 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.setGState([
|
|
|
|
["BM", "source-over"],
|
|
|
|
["ca", 1],
|
|
|
|
["CA", 1],
|
|
|
|
]);
|
|
|
|
}
|
2014-01-24 02:13:32 +09:00
|
|
|
|
2021-10-20 12:14:48 +09:00
|
|
|
endSMaskMode() {
|
|
|
|
if (!this.suspendedCtx) {
|
|
|
|
throw new Error("endSMaskMode called while not in smask mode");
|
|
|
|
}
|
|
|
|
// The soft mask is done, now restore the suspended canvas as the main
|
|
|
|
// drawing canvas.
|
|
|
|
this.ctx._removeMirroring();
|
|
|
|
copyCtxState(this.ctx, this.suspendedCtx);
|
|
|
|
this.ctx = this.suspendedCtx;
|
2016-04-10 08:46:15 +09:00
|
|
|
|
2021-10-20 12:14:48 +09:00
|
|
|
this.current.activeSMask = null;
|
|
|
|
this.suspendedCtx = null;
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-10-20 12:14:48 +09:00
|
|
|
compose(dirtyBox) {
|
|
|
|
if (!this.current.activeSMask) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!dirtyBox) {
|
|
|
|
dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height];
|
|
|
|
}
|
|
|
|
const smask = this.current.activeSMask;
|
|
|
|
const suspendedCtx = this.suspendedCtx;
|
2021-07-25 19:55:51 +09:00
|
|
|
|
2021-10-20 12:14:48 +09:00
|
|
|
composeSMask(suspendedCtx, smask, this.ctx, dirtyBox);
|
|
|
|
// Whatever was drawn has been moved to the suspended canvas, now clear it
|
|
|
|
// out of the current canvas.
|
|
|
|
this.ctx.save();
|
|
|
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
|
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
2021-07-25 19:55:51 +09:00
|
|
|
this.ctx.restore();
|
|
|
|
}
|
2016-04-10 08:46:15 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
save() {
|
|
|
|
this.ctx.save();
|
|
|
|
const old = this.current;
|
|
|
|
this.stateStack.push(old);
|
|
|
|
this.current = old.clone();
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
restore() {
|
2021-10-20 12:14:48 +09:00
|
|
|
if (this.stateStack.length === 0 && this.current.activeSMask) {
|
|
|
|
this.endSMaskMode();
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (this.stateStack.length !== 0) {
|
|
|
|
this.current = this.stateStack.pop();
|
2014-01-24 02:13:32 +09:00
|
|
|
this.ctx.restore();
|
2021-10-20 12:14:48 +09:00
|
|
|
this.checkSMaskState();
|
2015-09-04 00:35:32 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Ensure that the clipping path is reset (fixes issue6413.pdf).
|
|
|
|
this.pendingClip = null;
|
2014-04-12 03:19:39 +09:00
|
|
|
|
2018-09-03 03:31:24 +09:00
|
|
|
this._cachedGetSinglePixelWidth = null;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
transform(a, b, c, d, e, f) {
|
|
|
|
this.ctx.transform(a, b, c, d, e, f);
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this._cachedGetSinglePixelWidth = null;
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Path
|
|
|
|
constructPath(ops, args) {
|
|
|
|
const ctx = this.ctx;
|
|
|
|
const current = this.current;
|
|
|
|
let x = current.x,
|
|
|
|
y = current.y;
|
|
|
|
for (let i = 0, j = 0, ii = ops.length; i < ii; i++) {
|
|
|
|
switch (ops[i] | 0) {
|
|
|
|
case OPS.rectangle:
|
|
|
|
x = args[j++];
|
|
|
|
y = args[j++];
|
|
|
|
const width = args[j++];
|
|
|
|
const height = args[j++];
|
|
|
|
|
|
|
|
const xw = x + width;
|
|
|
|
const yh = y + height;
|
|
|
|
ctx.moveTo(x, y);
|
|
|
|
if (width === 0 || height === 0) {
|
|
|
|
ctx.lineTo(xw, yh);
|
2021-01-04 22:25:30 +09:00
|
|
|
} else {
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.lineTo(xw, y);
|
|
|
|
ctx.lineTo(xw, yh);
|
|
|
|
ctx.lineTo(x, yh);
|
2021-01-04 22:25:30 +09:00
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.closePath();
|
|
|
|
break;
|
|
|
|
case OPS.moveTo:
|
|
|
|
x = args[j++];
|
|
|
|
y = args[j++];
|
|
|
|
ctx.moveTo(x, y);
|
|
|
|
break;
|
|
|
|
case OPS.lineTo:
|
|
|
|
x = args[j++];
|
|
|
|
y = args[j++];
|
|
|
|
ctx.lineTo(x, y);
|
|
|
|
break;
|
|
|
|
case OPS.curveTo:
|
|
|
|
x = args[j + 4];
|
|
|
|
y = args[j + 5];
|
|
|
|
ctx.bezierCurveTo(
|
|
|
|
args[j],
|
|
|
|
args[j + 1],
|
|
|
|
args[j + 2],
|
|
|
|
args[j + 3],
|
|
|
|
x,
|
|
|
|
y
|
|
|
|
);
|
|
|
|
j += 6;
|
|
|
|
break;
|
|
|
|
case OPS.curveTo2:
|
|
|
|
ctx.bezierCurveTo(
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
args[j],
|
|
|
|
args[j + 1],
|
|
|
|
args[j + 2],
|
|
|
|
args[j + 3]
|
|
|
|
);
|
|
|
|
x = args[j + 2];
|
|
|
|
y = args[j + 3];
|
|
|
|
j += 4;
|
|
|
|
break;
|
|
|
|
case OPS.curveTo3:
|
|
|
|
x = args[j + 2];
|
|
|
|
y = args[j + 3];
|
|
|
|
ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
|
|
|
|
j += 4;
|
|
|
|
break;
|
|
|
|
case OPS.closePath:
|
|
|
|
ctx.closePath();
|
|
|
|
break;
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
current.setCurrentPoint(x, y);
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
closePath() {
|
|
|
|
this.ctx.closePath();
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
stroke(consumePath) {
|
|
|
|
consumePath = typeof consumePath !== "undefined" ? consumePath : true;
|
|
|
|
const ctx = this.ctx;
|
|
|
|
const strokeColor = this.current.strokeColor;
|
|
|
|
// For stroke we want to temporarily change the global alpha to the
|
|
|
|
// stroking alpha.
|
|
|
|
ctx.globalAlpha = this.current.strokeAlpha;
|
|
|
|
if (this.contentVisible) {
|
|
|
|
if (typeof strokeColor === "object" && strokeColor?.getPattern) {
|
|
|
|
const lineWidth = this.getSinglePixelWidth();
|
2011-10-25 08:55:23 +09:00
|
|
|
ctx.save();
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.strokeStyle = strokeColor.getPattern(
|
2021-07-01 07:09:07 +09:00
|
|
|
ctx,
|
|
|
|
this,
|
|
|
|
ctx.mozCurrentTransformInverse
|
|
|
|
);
|
2021-07-25 19:55:51 +09:00
|
|
|
// Prevent drawing too thin lines by enforcing a minimum line width.
|
|
|
|
ctx.lineWidth = Math.max(lineWidth, this.current.lineWidth);
|
|
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
|
|
} else {
|
|
|
|
const lineWidth = this.getSinglePixelWidth();
|
|
|
|
if (lineWidth < 0 && -lineWidth >= this.current.lineWidth) {
|
|
|
|
// The current transform will transform a square pixel into a
|
|
|
|
// parallelogram where both heights are lower than 1 and not equal.
|
|
|
|
ctx.save();
|
|
|
|
ctx.resetTransform();
|
|
|
|
ctx.lineWidth = Math.round(this._combinedScaleFactor);
|
|
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
2020-07-15 07:17:27 +09:00
|
|
|
} else {
|
2021-07-25 19:55:51 +09:00
|
|
|
// Prevent drawing too thin lines by enforcing a minimum line width.
|
|
|
|
ctx.lineWidth = Math.max(lineWidth, this.current.lineWidth);
|
|
|
|
ctx.stroke();
|
2020-07-15 07:17:27 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
if (consumePath) {
|
|
|
|
this.consumePath();
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
// Restore the global alpha to the fill alpha
|
|
|
|
ctx.globalAlpha = this.current.fillAlpha;
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
closeStroke() {
|
|
|
|
this.closePath();
|
|
|
|
this.stroke();
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
fill(consumePath) {
|
|
|
|
consumePath = typeof consumePath !== "undefined" ? consumePath : true;
|
|
|
|
const ctx = this.ctx;
|
|
|
|
const fillColor = this.current.fillColor;
|
|
|
|
const isPatternFill = this.current.patternFill;
|
|
|
|
let needRestore = false;
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (isPatternFill) {
|
|
|
|
ctx.save();
|
|
|
|
ctx.fillStyle = fillColor.getPattern(
|
|
|
|
ctx,
|
|
|
|
this,
|
|
|
|
ctx.mozCurrentTransformInverse
|
|
|
|
);
|
|
|
|
needRestore = true;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (this.contentVisible) {
|
|
|
|
if (this.pendingEOFill) {
|
|
|
|
ctx.fill("evenodd");
|
|
|
|
this.pendingEOFill = false;
|
|
|
|
} else {
|
|
|
|
ctx.fill();
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (needRestore) {
|
|
|
|
ctx.restore();
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
if (consumePath) {
|
2011-10-25 08:55:23 +09:00
|
|
|
this.consumePath();
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
eoFill() {
|
|
|
|
this.pendingEOFill = true;
|
|
|
|
this.fill();
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
fillStroke() {
|
|
|
|
this.fill(false);
|
|
|
|
this.stroke(false);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.consumePath();
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
eoFillStroke() {
|
|
|
|
this.pendingEOFill = true;
|
|
|
|
this.fillStroke();
|
|
|
|
}
|
2012-10-13 12:33:56 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
closeFillStroke() {
|
|
|
|
this.closePath();
|
|
|
|
this.fillStroke();
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
closeEOFillStroke() {
|
|
|
|
this.pendingEOFill = true;
|
|
|
|
this.closePath();
|
|
|
|
this.fillStroke();
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
endPath() {
|
|
|
|
this.consumePath();
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Clipping
|
|
|
|
clip() {
|
|
|
|
this.pendingClip = NORMAL_CLIP;
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
eoClip() {
|
|
|
|
this.pendingClip = EO_CLIP;
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Text
|
|
|
|
beginText() {
|
|
|
|
this.current.textMatrix = IDENTITY_MATRIX;
|
|
|
|
this.current.textMatrixScale = 1;
|
|
|
|
this.current.x = this.current.lineX = 0;
|
|
|
|
this.current.y = this.current.lineY = 0;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
endText() {
|
|
|
|
const paths = this.pendingTextPaths;
|
|
|
|
const ctx = this.ctx;
|
|
|
|
if (paths === undefined) {
|
|
|
|
ctx.beginPath();
|
|
|
|
return;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.save();
|
|
|
|
ctx.beginPath();
|
|
|
|
for (let i = 0; i < paths.length; i++) {
|
|
|
|
const path = paths[i];
|
|
|
|
ctx.setTransform.apply(ctx, path.transform);
|
|
|
|
ctx.translate(path.x, path.y);
|
|
|
|
path.addToPath(ctx, path.fontSize);
|
|
|
|
}
|
|
|
|
ctx.restore();
|
|
|
|
ctx.clip();
|
|
|
|
ctx.beginPath();
|
|
|
|
delete this.pendingTextPaths;
|
|
|
|
}
|
2012-01-21 04:55:52 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setCharSpacing(spacing) {
|
|
|
|
this.current.charSpacing = spacing;
|
|
|
|
}
|
2012-01-21 08:41:01 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setWordSpacing(spacing) {
|
|
|
|
this.current.wordSpacing = spacing;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setHScale(scale) {
|
|
|
|
this.current.textHScale = scale / 100;
|
|
|
|
}
|
2012-02-05 03:45:18 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setLeading(leading) {
|
|
|
|
this.current.leading = -leading;
|
|
|
|
}
|
2020-01-13 03:47:13 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setFont(fontRefName, size) {
|
|
|
|
const fontObj = this.commonObjs.get(fontRefName);
|
|
|
|
const current = this.current;
|
2012-02-05 03:45:18 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (!fontObj) {
|
|
|
|
throw new Error(`Can't find font for ${fontRefName}`);
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
current.fontMatrix = fontObj.fontMatrix || FONT_IDENTITY_MATRIX;
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// A valid matrix needs all main diagonal elements to be non-zero
|
|
|
|
// This also ensures we bypass FF bugzilla bug #719844.
|
|
|
|
if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) {
|
|
|
|
warn("Invalid font matrix for font " + fontRefName);
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// The spec for Tf (setFont) says that 'size' specifies the font 'scale',
|
|
|
|
// and in some docs this can be negative (inverted x-y axes).
|
|
|
|
if (size < 0) {
|
|
|
|
size = -size;
|
|
|
|
current.fontDirection = -1;
|
|
|
|
} else {
|
|
|
|
current.fontDirection = 1;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.current.font = fontObj;
|
|
|
|
this.current.fontSize = size;
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (fontObj.isType3Font) {
|
|
|
|
return; // we don't need ctx.font for Type3 fonts
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const name = fontObj.loadedName || "sans-serif";
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
let bold = "normal";
|
|
|
|
if (fontObj.black) {
|
|
|
|
bold = "900";
|
|
|
|
} else if (fontObj.bold) {
|
|
|
|
bold = "bold";
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
const italic = fontObj.italic ? "italic" : "normal";
|
|
|
|
const typeface = `"${name}", ${fontObj.fallbackName}`;
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Some font backends cannot handle fonts below certain size.
|
|
|
|
// Keeping the font at minimal size and using the fontSizeScale to change
|
|
|
|
// the current transformation matrix before the fillText/strokeText.
|
|
|
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=726227
|
|
|
|
let browserFontSize = size;
|
|
|
|
if (size < MIN_FONT_SIZE) {
|
|
|
|
browserFontSize = MIN_FONT_SIZE;
|
|
|
|
} else if (size > MAX_FONT_SIZE) {
|
|
|
|
browserFontSize = MAX_FONT_SIZE;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
this.current.fontSizeScale = size / browserFontSize;
|
2011-12-02 04:11:17 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`;
|
|
|
|
}
|
2013-05-16 05:57:27 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setTextRenderingMode(mode) {
|
|
|
|
this.current.textRenderingMode = mode;
|
|
|
|
}
|
2013-05-16 05:57:27 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setTextRise(rise) {
|
|
|
|
this.current.textRise = rise;
|
|
|
|
}
|
2013-05-16 05:57:27 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
moveText(x, y) {
|
|
|
|
this.current.x = this.current.lineX += x;
|
|
|
|
this.current.y = this.current.lineY += y;
|
|
|
|
}
|
|
|
|
|
|
|
|
setLeadingMoveText(x, y) {
|
|
|
|
this.setLeading(-y);
|
|
|
|
this.moveText(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
setTextMatrix(a, b, c, d, e, f) {
|
|
|
|
this.current.textMatrix = [a, b, c, d, e, f];
|
|
|
|
this.current.textMatrixScale = Math.hypot(a, b);
|
|
|
|
|
|
|
|
this.current.x = this.current.lineX = 0;
|
|
|
|
this.current.y = this.current.lineY = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nextLine() {
|
|
|
|
this.moveText(0, this.current.leading);
|
|
|
|
}
|
|
|
|
|
|
|
|
paintChar(character, x, y, patternTransform, resetLineWidthToOne) {
|
|
|
|
const ctx = this.ctx;
|
|
|
|
const current = this.current;
|
|
|
|
const font = current.font;
|
|
|
|
const textRenderingMode = current.textRenderingMode;
|
|
|
|
const fontSize = current.fontSize / current.fontSizeScale;
|
|
|
|
const fillStrokeMode =
|
|
|
|
textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
|
|
|
|
const isAddToPathSet = !!(
|
|
|
|
textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG
|
|
|
|
);
|
|
|
|
const patternFill = current.patternFill && !font.missingFile;
|
|
|
|
|
|
|
|
let addToPath;
|
|
|
|
if (font.disableFontFace || isAddToPathSet || patternFill) {
|
|
|
|
addToPath = font.getPathGenerator(this.commonObjs, character);
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2013-05-16 05:57:27 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (font.disableFontFace || patternFill) {
|
|
|
|
ctx.save();
|
|
|
|
ctx.translate(x, y);
|
|
|
|
ctx.beginPath();
|
|
|
|
addToPath(ctx, fontSize);
|
|
|
|
if (patternTransform) {
|
|
|
|
ctx.setTransform.apply(ctx, patternTransform);
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
fillStrokeMode === TextRenderingMode.FILL ||
|
|
|
|
fillStrokeMode === TextRenderingMode.FILL_STROKE
|
|
|
|
) {
|
|
|
|
ctx.fill();
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
fillStrokeMode === TextRenderingMode.STROKE ||
|
|
|
|
fillStrokeMode === TextRenderingMode.FILL_STROKE
|
|
|
|
) {
|
|
|
|
if (resetLineWidthToOne) {
|
|
|
|
ctx.resetTransform();
|
|
|
|
ctx.lineWidth = Math.round(this._combinedScaleFactor);
|
|
|
|
}
|
|
|
|
ctx.stroke();
|
|
|
|
}
|
|
|
|
ctx.restore();
|
|
|
|
} else {
|
|
|
|
if (
|
|
|
|
fillStrokeMode === TextRenderingMode.FILL ||
|
|
|
|
fillStrokeMode === TextRenderingMode.FILL_STROKE
|
|
|
|
) {
|
|
|
|
ctx.fillText(character, x, y);
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
fillStrokeMode === TextRenderingMode.STROKE ||
|
|
|
|
fillStrokeMode === TextRenderingMode.FILL_STROKE
|
|
|
|
) {
|
|
|
|
if (resetLineWidthToOne) {
|
|
|
|
ctx.save();
|
|
|
|
ctx.moveTo(x, y);
|
|
|
|
ctx.resetTransform();
|
|
|
|
ctx.lineWidth = Math.round(this._combinedScaleFactor);
|
|
|
|
ctx.strokeText(character, 0, 0);
|
|
|
|
ctx.restore();
|
|
|
|
} else {
|
|
|
|
ctx.strokeText(character, x, y);
|
2014-01-15 08:31:23 +09:00
|
|
|
}
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2014-01-15 08:31:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (isAddToPathSet) {
|
|
|
|
const paths = this.pendingTextPaths || (this.pendingTextPaths = []);
|
|
|
|
paths.push({
|
|
|
|
transform: ctx.mozCurrentTransform,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
fontSize,
|
|
|
|
addToPath,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2014-05-24 03:36:54 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
get isFontSubpixelAAEnabled() {
|
|
|
|
// Checks if anti-aliasing is enabled when scaled text is painted.
|
|
|
|
// On Windows GDI scaled fonts looks bad.
|
|
|
|
const { context: ctx } = this.cachedCanvases.getCanvas(
|
|
|
|
"isFontSubpixelAAEnabled",
|
|
|
|
10,
|
|
|
|
10
|
|
|
|
);
|
|
|
|
ctx.scale(1.5, 1);
|
|
|
|
ctx.fillText("I", 0, 10);
|
|
|
|
const data = ctx.getImageData(0, 0, 10, 10).data;
|
|
|
|
let enabled = false;
|
|
|
|
for (let i = 3; i < data.length; i += 4) {
|
|
|
|
if (data[i] > 0 && data[i] < 255) {
|
|
|
|
enabled = true;
|
|
|
|
break;
|
2014-05-24 03:36:54 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
|
|
|
return shadow(this, "isFontSubpixelAAEnabled", enabled);
|
|
|
|
}
|
2014-05-24 03:36:54 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
showText(glyphs) {
|
|
|
|
const current = this.current;
|
|
|
|
const font = current.font;
|
|
|
|
if (font.isType3Font) {
|
|
|
|
return this.showType3Text(glyphs);
|
|
|
|
}
|
2014-03-30 20:36:21 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const fontSize = current.fontSize;
|
|
|
|
if (fontSize === 0) {
|
|
|
|
return undefined;
|
|
|
|
}
|
Apply Patterns, if necessary, when rendering text
Currently we're not applying Patterns for text, but only for graphics.
This patch is unfortunately not a complete solution, but rather a step on the way, since there are still some PDF files where the Patterns look more like a solid colour, rather than the intended gradient.
I've been unable to fix these issues completely, and I've not managed to determine if the remaining issues are caused either by the pattern code, the canvas code, or perhaps both.
However, given that even this simple patch improves the current situation quite a bit, I figured that it couldn't hurt to submit it as-is.
- Fixes 5804.
- Fixes 6130.
- Improves 3988 a lot, since the text is now visible. However, it looks like the text is *one* solid colour, instead of the correct gradient.
- Improves 5432, since the text is no longer gray. (This file also suffers from the same problem as the previous one.)
2015-12-30 01:57:10 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const ctx = this.ctx;
|
|
|
|
const fontSizeScale = current.fontSizeScale;
|
|
|
|
const charSpacing = current.charSpacing;
|
|
|
|
const wordSpacing = current.wordSpacing;
|
|
|
|
const fontDirection = current.fontDirection;
|
|
|
|
const textHScale = current.textHScale * fontDirection;
|
|
|
|
const glyphsLength = glyphs.length;
|
|
|
|
const vertical = font.vertical;
|
|
|
|
const spacingDir = vertical ? 1 : -1;
|
|
|
|
const defaultVMetrics = font.defaultVMetrics;
|
|
|
|
const widthAdvanceScale = fontSize * current.fontMatrix[0];
|
|
|
|
|
|
|
|
const simpleFillText =
|
|
|
|
current.textRenderingMode === TextRenderingMode.FILL &&
|
|
|
|
!font.disableFontFace &&
|
|
|
|
!current.patternFill;
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
let patternTransform;
|
|
|
|
if (current.patternFill) {
|
|
|
|
// TODO: Patterns are not applied correctly to text if a non-embedded
|
|
|
|
// font is used. E.g. issue 8111 and ShowText-ShadingPattern.pdf.
|
|
|
|
ctx.save();
|
|
|
|
const pattern = current.fillColor.getPattern(
|
|
|
|
ctx,
|
|
|
|
this,
|
|
|
|
ctx.mozCurrentTransformInverse
|
|
|
|
);
|
|
|
|
patternTransform = ctx.mozCurrentTransform;
|
|
|
|
ctx.restore();
|
|
|
|
ctx.fillStyle = pattern;
|
|
|
|
}
|
|
|
|
ctx.transform.apply(ctx, current.textMatrix);
|
|
|
|
ctx.translate(current.x, current.y + current.textRise);
|
2011-12-13 12:32:20 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (fontDirection > 0) {
|
|
|
|
ctx.scale(textHScale, -1);
|
|
|
|
} else {
|
|
|
|
ctx.scale(textHScale, 1);
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
let lineWidth = current.lineWidth;
|
|
|
|
let resetLineWidthToOne = false;
|
|
|
|
const scale = current.textMatrixScale;
|
|
|
|
if (scale === 0 || lineWidth === 0) {
|
|
|
|
const fillStrokeMode =
|
|
|
|
current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
|
|
|
|
if (
|
|
|
|
fillStrokeMode === TextRenderingMode.STROKE ||
|
|
|
|
fillStrokeMode === TextRenderingMode.FILL_STROKE
|
|
|
|
) {
|
|
|
|
this._cachedGetSinglePixelWidth = null;
|
|
|
|
lineWidth = this.getSinglePixelWidth();
|
|
|
|
resetLineWidthToOne = lineWidth < 0;
|
2014-05-24 03:36:54 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
} else {
|
|
|
|
lineWidth /= scale;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (fontSizeScale !== 1.0) {
|
|
|
|
ctx.scale(fontSizeScale, fontSizeScale);
|
|
|
|
lineWidth /= fontSizeScale;
|
|
|
|
}
|
2014-05-24 03:36:54 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.lineWidth = lineWidth;
|
2012-01-18 13:50:49 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
let x = 0,
|
|
|
|
i;
|
|
|
|
for (i = 0; i < glyphsLength; ++i) {
|
|
|
|
const glyph = glyphs[i];
|
|
|
|
if (isNum(glyph)) {
|
|
|
|
x += (spacingDir * glyph * fontSize) / 1000;
|
|
|
|
continue;
|
|
|
|
}
|
2012-01-18 13:50:49 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
let restoreNeeded = false;
|
|
|
|
const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
|
|
|
|
const character = glyph.fontChar;
|
|
|
|
const accent = glyph.accent;
|
|
|
|
let scaledX, scaledY;
|
|
|
|
let width = glyph.width;
|
|
|
|
if (vertical) {
|
|
|
|
const vmetric = glyph.vmetric || defaultVMetrics;
|
|
|
|
const vx =
|
|
|
|
-(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale;
|
|
|
|
const vy = vmetric[2] * widthAdvanceScale;
|
|
|
|
|
|
|
|
width = vmetric ? -vmetric[0] : width;
|
|
|
|
scaledX = vx / fontSizeScale;
|
|
|
|
scaledY = (x + vy) / fontSizeScale;
|
|
|
|
} else {
|
|
|
|
scaledX = x / fontSizeScale;
|
|
|
|
scaledY = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (font.remeasure && width > 0) {
|
|
|
|
// Some standard fonts may not have the exact width: rescale per
|
|
|
|
// character if measured width is greater than expected glyph width
|
|
|
|
// and subpixel-aa is enabled, otherwise just center the glyph.
|
|
|
|
const measuredWidth =
|
|
|
|
((ctx.measureText(character).width * 1000) / fontSize) *
|
|
|
|
fontSizeScale;
|
|
|
|
if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
|
|
|
|
const characterScaleX = width / measuredWidth;
|
|
|
|
restoreNeeded = true;
|
|
|
|
ctx.save();
|
|
|
|
ctx.scale(characterScaleX, 1);
|
|
|
|
scaledX /= characterScaleX;
|
|
|
|
} else if (width !== measuredWidth) {
|
|
|
|
scaledX +=
|
|
|
|
(((width - measuredWidth) / 2000) * fontSize) / fontSizeScale;
|
2012-02-14 12:12:55 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2012-02-14 12:12:55 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Only attempt to draw the glyph if it is actually in the embedded font
|
|
|
|
// file or if there isn't a font file so the fallback font is shown.
|
|
|
|
if (this.contentVisible && (glyph.isInFont || font.missingFile)) {
|
|
|
|
if (simpleFillText && !accent) {
|
|
|
|
// common case
|
|
|
|
ctx.fillText(character, scaledX, scaledY);
|
|
|
|
} else {
|
|
|
|
this.paintChar(
|
|
|
|
character,
|
|
|
|
scaledX,
|
|
|
|
scaledY,
|
|
|
|
patternTransform,
|
|
|
|
resetLineWidthToOne
|
|
|
|
);
|
|
|
|
if (accent) {
|
|
|
|
const scaledAccentX =
|
|
|
|
scaledX + (fontSize * accent.offset.x) / fontSizeScale;
|
|
|
|
const scaledAccentY =
|
|
|
|
scaledY - (fontSize * accent.offset.y) / fontSizeScale;
|
2021-01-04 22:25:30 +09:00
|
|
|
this.paintChar(
|
2021-07-25 19:55:51 +09:00
|
|
|
accent.fontChar,
|
|
|
|
scaledAccentX,
|
|
|
|
scaledAccentY,
|
2021-01-04 22:25:30 +09:00
|
|
|
patternTransform,
|
|
|
|
resetLineWidthToOne
|
|
|
|
);
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
2014-05-24 03:36:54 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
|
|
|
|
let charWidth;
|
2014-05-24 03:36:54 +09:00
|
|
|
if (vertical) {
|
2021-07-25 19:55:51 +09:00
|
|
|
charWidth = width * widthAdvanceScale - spacing * fontDirection;
|
2014-05-24 03:36:54 +09:00
|
|
|
} else {
|
2021-07-25 19:55:51 +09:00
|
|
|
charWidth = width * widthAdvanceScale + spacing * fontDirection;
|
2014-05-24 03:36:54 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
x += charWidth;
|
2014-05-24 03:36:54 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (restoreNeeded) {
|
|
|
|
ctx.restore();
|
2014-05-24 03:36:54 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
|
|
|
if (vertical) {
|
|
|
|
current.y -= x;
|
|
|
|
} else {
|
|
|
|
current.x += x * textHScale;
|
|
|
|
}
|
|
|
|
ctx.restore();
|
2021-10-20 12:14:48 +09:00
|
|
|
this.compose();
|
2021-07-25 19:55:51 +09:00
|
|
|
return undefined;
|
|
|
|
}
|
2011-10-29 06:37:55 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
showType3Text(glyphs) {
|
|
|
|
// Type3 fonts - each glyph is a "mini-PDF"
|
|
|
|
const ctx = this.ctx;
|
|
|
|
const current = this.current;
|
|
|
|
const font = current.font;
|
|
|
|
const fontSize = current.fontSize;
|
|
|
|
const fontDirection = current.fontDirection;
|
|
|
|
const spacingDir = font.vertical ? 1 : -1;
|
|
|
|
const charSpacing = current.charSpacing;
|
|
|
|
const wordSpacing = current.wordSpacing;
|
|
|
|
const textHScale = current.textHScale * fontDirection;
|
|
|
|
const fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
|
|
|
|
const glyphsLength = glyphs.length;
|
|
|
|
const isTextInvisible =
|
|
|
|
current.textRenderingMode === TextRenderingMode.INVISIBLE;
|
|
|
|
let i, glyph, width, spacingLength;
|
|
|
|
|
|
|
|
if (isTextInvisible || fontSize === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._cachedGetSinglePixelWidth = null;
|
2014-05-24 03:36:54 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.save();
|
|
|
|
ctx.transform.apply(ctx, current.textMatrix);
|
|
|
|
ctx.translate(current.x, current.y);
|
2014-05-24 03:36:54 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.scale(textHScale, fontDirection);
|
2014-05-24 03:36:54 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
for (i = 0; i < glyphsLength; ++i) {
|
|
|
|
glyph = glyphs[i];
|
|
|
|
if (isNum(glyph)) {
|
|
|
|
spacingLength = (spacingDir * glyph * fontSize) / 1000;
|
|
|
|
this.ctx.translate(spacingLength, 0);
|
|
|
|
current.x += spacingLength * textHScale;
|
|
|
|
continue;
|
2012-09-20 05:17:01 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
|
|
|
|
const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
|
|
|
|
const operatorList = font.charProcOperatorList[glyph.operatorListId];
|
|
|
|
if (!operatorList) {
|
|
|
|
warn(`Type3 character "${glyph.operatorListId}" is not available.`);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (this.contentVisible) {
|
|
|
|
this.processingType3 = glyph;
|
|
|
|
this.save();
|
|
|
|
ctx.scale(fontSize, fontSize);
|
|
|
|
ctx.transform.apply(ctx, fontMatrix);
|
|
|
|
this.executeOperatorList(operatorList);
|
|
|
|
this.restore();
|
|
|
|
}
|
|
|
|
|
|
|
|
const transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
|
|
|
|
width = transformed[0] * fontSize + spacing;
|
|
|
|
|
|
|
|
ctx.translate(width, 0);
|
|
|
|
current.x += width * textHScale;
|
|
|
|
}
|
|
|
|
ctx.restore();
|
|
|
|
this.processingType3 = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type3 fonts
|
|
|
|
setCharWidth(xWidth, yWidth) {
|
|
|
|
// We can safely ignore this since the width should be the same
|
|
|
|
// as the width in the Widths array.
|
|
|
|
}
|
|
|
|
|
|
|
|
setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) {
|
|
|
|
// TODO According to the spec we're also suppose to ignore any operators
|
|
|
|
// that set color or include images while processing this type3 font.
|
|
|
|
this.ctx.rect(llx, lly, urx - llx, ury - lly);
|
|
|
|
this.clip();
|
|
|
|
this.endPath();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Color
|
|
|
|
getColorN_Pattern(IR) {
|
|
|
|
let pattern;
|
|
|
|
if (IR[0] === "TilingPattern") {
|
|
|
|
const color = IR[1];
|
|
|
|
const baseTransform =
|
|
|
|
this.baseTransform || this.ctx.mozCurrentTransform.slice();
|
|
|
|
const canvasGraphicsFactory = {
|
|
|
|
createCanvasGraphics: ctx => {
|
|
|
|
return new CanvasGraphics(
|
|
|
|
ctx,
|
|
|
|
this.commonObjs,
|
|
|
|
this.objs,
|
|
|
|
this.canvasFactory
|
|
|
|
);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
pattern = new TilingPattern(
|
|
|
|
IR,
|
|
|
|
color,
|
|
|
|
this.ctx,
|
|
|
|
canvasGraphicsFactory,
|
|
|
|
baseTransform
|
|
|
|
);
|
|
|
|
} else {
|
2021-07-28 11:58:06 +09:00
|
|
|
pattern = this._getPattern(IR[1], IR[2]);
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
return pattern;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setStrokeColorN() {
|
|
|
|
this.current.strokeColor = this.getColorN_Pattern(arguments);
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setFillColorN() {
|
|
|
|
this.current.fillColor = this.getColorN_Pattern(arguments);
|
|
|
|
this.current.patternFill = true;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setStrokeRGBColor(r, g, b) {
|
|
|
|
const color = Util.makeHexColor(r, g, b);
|
|
|
|
this.ctx.strokeStyle = color;
|
|
|
|
this.current.strokeColor = color;
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
setFillRGBColor(r, g, b) {
|
|
|
|
const color = Util.makeHexColor(r, g, b);
|
|
|
|
this.ctx.fillStyle = color;
|
|
|
|
this.current.fillColor = color;
|
|
|
|
this.current.patternFill = false;
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-28 11:58:06 +09:00
|
|
|
_getPattern(objId, matrix = null) {
|
|
|
|
let pattern;
|
2021-07-25 19:55:51 +09:00
|
|
|
if (this.cachedPatterns.has(objId)) {
|
2021-07-28 11:58:06 +09:00
|
|
|
pattern = this.cachedPatterns.get(objId);
|
|
|
|
} else {
|
|
|
|
pattern = getShadingPattern(
|
|
|
|
this.objs.get(objId),
|
|
|
|
this.cachedCanvasPatterns
|
|
|
|
);
|
|
|
|
this.cachedPatterns.set(objId, pattern);
|
|
|
|
}
|
|
|
|
if (matrix) {
|
|
|
|
pattern.matrix = matrix;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
return pattern;
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
shadingFill(objId) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
const ctx = this.ctx;
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.save();
|
|
|
|
const pattern = this._getPattern(objId);
|
|
|
|
ctx.fillStyle = pattern.getPattern(
|
|
|
|
ctx,
|
|
|
|
this,
|
|
|
|
ctx.mozCurrentTransformInverse,
|
|
|
|
true
|
|
|
|
);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const inv = ctx.mozCurrentTransformInverse;
|
2021-10-20 12:14:48 +09:00
|
|
|
let dirtyBox = [0, 0, ctx.canvas.width, ctx.canvas.height];
|
2021-07-25 19:55:51 +09:00
|
|
|
if (inv) {
|
|
|
|
const canvas = ctx.canvas;
|
|
|
|
const width = canvas.width;
|
|
|
|
const height = canvas.height;
|
2021-07-22 04:27:39 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const bl = Util.applyTransform([0, 0], inv);
|
|
|
|
const br = Util.applyTransform([0, height], inv);
|
|
|
|
const ul = Util.applyTransform([width, 0], inv);
|
|
|
|
const ur = Util.applyTransform([width, height], inv);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
|
|
|
|
const y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
|
|
|
|
const x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
|
|
|
|
const y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
|
2021-10-20 12:14:48 +09:00
|
|
|
dirtyBox = Util.getAxialAlignedBoundingBox(
|
|
|
|
[x0, y0, x1, y1],
|
|
|
|
ctx.mozCurrentTransform
|
|
|
|
);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
|
|
|
|
} else {
|
|
|
|
// HACK to draw the gradient onto an infinite rectangle.
|
|
|
|
// PDF gradients are drawn across the entire image while
|
|
|
|
// Canvas only allows gradients to be drawn in a rectangle
|
|
|
|
// The following bug should allow us to remove this.
|
|
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=664884
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-10-20 12:14:48 +09:00
|
|
|
this.compose(dirtyBox);
|
2021-07-25 19:55:51 +09:00
|
|
|
this.restore();
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Images
|
|
|
|
beginInlineImage() {
|
|
|
|
unreachable("Should not call beginInlineImage");
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
beginImageData() {
|
|
|
|
unreachable("Should not call beginImageData");
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
paintFormXObjectBegin(matrix, bbox) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
this.save();
|
|
|
|
this.baseTransformStack.push(this.baseTransform);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (Array.isArray(matrix) && matrix.length === 6) {
|
|
|
|
this.transform.apply(this, matrix);
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.baseTransform = this.ctx.mozCurrentTransform;
|
|
|
|
|
|
|
|
if (bbox) {
|
|
|
|
const width = bbox[2] - bbox[0];
|
|
|
|
const height = bbox[3] - bbox[1];
|
|
|
|
this.ctx.rect(bbox[0], bbox[1], width, height);
|
|
|
|
this.clip();
|
|
|
|
this.endPath();
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
paintFormXObjectEnd() {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.restore();
|
|
|
|
this.baseTransform = this.baseTransformStack.pop();
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
beginGroup(group) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.save();
|
2021-10-20 12:14:48 +09:00
|
|
|
// If there's an active soft mask we don't want it enabled for the group, so
|
|
|
|
// clear it out. The mask and suspended canvas will be restored in endGroup.
|
|
|
|
const suspendedCtx = this.suspendedCtx;
|
|
|
|
if (this.current.activeSMask) {
|
|
|
|
this.suspendedCtx = null;
|
|
|
|
this.current.activeSMask = null;
|
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const 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 implementing:
|
|
|
|
// - 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) {
|
|
|
|
info("TODO: Support non-isolated groups.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO knockout - supposedly possible with the clever use of compositing
|
|
|
|
// modes.
|
|
|
|
if (group.knockout) {
|
|
|
|
warn("Knockout groups not supported.");
|
|
|
|
}
|
|
|
|
|
|
|
|
const currentTransform = currentCtx.mozCurrentTransform;
|
|
|
|
if (group.matrix) {
|
|
|
|
currentCtx.transform.apply(currentCtx, group.matrix);
|
|
|
|
}
|
|
|
|
if (!group.bbox) {
|
|
|
|
throw new Error("Bounding box is required.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Based on the current transform figure out how big the bounding box
|
|
|
|
// will actually be.
|
|
|
|
let bounds = Util.getAxialAlignedBoundingBox(
|
|
|
|
group.bbox,
|
|
|
|
currentCtx.mozCurrentTransform
|
|
|
|
);
|
|
|
|
// Clip the bounding box to the current canvas.
|
|
|
|
const canvasBounds = [
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
currentCtx.canvas.width,
|
|
|
|
currentCtx.canvas.height,
|
|
|
|
];
|
|
|
|
bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
|
|
|
|
// Use ceil in case we're between sizes so we don't create canvas that is
|
|
|
|
// too small and make the canvas at least 1x1 pixels.
|
|
|
|
const offsetX = Math.floor(bounds[0]);
|
|
|
|
const offsetY = Math.floor(bounds[1]);
|
|
|
|
let drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
|
|
|
|
let drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
|
|
|
|
let scaleX = 1,
|
|
|
|
scaleY = 1;
|
|
|
|
if (drawnWidth > MAX_GROUP_SIZE) {
|
|
|
|
scaleX = drawnWidth / MAX_GROUP_SIZE;
|
|
|
|
drawnWidth = MAX_GROUP_SIZE;
|
|
|
|
}
|
|
|
|
if (drawnHeight > MAX_GROUP_SIZE) {
|
|
|
|
scaleY = drawnHeight / MAX_GROUP_SIZE;
|
|
|
|
drawnHeight = MAX_GROUP_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
let cacheId = "groupAt" + this.groupLevel;
|
|
|
|
if (group.smask) {
|
|
|
|
// Using two cache entries is case if masks are used one after another.
|
|
|
|
cacheId += "_smask_" + (this.smaskCounter++ % 2);
|
|
|
|
}
|
|
|
|
const scratchCanvas = this.cachedCanvases.getCanvas(
|
|
|
|
cacheId,
|
|
|
|
drawnWidth,
|
|
|
|
drawnHeight,
|
|
|
|
true
|
|
|
|
);
|
|
|
|
const groupCtx = scratchCanvas.context;
|
|
|
|
|
|
|
|
// Since we created a new canvas that is just the size of the bounding box
|
|
|
|
// we have to translate the group ctx.
|
|
|
|
groupCtx.scale(1 / scaleX, 1 / scaleY);
|
|
|
|
groupCtx.translate(-offsetX, -offsetY);
|
|
|
|
groupCtx.transform.apply(groupCtx, currentTransform);
|
|
|
|
|
|
|
|
if (group.smask) {
|
|
|
|
// Saving state and cached mask to be used in setGState.
|
|
|
|
this.smaskStack.push({
|
|
|
|
canvas: scratchCanvas.canvas,
|
|
|
|
context: groupCtx,
|
|
|
|
offsetX,
|
|
|
|
offsetY,
|
|
|
|
scaleX,
|
|
|
|
scaleY,
|
|
|
|
subtype: group.smask.subtype,
|
|
|
|
backdrop: group.smask.backdrop,
|
|
|
|
transferMap: group.smask.transferMap || null,
|
|
|
|
startTransformInverse: null, // used during suspend operation
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// Setup the current ctx so when the group is popped we draw it at the
|
|
|
|
// right location.
|
|
|
|
currentCtx.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
|
currentCtx.translate(offsetX, offsetY);
|
|
|
|
currentCtx.scale(scaleX, scaleY);
|
2021-10-20 12:14:48 +09:00
|
|
|
currentCtx.save();
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
|
|
|
// 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([
|
|
|
|
["BM", "source-over"],
|
|
|
|
["ca", 1],
|
|
|
|
["CA", 1],
|
|
|
|
]);
|
2021-10-20 12:14:48 +09:00
|
|
|
this.groupStack.push({
|
|
|
|
ctx: currentCtx,
|
|
|
|
suspendedCtx,
|
|
|
|
});
|
2021-07-25 19:55:51 +09:00
|
|
|
this.groupLevel++;
|
|
|
|
}
|
2013-07-23 06:52:44 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
endGroup(group) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
this.groupLevel--;
|
|
|
|
const groupCtx = this.ctx;
|
2021-10-20 12:14:48 +09:00
|
|
|
const { ctx, suspendedCtx } = this.groupStack.pop();
|
|
|
|
this.ctx = ctx;
|
2021-07-25 19:55:51 +09:00
|
|
|
// Turn off image smoothing to avoid sub pixel interpolation which can
|
|
|
|
// look kind of blurry for some pdfs.
|
2021-09-11 17:26:41 +09:00
|
|
|
this.ctx.imageSmoothingEnabled = false;
|
|
|
|
|
2021-10-20 12:14:48 +09:00
|
|
|
if (suspendedCtx) {
|
|
|
|
this.suspendedCtx = suspendedCtx;
|
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (group.smask) {
|
|
|
|
this.tempSMask = this.smaskStack.pop();
|
2021-10-20 12:14:48 +09:00
|
|
|
this.restore();
|
2021-07-25 19:55:51 +09:00
|
|
|
} else {
|
2021-10-20 12:14:48 +09:00
|
|
|
this.ctx.restore();
|
|
|
|
const currentMtx = this.ctx.mozCurrentTransform;
|
|
|
|
this.restore();
|
|
|
|
this.ctx.save();
|
|
|
|
this.ctx.setTransform.apply(this.ctx, currentMtx);
|
|
|
|
const dirtyBox = Util.getAxialAlignedBoundingBox(
|
|
|
|
[0, 0, groupCtx.canvas.width, groupCtx.canvas.height],
|
|
|
|
currentMtx
|
|
|
|
);
|
2021-07-25 19:55:51 +09:00
|
|
|
this.ctx.drawImage(groupCtx.canvas, 0, 0);
|
2021-10-20 12:14:48 +09:00
|
|
|
this.ctx.restore();
|
|
|
|
this.compose(dirtyBox);
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
beginAnnotations() {
|
|
|
|
this.save();
|
|
|
|
if (this.baseTransform) {
|
|
|
|
this.ctx.setTransform.apply(this.ctx, this.baseTransform);
|
|
|
|
}
|
|
|
|
}
|
2020-07-15 07:17:27 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
endAnnotations() {
|
|
|
|
this.restore();
|
|
|
|
}
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
beginAnnotation(id, rect, transform, matrix) {
|
|
|
|
this.save();
|
|
|
|
resetCtxToDefault(this.ctx);
|
|
|
|
this.current = new CanvasExtraState();
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (Array.isArray(rect) && rect.length === 4) {
|
|
|
|
const width = rect[2] - rect[0];
|
|
|
|
const height = rect[3] - rect[1];
|
|
|
|
this.ctx.rect(rect[0], rect[1], width, height);
|
|
|
|
this.clip();
|
|
|
|
this.endPath();
|
|
|
|
}
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.transform.apply(this, transform);
|
|
|
|
this.transform.apply(this, matrix);
|
|
|
|
}
|
2013-04-12 03:19:42 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
endAnnotation() {
|
|
|
|
this.restore();
|
|
|
|
}
|
2016-04-10 08:46:15 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
paintImageMaskXObject(img) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
const ctx = this.ctx;
|
|
|
|
const width = img.width,
|
|
|
|
height = img.height;
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const glyph = this.processingType3;
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (COMPILE_TYPE3_GLYPHS && glyph && glyph.compiled === undefined) {
|
|
|
|
if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
|
|
|
|
glyph.compiled = compileType3Glyph({ data: img.data, width, height });
|
|
|
|
} else {
|
|
|
|
glyph.compiled = null;
|
2015-12-28 22:10:30 +09:00
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2013-05-29 07:12:35 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (glyph?.compiled) {
|
|
|
|
glyph.compiled(ctx);
|
|
|
|
return;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
const mask = this._createMaskCanvas(img);
|
|
|
|
const maskCanvas = mask.canvas;
|
2013-05-29 07:12:35 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.save();
|
|
|
|
// The mask is drawn with the transform applied. Reset the current
|
|
|
|
// transform to draw to the identity.
|
|
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
|
ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY);
|
|
|
|
ctx.restore();
|
2021-10-20 12:14:48 +09:00
|
|
|
this.compose();
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2013-03-14 04:24:55 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
paintImageMaskXObjectRepeat(
|
|
|
|
imgData,
|
|
|
|
scaleX,
|
|
|
|
skewX = 0,
|
|
|
|
skewY = 0,
|
|
|
|
scaleY,
|
|
|
|
positions
|
|
|
|
) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const ctx = this.ctx;
|
|
|
|
ctx.save();
|
|
|
|
const currentTransform = ctx.mozCurrentTransform;
|
|
|
|
ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0);
|
|
|
|
const mask = this._createMaskCanvas(imgData);
|
|
|
|
|
|
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
|
for (let i = 0, ii = positions.length; i < ii; i += 2) {
|
|
|
|
const trans = Util.transform(currentTransform, [
|
|
|
|
scaleX,
|
|
|
|
skewX,
|
|
|
|
skewY,
|
|
|
|
scaleY,
|
|
|
|
positions[i],
|
|
|
|
positions[i + 1],
|
|
|
|
]);
|
2013-03-14 04:24:55 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const [x, y] = Util.applyTransform([0, 0], trans);
|
|
|
|
ctx.drawImage(mask.canvas, x, y);
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.restore();
|
2021-10-20 12:14:48 +09:00
|
|
|
this.compose();
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2013-03-14 04:24:55 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
paintImageMaskXObjectGroup(images) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
const ctx = this.ctx;
|
2013-03-14 04:24:55 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const fillColor = this.current.fillColor;
|
|
|
|
const isPatternFill = this.current.patternFill;
|
|
|
|
for (let i = 0, ii = images.length; i < ii; i++) {
|
|
|
|
const image = images[i];
|
|
|
|
const width = image.width,
|
|
|
|
height = image.height;
|
2013-05-31 09:42:26 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const maskCanvas = this.cachedCanvases.getCanvas(
|
|
|
|
"maskCanvas",
|
|
|
|
width,
|
|
|
|
height
|
|
|
|
);
|
|
|
|
const maskCtx = maskCanvas.context;
|
|
|
|
maskCtx.save();
|
2013-05-11 12:50:14 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
putBinaryImageMask(maskCtx, image);
|
2013-05-11 12:50:14 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
maskCtx.globalCompositeOperation = "source-in";
|
2013-05-11 12:50:14 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
maskCtx.fillStyle = isPatternFill
|
|
|
|
? fillColor.getPattern(
|
|
|
|
maskCtx,
|
|
|
|
this,
|
|
|
|
ctx.mozCurrentTransformInverse,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
: fillColor;
|
|
|
|
maskCtx.fillRect(0, 0, width, height);
|
|
|
|
|
|
|
|
maskCtx.restore();
|
2012-12-08 03:19:43 +09:00
|
|
|
|
2021-07-01 07:09:07 +09:00
|
|
|
ctx.save();
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.transform.apply(ctx, image.transform);
|
|
|
|
ctx.scale(1, -1);
|
|
|
|
ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
|
2021-07-01 07:09:07 +09:00
|
|
|
ctx.restore();
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-10-20 12:14:48 +09:00
|
|
|
this.compose();
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2014-02-25 00:59:02 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
paintImageXObject(objId) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const imgData = objId.startsWith("g_")
|
|
|
|
? this.commonObjs.get(objId)
|
|
|
|
: this.objs.get(objId);
|
|
|
|
if (!imgData) {
|
|
|
|
warn("Dependent image isn't ready yet");
|
|
|
|
return;
|
|
|
|
}
|
2012-12-08 03:19:43 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.paintInlineImageXObject(imgData);
|
|
|
|
}
|
2012-12-08 03:19:43 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
paintImageXObjectRepeat(objId, scaleX, scaleY, positions) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const imgData = objId.startsWith("g_")
|
|
|
|
? this.commonObjs.get(objId)
|
|
|
|
: this.objs.get(objId);
|
|
|
|
if (!imgData) {
|
|
|
|
warn("Dependent image isn't ready yet");
|
|
|
|
return;
|
|
|
|
}
|
2013-05-31 09:42:26 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const width = imgData.width;
|
|
|
|
const height = imgData.height;
|
|
|
|
const map = [];
|
|
|
|
for (let i = 0, ii = positions.length; i < ii; i += 2) {
|
|
|
|
map.push({
|
|
|
|
transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]],
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
w: width,
|
|
|
|
h: height,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
this.paintInlineImageXObjectGroup(imgData, map);
|
|
|
|
}
|
2012-12-08 03:19:43 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
paintInlineImageXObject(imgData) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
const width = imgData.width;
|
|
|
|
const height = imgData.height;
|
|
|
|
const ctx = this.ctx;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
this.save();
|
|
|
|
// scale the image to the unit square
|
|
|
|
ctx.scale(1 / width, -1 / height);
|
2011-12-16 08:13:48 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
let imgToPaint;
|
|
|
|
// typeof check is needed due to node.js support, see issue #8489
|
|
|
|
if (
|
|
|
|
(typeof HTMLElement === "function" && imgData instanceof HTMLElement) ||
|
|
|
|
!imgData.data
|
|
|
|
) {
|
|
|
|
imgToPaint = imgData;
|
|
|
|
} else {
|
|
|
|
const tmpCanvas = this.cachedCanvases.getCanvas(
|
|
|
|
"inlineImage",
|
|
|
|
width,
|
|
|
|
height
|
|
|
|
);
|
|
|
|
const tmpCtx = tmpCanvas.context;
|
|
|
|
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
|
|
|
|
imgToPaint = tmpCanvas.canvas;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2012-12-05 02:36:42 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const scaled = this._scaleImage(imgToPaint, ctx.mozCurrentTransformInverse);
|
2021-09-09 09:31:10 +09:00
|
|
|
ctx.imageSmoothingEnabled = getImageSmoothingEnabled(
|
|
|
|
ctx.mozCurrentTransform,
|
|
|
|
imgData.interpolate
|
|
|
|
);
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.drawImage(
|
|
|
|
scaled.img,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
scaled.paintWidth,
|
|
|
|
scaled.paintHeight,
|
|
|
|
0,
|
|
|
|
-height,
|
|
|
|
width,
|
|
|
|
height
|
|
|
|
);
|
2014-02-24 23:00:08 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
if (this.imageLayer) {
|
|
|
|
const position = this.getCanvasPosition(0, -height);
|
|
|
|
this.imageLayer.appendImage({
|
|
|
|
imgData,
|
|
|
|
left: position[0],
|
|
|
|
top: position[1],
|
|
|
|
width: width / ctx.mozCurrentTransformInverse[0],
|
|
|
|
height: height / ctx.mozCurrentTransformInverse[3],
|
|
|
|
});
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-10-20 12:14:48 +09:00
|
|
|
this.compose();
|
2021-07-25 19:55:51 +09:00
|
|
|
this.restore();
|
|
|
|
}
|
2014-02-24 23:00:08 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
paintInlineImageXObjectGroup(imgData, map) {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const ctx = this.ctx;
|
|
|
|
const w = imgData.width;
|
|
|
|
const h = imgData.height;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
|
|
|
|
const tmpCtx = tmpCanvas.context;
|
|
|
|
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
|
2013-05-31 09:42:26 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
for (let i = 0, ii = map.length; i < ii; i++) {
|
|
|
|
const entry = map[i];
|
|
|
|
ctx.save();
|
|
|
|
ctx.transform.apply(ctx, entry.transform);
|
|
|
|
ctx.scale(1, -1);
|
2021-07-01 07:09:07 +09:00
|
|
|
ctx.drawImage(
|
2021-07-25 19:55:51 +09:00
|
|
|
tmpCanvas.canvas,
|
|
|
|
entry.x,
|
|
|
|
entry.y,
|
|
|
|
entry.w,
|
|
|
|
entry.h,
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
0,
|
2021-07-25 19:55:51 +09:00
|
|
|
-1,
|
|
|
|
1,
|
|
|
|
1
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
);
|
2013-02-11 03:19:36 +09:00
|
|
|
if (this.imageLayer) {
|
2021-07-25 19:55:51 +09:00
|
|
|
const position = this.getCanvasPosition(entry.x, entry.y);
|
2013-02-11 03:19:36 +09:00
|
|
|
this.imageLayer.appendImage({
|
2017-04-25 23:17:18 +09:00
|
|
|
imgData,
|
2013-02-11 03:19:36 +09:00
|
|
|
left: position[0],
|
|
|
|
top: position[1],
|
2021-07-25 19:55:51 +09:00
|
|
|
width: w,
|
|
|
|
height: h,
|
2013-02-11 03:19:36 +09:00
|
|
|
});
|
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.restore();
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-10-20 12:14:48 +09:00
|
|
|
this.compose();
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
paintSolidColorImageMask() {
|
|
|
|
if (!this.contentVisible) {
|
|
|
|
return;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
this.ctx.fillRect(0, 0, 1, 1);
|
2021-10-20 12:14:48 +09:00
|
|
|
this.compose();
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2012-12-08 03:19:43 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Marked content
|
2014-03-17 00:17:13 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
markPoint(tag) {
|
|
|
|
// TODO Marked content.
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
markPointProps(tag, properties) {
|
|
|
|
// TODO Marked content.
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
beginMarkedContent(tag) {
|
|
|
|
this.markedContentStack.push({
|
|
|
|
visible: true,
|
|
|
|
});
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
beginMarkedContentProps(tag, properties) {
|
|
|
|
if (tag === "OC") {
|
|
|
|
this.markedContentStack.push({
|
|
|
|
visible: this.optionalContentConfig.isVisible(properties),
|
|
|
|
});
|
|
|
|
} else {
|
2020-07-15 07:17:27 +09:00
|
|
|
this.markedContentStack.push({
|
|
|
|
visible: true,
|
|
|
|
});
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
this.contentVisible = this.isContentVisible();
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
endMarkedContent() {
|
|
|
|
this.markedContentStack.pop();
|
|
|
|
this.contentVisible = this.isContentVisible();
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Compatibility
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
beginCompat() {
|
|
|
|
// TODO ignore undefined operators (should we do that anyway?)
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
endCompat() {
|
|
|
|
// TODO stop ignoring undefined operators
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
// Helper functions
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
consumePath() {
|
2021-10-20 12:14:48 +09:00
|
|
|
if (!this.pendingClip) {
|
|
|
|
this.compose();
|
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
const ctx = this.ctx;
|
|
|
|
if (this.pendingClip) {
|
|
|
|
if (this.pendingClip === EO_CLIP) {
|
|
|
|
ctx.clip("evenodd");
|
|
|
|
} else {
|
|
|
|
ctx.clip();
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
this.pendingClip = null;
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
ctx.beginPath();
|
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
getSinglePixelWidth() {
|
|
|
|
if (this._cachedGetSinglePixelWidth === null) {
|
|
|
|
// If transform is [a b] then a pixel (square) is transformed
|
|
|
|
// [c d]
|
|
|
|
// into a parallelogram: its area is the abs value of the determinant.
|
|
|
|
// This parallelogram has 2 heights:
|
|
|
|
// - Area / |col_1|;
|
|
|
|
// - Area / |col_2|.
|
|
|
|
// so in order to get a height of at least 1, pixel height
|
|
|
|
// must be computed as followed:
|
|
|
|
// h = max(sqrt(a² + c²) / |det(M)|, sqrt(b² + d²) / |det(M)|).
|
|
|
|
// This is equivalent to:
|
|
|
|
// h = max(|line_1_inv(M)|, |line_2_inv(M)|)
|
|
|
|
const m = this.ctx.mozCurrentTransform;
|
|
|
|
|
|
|
|
const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]);
|
|
|
|
const sqNorm1 = m[0] ** 2 + m[2] ** 2;
|
|
|
|
const sqNorm2 = m[1] ** 2 + m[3] ** 2;
|
|
|
|
const pixelHeight = Math.sqrt(Math.max(sqNorm1, sqNorm2)) / absDet;
|
|
|
|
if (sqNorm1 !== sqNorm2 && this._combinedScaleFactor * pixelHeight > 1) {
|
|
|
|
// The parallelogram isn't a square and at least one height
|
|
|
|
// is lower than 1 so the resulting line width must be 1
|
|
|
|
// but it cannot be achieved with one scale: when scaling a pixel
|
|
|
|
// we'll get a rectangle (see issue #12295).
|
|
|
|
// For example with matrix [0.001 0, 0, 100], a pixel is transformed
|
|
|
|
// in a rectangle 0.001x100. If we just scale by 1000 (to have a 1)
|
|
|
|
// then we'll get a rectangle 1x1e5 which is wrong.
|
|
|
|
// In this case, we must reset the transform, set linewidth to 1
|
|
|
|
// and then stroke.
|
|
|
|
this._cachedGetSinglePixelWidth = -(
|
|
|
|
this._combinedScaleFactor * pixelHeight
|
|
|
|
);
|
|
|
|
} else if (absDet > Number.EPSILON) {
|
|
|
|
this._cachedGetSinglePixelWidth = pixelHeight;
|
|
|
|
} else {
|
|
|
|
// Matrix is non-invertible.
|
|
|
|
this._cachedGetSinglePixelWidth = 1;
|
2014-04-12 03:19:39 +09:00
|
|
|
}
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
return this._cachedGetSinglePixelWidth;
|
|
|
|
}
|
2020-07-15 07:17:27 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
getCanvasPosition(x, y) {
|
|
|
|
const transform = this.ctx.mozCurrentTransform;
|
|
|
|
return [
|
|
|
|
transform[0] * x + transform[2] * y + transform[4],
|
|
|
|
transform[1] * x + transform[3] * y + transform[5],
|
|
|
|
];
|
2021-04-27 04:42:11 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
isContentVisible() {
|
|
|
|
for (let i = this.markedContentStack.length - 1; i >= 0; i--) {
|
|
|
|
if (!this.markedContentStack[i].visible) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
2013-11-14 04:43:38 +09:00
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2013-11-14 04:43:38 +09:00
|
|
|
|
2021-07-25 19:55:51 +09:00
|
|
|
for (const op in OPS) {
|
2021-07-25 20:18:24 +09:00
|
|
|
if (CanvasGraphics.prototype[op] !== undefined) {
|
|
|
|
CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
|
|
|
|
}
|
2021-07-25 19:55:51 +09:00
|
|
|
}
|
2015-11-22 01:32:47 +09:00
|
|
|
|
Enable auto-formatting of the entire code-base using Prettier (issue 11444)
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes).
Prettier is being used for a couple of reasons:
- To be consistent with `mozilla-central`, where Prettier is already in use across the tree.
- To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters.
Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some).
Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long.
*Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit.
(On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
|
|
|
export { CanvasGraphics };
|