Skip to content

Commit

Permalink
add Table cell vertical alignment
Browse files Browse the repository at this point in the history
  • Loading branch information
dumbasPL committed Jun 10, 2022
1 parent 8bb8a94 commit 3dbdf2b
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 4 deletions.
13 changes: 12 additions & 1 deletion src/docPreprocessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ DocPreprocessor.prototype.preprocessDocument = function (docStructure) {
return this.preprocessNode(docStructure);
};

DocPreprocessor.prototype.preprocessNode = function (node) {
DocPreprocessor.prototype.checkNode = function (node) {
// expand shortcuts and casting values
if (isArray(node)) {
node = { stack: node };
Expand All @@ -33,6 +33,11 @@ DocPreprocessor.prototype.preprocessNode = function (node) {
} else if ('text' in node && (node.text === undefined || node.text === null)) {
node.text = '';
}
return node;
};

DocPreprocessor.prototype.preprocessNode = function (node) {
node = this.checkNode(node);

if (node.columns) {
return this.preprocessColumns(node);
Expand Down Expand Up @@ -67,6 +72,8 @@ DocPreprocessor.prototype.preprocessColumns = function (node) {
var columns = node.columns;

for (var i = 0, l = columns.length; i < l; i++) {
columns[i] = this.checkNode(columns[i]);
columns[i].__nodeRef = node.__nodeRef ?? node;
columns[i] = this.preprocessNode(columns[i]);
}

Expand All @@ -77,6 +84,8 @@ DocPreprocessor.prototype.preprocessVerticalContainer = function (node) {
var items = node.stack;

for (var i = 0, l = items.length; i < l; i++) {
items[i] = this.checkNode(items[i]);
items[i].__nodeRef = node.__nodeRef ?? node;
items[i] = this.preprocessNode(items[i]);
}

Expand All @@ -87,6 +96,8 @@ DocPreprocessor.prototype.preprocessList = function (node) {
var items = node.ul || node.ol;

for (var i = 0, l = items.length; i < l; i++) {
items[i] = this.checkNode(items[i]);
items[i].__nodeRef = node.__nodeRef ?? node;
items[i] = this.preprocessNode(items[i]);
}

Expand Down
1 change: 1 addition & 0 deletions src/elementWriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ ElementWriter.prototype.addQr = function (qr, index) {
var vector = qr._canvas[i];
vector.x += qr.x;
vector.y += qr.y;
vector.__nodeRef = qr.__nodeRef ?? qr;
this.addVector(vector, true, true, index);
}

Expand Down
27 changes: 26 additions & 1 deletion src/layoutBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ LayoutBuilder.prototype.layoutDocument = function (docStructure, fontProvider, s

this.docPreprocessor = new DocPreprocessor();
this.docMeasure = new DocMeasure(fontProvider, styleDictionary, defaultStyle, this.imageMeasure, this.svgMeasure, this.tableLayouts, images);

this.__nodesHierarchy = [];

function resetXYs(result) {
result.linearNodeList.forEach(function (node) {
Expand Down Expand Up @@ -471,13 +471,18 @@ LayoutBuilder.prototype.processNode = function (node) {

// vertical container
LayoutBuilder.prototype.processVerticalContainer = function (node) {
this.__nodesHierarchy.push(node);
node.__contentHeight = 0;

var self = this;
node.stack.forEach(function (item) {
self.processNode(item);
addAll(node.positions, item.positions);

//TODO: paragraph gap
});
const lastNode = this.__nodesHierarchy.pop();
this.__nodesHierarchy.length > 0 && (this.__nodesHierarchy[this.__nodesHierarchy.length - 1].__contentHeight += lastNode.__contentHeight);
};

// columns
Expand All @@ -489,6 +494,8 @@ LayoutBuilder.prototype.processColumns = function (columnNode) {
if (gaps) {
availableWidth -= (gaps.length - 1) * columnNode._gap;
}
columnNode.__contentHeight = 0;
this.__nodesHierarchy.push(columnNode);

ColumnCalculator.buildColumnWidths(columns, availableWidth);
var result = this.processRow(columns, columns, gaps);
Expand All @@ -509,6 +516,9 @@ LayoutBuilder.prototype.processColumns = function (columnNode) {

return gaps;
}
const lastNode = this.__nodesHierarchy.pop();
lastNode.__contentHeight = Math.max(...columns.map(c => c.__contentHeight));
this.__nodesHierarchy.length > 0 && (this.__nodesHierarchy[this.__nodesHierarchy.length - 1].__contentHeight += lastNode.__contentHeight);
};

LayoutBuilder.prototype.processRow = function (columns, widths, gaps, tableBody, tableRow, height) {
Expand Down Expand Up @@ -587,6 +597,9 @@ LayoutBuilder.prototype.processRow = function (columns, widths, gaps, tableBody,

// lists
LayoutBuilder.prototype.processList = function (orderedList, node) {
this.__nodesHierarchy.push(node);
node.__contentHeight = 0;

var self = this,
items = orderedList ? node.ol : node.ul,
gapSize = node._gapSize;
Expand All @@ -596,6 +609,7 @@ LayoutBuilder.prototype.processList = function (orderedList, node) {
var nextMarker;
this.tracker.auto('lineAdded', addMarkerToFirstLeaf, function () {
items.forEach(function (item) {
item.__nodeRef = node.__nodeRef ?? node;
nextMarker = item.listMarker;
self.processNode(item);
addAll(node.positions, item.positions);
Expand All @@ -604,6 +618,9 @@ LayoutBuilder.prototype.processList = function (orderedList, node) {

this.writer.context().addMargin(-gapSize.width);

const lastNode = this.__nodesHierarchy.pop();
this.__nodesHierarchy.length > 0 && (this.__nodesHierarchy[this.__nodesHierarchy.length - 1].__contentHeight += lastNode.__contentHeight);

function addMarkerToFirstLeaf(line) {
// I'm not very happy with the way list processing is implemented
// (both code and algorithm should be rethinked)
Expand All @@ -613,11 +630,14 @@ LayoutBuilder.prototype.processList = function (orderedList, node) {

if (marker.canvas) {
var vector = marker.canvas[0];
vector.__nodeRef = line.__nodeRef ?? line;
vector._height = marker._maxHeight;

offsetVector(vector, -marker._minWidth, 0);
self.writer.addVector(vector);
} else if (marker._inlines) {
var markerLine = new Line(self.pageSize.width);
markerLine.__nodeRef = line.__nodeRef ?? line;
markerLine.addInline(marker._inlines[0]);
markerLine.x = -marker._minWidth;
markerLine.y = line.getAscenderHeight() - markerLine.getAscenderHeight();
Expand Down Expand Up @@ -661,7 +681,9 @@ LayoutBuilder.prototype.processTable = function (tableNode) {

// leafs (texts)
LayoutBuilder.prototype.processLeaf = function (node) {
node = this.docPreprocessor.checkNode(node);
var line = this.buildNextLine(node);
line && (line.__nodeRef = node.__nodeRef ?? node);
if (line && (node.tocItem || node.id)) {
line._node = node;
}
Expand Down Expand Up @@ -700,9 +722,12 @@ LayoutBuilder.prototype.processLeaf = function (node) {
node.positions.push(positions);
line = this.buildNextLine(node);
if (line) {
line.__nodeRef = node.__nodeRef ?? node;
currentHeight += line.getHeight();
}
}
node.__contentHeight = currentHeight;
this.__nodesHierarchy.length > 0 && (this.__nodesHierarchy[this.__nodesHierarchy.length - 1].__contentHeight += currentHeight);
};

LayoutBuilder.prototype.processToc = function (node) {
Expand Down
86 changes: 84 additions & 2 deletions src/tableProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
var ColumnCalculator = require('./columnCalculator');
var isFunction = require('./helpers').isFunction;
var isNumber = require('./helpers').isNumber;
var offsetVector = require('./helpers').offsetVector;

var TABLE_FILL_CORRECTION = 0.5;

Expand All @@ -11,6 +12,8 @@ function TableProcessor(tableNode) {
}

TableProcessor.prototype.beginTable = function (writer) {
this.tableNode.table.__rowsHeight = [];

var tableNode;
var availableWidth;
var self = this;
Expand Down Expand Up @@ -39,6 +42,8 @@ TableProcessor.prototype.beginTable = function (writer) {

this.drawHorizontalLine(0, writer);

this.tableNode.__height = writer.context().y;

function getTableInnerContentWidth() {
var width = 0;

Expand Down Expand Up @@ -146,6 +151,8 @@ TableProcessor.prototype.beginRow = function (rowIndex, writer) {
writer.context().availableHeight -= this.reservedAtBottom;

writer.context().moveDown(this.rowPaddingTop);

this.tableNode.table.__rowsHeight[rowIndex] = { top: this.rowTopY, height: 0 };
};

TableProcessor.prototype.drawHorizontalLine = function (lineIndex, writer, overrideY) {
Expand Down Expand Up @@ -246,7 +253,8 @@ TableProcessor.prototype.drawHorizontalLine = function (lineIndex, writer, overr
y2: y,
lineWidth: lineWidth,
dash: dash,
lineColor: borderColor
lineColor: borderColor,
__tableRef: this.tableNode.table
}, false, overrideY);
currentLine = null;
borderColor = null;
Expand Down Expand Up @@ -326,14 +334,86 @@ TableProcessor.prototype.drawVerticalLine = function (x, y0, y1, vLineColIndex,
y2: y1,
lineWidth: width,
dash: dash,
lineColor: borderColor
lineColor: borderColor,
__tableRef: this.tableNode.table
}, false, true);
cellBefore = null;
currentCell = null;
borderColor = null;
};

TableProcessor.prototype.getCellContentHeight = function (cell, items) {
let contentHeight = 0;
cell._maxHeight && (contentHeight = cell._maxHeight); // for canvas
// for forced multiline text, text with lineHeight, ul, ol
!cell.lineHeight && cell.__contentHeight && (contentHeight = cell.__contentHeight);
!contentHeight && (contentHeight = items.reduce((p, v) => {
const item = v.item.inlines ? (v.item.inlines[0] ?? null) : v.item;
const lineHeight = v.item.__nodeRef?.lineHeight ?? (v.item._height ?? v.item.h);
let height = item.height ?? (item.h ?? 0);
(v.type === 'vector') || (cell.ol && !v.item.lastLineInParagraph) && (height = 0); // for ol with counter
return p + (height / (lineHeight ?? 1))
},0));
!contentHeight && cell._height && (contentHeight = cell._height); // for text, image, svg, qr
return contentHeight;
};

TableProcessor.prototype.processTableVerticalAlignment = function (writer, table) {
const getCells = (node) => node.table ? node.table.body.flat().map(getCells).flat() : node;
const getNestedTables = (node) => node.table ? [node, ...node.table.body.flat().map(getNestedTables).filter(Boolean).flat()] : null;
// for all rows in table
table.body.forEach((row, rowIndex) => {
// filter only cells with vertical alignment (middle / bottom)
!Array.isArray(row) && row.columns && (row = row.columns);
row.filter(cell => cell.verticalAlign && ['middle', 'bottom'].indexOf(cell.verticalAlign) > -1).forEach(cell => {
let nestedTables;
if (!cell._span) {
let cellHeight = 0;
if (cell.rowSpan && cell.rowSpan > 1) {
const heights = table.__rowsHeight.slice(rowIndex, rowIndex + cell.rowSpan);
cellHeight = heights.reduce((previousValue, value) => previousValue + value.height, 0);
} else {
cellHeight = table.__rowsHeight[rowIndex].height;
}
if (cellHeight) {
const pageItems = writer.writer.context.pages.flatMap(x => x.items);
let items = pageItems.filter(i => i.item.__nodeRef === cell || i.item === cell);
let itemHeight = 0;
if (items.length === 0 && cell.table) {
itemHeight = cell.table.__rowsHeight.reduce((p, v) => p + v.height, 0);
nestedTables = getNestedTables(cell);
items = pageItems.filter(i => getCells(cell).indexOf(i.item.__nodeRef) > -1 ||
i.item.__tableRef && nestedTables.some(nt => nt.table === i.item.__tableRef));
} else if (cell.stack) {
const tables = cell.stack.filter(x => x.table);
nestedTables = getNestedTables(tables[0]);
itemHeight = tables.reduce((p, v) => p + v.__height, 0) +
cell.stack.flatMap(x => x.__contentHeight).filter(Boolean).reduce((p, v) => p + v, 0);
items = [...items, pageItems.filter(i => getCells(tables[0]).indexOf(i.item.__nodeRef) > -1 ||
i.item.__tableRef && nestedTables.some(nt => nt.table === i.item.__tableRef))].flat();
} else {
itemHeight = this.getCellContentHeight(cell, items);
}
items.forEach(x => {
const offsetTop = cell.verticalAlign === 'bottom'
? cellHeight - itemHeight - 3
: (cellHeight - itemHeight) / 2;
if (x && x.item) {
x.item.type && offsetVector(x.item, 0, Math.max(0, offsetTop) - 1.5);
!x.item.type && (x.item.y += Math.max(0, offsetTop) - 1.5);
}
});
}
}
});
});
};

TableProcessor.prototype.endTable = function (writer) {
this.processTableVerticalAlignment(writer, this.tableNode.table);
this.tableNode.__height = writer.context().y - this.tableNode.__height +
Math.ceil(this.layout.hLineWidth(0, this.tableNode)) * this.tableNode.table.body.length;

if (this.cleanUpRepeatables) {
writer.popFromRepeatables();
}
Expand Down Expand Up @@ -553,6 +633,8 @@ TableProcessor.prototype.endRow = function (rowIndex, writer, pageBreaks) {
this.headerRepeatable = null;
}

this.tableNode.table.__rowsHeight[rowIndex].height = endingY - this.tableNode.table.__rowsHeight[rowIndex].top;

function getLineXs() {
var result = [];
var cols = 0;
Expand Down

0 comments on commit 3dbdf2b

Please sign in to comment.