UNPKG

harfbuzzjs

Version:

Minimal version of HarfBuzz for JavaScript use

309 lines (285 loc) 12.2 kB
const fs = require('fs'); const path = require('path'); const { expect } = require('chai'); let hb; let blob, face, font, buffer; before(async function () { hb = await require('..'); }); afterEach(function () { if (blob) blob.destroy(); if (face) face.destroy(); if (font) font.destroy(); if (buffer) buffer.destroy(); blob = face = font = buffer = undefined; }); describe('Face', function () { it('collectUnicodes reflects codepoints supported by the font', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); const codepoints = [...face.collectUnicodes()]; expect(codepoints).to.include('a'.codePointAt(0)); expect(codepoints).not.to.include('ا'.codePointAt(0)); }); it('exposes upem', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); expect(face.upem).to.equal(1000); }); it('getAxisInfos returns details of a variable font', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSansArabic-Variable.ttf'))); face = hb.createFace(blob); expect(face.getAxisInfos()).to.deep.equal({ wght: { min: 100, default: 400, max: 900 }, wdth: { min: 62.5, default: 100, max: 100 } }); }); it('getAxisInfos returns an empty object for a non-variable font', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); expect(Object.keys(face.getAxisInfos())).to.have.lengthOf(0); }); }); describe('Font', function () { it('glyphName returns names for glyph ids', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); expect(font.glyphName(20)).to.equal('one'); }); it('setScale affects advances', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); buffer = hb.createBuffer(); buffer.addText('a'); buffer.guessSegmentProperties(); font.setScale(1000 * 2, 1000 * 2); hb.shape(font, buffer) const glyphs = buffer.json(); expect(glyphs[0].ax).to.equal(561 * 2); }); it('setVariations affects advances', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSansArabic-Variable.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); font.setVariations({ 'wght': 789 }); buffer = hb.createBuffer(); buffer.addText('آلو'); buffer.guessSegmentProperties(); hb.shape(font, buffer) const glyphs = buffer.json(); expect(glyphs[0].ax).to.equal(526); }); it('glyphToPath converts quadratic glyph to path', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); const expected21 = 'M520,0L48,0L48,73L235,262Q289,316 326,358Q363,400 382,440.5Q401,481 401,529Q401,\ 588 366,618.5Q331,649 275,649Q223,649 183.5,631Q144,613 103,581L56,640Q98,675 152.5,699.5Q207,724 275,\ 724Q375,724 433,673.5Q491,623 491,534Q491,478 468,429Q445,380 404,332.5Q363,285 308,231L159,84L159,80L520,80L520,0Z'; expect(font.glyphToPath(21)).to.equal(expected21); const expected22 = 'M493,547Q493,475 453,432.5Q413,390 345,376L345,372Q431,362 473,318Q515,274 515,203Q515,\ 141 486,92.5Q457,44 396.5,17Q336,-10 241,-10Q185,-10 137,-1.5Q89,7 45,29L45,111Q90,89 142,76.5Q194,64 242,64Q338,\ 64 380.5,101.5Q423,139 423,205Q423,272 370.5,301.5Q318,331 223,331L154,331L154,406L224,406Q312,406 357.5,443Q403,\ 480 403,541Q403,593 368,621.5Q333,650 273,650Q215,650 174,633Q133,616 93,590L49,650Q87,680 143.5,702Q200,724 272,\ 724Q384,724 438.5,674Q493,624 493,547Z'; expect(font.glyphToPath(22)).to.equal(expected22); }); it('glyphToPath converts cubic glyph to path', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.otf'))); face = hb.createFace(blob); font = hb.createFont(face); const expected21 = 'M520,0L520,80L159,80L159,84L308,231C418,338 491,422 491,534C491,652 408,724 275,724C184,724 112,\ 687 56,640L103,581C158,624 205,649 275,649C350,649 401,607 401,529C401,432 342,370 235,262L48,73L48,0L520,0Z'; expect(font.glyphToPath(21)).to.equal(expected21); const expected22 = 'M493,547C493,649 421,724 272,724C176,724 100,690 49,650L93,590C146,625 196,650 273,650C353,\ 650 403,610 403,541C403,460 341,406 224,406L154,406L154,331L223,331C349,331 423,294 423,205C423,117 370,64 242,64C178,\ 64 105,81 45,111L45,29C104,0 166,-10 241,-10C430,-10 515,78 515,203C515,297 459,358 345,372L345,376C435,394 493,451 493,547Z'; expect(font.glyphToPath(22)).to.equal(expected22); }); }); describe('Buffer', function () { it('setDirection controls direction of glyphs', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); buffer = hb.createBuffer(); buffer.addText('rtl'); buffer.setDirection('rtl'); hb.shape(font, buffer) const glyphs = buffer.json(); expect(glyphs[0].g).to.equal(79); // l expect(glyphs[1].g).to.equal(87); // t expect(glyphs[2].g).to.equal(85); // r }); it('setClusterLevel affects cluster merging', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); buffer = hb.createBuffer(); buffer.setClusterLevel(1); buffer.addText('x́'); buffer.guessSegmentProperties(); hb.shape(font, buffer) const glyphs = buffer.json(); expect(glyphs[0].cl).to.equal(0); expect(glyphs[1].cl).to.equal(1); }); it('setFlags with PRESERVE_DEFAULT_IGNORABLES affects glyph ids', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); buffer = hb.createBuffer(); buffer.addText('\u200dhi'); buffer.setFlags(['PRESERVE_DEFAULT_IGNORABLES']); buffer.guessSegmentProperties(); hb.shape(font, buffer) const glyphs = buffer.json(); expect(glyphs[0].g).not.to.equal(3 /* space */); }); }); describe('shape', function () { it('shape Latin string', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); buffer = hb.createBuffer(); buffer.addText('abc'); buffer.guessSegmentProperties(); hb.shape(font, buffer) const glyphs = buffer.json(); expect(glyphs[0]).to.deep.equal({ cl: 0, g: 68, ax: 561, ay: 0, dx: 0, dy: 0, flags: 0 } /* a */); expect(glyphs[1]).to.deep.equal({ cl: 1, g: 69, ax: 615, ay: 0, dx: 0, dy: 0, flags: 0 } /* b */); expect(glyphs[2]).to.deep.equal({ cl: 2, g: 70, ax: 480, ay: 0, dx: 0, dy: 0, flags: 0 } /* c */); }); it('shape Arabic string', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSansArabic-Variable.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); buffer = hb.createBuffer(); buffer.addText('أبجد'); buffer.guessSegmentProperties(); hb.shape(font, buffer) const glyphs = buffer.json(); expect(glyphs[0]).to.deep.equal({ cl: 3, g: 213, ax: 532, ay: 0, dx: 0, dy: 0, flags: 1 } /* د */); expect(glyphs[1]).to.deep.equal({ cl: 2, g: 529, ax: 637, ay: 0, dx: 0, dy: 0, flags: 1 } /* ج */); expect(glyphs[2]).to.deep.equal({ cl: 1, g: 101, ax: 269, ay: 0, dx: 0, dy: 0, flags: 0 } /* ب */); expect(glyphs[3]).to.deep.equal({ cl: 0, g: 50, ax: 235, ay: 0, dx: 0, dy: 0, flags: 0 } /* أ */); }); it('shape with tracing', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); buffer = hb.createBuffer(); buffer.addText('abc'); buffer.guessSegmentProperties(); const result = hb.shapeWithTrace(font, buffer, "", 0, 0) expect(result).to.have.lengthOf(42); expect(result[0]).to.deep.equal({ "m": "start table GSUB script tag 'latn'", "glyphs": true, "t": [ { cl: 0, g: 68 }, { cl: 1, g: 69 }, { cl: 2, g: 70 }, ], }); expect(result[41]).to.deep.equal({ "m": "end table GPOS script tag 'latn'", "glyphs": true, "t": [ { cl: 0, g: 68, ax: 561, ay: 0, dx: 0, dy: 0 }, { cl: 1, g: 69, ax: 615, ay: 0, dx: 0, dy: 0 }, { cl: 2, g: 70, ax: 480, ay: 0, dx: 0, dy: 0 }, ], }); }); it('shape with tracing and features', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); face = hb.createFace(blob); font = hb.createFont(face); buffer = hb.createBuffer(); buffer.addText('fi AV'); buffer.guessSegmentProperties(); const result = hb.shapeWithTrace(font, buffer, "-liga,-kern", 0, 0) expect(result).to.have.lengthOf(29); expect(result[0]).to.deep.equal({ "m": "start table GSUB script tag 'latn'", "glyphs": true, "t": [ { cl: 0, g: 73 }, { cl: 1, g: 76 }, { cl: 2, g: 3 }, { cl: 3, g: 36 }, { cl: 4, g: 57 }, ], }); expect(result[28]).to.deep.equal({ "m": "end table GPOS script tag 'latn'", "glyphs": true, "t": [ { cl: 0, g: 73, ax: 344, ay: 0, dx: 0, dy: 0 }, { cl: 1, g: 76, ax: 258, ay: 0, dx: 0, dy: 0 }, { cl: 2, g: 3, ax: 260, ay: 0, dx: 0, dy: 0 }, { cl: 3, g: 36, ax: 639, ay: 0, dx: 0, dy: 0 }, { cl: 4, g: 57, ax: 600, ay: 0, dx: 0, dy: 0 }, ], }); }); it('shape with 3-letter languae tag', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSansDevanagari-Regular.otf'))); face = hb.createFace(blob); font = hb.createFont(face); buffer = hb.createBuffer(); buffer.addText('५ल'); buffer.guessSegmentProperties(); hb.shape(font, buffer) var glyphs = buffer.json(); expect(glyphs).to.have.lengthOf(2); expect(glyphs[0].g).to.equal(118); buffer.destroy(); buffer = hb.createBuffer(); buffer.addText('५ल'); buffer.setLanguage('dty'); buffer.guessSegmentProperties(); hb.shape(font, buffer) var glyphs = buffer.json(); expect(glyphs).to.have.lengthOf(2); expect(glyphs[0].g).to.equal(123); }); it('shape with OpenType language tag', function () { blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSansDevanagari-Regular.otf'))); face = hb.createFace(blob); font = hb.createFont(face); buffer = hb.createBuffer(); buffer.addText('५ल'); buffer.guessSegmentProperties(); hb.shape(font, buffer) var glyphs = buffer.json(); expect(glyphs).to.have.lengthOf(2); expect(glyphs[0].g).to.equal(118); buffer.destroy(); buffer = hb.createBuffer(); buffer.addText('५ल'); buffer.setLanguage('x-hbot-4e455020'); // 'NEP ' buffer.guessSegmentProperties(); hb.shape(font, buffer) var glyphs = buffer.json(); expect(glyphs).to.have.lengthOf(2); expect(glyphs[0].g).to.equal(123); }); }); describe('misc', function () { it('get version', function () { const version = hb.version(); expect(version).to.have.property('major').that.is.a('number'); expect(version).to.have.property('minor').that.is.a('number'); expect(version).to.have.property('micro').that.is.a('number'); expect(version.major).to.be.at.least(10); }); it('get version string', function () { const version_string = hb.version_string(); expect(version_string).to.match(/^\d+\.\d+\.\d+$/); }); });