'use strict'; var isUndefined = require('./helpers').isUndefined; var ElementWriter = require('./elementWriter'); /** * Creates an instance of PageElementWriter - an extended ElementWriter * which can handle: * - page-breaks (it adds new pages when there's not enough space left), * - repeatable fragments (like table-headers, which are repeated everytime * a page-break occurs) * - transactions (used for unbreakable-blocks when we want to make sure * whole block will be rendered on the same page) */ function PageElementWriter(context, tracker) { this.transactionLevel = 0; this.repeatables = []; this.tracker = tracker; this.writer = new ElementWriter(context, tracker); } function fitOnPage(self, addFct) { var position = addFct(self); if (!position) { self.moveToNextPage(); position = addFct(self); } return position; } PageElementWriter.prototype.addLine = function (line, dontUpdateContextPosition, index) { return fitOnPage(this, function (self) { return self.writer.addLine(line, dontUpdateContextPosition, index); }); }; PageElementWriter.prototype.addImage = function (image, index) { return fitOnPage(this, function (self) { return self.writer.addImage(image, index); }); }; PageElementWriter.prototype.addSVG = function (image, index) { return fitOnPage(this, function (self) { return self.writer.addSVG(image, index); }); }; PageElementWriter.prototype.addQr = function (qr, index) { return fitOnPage(this, function (self) { return self.writer.addQr(qr, index); }); }; PageElementWriter.prototype.addVector = function (vector, ignoreContextX, ignoreContextY, index) { return this.writer.addVector(vector, ignoreContextX, ignoreContextY, index); }; PageElementWriter.prototype.beginClip = function (width, height) { return this.writer.beginClip(width, height); }; PageElementWriter.prototype.endClip = function () { return this.writer.endClip(); }; PageElementWriter.prototype.alignCanvas = function (node) { this.writer.alignCanvas(node); }; PageElementWriter.prototype.addFragment = function (fragment, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition) { if (!this.writer.addFragment(fragment, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition)) { this.moveToNextPage(); this.writer.addFragment(fragment, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition); } }; PageElementWriter.prototype.moveToNextPage = function (pageOrientation) { var nextPage = this.writer.context.moveToNextPage(pageOrientation); // moveToNextPage is called multiple times for table, because is called for each column // and repeatables are inserted only in the first time. If columns are used, is needed // call for table in first column and then for table in the second column (is other repeatables). this.repeatables.forEach(function (rep) { if (isUndefined(rep.insertedOnPages[this.writer.context.page])) { rep.insertedOnPages[this.writer.context.page] = true; this.writer.addFragment(rep, true); } else { this.writer.context.moveDown(rep.height); } }, this); this.writer.tracker.emit('pageChanged', { prevPage: nextPage.prevPage, prevY: nextPage.prevY, y: this.writer.context.y }); }; PageElementWriter.prototype.beginUnbreakableBlock = function (width, height) { if (this.transactionLevel++ === 0) { this.originalX = this.writer.context.x; this.writer.pushContext(width, height); } }; PageElementWriter.prototype.commitUnbreakableBlock = function (forcedX, forcedY) { if (--this.transactionLevel === 0) { var unbreakableContext = this.writer.context; this.writer.popContext(); var nbPages = unbreakableContext.pages.length; if (nbPages > 0) { // no support for multi-page unbreakableBlocks var fragment = unbreakableContext.pages[0]; fragment.xOffset = forcedX; fragment.yOffset = forcedY; //TODO: vectors can influence height in some situations if (nbPages > 1) { // on out-of-context blocs (headers, footers, background) height should be the whole DocumentContext height if (forcedX !== undefined || forcedY !== undefined) { fragment.height = unbreakableContext.getCurrentPage().pageSize.height - unbreakableContext.pageMargins.top - unbreakableContext.pageMargins.bottom; } else { fragment.height = this.writer.context.getCurrentPage().pageSize.height - this.writer.context.pageMargins.top - this.writer.context.pageMargins.bottom; for (var i = 0, l = this.repeatables.length; i < l; i++) { fragment.height -= this.repeatables[i].height; } } } else { fragment.height = unbreakableContext.y; } if (forcedX !== undefined || forcedY !== undefined) { this.writer.addFragment(fragment, true, true, true); } else { this.addFragment(fragment); } } } }; PageElementWriter.prototype.currentBlockToRepeatable = function () { var unbreakableContext = this.writer.context; var rep = { items: [] }; unbreakableContext.pages[0].items.forEach(function (item) { rep.items.push(item); }); rep.xOffset = this.originalX; //TODO: vectors can influence height in some situations rep.height = unbreakableContext.y; rep.insertedOnPages = []; return rep; }; PageElementWriter.prototype.pushToRepeatables = function (rep) { this.repeatables.push(rep); }; PageElementWriter.prototype.popFromRepeatables = function () { this.repeatables.pop(); }; PageElementWriter.prototype.context = function () { return this.writer.context; }; module.exports = PageElementWriter;