Repository: bpampuch/pdfmake (language: javascript)

Issue:
Table width as percentage is miscalculated
If tables are created with widths specified as percentage, the columns do not align.

![screenshot from 2015-10-06 15 08 45](https://cloud.githubusercontent.com/assets/1926428/10309341/4166a9b0-6c3c-11e5-94a2-bec276218f99.png)

``` javascript
var dd = {
    content: [
        {
            table: {
                widths: ['25%', '75%'],
                body: [['25%', '75%']]
            }
        },
        {
            table: {
                widths: ['25%', '25%', '25%', '25%'],
                body: [['25%', '25%', '25%', '25%']]
            }
        }
    ]
}
```

I've looked at the source, but find it hard to pinpoint exactly where the miscalculation occurs.
Are there any workarounds for this? (or hints so I can make a pull request)



Discussion / hints:
I strongly suspect this is related to pdfmake/pdfmake#30. I have not confirmed, but I suspect that the percents are calculated before accounting for padding and borders.

any updates ?
@jafar-rezaei To mitigate the issue when I first stumbled upon it I switched away from using percent when defining widths. Since the pdf is scaled after a physical format, the sizes for the page is known from [standardPageSizes.js](https://github.com/bpampuch/pdfmake/blob/e3988af8baccd8a2a4eea0ffcf6a68506b937900/src/standardPageSizes.js).
The size of the table padding and vertical lines are known through
```javascript
{
  table: { ... },
  layout: {
    paddingLeft: function(i, node) { return 4; },
    paddingRight: function(i, node) { return 4; },
    hLineWidth: function (i, node) { return 1; },
    vLineWidth: function (i, node) { return 1; },
  }
}
```
and the size of the page margins are known through
```javascript
const docDefinition = {
    pageSize: [595.28, 841.89],
    pageMargins: [ 10, 10 ]
}
```
Things in pdfmake may have been changed since I implemented it in our codebase, but if I remember correctly i had to account for those things when calculating 25% width out of a page that is e.g. A4.
I might have forgotten some things to take into account when calculating, but I guess this is a starting point to get you going in the playground.

Fix patch:
diff --git a/src/TableProcessor.js b/src/TableProcessor.js
index b9f03f9f5..dfe8ebde9 100644
--- a/src/TableProcessor.js
+++ b/src/TableProcessor.js
@@ -95,7 +95,7 @@ class TableProcessor {
 		this.layout = tableNode._layout;
 
 		availableWidth = writer.context().availableWidth - this.offsets.total;
-		ColumnCalculator.buildColumnWidths(tableNode.table.widths, availableWidth);
+		ColumnCalculator.buildColumnWidths(tableNode.table.widths, availableWidth, this.offsets.total, tableNode);
 
 		this.tableWidth = tableNode._offsets.total + getTableInnerContentWidth();
 		this.rowSpanData = prepareRowSpanData();
diff --git a/src/columnCalculator.js b/src/columnCalculator.js
index 1a224b299..710b2e973 100644
--- a/src/columnCalculator.js
+++ b/src/columnCalculator.js
@@ -1,6 +1,6 @@
 import { isString } from './helpers/variableType';
 
-function buildColumnWidths(columns, availableWidth) {
+function buildColumnWidths(columns, availableWidth, offsetTotal = 0, tableNode) {
 	let autoColumns = [];
 	let autoMin = 0;
 	let autoMax = 0;
@@ -24,10 +24,31 @@ function buildColumnWidths(columns, availableWidth) {
 		}
 	});
 
-	fixedColumns.forEach(col => {
+	fixedColumns.forEach((col, colIndex) => {
 		// width specified as %
 		if (isString(col.width) && /\d+%/.test(col.width)) {
-			col.width = parseFloat(col.width) * initial_availableWidth / 100;
+			// In tables we have to take into consideration the reserved width for paddings and borders
+			let reservedWidth = 0;
+			if (tableNode) {
+        const paddingLeft = tableNode._layout.paddingLeft(colIndex, tableNode);
+        const paddingRight = tableNode._layout.paddingRight(colIndex, tableNode);
+        const borderLeft = tableNode._layout.vLineWidth (colIndex, tableNode);
+        const borderRight = tableNode._layout.vLineWidth (colIndex + 1, tableNode);
+        if (colIndex === 0) {
+          // first column assumes whole borderLeft and half of border right
+					reservedWidth = paddingLeft + paddingRight + borderLeft + (borderRight / 2);
+
+        } else if (colIndex === fixedColumns.length - 1) {
+          // last column assumes whole borderRight and half of border left
+					reservedWidth = paddingLeft + paddingRight + (borderLeft / 2) + borderRight;
+
+        } else {
+          // Columns in the middle assume half of each border
+					reservedWidth = paddingLeft + paddingRight + (borderLeft / 2) + (borderRight / 2);
+        }
+			}
+			const totalAvailableWidth = initial_availableWidth + offsetTotal;
+      col.width = (parseFloat(col.width) * totalAvailableWidth / 100) - reservedWidth;
 		}
 		if (col.width < (col._minWidth) && col.elasticWidth) {
 			col._calcWidth = col._minWidth;


Test patch:
diff --git a/tests/unit/columnCalculator.spec.js b/tests/unit/columnCalculator.spec.js
index 419807cdd..120dda9c2 100644
--- a/tests/unit/columnCalculator.spec.js
+++ b/tests/unit/columnCalculator.spec.js
@@ -114,5 +114,83 @@ describe('ColumnCalculator', function () {
 			assert.equal(columns[0]._calcWidth, columns[0]._calcWidth);
 			assert.equal(columns[0]._calcWidth + columns[1]._calcWidth + columns[2]._calcWidth, 320);
 		});
+
+		it('should calculate widths of columns correctly', function () {
+			const availableWidth = 200;
+			const columns = [
+				{ width: '25%', _minWidth: 30, _maxWidth: 41 },
+				{ width: '50%', _minWidth: 31, _maxWidth: 42 },
+				{ width: '25%', _minWidth: 33, _maxWidth: 421 },
+			];
+
+			ColumnCalculator.buildColumnWidths(columns, availableWidth);
+			assert.equal(columns[0]._calcWidth, 50);
+			assert.equal(columns[1]._calcWidth, 100);
+			assert.equal(columns[2]._calcWidth, 50);
+		});
+
+		it('should calculate widths of the table columns correctly when using percentages', function () {
+			const availableWidth = 149;
+			const offsetTotal = 51;
+			const columns = [
+				{
+					width: "50%",
+					_minWidth: 22.27734375,
+					_maxWidth: 22.27734375,
+				},
+				{
+					width: "25%",
+					_minWidth: 22.27734375,
+					_maxWidth: 22.27734375,
+				},
+				{
+					width: "25%",
+					_minWidth: 22.27734375,
+					_maxWidth: 22.27734375,
+				},
+			];
+			const tableNode = {
+				table: {
+						widths: ['50%', '25%', '25%'],
+						body: [['50%', '25%', '25%']]
+				},
+				_layout: {
+					vLineWidth: function (i) {
+						if (i === 0) {
+							return 4;
+						}
+						if (i === 1) {
+							return 6;
+						}
+						if (i === 2) {
+							return 5;
+						}
+						if (i === 3) {
+							return 4;
+						}
+					},
+					paddingLeft: function(i) {
+						return i === 0 ? 5 : 3;
+					},
+					paddingRight: function() {
+						return 7;
+					}
+				}
+			};
+
+			ColumnCalculator.buildColumnWidths(columns, availableWidth, offsetTotal, tableNode);
+			// 200 Total available width (availableWidth + offsetTotal) === 149 + 51
+			// Fist column is 50% width of 200 === 100
+			// 100 - 4 borderLeft - 6/2 = 3 borderRight - 5 paddingLeft - 7 paddingRight
+			assert.equal(columns[0].width, 81);
+			// Second column has 25% width of 200 which is 50
+			// 50 - 3 (6/2) borderLeft - 2.5 (5/2) borderRight - 3 paddingLeft - 7 paddingRight
+			assert.equal(columns[1].width, 34.5);
+			// Third column has 25% width of 200 which is 50
+			// 50 - 2.5 (5/2) borderLeft - 4 borderRight - 3 paddingLeft - 7 paddingRight
+			assert.equal(columns[2].width, 33.5);
+			// The sum of all column width should be equal to totalAvailableWidth - offsetTotal
+			assert.equal(availableWidth, 81 + 34.5 + 33.5);
+		});
 	});
 });