Skip to content

Commit

Permalink
allow pageMargins to be defined with a function
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Trim committed Jul 26, 2022
1 parent 8028139 commit 142df4a
Show file tree
Hide file tree
Showing 11 changed files with 481 additions and 29 deletions.
40 changes: 27 additions & 13 deletions src/DocumentContext.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isString } from './helpers/variableType';
import { functionalizePageMargin } from './PageSize';
import { EventEmitter } from 'events';

/**
Expand All @@ -10,10 +11,10 @@ class DocumentContext extends EventEmitter {
super();
this.pages = [];

this.pageMargins = pageMargins;
this.pageMargins = functionalizePageMargin(pageMargins);

this.x = pageMargins.left;
this.availableWidth = pageSize.width - pageMargins.left - pageMargins.right;
this.x = this.pageMargins(1).left;
this.availableWidth = pageSize.width - this.pageMargins(1).left - this.pageMargins(1).right;
this.availableHeight = 0;
this.page = -1;

Expand Down Expand Up @@ -131,9 +132,13 @@ class DocumentContext extends EventEmitter {
}

initializePage() {
this.y = this.pageMargins.top;
this.availableHeight = this.getCurrentPage().pageSize.height - this.pageMargins.top - this.pageMargins.bottom;
this.pageSnapshot().availableWidth = this.getCurrentPage().pageSize.width - this.pageMargins.left - this.pageMargins.right;
this.y = this.getCurrentPage().pageMargins.top;
this.availableHeight = this.getCurrentPage().pageSize.height
- this.getCurrentPage().pageMargins.top
- this.getCurrentPage().pageMargins.bottom;
this.pageSnapshot().availableWidth = this.getCurrentPage().pageSize.width
- this.getCurrentPage().pageMargins.left
- this.getCurrentPage().pageMargins.right;
}

pageSnapshot() {
Expand All @@ -147,11 +152,11 @@ class DocumentContext extends EventEmitter {
moveTo(x, y) {
if (x !== undefined && x !== null) {
this.x = x;
this.availableWidth = this.getCurrentPage().pageSize.width - this.x - this.pageMargins.right;
this.availableWidth = this.getCurrentPage().pageSize.width - this.x - this.getCurrentPage().pageMargins.right;
}
if (y !== undefined && y !== null) {
this.y = y;
this.availableHeight = this.getCurrentPage().pageSize.height - this.y - this.pageMargins.bottom;
this.availableHeight = this.getCurrentPage().pageSize.height - this.y - this.getCurrentPage().pageMargins.bottom;
}
}

Expand Down Expand Up @@ -218,7 +223,11 @@ class DocumentContext extends EventEmitter {
}

addPage(pageSize) {
let page = { items: [], pageSize: pageSize };
let page = {
items: [],
pageSize: pageSize,
pageMargins: this.pageMargins(this.pages.length + 1),
};
this.pages.push(page);
this.backgroundLength.push(0);
this.page = this.pages.length - 1;
Expand All @@ -239,8 +248,12 @@ class DocumentContext extends EventEmitter {

getCurrentPosition() {
let pageSize = this.getCurrentPage().pageSize;
let innerHeight = pageSize.height - this.pageMargins.top - this.pageMargins.bottom;
let innerWidth = pageSize.width - this.pageMargins.left - this.pageMargins.right;
let innerHeight = pageSize.height
- this.getCurrentPage().pageMargins.top
- this.getCurrentPage().pageMargins.bottom;
let innerWidth = pageSize.width
- this.getCurrentPage().pageMargins.left
- this.getCurrentPage().pageMargins.right;

return {
pageNumber: this.page + 1,
Expand All @@ -249,8 +262,9 @@ class DocumentContext extends EventEmitter {
pageInnerWidth: innerWidth,
left: this.x,
top: this.y,
verticalRatio: ((this.y - this.pageMargins.top) / innerHeight),
horizontalRatio: ((this.x - this.pageMargins.left) / innerWidth)
verticalRatio: ((this.y - this.getCurrentPage().pageMargins.top) / innerHeight),
horizontalRatio: ((this.x - this.getCurrentPage().pageMargins.left) / innerWidth),
pageMargins: this.getCurrentPage().pageMargins,
};
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/ElementWriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,9 @@ class ElementWriter extends EventEmitter {
*/
pushContext(contextOrWidth, height) {
if (contextOrWidth === undefined) {
height = this.context().getCurrentPage().height - this.context().pageMargins.top - this.context().pageMargins.bottom;
height = this.context().getCurrentPage().height
- this.context().getCurrentPage().pageMargins.top
- this.context().getCurrentPage().pageMargins.bottom;
contextOrWidth = this.context().availableWidth;
}

Expand Down
2 changes: 1 addition & 1 deletion src/LayoutBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class LayoutBuilder {
let node = nodeGetter(pageIndex + 1, l, this.writer.context().pages[pageIndex].pageSize);

if (node) {
let sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.pageMargins);
let sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.writer.context().getCurrentPage().pageMargins);
this.writer.beginUnbreakableBlock(sizes.width, sizes.height);
node = this.docPreprocessor.preprocessDocument(node);
this.processNode(this.docMeasure.measureDocument(node));
Expand Down
61 changes: 60 additions & 1 deletion src/PageSize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import sizes from './standardPageSizes';
import { isString, isNumber } from './helpers/variableType';
import { isString, isNumber, isFunction, isObject, isValue } from './helpers/variableType';

export function normalizePageSize(pageSize, pageOrientation) {
function isNeedSwapPageSizes(pageOrientation) {
Expand Down Expand Up @@ -36,6 +36,27 @@ export function normalizePageSize(pageSize, pageOrientation) {
return size;
}

function isPageMarginObject(margin) {
if (isObject(margin)) {
const { left, top, right, bottom } = margin;

if (isValue(left) && isValue(top) && isValue(right) && isValue(bottom)) {
return true;
}
}
return false;
}


/*
* Accepts margin definition as being:
* * a number to set same margin size on all margins
* * a function which will receive pageNumber as argument
* * an array with two numbers to set horizontal and vertical margin respectively
* * an array with four numbers to set left, top, right and bottom margin respectively
*
* Normalized value is an object with the four margins as property.
* */
export function normalizePageMargin(margin) {
if (isNumber(margin)) {
margin = { left: margin, right: margin, top: margin, bottom: margin };
Expand All @@ -49,5 +70,43 @@ export function normalizePageMargin(margin) {
}
}

if (!isFunction(margin) && !isPageMarginObject(margin)) {
throw new Error('Invalid pageMargins definition');
}

return margin;
}

/*
* Returns a function accepting pageNumber as argument and returning
* a normalized page margin object
* */
export function functionalizePageMargin(margin) {
let marginFn;
if (isFunction(margin)) {
marginFn = function (pageNumber) {
return normalizePageMargin(margin(pageNumber));
};
} else {
if (!isPageMarginObject(margin)) {
margin = normalizePageMargin(margin);
}

const { left, top, right, bottom } = margin;
marginFn = function () {
return {
left,
top,
right,
bottom,
};
};

}

if (!isFunction(marginFn)) {
throw new Error(`Unable to functionalize pageMargin: ${margin}`);
}

return marginFn;
}
8 changes: 8 additions & 0 deletions src/helpers/variableType.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ export function isNumber(variable) {
return (typeof variable === 'number') || (variable instanceof Number);
}

/**
* @param {any} variable
* @returns {boolean}
*/
export function isFunction(variable) {
return (typeof variable === 'function') || (variable instanceof Function);
}

/**
* @param {any} variable
* @returns {boolean}
Expand Down
6 changes: 3 additions & 3 deletions tests/integration/integrationTestHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ var LayoutBuilder = require('../../js/LayoutBuilder').default;
var SVGMeasure = require('../../js/SVGMeasure').default;

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

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

return builder.layoutDocument(
docDefinition.content,
Expand Down
125 changes: 125 additions & 0 deletions tests/integration/pageMargins.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
'use strict';

var assert = require('assert');

var integrationTestHelper = require('./integrationTestHelper');

describe('Integration test: pageMargins', function () {
it('margins values with function using different values for even and odd pages', function () {
function marginFn(pageNumber) {
if (pageNumber % 2 === 0) {
return 10;
}
return [20, 30];
}

var testHelper = new integrationTestHelper({ margins: marginFn });

var pages = testHelper.renderPages('A7', {
content: [
{ text: 'First page', pageBreak: 'after' },
{ text: 'Second page', pageBreak: 'after' },
{ text: 'Third page', pageBreak: 'after' },
{ text: 'Fourth page'},
]
});

assert.equal(pages.length, 4);
assert.deepEqual(pages[0].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
assert.deepEqual(pages[1].pageMargins, { left: 10, right: 10, top: 10, bottom: 10 });
assert.deepEqual(pages[2].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
assert.deepEqual(pages[3].pageMargins, { left: 10, right: 10, top: 10, bottom: 10 });
});

it('margins values when function use dynamic left value', function () {
function marginFn(pageNumber) {
return [(pageNumber %2 === 0) ? 0 : 20, 30, 30, 30];
}

var testHelper = new integrationTestHelper({ margins: marginFn });

var pages = testHelper.renderPages('A7', {
content: [
{ text: 'First page', pageBreak: 'after' },
{ text: 'Second page', pageBreak: 'after' },
{ text: 'Third page', pageBreak: 'after' },
{ text: 'Fourth page'},
]
});

assert.equal(pages.length, 4);
assert.deepEqual(pages[0].pageMargins, { left: 20, right: 30, top: 30, bottom: 30 });
assert.deepEqual(pages[1].pageMargins, { left: 0, right: 30, top: 30, bottom: 30 });
assert.deepEqual(pages[2].pageMargins, { left: 20, right: 30, top: 30, bottom: 30 });
assert.deepEqual(pages[3].pageMargins, { left: 0, right: 30, top: 30, bottom: 30 });
});

it('margins values when function use dynamic top value', function () {
function marginFn(pageNumber) {
return [30, (pageNumber % 2 === 0) ? 0 : 20, 30, 30];
}

var testHelper = new integrationTestHelper({ margins: marginFn });

var pages = testHelper.renderPages('A7', {
content: [
{ text: 'First page', pageBreak: 'after' },
{ text: 'Second page', pageBreak: 'after' },
{ text: 'Third page', pageBreak: 'after' },
{ text: 'Fourth page'},
]
});

assert.equal(pages.length, 4);
assert.deepEqual(pages[0].pageMargins, { left: 30, right: 30, top: 20, bottom: 30 });
assert.deepEqual(pages[1].pageMargins, { left: 30, right: 30, top: 0, bottom: 30 });
assert.deepEqual(pages[2].pageMargins, { left: 30, right: 30, top: 20, bottom: 30 });
assert.deepEqual(pages[3].pageMargins, { left: 30, right: 30, top: 0, bottom: 30 });
});

it('margins values when function use dynamic horizontal value', function () {
function marginFn(pageNumber) {
return [(pageNumber %2 === 0) ? 0 : 20, 30];
}

var testHelper = new integrationTestHelper({ margins: marginFn });

var pages = testHelper.renderPages('A7', {
content: [
{ text: 'First page', pageBreak: 'after' },
{ text: 'Second page', pageBreak: 'after' },
{ text: 'Third page', pageBreak: 'after' },
{ text: 'Fourth page'},
]
});

assert.equal(pages.length, 4);
assert.deepEqual(pages[0].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
assert.deepEqual(pages[1].pageMargins, { left: 0, right: 0, top: 30, bottom: 30 });
assert.deepEqual(pages[2].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
assert.deepEqual(pages[3].pageMargins, { left: 0, right: 0, top: 30, bottom: 30 });
});

it('margins values when function use dynamic vertical value', function () {
function marginFn(pageNumber) {
return [20, (pageNumber %2 === 0) ? 0 : 30];
}

var testHelper = new integrationTestHelper({ margins: marginFn });

var pages = testHelper.renderPages('A7', {
content: [
{ text: 'First page', pageBreak: 'after' },
{ text: 'Second page', pageBreak: 'after' },
{ text: 'Third page', pageBreak: 'after' },
{ text: 'Fourth page'},
]
});

assert.equal(pages.length, 4);
assert.deepEqual(pages[0].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
assert.deepEqual(pages[1].pageMargins, { left: 20, right: 20, top: 0, bottom: 0 });
assert.deepEqual(pages[2].pageMargins, { left: 20, right: 20, top: 30, bottom: 30 });
assert.deepEqual(pages[3].pageMargins, { left: 20, right: 20, top: 0, bottom: 0 });
});
});
2 changes: 1 addition & 1 deletion tests/unit/LayoutBuilder.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1910,7 +1910,7 @@ describe('LayoutBuilder', function () {

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

assert.deepEqual(pageBreakBeforeFunction.getCall(0).args[0].startPosition, { pageNumber: 1, left: 40, top: 40, verticalRatio: 0, horizontalRatio: 0, pageOrientation: 'portrait', pageInnerHeight: 720, pageInnerWidth: 320 });
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 } });
});

it('should provide the pageOrientation of the node', function () {
Expand Down
Loading

0 comments on commit 142df4a

Please sign in to comment.