Width Tables of Built-in Fonts are Imprecise — Causes Centering Errors
Summary
Text centering is off when using built-in standard PDF fonts. Investigation revealed two separate bugs working together.
Bug 1: Built-in font width tables have insufficient precision
The Adobe standard defines character widths in a 1000-unit em square. jsPDF stores these widths as integers in a 100-unit em table, then computes: entry × fontSize / 100. Because the entries are integers, any value that requires a decimal at 1/100th-em resolution gets truncated. For example, a is 55.6 units in a 100-unit em table (equivalent to 556 in the standard 1000-unit table), but is stored as 55, losing the .6. This truncation error affects every character whose correct 100-unit value is not a whole number.
Example at 22pt:
| Character |
jsPDF entry |
jsPDF width |
Correct entry (1000-unit) |
Correct width |
Error |
a |
55 |
12.100pt |
556 |
12.232pt |
−0.132pt |
A |
72 |
15.840pt |
722 |
15.884pt |
−0.044pt |
0–9 |
55 |
12.100pt |
556 |
12.232pt |
−0.132pt |
E |
66 |
14.520pt |
667 |
14.674pt |
−0.154pt |
These errors accumulate across a string. A 20-character string can accumulate 1pt or more of total error, causing visibly off-center text.
The correct Adobe Helvetica Bold standard widths (1000-unit em) are:
' ':278, '!':333, '"':474, '#':556, '$':556, '%':889, '&':722,
"'":278, '(':333, ')':333, '*':389, '+':584, ',':278, '-':333, '.':278, '/':278,
'0'–'9': 556 each,
':':333, ';':333, '<':584, '=':584, '>':584, '?':611, '@':975,
'A':722, 'B':722, 'C':722, 'D':722, 'E':667, 'F':611, 'G':778, 'H':722,
'I':278, 'J':556, 'K':722, 'L':611, 'M':833, 'N':722, 'O':778, 'P':667,
'Q':778, 'R':722, 'S':667, 'T':611, 'U':722, 'V':667, 'W':944,
'X':667, 'Y':667, 'Z':611,
'a':556, 'b':611, 'c':556, 'd':611, 'e':556, 'f':333, 'g':611, 'h':611,
'i':278, 'j':278, 'k':556, 'l':278, 'm':889, 'n':611, 'o':611, 'p':611,
'q':611, 'r':389, 's':556, 't':333, 'u':611, 'v':556, 'w':778,
'x':556, 'y':556, 'z':500
These were verified by extracting actual character positions from generated PDFs using pdfplumber, then comparing to the Adobe Type 1 specification.
Fix: Store widths as the correct 1000-unit Adobe values divided by 10 (non-integer), and divide by 100 as now — or switch the divisor to 1000 and store the full integer values directly.
Bug 2: getTextWidth() subtracts kerning that doc.text() never applies
getTextWidth() subtracts kerning pair adjustments from the total string width. However, doc.text() does not apply those same kerning adjustments when actually placing characters on the page.
This means getTextWidth() returns a value smaller than the actual rendered string width for any string containing a kerning pair, making it unsuitable for computing centered or right-aligned text positions. Combined with Bug 1, the two errors compound each other for strings that contain kerning pairs.
Example:
doc.setFont('helvetica', 'bold');
doc.setFontSize(22);
// 'T ' is a kerning pair (kern value 10 → 2.200pt at 22pt)
doc.getTextWidth('Car: T records'); // returns 147.620pt
// But actual rendered width in PDF: 150.370pt
// Difference: 2.750pt
// — 0.550pt from width table imprecision (Bug 1)
// — 2.200pt from kerning subtracted by getTextWidth but not applied by doc.text (Bug 2)
Verified by extracting exact x0/x1 character positions from generated PDFs using pdfplumber.
Fix: Either apply kerning consistently in both getTextWidth() and doc.text(), or remove it from both. Currently the inconsistency means neither approach produces correct centered text.
Impact
Any developer using getTextWidth() to compute a centered or right-aligned text position will get incorrect results. The combination of both bugs means centering errors of 1pt or more are common for typical strings.
Workaround
Compute text width by summing individual character widths using the correct Adobe 1000-unit values, bypassing both getTextWidth() and the internal width table entirely:
const HELVETICA_BOLD_WIDTHS = {
32:278, 33:333, 34:474, 35:556, 36:556, 37:889, 38:722, 39:278,
40:333, 41:333, 42:389, 43:584, 44:278, 45:333, 46:278, 47:278,
48:556, 49:556, 50:556, 51:556, 52:556, 53:556, 54:556, 55:556, 56:556, 57:556,
58:333, 59:333, 60:584, 61:584, 62:584, 63:611, 64:975,
65:722, 66:722, 67:722, 68:722, 69:667, 70:611, 71:778, 72:722,
73:278, 74:556, 75:722, 76:611, 77:833, 78:722, 79:778, 80:667,
81:778, 82:722, 83:667, 84:611, 85:722, 86:667, 87:944, 88:667, 89:667, 90:611,
97:556, 98:611, 99:556, 100:611, 101:556, 102:333, 103:611, 104:611,
105:278, 106:278, 107:556, 108:278, 109:889, 110:611, 111:611, 112:611,
113:611, 114:389, 115:556, 116:333, 117:611, 118:556, 119:778, 120:556,
121:556, 122:500,
};
function getCorrectTextWidth(text, fontSize) {
let total = 0;
for (const ch of text) {
total += (HELVETICA_BOLD_WIDTHS[ch.charCodeAt(0)] || 556) * fontSize / 1000;
}
return total;
}
// Use instead of doc.getTextWidth(text):
const tx = cellX + (cellWidth - getCorrectTextWidth(line, fontSize)) / 2;
doc.text(line, tx, ty);
This produces centering accurate to within 0.001pt, verified via pdfplumber across 30+ strings at font sizes from 14pt to 22pt.
jsPDF versions tested: 2.5.1 (CDN) and 4.2.1 (latest) — bug confirmed present in both
Browser tested: Safari on macOS
Width Tables of Built-in Fonts are Imprecise — Causes Centering Errors
Summary
Text centering is off when using built-in standard PDF fonts. Investigation revealed two separate bugs working together.
Bug 1: Built-in font width tables have insufficient precision
The Adobe standard defines character widths in a 1000-unit em square. jsPDF stores these widths as integers in a 100-unit em table, then computes:
entry × fontSize / 100. Because the entries are integers, any value that requires a decimal at 1/100th-em resolution gets truncated. For example,ais 55.6 units in a 100-unit em table (equivalent to 556 in the standard 1000-unit table), but is stored as55, losing the.6. This truncation error affects every character whose correct 100-unit value is not a whole number.Example at 22pt:
aA0–9EThese errors accumulate across a string. A 20-character string can accumulate 1pt or more of total error, causing visibly off-center text.
The correct Adobe Helvetica Bold standard widths (1000-unit em) are:
These were verified by extracting actual character positions from generated PDFs using pdfplumber, then comparing to the Adobe Type 1 specification.
Fix: Store widths as the correct 1000-unit Adobe values divided by 10 (non-integer), and divide by 100 as now — or switch the divisor to 1000 and store the full integer values directly.
Bug 2:
getTextWidth()subtracts kerning thatdoc.text()never appliesgetTextWidth()subtracts kerning pair adjustments from the total string width. However,doc.text()does not apply those same kerning adjustments when actually placing characters on the page.This means
getTextWidth()returns a value smaller than the actual rendered string width for any string containing a kerning pair, making it unsuitable for computing centered or right-aligned text positions. Combined with Bug 1, the two errors compound each other for strings that contain kerning pairs.Example:
Verified by extracting exact x0/x1 character positions from generated PDFs using pdfplumber.
Fix: Either apply kerning consistently in both
getTextWidth()anddoc.text(), or remove it from both. Currently the inconsistency means neither approach produces correct centered text.Impact
Any developer using
getTextWidth()to compute a centered or right-aligned text position will get incorrect results. The combination of both bugs means centering errors of 1pt or more are common for typical strings.Workaround
Compute text width by summing individual character widths using the correct Adobe 1000-unit values, bypassing both
getTextWidth()and the internal width table entirely:This produces centering accurate to within 0.001pt, verified via pdfplumber across 30+ strings at font sizes from 14pt to 22pt.
jsPDF versions tested: 2.5.1 (CDN) and 4.2.1 (latest) — bug confirmed present in both
Browser tested: Safari on macOS