pdfmake
Version:
Client/server side PDF printing in pure JavaScript
418 lines (336 loc) • 10.1 kB
JavaScript
import { isNumber } from './helpers/variableType';
import { pack, offsetVector } from './helpers/tools';
import DocumentContext from './DocumentContext';
import { EventEmitter } from 'events';
/**
* A line/vector writer, which adds elements to current page and sets
* their positions based on the context
*/
class ElementWriter extends EventEmitter {
/**
* @param {DocumentContext} context
*/
constructor(context) {
super();
this._context = context;
this.contextStack = [];
}
/**
* @returns {DocumentContext}
*/
context() {
return this._context;
}
addLine(line, dontUpdateContextPosition, index) {
let height = line.getHeight();
let context = this.context();
let page = context.getCurrentPage();
let position = this.getCurrentPositionOnPage();
if (context.availableHeight < height || !page) {
return false;
}
line.x = context.x + (line.x || 0);
line.y = context.y + (line.y || 0);
this.alignLine(line);
addPageItem(page, {
type: 'line',
item: line
}, index);
this.emit('lineAdded', line);
if (!dontUpdateContextPosition) {
context.moveDown(height);
}
return position;
}
alignLine(line) {
let width = this.context().availableWidth;
let lineWidth = line.getWidth();
let alignment = line.inlines && line.inlines.length > 0 && line.inlines[0].alignment;
let offset = 0;
switch (alignment) {
case 'right':
offset = width - lineWidth;
break;
case 'center':
offset = (width - lineWidth) / 2;
break;
}
if (offset) {
line.x = (line.x || 0) + offset;
}
if (alignment === 'justify' &&
!line.newLineForced &&
!line.lastLineInParagraph &&
line.inlines.length > 1) {
let additionalSpacing = (width - lineWidth) / (line.inlines.length - 1);
for (let i = 1, l = line.inlines.length; i < l; i++) {
offset = i * additionalSpacing;
line.inlines[i].x += offset;
line.inlines[i].justifyShift = additionalSpacing;
}
}
}
addImage(image, index) {
let context = this.context();
let page = context.getCurrentPage();
let position = this.getCurrentPositionOnPage();
if (!page || (image.absolutePosition === undefined && context.availableHeight < image._height && page.items.length > 0)) {
return false;
}
if (image._x === undefined) {
image._x = image.x || 0;
}
image.x = context.x + image._x;
image.y = context.y;
this.alignImage(image);
addPageItem(page, {
type: 'image',
item: image
}, index);
context.moveDown(image._height);
return position;
}
addCanvas(node, index) {
let context = this.context();
let page = context.getCurrentPage();
let positions = [];
let height = node._minHeight;
if (!page || (node.absolutePosition === undefined && context.availableHeight < height)) {
// TODO: support for canvas larger than a page
// TODO: support for other overflow methods
return false;
}
this.alignCanvas(node);
node.canvas.forEach(function (vector) {
let position = this.addVector(vector, false, false, index);
positions.push(position);
if (index !== undefined) {
index++;
}
}, this);
context.moveDown(height);
return positions;
}
addSVG(image, index) {
// TODO: same as addImage
let context = this.context();
let page = context.getCurrentPage();
let position = this.getCurrentPositionOnPage();
if (!page || (image.absolutePosition === undefined && context.availableHeight < image._height && page.items.length > 0)) {
return false;
}
if (image._x === undefined) {
image._x = image.x || 0;
}
image.x = context.x + image._x;
image.y = context.y;
this.alignImage(image);
addPageItem(page, {
type: 'svg',
item: image
}, index);
context.moveDown(image._height);
return position;
}
addQr(qr, index) {
let context = this.context();
let page = context.getCurrentPage();
let position = this.getCurrentPositionOnPage();
if (!page || (qr.absolutePosition === undefined && context.availableHeight < qr._height)) {
return false;
}
if (qr._x === undefined) {
qr._x = qr.x || 0;
}
qr.x = context.x + qr._x;
qr.y = context.y;
this.alignImage(qr);
for (let i = 0, l = qr._canvas.length; i < l; i++) {
let vector = qr._canvas[i];
vector.x += qr.x;
vector.y += qr.y;
this.addVector(vector, true, true, index);
}
context.moveDown(qr._height);
return position;
}
addAttachment(attachment, index) {
let context = this.context();
let page = context.getCurrentPage();
let position = this.getCurrentPositionOnPage();
if (!page || (attachment.absolutePosition === undefined && context.availableHeight < attachment._height && page.items.length > 0)) {
return false;
}
if (attachment._x === undefined) {
attachment._x = attachment.x || 0;
}
attachment.x = context.x + attachment._x;
attachment.y = context.y;
addPageItem(page, {
type: 'attachment',
item: attachment
}, index);
context.moveDown(attachment._height);
return position;
}
alignImage(image) {
let width = this.context().availableWidth;
let imageWidth = image._minWidth;
let offset = 0;
switch (image._alignment) {
case 'right':
offset = width - imageWidth;
break;
case 'center':
offset = (width - imageWidth) / 2;
break;
}
if (offset) {
image.x = (image.x || 0) + offset;
}
}
alignCanvas(node) {
let width = this.context().availableWidth;
let canvasWidth = node._minWidth;
let offset = 0;
switch (node._alignment) {
case 'right':
offset = width - canvasWidth;
break;
case 'center':
offset = (width - canvasWidth) / 2;
break;
}
if (offset) {
node.canvas.forEach(vector => {
offsetVector(vector, offset, 0);
});
}
}
addVector(vector, ignoreContextX, ignoreContextY, index, forcePage) {
let context = this.context();
let page = context.getCurrentPage();
if (isNumber(forcePage)) {
page = context.pages[forcePage];
}
let position = this.getCurrentPositionOnPage();
if (page) {
offsetVector(vector, ignoreContextX ? 0 : context.x, ignoreContextY ? 0 : context.y);
addPageItem(page, {
type: 'vector',
item: vector
}, index);
return position;
}
}
beginClip(width, height) {
let ctx = this.context();
let page = ctx.getCurrentPage();
page.items.push({
type: 'beginClip',
item: { x: ctx.x, y: ctx.y, width: width, height: height }
});
return true;
}
endClip() {
let ctx = this.context();
let page = ctx.getCurrentPage();
page.items.push({
type: 'endClip'
});
return true;
}
addFragment(block, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition) {
let ctx = this.context();
let page = ctx.getCurrentPage();
if (!useBlockXOffset && block.height > ctx.availableHeight) {
return false;
}
block.items.forEach(item => {
switch (item.type) {
case 'line':
var l = item.item.clone();
if (l._node) {
l._node.positions[0].pageNumber = ctx.page + 1;
}
l.x = (l.x || 0) + (useBlockXOffset ? (block.xOffset || 0) : ctx.x);
l.y = (l.y || 0) + (useBlockYOffset ? (block.yOffset || 0) : ctx.y);
page.items.push({
type: 'line',
item: l
});
break;
case 'vector':
var v = pack(item.item);
offsetVector(v, useBlockXOffset ? (block.xOffset || 0) : ctx.x, useBlockYOffset ? (block.yOffset || 0) : ctx.y);
if (v._isFillColorFromUnbreakable) {
// If the item is a fillColor from an unbreakable block
// We have to add it at the beginning of the items body array of the page
delete v._isFillColorFromUnbreakable;
const endOfBackgroundItemsIndex = ctx.backgroundLength[ctx.page];
page.items.splice(endOfBackgroundItemsIndex, 0, {
type: 'vector',
item: v
});
} else {
page.items.push({
type: 'vector',
item: v
});
}
break;
case 'image':
case 'svg':
var img = pack(item.item);
img.x = (img.x || 0) + (useBlockXOffset ? (block.xOffset || 0) : ctx.x);
img.y = (img.y || 0) + (useBlockYOffset ? (block.yOffset || 0) : ctx.y);
page.items.push({
type: item.type,
item: img
});
break;
}
});
if (!dontUpdateContextPosition) {
ctx.moveDown(block.height);
}
return true;
}
/**
* Pushes the provided context onto the stack or creates a new one
*
* pushContext(context) - pushes the provided context and makes it current
* pushContext(width, height) - creates and pushes a new context with the specified width and height
* pushContext() - creates a new context for unbreakable blocks (with current availableWidth and full-page-height)
*
* @param {DocumentContext|number} contextOrWidth
* @param {number} height
*/
pushContext(contextOrWidth, height) {
if (contextOrWidth === undefined) {
height = this.context().getCurrentPage().height - this.context().pageMargins.top - this.context().pageMargins.bottom;
contextOrWidth = this.context().availableWidth;
}
if (isNumber(contextOrWidth)) {
let width = contextOrWidth;
contextOrWidth = new DocumentContext();
contextOrWidth.addPage({ width: width, height: height }, { left: 0, right: 0, top: 0, bottom: 0 });
}
this.contextStack.push(this.context());
this._context = contextOrWidth;
}
popContext() {
this._context = this.contextStack.pop();
}
getCurrentPositionOnPage() {
return (this.contextStack[0] || this.context()).getCurrentPosition();
}
}
function addPageItem(page, item, index) {
if (index === null || index === undefined || index < 0 || index > page.items.length) {
page.items.push(item);
} else {
page.items.splice(index, 0, item);
}
}
export default ElementWriter;