diff --git a/src/core/operator_list.js b/src/core/operator_list.js index 00a732997..69a4650cd 100644 --- a/src/core/operator_list.js +++ b/src/core/operator_list.js @@ -13,7 +13,14 @@ * limitations under the License. */ -import { ImageKind, OPS, RenderingIntentFlag, warn } from "../shared/util.js"; +import { + DrawOPS, + ImageKind, + OPS, + RenderingIntentFlag, + Util, + warn, +} from "../shared/util.js"; function addState(parentState, pattern, checkFn, iterateFn, processFn) { let state = parentState; @@ -470,6 +477,70 @@ addState( } ); +// This replaces (save, transform, constructPath, restore) +// sequences with |constructPath| operation. +addState( + InitialState, + [OPS.save, OPS.transform, OPS.constructPath, OPS.restore], + context => { + const argsArray = context.argsArray; + const iFirstConstructPath = context.iCurr - 1; + const op = argsArray[iFirstConstructPath][0]; + + // When stroking the transform has to be applied to the line width too. + // So we can only optimize if the transform is an identity. + if ( + op !== OPS.stroke && + op !== OPS.closeStroke && + op !== OPS.fillStroke && + op !== OPS.eoFillStroke && + op !== OPS.closeFillStroke && + op !== OPS.closeEOFillStroke + ) { + return true; + } + const iFirstTransform = context.iCurr - 2; + const transform = argsArray[iFirstTransform]; + return ( + transform[0] === 1 && + transform[1] === 0 && + transform[2] === 0 && + transform[3] === 1 + ); + }, + () => false, + (context, i) => { + const { fnArray, argsArray } = context; + const curr = context.iCurr; + const iFirstSave = curr - 3; + const iFirstTransform = curr - 2; + const iFirstConstructPath = curr - 1; + const args = argsArray[iFirstConstructPath]; + const transform = argsArray[iFirstTransform]; + const [, [buffer], minMax] = args; + + Util.scaleMinMax(transform, minMax); + for (let k = 0, kk = buffer.length; k < kk; ) { + switch (buffer[k++]) { + case DrawOPS.moveTo: + case DrawOPS.lineTo: + Util.applyTransformInPlace(buffer.subarray(k), transform); + k += 2; + break; + case DrawOPS.curveTo: + Util.applyTransformToBezierInPlace(buffer.subarray(k), transform); + k += 6; + break; + } + } + // Replace queue items. + fnArray.splice(iFirstSave, 4, OPS.constructPath); + argsArray.splice(iFirstSave, 4, args); + + return iFirstSave + 1; + } +); + class NullOptimizer { constructor(queue) { this.queue = queue; diff --git a/src/shared/util.js b/src/shared/util.js index b36a7383a..3274885f4 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -676,6 +676,57 @@ class Util { return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`; } + // Apply a scaling matrix to some min/max values. + // If a scaling factor is negative then min and max must be + // swapped. + static scaleMinMax(transform, minMax) { + let temp; + if (transform[0]) { + if (transform[0] < 0) { + temp = minMax[0]; + minMax[0] = minMax[2]; + minMax[2] = temp; + } + minMax[0] *= transform[0]; + minMax[2] *= transform[0]; + + if (transform[3] < 0) { + temp = minMax[1]; + minMax[1] = minMax[3]; + minMax[3] = temp; + } + minMax[1] *= transform[3]; + minMax[3] *= transform[3]; + } else { + temp = minMax[0]; + minMax[0] = minMax[1]; + minMax[1] = temp; + temp = minMax[2]; + minMax[2] = minMax[3]; + minMax[3] = temp; + + if (transform[1] < 0) { + temp = minMax[1]; + minMax[1] = minMax[3]; + minMax[3] = temp; + } + minMax[1] *= transform[1]; + minMax[3] *= transform[1]; + + if (transform[2] < 0) { + temp = minMax[0]; + minMax[0] = minMax[2]; + minMax[2] = temp; + } + minMax[0] *= transform[2]; + minMax[2] *= transform[2]; + } + minMax[0] += transform[4]; + minMax[1] += transform[5]; + minMax[2] += transform[4]; + minMax[3] += transform[5]; + } + // Concatenates two transformation matrices together and returns the result. static transform(m1, m2) { return [ @@ -695,6 +746,22 @@ class Util { return [xt, yt]; } + static applyTransformInPlace(p, m) { + const [p0, p1] = p; + p[0] = p0 * m[0] + p1 * m[2] + m[4]; + p[1] = p0 * m[1] + p1 * m[3] + m[5]; + } + + // For 2d affine transforms + static applyTransformToBezierInPlace(p, [m0, m1, m2, m3, m4, m5]) { + for (let i = 0; i < 6; i += 2) { + const pI = p[i]; + const pI1 = p[i + 1]; + p[i] = pI * m0 + pI1 * m2 + m4; + p[i + 1] = pI * m1 + pI1 * m3 + m5; + } + } + static applyInverseTransform(p, m) { const d = m[0] * m[3] - m[1] * m[2]; const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;