Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [BREAKING] Node builds now targets Node 22+, Browser build targets Firefox 115+, Safari 16+, Chrome 120+
- Replace cache decorators with specialized lazy caching
- Fix crash when using TrueType font with empty glyphs (#353, #378, #303, #312). Based on #379
- Read hdmx device records at their padded size
- Map space and missing code points correctly with a legacy Mac cmap (#394)
- Keep a glyph's variation metrics stable when several composites use it
- Return the requested code points for glyphs shared by several code points (#342) (#395)
Expand Down
4 changes: 4 additions & 0 deletions src/tables/hdmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ let DeviceRecord = new r.Struct({
pixelSize: r.uint8,
maximumWidth: r.uint8,
widths: new r.Array(r.uint8, (t) => t.parent.parent.maxp.numGlyphs),
// Each record is padded to sizeDeviceRecord bytes, a multiple of four
padding: new r.Reserved(r.uint8, (t) =>
Math.max(0, t.parent.sizeDeviceRecord - t._currentOffset),
),
});

// The Horizontal Device Metrics table stores integer advance widths scaled to particular pixel sizes
Expand Down
52 changes: 52 additions & 0 deletions test/device_metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as fontkit from 'fontkit';
import assert from 'assert';

// Builds a minimal SFNT font that only holds the given tables
function buildFont(tables) {
let tags = Object.keys(tables);
let offset = 12 + tags.length * 16;
let length = tags.reduce((sum, tag) => sum + tables[tag].length, offset);
let buffer = new Uint8Array(length);
let view = new DataView(buffer.buffer);

view.setUint32(0, 0x00010000);
view.setUint16(4, tags.length);

tags.forEach((tag, i) => {
let entry = 12 + i * 16;
for (let j = 0; j < 4; j++) {
buffer[entry + j] = tag.charCodeAt(j);
}

view.setUint32(entry + 8, offset);
view.setUint32(entry + 12, tables[tag].length);
buffer.set(tables[tag], offset);
offset += tables[tag].length;
});

return buffer;
}

describe('device metrics', function () {
it('should read hdmx records padded to sizeDeviceRecord', function () {
// With 5 glyphs, a record holds 7 bytes of data and 1 byte of padding
let maxp = new Uint8Array(32);
new DataView(maxp.buffer).setUint16(4, 5);

// prettier-ignore
let hdmx = new Uint8Array([
0, 0, // version
0, 2, // numRecords
0, 0, 0, 8, // sizeDeviceRecord
11, 7, 3, 4, 5, 6, 7, 0, // pixelSize, maximumWidth, widths, padding
12, 9, 5, 6, 7, 8, 9, 0,
]);

let font = fontkit.create(buildFont({ maxp, hdmx }));

assert.deepEqual(font.hdmx.records, [
{ pixelSize: 11, maximumWidth: 7, widths: [3, 4, 5, 6, 7] },
{ pixelSize: 12, maximumWidth: 9, widths: [5, 6, 7, 8, 9] },
]);
});
});
Loading