mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-25 08:27:19 +02:00
Merge pull request #18815 from calixteman/dont_always_use_pattern
Don't use an intermediate canvas when rendering a tiling pattern bigger than the rectangle to fill
This commit is contained in:
commit
b0c0680bea
@ -318,6 +318,8 @@ class CanvasExtraState {
|
|||||||
|
|
||||||
strokeColor = "#000000";
|
strokeColor = "#000000";
|
||||||
|
|
||||||
|
tilingPatternDims = null;
|
||||||
|
|
||||||
patternFill = false;
|
patternFill = false;
|
||||||
|
|
||||||
patternStroke = false;
|
patternStroke = false;
|
||||||
@ -343,6 +345,7 @@ class CanvasExtraState {
|
|||||||
const clone = Object.create(this);
|
const clone = Object.create(this);
|
||||||
clone.clipBox = this.clipBox.slice();
|
clone.clipBox = this.clipBox.slice();
|
||||||
clone.minMax = this.minMax.slice();
|
clone.minMax = this.minMax.slice();
|
||||||
|
clone.tilingPatternDims = this.tilingPatternDims?.slice();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1494,6 +1497,9 @@ class CanvasGraphics {
|
|||||||
if (!minMax) {
|
if (!minMax) {
|
||||||
// The path is empty, so no need to update the current minMax.
|
// The path is empty, so no need to update the current minMax.
|
||||||
path ||= data[0] = new Path2D();
|
path ||= data[0] = new Path2D();
|
||||||
|
if (op !== OPS.stroke && op !== OPS.closeStroke) {
|
||||||
|
this.current.tilingPatternDims = null;
|
||||||
|
}
|
||||||
this[op](opIdx, path);
|
this[op](opIdx, path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1521,6 +1527,27 @@ class CanvasGraphics {
|
|||||||
getCurrentTransform(this.ctx),
|
getCurrentTransform(this.ctx),
|
||||||
this.current.minMax
|
this.current.minMax
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tilingDims = this.current.tilingPatternDims;
|
||||||
|
if (
|
||||||
|
tilingDims &&
|
||||||
|
op !== OPS.stroke &&
|
||||||
|
op !== OPS.closeStroke &&
|
||||||
|
this.current.fillColor instanceof TilingPattern
|
||||||
|
) {
|
||||||
|
// Intersect with clip box to get the actual fill area, then convert
|
||||||
|
// to pattern space.
|
||||||
|
const clippedBBox = Util.intersect(
|
||||||
|
this.current.clipBox,
|
||||||
|
this.current.minMax
|
||||||
|
);
|
||||||
|
if (!clippedBBox) {
|
||||||
|
this.current.tilingPatternDims = null;
|
||||||
|
} else {
|
||||||
|
this.current.fillColor.updatePatternDims(clippedBBox, tilingDims);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this[op](opIdx, path);
|
this[op](opIdx, path);
|
||||||
|
|
||||||
this._pathStartIdx = opIdx;
|
this._pathStartIdx = opIdx;
|
||||||
@ -1590,8 +1617,21 @@ class CanvasGraphics {
|
|||||||
const fillColor = this.current.fillColor;
|
const fillColor = this.current.fillColor;
|
||||||
const isPatternFill = this.current.patternFill;
|
const isPatternFill = this.current.patternFill;
|
||||||
let needRestore = false;
|
let needRestore = false;
|
||||||
|
const intersect = this.current.getClippedPathBoundingBox();
|
||||||
|
|
||||||
if (isPatternFill) {
|
if (isPatternFill) {
|
||||||
|
const dims = this.current.tilingPatternDims;
|
||||||
|
const tileIdx = dims && fillColor.canSkipPatternCanvas(dims);
|
||||||
|
if (tileIdx) {
|
||||||
|
// Draw the tile directly, skipping the pattern canvas.
|
||||||
|
fillColor.drawPattern(this, path, this.pendingEOFill, tileIdx, opIdx);
|
||||||
|
this.pendingEOFill = false;
|
||||||
|
if (consumePath) {
|
||||||
|
this.consumePath(opIdx, path, intersect);
|
||||||
|
}
|
||||||
|
this.current.tilingPatternDims = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const baseTransform = fillColor.isModifyingCurrentTransform()
|
const baseTransform = fillColor.isModifyingCurrentTransform()
|
||||||
? ctx.getTransform()
|
? ctx.getTransform()
|
||||||
: null;
|
: null;
|
||||||
@ -1615,7 +1655,6 @@ class CanvasGraphics {
|
|||||||
needRestore = true;
|
needRestore = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const intersect = this.current.getClippedPathBoundingBox();
|
|
||||||
if (this.contentVisible && intersect !== null) {
|
if (this.contentVisible && intersect !== null) {
|
||||||
if (this.pendingEOFill) {
|
if (this.pendingEOFill) {
|
||||||
ctx.fill(path, "evenodd");
|
ctx.fill(path, "evenodd");
|
||||||
@ -2422,8 +2461,13 @@ class CanvasGraphics {
|
|||||||
|
|
||||||
setFillColorN(opIdx, ...args) {
|
setFillColorN(opIdx, ...args) {
|
||||||
this.dependencyTracker?.recordSimpleData("fillColor", opIdx);
|
this.dependencyTracker?.recordSimpleData("fillColor", opIdx);
|
||||||
this.current.fillColor = this.getColorN_Pattern(opIdx, args);
|
const pattern = (this.current.fillColor = this.getColorN_Pattern(
|
||||||
|
opIdx,
|
||||||
|
args
|
||||||
|
));
|
||||||
this.current.patternFill = true;
|
this.current.patternFill = true;
|
||||||
|
this.current.tilingPatternDims =
|
||||||
|
pattern instanceof TilingPattern ? [0, 0, 0, 0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStrokeRGBColor(opIdx, color) {
|
setStrokeRGBColor(opIdx, color) {
|
||||||
@ -2442,12 +2486,14 @@ class CanvasGraphics {
|
|||||||
this.dependencyTracker?.recordSimpleData("fillColor", opIdx);
|
this.dependencyTracker?.recordSimpleData("fillColor", opIdx);
|
||||||
this.ctx.fillStyle = this.current.fillColor = color;
|
this.ctx.fillStyle = this.current.fillColor = color;
|
||||||
this.current.patternFill = false;
|
this.current.patternFill = false;
|
||||||
|
this.current.tilingPatternDims = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFillTransparent(opIdx) {
|
setFillTransparent(opIdx) {
|
||||||
this.dependencyTracker?.recordSimpleData("fillColor", opIdx);
|
this.dependencyTracker?.recordSimpleData("fillColor", opIdx);
|
||||||
this.ctx.fillStyle = this.current.fillColor = "transparent";
|
this.ctx.fillStyle = this.current.fillColor = "transparent";
|
||||||
this.current.patternFill = false;
|
this.current.patternFill = false;
|
||||||
|
this.current.tilingPatternDims = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPattern(opIdx, objId, matrix = null) {
|
_getPattern(opIdx, objId, matrix = null) {
|
||||||
|
|||||||
@ -616,22 +616,134 @@ class TilingPattern {
|
|||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.canvasGraphicsFactory = canvasGraphicsFactory;
|
this.canvasGraphicsFactory = canvasGraphicsFactory;
|
||||||
this.baseTransform = baseTransform;
|
this.baseTransform = baseTransform;
|
||||||
|
// baseTransform * patternMatrix.
|
||||||
|
this.patternBaseMatrix = this.matrix
|
||||||
|
? Util.transform(baseTransform, this.matrix)
|
||||||
|
: baseTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns [n, m] tile index if the fill area fits within one tile,
|
||||||
|
// null otherwise.
|
||||||
|
canSkipPatternCanvas([width, height, offsetX, offsetY]) {
|
||||||
|
const [x0, y0, x1, y1] = this.bbox;
|
||||||
|
const absXStep = Math.abs(this.xstep);
|
||||||
|
const absYStep = Math.abs(this.ystep);
|
||||||
|
|
||||||
|
// dims is in pattern space, so compare directly with xstep/ystep.
|
||||||
|
if (width > absXStep + 1e-6 || height > absYStep + 1e-6) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tile n covers [x0+n·xstep, x1+n·xstep]; find the range intersecting
|
||||||
|
// [offsetX, offsetX+width].
|
||||||
|
const nXFirst = Math.floor((offsetX - x1) / absXStep) + 1;
|
||||||
|
const nXLast = Math.ceil((offsetX + width - x0) / absXStep) - 1;
|
||||||
|
const nYFirst = Math.floor((offsetY - y1) / absYStep) + 1;
|
||||||
|
const nYLast = Math.ceil((offsetY + height - y0) / absYStep) - 1;
|
||||||
|
return nXLast <= nXFirst && nYLast <= nYFirst ? [nXFirst, nYFirst] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts clippedBBox from device space to pattern space and stores it
|
||||||
|
// as [width, height, offsetX, offsetY] in dims.
|
||||||
|
updatePatternDims(clippedBBox, dims) {
|
||||||
|
const inv = Util.inverseTransform(this.patternBaseMatrix);
|
||||||
|
const c1 = [clippedBBox[0], clippedBBox[1]];
|
||||||
|
const c2 = [clippedBBox[2], clippedBBox[3]];
|
||||||
|
Util.applyTransform(c1, inv);
|
||||||
|
Util.applyTransform(c2, inv);
|
||||||
|
dims[0] = Math.abs(c2[0] - c1[0]);
|
||||||
|
dims[1] = Math.abs(c2[1] - c1[1]);
|
||||||
|
dims[2] = Math.min(c1[0], c2[0]);
|
||||||
|
dims[3] = Math.min(c1[1], c2[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renders the tile operators onto a fresh canvas and returns it.
|
||||||
|
_renderTileCanvas(owner, opIdx, dimx, dimy) {
|
||||||
|
const [x0, y0, x1, y1] = this.bbox;
|
||||||
|
const tmpCanvas = owner.canvasFactory.create(dimx.size, dimy.size);
|
||||||
|
const tmpCtx = tmpCanvas.context;
|
||||||
|
const graphics = this.canvasGraphicsFactory.createCanvasGraphics(
|
||||||
|
tmpCtx,
|
||||||
|
opIdx
|
||||||
|
);
|
||||||
|
graphics.groupLevel = owner.groupLevel;
|
||||||
|
|
||||||
|
this.setFillAndStrokeStyleToContext(graphics, this.paintType, this.color);
|
||||||
|
|
||||||
|
tmpCtx.translate(-dimx.scale * x0, -dimy.scale * y0);
|
||||||
|
// 0: sub-ops are indexed under the top-level opIdx from
|
||||||
|
// createCanvasGraphics.
|
||||||
|
graphics.transform(0, dimx.scale, 0, 0, dimy.scale, 0, 0);
|
||||||
|
|
||||||
|
// Required to balance the save/restore in CanvasGraphics beginDrawing.
|
||||||
|
tmpCtx.save();
|
||||||
|
graphics.dependencyTracker?.save();
|
||||||
|
|
||||||
|
this.clipBbox(graphics, x0, y0, x1, y1);
|
||||||
|
graphics.baseTransform = getCurrentTransform(graphics.ctx);
|
||||||
|
graphics.executeOperatorList(this.operatorList);
|
||||||
|
|
||||||
|
graphics.endDrawing();
|
||||||
|
graphics.dependencyTracker?.restore();
|
||||||
|
tmpCtx.restore();
|
||||||
|
|
||||||
|
return tmpCanvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getCombinedScales() {
|
||||||
|
const scale = new Float32Array(2);
|
||||||
|
Util.singularValueDecompose2dScale(this.matrix, scale);
|
||||||
|
const [matrixScaleX, matrixScaleY] = scale;
|
||||||
|
Util.singularValueDecompose2dScale(this.baseTransform, scale);
|
||||||
|
return [matrixScaleX * scale[0], matrixScaleY * scale[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws a single tile directly onto owner, clipped to path.
|
||||||
|
drawPattern(owner, path, useEOFill = false, [n, m], opIdx) {
|
||||||
|
const [x0, y0, x1, y1] = this.bbox;
|
||||||
|
const bboxWidth = x1 - x0;
|
||||||
|
const bboxHeight = y1 - y0;
|
||||||
|
|
||||||
|
const [combinedScaleX, combinedScaleY] = this._getCombinedScales();
|
||||||
|
const dimx = this.getSizeAndScale(
|
||||||
|
bboxWidth,
|
||||||
|
this.ctx.canvas.width,
|
||||||
|
combinedScaleX
|
||||||
|
);
|
||||||
|
const dimy = this.getSizeAndScale(
|
||||||
|
bboxHeight,
|
||||||
|
this.ctx.canvas.height,
|
||||||
|
combinedScaleY
|
||||||
|
);
|
||||||
|
|
||||||
|
// Isolate blend modes from the main canvas.
|
||||||
|
const tmpCanvas = this._renderTileCanvas(owner, opIdx, dimx, dimy);
|
||||||
|
|
||||||
|
owner.save();
|
||||||
|
if (useEOFill) {
|
||||||
|
owner.ctx.clip(path, "evenodd");
|
||||||
|
} else {
|
||||||
|
owner.ctx.clip(path);
|
||||||
|
}
|
||||||
|
// Position tile (n, m) in device space; the clip above is unaffected
|
||||||
|
// by setTransform.
|
||||||
|
owner.ctx.setTransform(...this.patternBaseMatrix);
|
||||||
|
owner.ctx.translate(n * this.xstep, m * this.ystep);
|
||||||
|
owner.ctx.drawImage(tmpCanvas.canvas, x0, y0, bboxWidth, bboxHeight);
|
||||||
|
|
||||||
|
owner.canvasFactory.destroy(tmpCanvas);
|
||||||
|
owner.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
createPatternCanvas(owner, opIdx) {
|
createPatternCanvas(owner, opIdx) {
|
||||||
const {
|
const [x0, y0, x1, y1] = this.bbox;
|
||||||
bbox,
|
const width = x1 - x0;
|
||||||
operatorList,
|
const height = y1 - y0;
|
||||||
paintType,
|
|
||||||
tilingType,
|
|
||||||
color,
|
|
||||||
canvasGraphicsFactory,
|
|
||||||
} = this;
|
|
||||||
let { xstep, ystep } = this;
|
let { xstep, ystep } = this;
|
||||||
xstep = Math.abs(xstep);
|
xstep = Math.abs(xstep);
|
||||||
ystep = Math.abs(ystep);
|
ystep = Math.abs(ystep);
|
||||||
|
|
||||||
info("TilingType: " + tilingType);
|
info("TilingType: " + this.tilingType);
|
||||||
|
|
||||||
// A tiling pattern as defined by PDF spec 8.7.2 is a cell whose size is
|
// A tiling pattern as defined by PDF spec 8.7.2 is a cell whose size is
|
||||||
// described by bbox, and may repeat regularly by shifting the cell by
|
// described by bbox, and may repeat regularly by shifting the cell by
|
||||||
@ -651,45 +763,32 @@ class TilingPattern {
|
|||||||
// "Figures on adjacent tiles should not overlap" (PDF spec 8.7.3.1),
|
// "Figures on adjacent tiles should not overlap" (PDF spec 8.7.3.1),
|
||||||
// but overlapping cells without common pixels are still valid.
|
// but overlapping cells without common pixels are still valid.
|
||||||
|
|
||||||
const x0 = bbox[0],
|
|
||||||
y0 = bbox[1],
|
|
||||||
x1 = bbox[2],
|
|
||||||
y1 = bbox[3];
|
|
||||||
const width = x1 - x0;
|
|
||||||
const height = y1 - y0;
|
|
||||||
|
|
||||||
// Obtain scale from matrix and current transformation matrix.
|
// Obtain scale from matrix and current transformation matrix.
|
||||||
const scale = new Float32Array(2);
|
const [combinedScaleX, combinedScaleY] = this._getCombinedScales();
|
||||||
Util.singularValueDecompose2dScale(this.matrix, scale);
|
|
||||||
const [matrixScaleX, matrixScaleY] = scale;
|
|
||||||
Util.singularValueDecompose2dScale(this.baseTransform, scale);
|
|
||||||
const combinedScaleX = matrixScaleX * scale[0];
|
|
||||||
const combinedScaleY = matrixScaleY * scale[1];
|
|
||||||
|
|
||||||
|
// Use width and height values that are as close as possible to the end
|
||||||
|
// result when the pattern is used. Too low value makes the pattern look
|
||||||
|
// blurry. Too large value makes it look too crispy.
|
||||||
let canvasWidth = width,
|
let canvasWidth = width,
|
||||||
canvasHeight = height,
|
canvasHeight = height,
|
||||||
redrawHorizontally = false,
|
redrawHorizontally = false,
|
||||||
redrawVertically = false;
|
redrawVertically = false;
|
||||||
|
|
||||||
const xScaledStep = Math.ceil(xstep * combinedScaleX);
|
if (
|
||||||
const yScaledStep = Math.ceil(ystep * combinedScaleY);
|
Math.ceil(xstep * combinedScaleX) >= Math.ceil(width * combinedScaleX)
|
||||||
const xScaledWidth = Math.ceil(width * combinedScaleX);
|
) {
|
||||||
const yScaledHeight = Math.ceil(height * combinedScaleY);
|
|
||||||
|
|
||||||
if (xScaledStep >= xScaledWidth) {
|
|
||||||
canvasWidth = xstep;
|
canvasWidth = xstep;
|
||||||
} else {
|
} else {
|
||||||
redrawHorizontally = true;
|
redrawHorizontally = true;
|
||||||
}
|
}
|
||||||
if (yScaledStep >= yScaledHeight) {
|
if (
|
||||||
|
Math.ceil(ystep * combinedScaleY) >= Math.ceil(height * combinedScaleY)
|
||||||
|
) {
|
||||||
canvasHeight = ystep;
|
canvasHeight = ystep;
|
||||||
} else {
|
} else {
|
||||||
redrawVertically = true;
|
redrawVertically = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use width and height values that are as close as possible to the end
|
|
||||||
// result when the pattern is used. Too low value makes the pattern look
|
|
||||||
// blurry. Too large value makes it look too crispy.
|
|
||||||
const dimx = this.getSizeAndScale(
|
const dimx = this.getSizeAndScale(
|
||||||
canvasWidth,
|
canvasWidth,
|
||||||
this.ctx.canvas.width,
|
this.ctx.canvas.width,
|
||||||
@ -701,43 +800,7 @@ class TilingPattern {
|
|||||||
combinedScaleY
|
combinedScaleY
|
||||||
);
|
);
|
||||||
|
|
||||||
const tmpCanvas = owner.canvasFactory.create(dimx.size, dimy.size);
|
const tmpCanvas = this._renderTileCanvas(owner, opIdx, dimx, dimy);
|
||||||
const tmpCtx = tmpCanvas.context;
|
|
||||||
const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx, opIdx);
|
|
||||||
graphics.groupLevel = owner.groupLevel;
|
|
||||||
|
|
||||||
this.setFillAndStrokeStyleToContext(graphics, paintType, color);
|
|
||||||
|
|
||||||
tmpCtx.translate(-dimx.scale * x0, -dimy.scale * y0);
|
|
||||||
graphics.transform(
|
|
||||||
// We pass 0 as the 'opIdx' argument, but the value is irrelevant.
|
|
||||||
// We know that we are in a 'CanvasNestedDependencyTracker' that captures
|
|
||||||
// all the sub-operations needed to create this pattern canvas and uses
|
|
||||||
// the top-level operation index as their index.
|
|
||||||
0,
|
|
||||||
dimx.scale,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
dimy.scale,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
// To match CanvasGraphics beginDrawing we must save the context here or
|
|
||||||
// else we end up with unbalanced save/restores.
|
|
||||||
tmpCtx.save();
|
|
||||||
graphics.dependencyTracker?.save();
|
|
||||||
|
|
||||||
this.clipBbox(graphics, x0, y0, x1, y1);
|
|
||||||
|
|
||||||
graphics.baseTransform = getCurrentTransform(graphics.ctx);
|
|
||||||
|
|
||||||
graphics.executeOperatorList(operatorList);
|
|
||||||
|
|
||||||
graphics.endDrawing();
|
|
||||||
|
|
||||||
graphics.dependencyTracker?.restore();
|
|
||||||
tmpCtx.restore();
|
|
||||||
|
|
||||||
if (redrawHorizontally || redrawVertically) {
|
if (redrawHorizontally || redrawVertically) {
|
||||||
// The tile is overlapping itself, so we create a new tile with
|
// The tile is overlapping itself, so we create a new tile with
|
||||||
@ -862,14 +925,12 @@ class TilingPattern {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getPattern(ctx, owner, inverse, pathType, opIdx) {
|
getPattern(ctx, owner, inverse, pathType, opIdx) {
|
||||||
// PDF spec 8.7.2 NOTE 1: pattern's matrix is relative to initial matrix.
|
// PDF spec 8.7.2: prepend inverse CTM to patternBaseMatrix to position
|
||||||
let matrix = inverse;
|
// the CSS pattern.
|
||||||
if (pathType !== PathType.SHADING) {
|
const matrix =
|
||||||
matrix = Util.transform(matrix, owner.baseTransform);
|
pathType !== PathType.SHADING
|
||||||
if (this.matrix) {
|
? Util.transform(inverse, this.patternBaseMatrix)
|
||||||
matrix = Util.transform(matrix, this.matrix);
|
: inverse;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const temporaryPatternCanvas = this.createPatternCanvas(owner, opIdx);
|
const temporaryPatternCanvas = this.createPatternCanvas(owner, opIdx);
|
||||||
|
|
||||||
|
|||||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -894,3 +894,4 @@
|
|||||||
!hello_world_rotated.pdf
|
!hello_world_rotated.pdf
|
||||||
!bug2025674.pdf
|
!bug2025674.pdf
|
||||||
!bug2026037.pdf
|
!bug2026037.pdf
|
||||||
|
!tiling_patterns_variations.pdf
|
||||||
|
|||||||
102
test/pdfs/tiling_patterns_variations.pdf
Executable file
102
test/pdfs/tiling_patterns_variations.pdf
Executable file
@ -0,0 +1,102 @@
|
|||||||
|
%PDF-1.4
|
||||||
|
1 0 obj
|
||||||
|
<< /Type /Catalog /Pages 2 0 R >>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<< /Type /Page
|
||||||
|
/Parent 2 0 R
|
||||||
|
/MediaBox [0 0 600 800]
|
||||||
|
/Resources <<
|
||||||
|
/Pattern << /P1 5 0 R /P2 6 0 R /P3 7 0 R /P4 8 0 R /P5 9 0 R >>
|
||||||
|
/ColorSpace << /Cs [/Pattern] /Cs2 [/Pattern /DeviceRGB] >>
|
||||||
|
>>
|
||||||
|
/Contents 4 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
4 0 obj
|
||||||
|
<< /Length 637 >>
|
||||||
|
stream
|
||||||
|
1 g
|
||||||
|
0 0 600 800 re f
|
||||||
|
q /Cs cs /P1 scn 20 600 150 150 re f Q
|
||||||
|
q /Cs cs /P2 scn 220 600 150 150 re f Q
|
||||||
|
q /Cs cs /P3 scn 420 600 150 150 re f Q
|
||||||
|
q /Cs cs /P1 scn 250 460 30 30 re f Q
|
||||||
|
q 250 460 30 30 re W n /Cs cs /P1 scn 220 400 150 150 re f Q
|
||||||
|
q /Cs cs /P4 scn 420 400 150 150 re f Q
|
||||||
|
q /Cs cs /P4 scn 20 250 30 30 re f Q
|
||||||
|
q /Cs cs /P1 scn 1 0 0 1 250 200 cm 0.5 0 0 0.5 0 0 cm 0 0 60 60 re f Q
|
||||||
|
q /Cs2 cs 0.8 0.2 0.2 /P5 scn 420 200 150 150 re f Q
|
||||||
|
0 G 0.5 w
|
||||||
|
20 600 150 150 re S
|
||||||
|
220 600 150 150 re S
|
||||||
|
420 600 150 150 re S
|
||||||
|
20 400 150 150 re S
|
||||||
|
220 400 150 150 re S
|
||||||
|
420 400 150 150 re S
|
||||||
|
20 200 150 150 re S
|
||||||
|
220 200 150 150 re S
|
||||||
|
420 200 150 150 re S
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
<< /Type /Pattern /PatternType 1 /PaintType 1 /TilingType 1 /BBox [0 0 50 50] /XStep 50 /YStep 50 /Resources << >> /Length 30 >>
|
||||||
|
stream
|
||||||
|
0.2 0.4 0.8 rg
|
||||||
|
5 5 40 40 re f
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<< /Type /Pattern /PatternType 1 /PaintType 1 /TilingType 1 /BBox [0 0 20 20] /XStep 50 /YStep 50 /Resources << >> /Length 30 >>
|
||||||
|
stream
|
||||||
|
0.9 0.5 0.1 rg
|
||||||
|
0 0 20 20 re f
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<< /Type /Pattern /PatternType 1 /PaintType 1 /TilingType 1 /BBox [0 0 70 70] /XStep 50 /YStep 50 /Resources << >> /Length 30 >>
|
||||||
|
stream
|
||||||
|
0.1 0.7 0.2 rg
|
||||||
|
0 0 70 70 re f
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
8 0 obj
|
||||||
|
<< /Type /Pattern /PatternType 1 /PaintType 1 /TilingType 1 /BBox [0 0 50 50] /XStep 50 /YStep 50 /Matrix [0.866025 0.5 -0.5 0.866025 0 0] /Resources << >> /Length 30 >>
|
||||||
|
stream
|
||||||
|
0.7 0.1 0.7 rg
|
||||||
|
5 5 40 40 re f
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
9 0 obj
|
||||||
|
<< /Type /Pattern /PatternType 1 /PaintType 2 /TilingType 1 /BBox [0 0 30 30] /XStep 30 /YStep 30 /Resources << >> /Length 31 >>
|
||||||
|
stream
|
||||||
|
0 0 10 30 re f
|
||||||
|
20 0 10 30 re f
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 10
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000009 00000 n
|
||||||
|
0000000058 00000 n
|
||||||
|
0000000115 00000 n
|
||||||
|
0000000369 00000 n
|
||||||
|
0000001058 00000 n
|
||||||
|
0000001250 00000 n
|
||||||
|
0000001442 00000 n
|
||||||
|
0000001634 00000 n
|
||||||
|
0000001867 00000 n
|
||||||
|
trailer
|
||||||
|
<< /Size 10 /Root 1 0 R >>
|
||||||
|
startxref
|
||||||
|
2060
|
||||||
|
%%EOF
|
||||||
@ -14028,5 +14028,12 @@
|
|||||||
"rounds": 1,
|
"rounds": 1,
|
||||||
"link": true,
|
"link": true,
|
||||||
"type": "eq"
|
"type": "eq"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tiling_patterns_variations",
|
||||||
|
"file": "pdfs/tiling_patterns_variations.pdf",
|
||||||
|
"md5": "2870c3136be00ddd975149b2c7d1e6df",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user