Skip to content

Commit 0da1f0a

Browse files
committed
allow pageMargins to be defined with a function
This function will receive the pageNumber as argument. This commit is a port of original work of @seahorsepip in pull request bpampuch#1746 This rework is rebuild on 0.3 version, it adds some unit tests for new helpers and it has integration tests following comments in original pull request bpampuch#1746.
1 parent 8028139 commit 0da1f0a

11 files changed

Lines changed: 481 additions & 29 deletions

src/DocumentContext.js

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isString } from './helpers/variableType';
2+
import { functionalizePageMargin } from './PageSize';
23
import { EventEmitter } from 'events';
34

45
/**
@@ -10,10 +11,10 @@ class DocumentContext extends EventEmitter {
1011
super();
1112
this.pages = [];
1213

13-
this.pageMargins = pageMargins;
14+
this.pageMargins = functionalizePageMargin(pageMargins);
1415

15-
this.x = pageMargins.left;
16-
this.availableWidth = pageSize.width - pageMargins.left - pageMargins.right;
16+
this.x = this.pageMargins(1).left;
17+
this.availableWidth = pageSize.width - this.pageMargins(1).left - this.pageMargins(1).right;
1718
this.availableHeight = 0;
1819
this.page = -1;
1920

@@ -131,9 +132,13 @@ class DocumentContext extends EventEmitter {
131132
}
132133

133134
initializePage() {
134-
this.y = this.pageMargins.top;
135-
this.availableHeight = this.getCurrentPage().pageSize.height - this.pageMargins.top - this.pageMargins.bottom;
136-
this.pageSnapshot().availableWidth = this.getCurrentPage().pageSize.width - this.pageMargins.left - this.pageMargins.right;
135+
this.y = this.getCurrentPage().pageMargins.top;
136+
this.availableHeight = this.getCurrentPage().pageSize.height
137+
- this.getCurrentPage().pageMargins.top
138+
- this.getCurrentPage().pageMargins.bottom;
139+
this.pageSnapshot().availableWidth = this.getCurrentPage().pageSize.width
140+
- this.getCurrentPage().pageMargins.left
141+
- this.getCurrentPage().pageMargins.right;
137142
}
138143

139144
pageSnapshot() {
@@ -147,11 +152,11 @@ class DocumentContext extends EventEmitter {
147152
moveTo(x, y) {
148153
if (x !== undefined && x !== null) {
149154
this.x = x;
150-
this.availableWidth = this.getCurrentPage().pageSize.width - this.x - this.pageMargins.right;
155+
this.availableWidth = this.getCurrentPage().pageSize.width - this.x - this.getCurrentPage().pageMargins.right;
151156
}
152157
if (y !== undefined && y !== null) {
153158
this.y = y;
154-
this.availableHeight = this.getCurrentPage().pageSize.height - this.y - this.pageMargins.bottom;
159+
this.availableHeight = this.getCurrentPage().pageSize.height - this.y - this.getCurrentPage().pageMargins.bottom;
155160
}
156161
}
157162

@@ -218,7 +223,11 @@ class DocumentContext extends EventEmitter {
218223
}
219224

220225
addPage(pageSize) {
221-
let page = { items: [], pageSize: pageSize };
226+
let page = {
227+
items: [],
228+
pageSize: pageSize,
229+
pageMargins: this.pageMargins(this.pages.length + 1),
230+
};
222231
this.pages.push(page);
223232
this.backgroundLength.push(0);
224233
this.page = this.pages.length - 1;
@@ -239,8 +248,12 @@ class DocumentContext extends EventEmitter {
239248

240249
getCurrentPosition() {
241250
let pageSize = this.getCurrentPage().pageSize;
242-
let innerHeight = pageSize.height - this.pageMargins.top - this.pageMargins.bottom;
243-
let innerWidth = pageSize.width - this.pageMargins.left - this.pageMargins.right;
251+
let innerHeight = pageSize.height
252+
- this.getCurrentPage().pageMargins.top
253+
- this.getCurrentPage().pageMargins.bottom;
254+
let innerWidth = pageSize.width
255+
- this.getCurrentPage().pageMargins.left
256+
- this.getCurrentPage().pageMargins.right;
244257

245258
return {
246259
pageNumber: this.page + 1,
@@ -249,8 +262,9 @@ class DocumentContext extends EventEmitter {
249262
pageInnerWidth: innerWidth,
250263
left: this.x,
251264
top: this.y,
252-
verticalRatio: ((this.y - this.pageMargins.top) / innerHeight),
253-
horizontalRatio: ((this.x - this.pageMargins.left) / innerWidth)
265+
verticalRatio: ((this.y - this.getCurrentPage().pageMargins.top) / innerHeight),
266+
horizontalRatio: ((this.x - this.getCurrentPage().pageMargins.left) / innerWidth),
267+
pageMargins: this.getCurrentPage().pageMargins,
254268
};
255269
}
256270
}

src/ElementWriter.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,9 @@ class ElementWriter extends EventEmitter {
362362
*/
363363
pushContext(contextOrWidth, height) {
364364
if (contextOrWidth === undefined) {
365-
height = this.context().getCurrentPage().height - this.context().pageMargins.top - this.context().pageMargins.bottom;
365+
height = this.context().getCurrentPage().height
366+
- this.context().getCurrentPage().pageMargins.top
367+
- this.context().getCurrentPage().pageMargins.bottom;
366368
contextOrWidth = this.context().availableWidth;
367369
}
368370

src/LayoutBuilder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ class LayoutBuilder {
219219
let node = nodeGetter(pageIndex + 1, l, this.writer.context().pages[pageIndex].pageSize);
220220

221221
if (node) {
222-
let sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.pageMargins);
222+
let sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.writer.context().getCurrentPage().pageMargins);
223223
this.writer.beginUnbreakableBlock(sizes.width, sizes.height);
224224
node = this.docPreprocessor.preprocessDocument(node);
225225
this.processNode(this.docMeasure.measureDocument(node));

src/PageSize.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import sizes from './standardPageSizes';
2-
import { isString, isNumber } from './helpers/variableType';
2+
import { isString, isNumber, isFunction, isObject, isValue } from './helpers/variableType';
33

44
export function normalizePageSize(pageSize, pageOrientation) {
55
function isNeedSwapPageSizes(pageOrientation) {
@@ -36,6 +36,27 @@ export function normalizePageSize(pageSize, pageOrientation) {
3636
return size;
3737
}
3838

39+
function isPageMarginObject(margin) {
40+
if (isObject(margin)) {
41+
const { left, top, right, bottom } = margin;
42+
43+
if (isValue(left) && isValue(top) && isValue(right) && isValue(bottom)) {
44+
return true;
45+
}
46+
}
47+
return false;
48+
}
49+
50+
51+
/*
52+
* Accepts margin definition as being:
53+
* * a number to set same margin size on all margins
54+
* * a function which will receive pageNumber as argument
55+
* * an array with two numbers to set horizontal and vertical margin respectively
56+
* * an array with four numbers to set left, top, right and bottom margin respectively
57+
*
58+
* Normalized value is an object with the four margins as property.
59+
* */
3960
export function normalizePageMargin(margin) {
4061
if (isNumber(margin)) {
4162
margin = { left: margin, right: margin, top: margin, bottom: margin };
@@ -49,5 +70,43 @@ export function normalizePageMargin(margin) {
4970
}
5071
}
5172

73+
if (!isFunction(margin) && !isPageMarginObject(margin)) {
74+
throw new Error('Invalid pageMargins definition');
75+
}
76+
5277
return margin;
5378
}
79+
80+
/*
81+
* Returns a function accepting pageNumber as argument and returning
82+
* a normalized page margin object
83+
* */
84+
export function functionalizePageMargin(margin) {
85+
let marginFn;
86+
if (isFunction(margin)) {
87+
marginFn = function (pageNumber) {
88+
return normalizePageMargin(margin(pageNumber));
89+
};
90+
} else {
91+
if (!isPageMarginObject(margin)) {
92+
margin = normalizePageMargin(margin);
93+
}
94+
95+
const { left, top, right, bottom } = margin;
96+
marginFn = function () {
97+
return {
98+
left,
99+
top,
100+
right,
101+
bottom,
102+
};
103+
};
104+
105+
}
106+
107+
if (!isFunction(marginFn)) {
108+
throw new Error(`Unable to functionalize pageMargin: ${margin}`);
109+
}
110+
111+
return marginFn;
112+
}

src/helpers/variableType.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ export function isNumber(variable) {
1414
return (typeof variable === 'number') || (variable instanceof Number);
1515
}
1616

17+
/**
18+
* @param {any} variable
19+
* @returns {boolean}
20+
*/
21+
export function isFunction(variable) {
22+
return (typeof variable === 'function') || (variable instanceof Function);
23+
}
24+
1725
/**
1826
* @param {any} variable
1927
* @returns {boolean}

tests/integration/integrationTestHelper.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ var LayoutBuilder = require('../../js/LayoutBuilder').default;
66
var SVGMeasure = require('../../js/SVGMeasure').default;
77

88
class IntegrationTestHelper {
9-
constructor() {
10-
this.MARGINS = { top: 40, left: 40, right: 40, bottom: 40 };
9+
constructor(options = {}) {
10+
this.MARGINS = options.margins || { top: 40, left: 40, right: 40, bottom: 40 };
1111
this.LINE_HEIGHT = 14.0625;
1212
this.DEFAULT_BULLET_SPACER = '9. ';
1313
}
@@ -28,7 +28,7 @@ class IntegrationTestHelper {
2828
var pageSize = { width: size[0], height: size[1], orientation: 'portrait' };
2929

3030
this.pdfDocument = new PDFDocument(fontDescriptors, docDefinition.images, docDefinition.attachments, { size: [pageSize.width, pageSize.height], compress: false });
31-
var builder = new LayoutBuilder(pageSize, { left: this.MARGINS.left, right: this.MARGINS.right, top: this.MARGINS.top, bottom: this.MARGINS.bottom }, new SVGMeasure());
31+
var builder = new LayoutBuilder(pageSize, this.MARGINS, new SVGMeasure());
3232

3333
return builder.layoutDocument(
3434
docDefinition.content,
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
'use strict';
2+
3+
var assert = require('assert');
4+
5+
var integrationTestHelper = require('./integrationTestHelper');
6+
7+
describe('Integration test: pageMargins', function () {
8+
it('margins values with function using different values for even and odd pages', function () {
9+
function marginFn(pageNumber) {
10+
if (pageNumber % 2 === 0) {
11+
return 10;
12+
}
13+
return [20, 30];
14+
}
15+
16+
var testHelper = new integrationTestHelper({ margins: marginFn });
17+
18+
var pages = testHelper.renderPages('A7', {
19+
content: [
20+
{ text: 'First page', pageBreak: 'after' },
21+
{ text: 'Second page', pageBreak: 'after' },
22+
{ text: 'Third page', pageBreak: 'after' },
23+
{ text: 'Fourth page'},
24+
]
25+
});
26+
27+
assert.equal(pages.length, 4);
28+
assert.deepEqual(pages[0].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
29+
assert.deepEqual(pages[1].pageMargins, { left: 10, right: 10, top: 10, bottom: 10 });
30+
assert.deepEqual(pages[2].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
31+
assert.deepEqual(pages[3].pageMargins, { left: 10, right: 10, top: 10, bottom: 10 });
32+
});
33+
34+
it('margins values when function use dynamic left value', function () {
35+
function marginFn(pageNumber) {
36+
return [(pageNumber %2 === 0) ? 0 : 20, 30, 30, 30];
37+
}
38+
39+
var testHelper = new integrationTestHelper({ margins: marginFn });
40+
41+
var pages = testHelper.renderPages('A7', {
42+
content: [
43+
{ text: 'First page', pageBreak: 'after' },
44+
{ text: 'Second page', pageBreak: 'after' },
45+
{ text: 'Third page', pageBreak: 'after' },
46+
{ text: 'Fourth page'},
47+
]
48+
});
49+
50+
assert.equal(pages.length, 4);
51+
assert.deepEqual(pages[0].pageMargins, { left: 20, right: 30, top: 30, bottom: 30 });
52+
assert.deepEqual(pages[1].pageMargins, { left: 0, right: 30, top: 30, bottom: 30 });
53+
assert.deepEqual(pages[2].pageMargins, { left: 20, right: 30, top: 30, bottom: 30 });
54+
assert.deepEqual(pages[3].pageMargins, { left: 0, right: 30, top: 30, bottom: 30 });
55+
});
56+
57+
it('margins values when function use dynamic top value', function () {
58+
function marginFn(pageNumber) {
59+
return [30, (pageNumber % 2 === 0) ? 0 : 20, 30, 30];
60+
}
61+
62+
var testHelper = new integrationTestHelper({ margins: marginFn });
63+
64+
var pages = testHelper.renderPages('A7', {
65+
content: [
66+
{ text: 'First page', pageBreak: 'after' },
67+
{ text: 'Second page', pageBreak: 'after' },
68+
{ text: 'Third page', pageBreak: 'after' },
69+
{ text: 'Fourth page'},
70+
]
71+
});
72+
73+
assert.equal(pages.length, 4);
74+
assert.deepEqual(pages[0].pageMargins, { left: 30, right: 30, top: 20, bottom: 30 });
75+
assert.deepEqual(pages[1].pageMargins, { left: 30, right: 30, top: 0, bottom: 30 });
76+
assert.deepEqual(pages[2].pageMargins, { left: 30, right: 30, top: 20, bottom: 30 });
77+
assert.deepEqual(pages[3].pageMargins, { left: 30, right: 30, top: 0, bottom: 30 });
78+
});
79+
80+
it('margins values when function use dynamic horizontal value', function () {
81+
function marginFn(pageNumber) {
82+
return [(pageNumber %2 === 0) ? 0 : 20, 30];
83+
}
84+
85+
var testHelper = new integrationTestHelper({ margins: marginFn });
86+
87+
var pages = testHelper.renderPages('A7', {
88+
content: [
89+
{ text: 'First page', pageBreak: 'after' },
90+
{ text: 'Second page', pageBreak: 'after' },
91+
{ text: 'Third page', pageBreak: 'after' },
92+
{ text: 'Fourth page'},
93+
]
94+
});
95+
96+
assert.equal(pages.length, 4);
97+
assert.deepEqual(pages[0].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
98+
assert.deepEqual(pages[1].pageMargins, { left: 0, right: 0, top: 30, bottom: 30 });
99+
assert.deepEqual(pages[2].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
100+
assert.deepEqual(pages[3].pageMargins, { left: 0, right: 0, top: 30, bottom: 30 });
101+
});
102+
103+
it('margins values when function use dynamic vertical value', function () {
104+
function marginFn(pageNumber) {
105+
return [20, (pageNumber %2 === 0) ? 0 : 30];
106+
}
107+
108+
var testHelper = new integrationTestHelper({ margins: marginFn });
109+
110+
var pages = testHelper.renderPages('A7', {
111+
content: [
112+
{ text: 'First page', pageBreak: 'after' },
113+
{ text: 'Second page', pageBreak: 'after' },
114+
{ text: 'Third page', pageBreak: 'after' },
115+
{ text: 'Fourth page'},
116+
]
117+
});
118+
119+
assert.equal(pages.length, 4);
120+
assert.deepEqual(pages[0].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
121+
assert.deepEqual(pages[1].pageMargins, { left: 20, right: 20, top: 0, bottom: 0 });
122+
assert.deepEqual(pages[2].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
123+
assert.deepEqual(pages[3].pageMargins, { left: 20, right: 20, top: 0, bottom: 0 });
124+
});
125+
});

tests/unit/LayoutBuilder.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1910,7 +1910,7 @@ describe('LayoutBuilder', function () {
19101910

19111911
builder.layoutDocument(docStructure, pdfDocument, styleDictionary, defaultStyle, background, header, footer, watermark, pageBreakBeforeFunction);
19121912

1913-
assert.deepEqual(pageBreakBeforeFunction.getCall(0).args[0].startPosition, { pageNumber: 1, left: 40, top: 40, verticalRatio: 0, horizontalRatio: 0, pageOrientation: 'portrait', pageInnerHeight: 720, pageInnerWidth: 320 });
1913+
assert.deepEqual(pageBreakBeforeFunction.getCall(0).args[0].startPosition, { pageNumber: 1, left: 40, top: 40, verticalRatio: 0, horizontalRatio: 0, pageOrientation: 'portrait', pageInnerHeight: 720, pageInnerWidth: 320, pageMargins: { left: 40, top: 40, right: 40, bottom: 40 } });
19141914
});
19151915

19161916
it('should provide the pageOrientation of the node', function () {

0 commit comments

Comments
 (0)