Ref: #1379 This takes a naive approach to font-width computation, the most compute-intensive part of rendering badges. 1. Add the widths of the individual characters. - These widths are measured on startup using PDFKit. 2. For each character pair, add a kerning adjustment - The difference between the width of each character pair, and the sum of the characters' separate widths. - These are computed for each character pair on startup using PDFKit. 3. For a string with characters outside the printable ASCII character set, fall back to PDFKit. This branch averaged 0.041 ms in `makeBadge`, compared to 0.144 ms on master, a speedup of 73%. That was on a test of 10,000 consecutive requests (using the `benchmark-performance.sh` script, now checked in). The speedup applies to badges containing exclusively printable ASCII characters. It wouldn't be as dramatic on non-ASCII text. Though, we could add some frequently used non-ASCII characters to the cached set.
141 lines
4.3 KiB
JavaScript
141 lines
4.3 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const sinon = require('sinon');
|
|
const { PDFKitTextMeasurer, QuickTextMeasurer } = require('./text-measurer');
|
|
const { starRating } = require('./text-formatters');
|
|
const defaults = require('./defaults');
|
|
const testHelpers = require('./make-badge-test-helpers');
|
|
|
|
function almostEqualInPixels(first, second) {
|
|
return require('almost-equal')(first, second, 1e-3);
|
|
}
|
|
|
|
describe('PDFKitTextMeasurer with DejaVu Sans', function () {
|
|
it('should produce the same length as before', function () {
|
|
const measurer = new PDFKitTextMeasurer(testHelpers.font.path);
|
|
const actual = measurer.widthOf('This is the dawning of the Age of Aquariums');
|
|
const expected = 243.546875;
|
|
assert.equal(actual, expected);
|
|
});
|
|
});
|
|
|
|
function registerTests(fontPath, skip) {
|
|
// Invoke `.skip()` within the `it`'s so we get logging of the skipped tests.
|
|
const displayName = path.basename(fontPath, path.extname(fontPath));
|
|
|
|
describe(`QuickTextMeasurer with ${displayName}`, function () {
|
|
let quickMeasurer;
|
|
if (! skip) {
|
|
before(function () {
|
|
// Since this is slow, share it across all tests.
|
|
quickMeasurer = new QuickTextMeasurer(fontPath);
|
|
});
|
|
}
|
|
|
|
let sandbox;
|
|
let pdfKitWidthOf;
|
|
let pdfKitMeasurer;
|
|
if (! skip) {
|
|
// Boo, the sandbox doesn't get cleaned up after a skipped test.
|
|
beforeEach(function () {
|
|
sandbox = sinon.sandbox.create();
|
|
pdfKitWidthOf = sandbox.spy(PDFKitTextMeasurer.prototype, 'widthOf');
|
|
pdfKitMeasurer = new PDFKitTextMeasurer(fontPath);
|
|
});
|
|
|
|
afterEach(function () {
|
|
if (sandbox) {
|
|
sandbox.restore();
|
|
sandbox = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
context('when given ASCII strings', function () {
|
|
const strings = [
|
|
'This is the dawning of the Age of Aquariums',
|
|
'v1.2.511',
|
|
'5 passed, 2 failed, 1 skipped',
|
|
'[prismic "1.1"]',
|
|
];
|
|
|
|
strings.forEach(function (str) {
|
|
it(`should measure '${str}' in parity with PDFKit`, function () {
|
|
if (skip) { this.skip(); }
|
|
assert.ok(almostEqualInPixels(quickMeasurer.widthOf(str), pdfKitMeasurer.widthOf(str)));
|
|
});
|
|
});
|
|
|
|
strings.forEach(function (str) {
|
|
it(`should measure '${str}' without invoking PDFKit`, function () {
|
|
if (skip) { this.skip(); }
|
|
quickMeasurer.widthOf(str);
|
|
assert.equal(pdfKitWidthOf.called, false);
|
|
});
|
|
});
|
|
|
|
context('when the font includes a kerning pair', function () {
|
|
const stringsWithKerningPairs = [
|
|
'Q-tips', // In DejaVu, Q- is a kerning pair.
|
|
'B-flat', // In Verdana, B- is a kerning pair.
|
|
];
|
|
|
|
function widthByMeasuringCharacters(str) {
|
|
let result = 0;
|
|
for (const char of str) {
|
|
result += pdfKitMeasurer.widthOf(char);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
it(`should apply a width correction`, function () {
|
|
if (skip) { this.skip(); }
|
|
|
|
const adjustedStrings = [];
|
|
|
|
stringsWithKerningPairs.forEach(str => {
|
|
const actual = quickMeasurer.widthOf(str);
|
|
const unadjusted = widthByMeasuringCharacters(str);
|
|
if (!almostEqualInPixels(actual, unadjusted)) {
|
|
adjustedStrings.push(str);
|
|
}
|
|
});
|
|
|
|
assert.ok(adjustedStrings.length > 0);
|
|
});
|
|
});
|
|
});
|
|
|
|
context('when given non-ASCII strings', function () {
|
|
const strings = [
|
|
starRating(3.5),
|
|
'\u2026',
|
|
];
|
|
|
|
strings.forEach(function (str) {
|
|
it(`should measure '${str}' in parity with PDFKit`, function () {
|
|
if (skip) { this.skip(); }
|
|
assert.ok(almostEqualInPixels(quickMeasurer.widthOf(str), pdfKitMeasurer.widthOf(str)));
|
|
});
|
|
});
|
|
|
|
strings.forEach(function (str) {
|
|
it(`should invoke the base when measuring '${str}'`, function () {
|
|
if (skip) { this.skip(); }
|
|
quickMeasurer.widthOf(str);
|
|
assert.equal(pdfKitWidthOf.called, true);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
// i.e. Verdana
|
|
registerTests(defaults.font.path, !fs.existsSync(defaults.font.path));
|
|
|
|
// i.e. DejaVu Sans
|
|
registerTests(testHelpers.font.path);
|