OwlCyberSecurity - MANAGER
Edit File: name.js
import assert from 'assert'; import table from '../../src/table'; import { encode } from '../../src/types'; import _name from '../../src/tables/name'; import { hex, unhex } from '../testutil'; // For testing, we need a custom function that builds name tables. // The public name.make() API of opentype.js is hiding the complexity // of the various historic encodings and language identification // systems that are used in OpenType and TrueType. Instead, it emits a // simple JavaScript dictionary keyed by IETF BCP 47 language codes, // which is the same format that is used for HTML and XML language // tags. That is convenient for users of opentype.js, but it // complicates testing. function makeNameTable(names) { const t = new table.Table('name', [ {name: 'format', type: 'USHORT', value: 0}, {name: 'count', type: 'USHORT', value: names.length}, {name: 'stringOffset', type: 'USHORT', value: 6 + names.length * 12} ]); const stringPool = []; for (let i = 0; i < names.length; i++) { const name = names[i]; const text = unhex(name[1]); t.fields.push({name: 'platformID_' + i, type: 'USHORT', value: name[2]}); t.fields.push({name: 'encodingID_' + i, type: 'USHORT', value: name[3]}); t.fields.push({name: 'languageID_' + i, type: 'USHORT', value: name[4]}); t.fields.push({name: 'nameID_' + i, type: 'USHORT', value: name[0]}); t.fields.push({name: 'length_' + i, type: 'USHORT', value: text.byteLength}); t.fields.push({name: 'offset_' + i, type: 'USHORT', value: stringPool.length}); for (let j = 0; j < text.byteLength; j++) { stringPool.push(text.getUint8(j)); } } t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); const bytes = encode.TABLE(t); const data = new DataView(new ArrayBuffer(bytes.length), 0); for (let k = 0; k < bytes.length; k++) { data.setUint8(k, bytes[k]); } return data; } function parseNameTable(names, ltag) { return _name.parse(makeNameTable(names), 0, ltag); } function getNameRecords(names) { const result = []; const stringPool = names.fields[names.fields.length - 1]; assert(stringPool.name === 'strings'); for (let i = 0; i < names.fields.length; i++) { const field = names.fields[i]; if (field.name.indexOf('record_') === 0) { const name = field.value; const encodedText = stringPool.value.slice(name.offset, name.offset + name.length); const plat = {0: 'Uni', 1: 'Mac', 2: 'ISO', 3: 'Win'}[name.platformID] || name.platormID; let enc; let lang; if (name.platformID === 0) { enc = {3: 'UCS-2', 4: 'UTF-16'}[name.encodingID]; lang = undefined; } else if (name.platformID === 1) { enc = {0: 'smRoman', 28: 'smEthiopic'}[name.encodingID]; lang = { 0: 'langEnglish', 2: 'langGerman', 81: 'langIndonesian', 143: 'langInuktitut' }[name.languageID]; } else if (name.platformID === 3) { enc = {1: 'UCS-2', 10: 'UCS-4'}[name.encodingID]; lang = { 0x0407: 'German/Germany', 0x0409: 'English/US', 0x0411: 'Japanese/Japan', 0x0421: 'Indonesian/Indonesia', 0x045d: 'Inuktitut/Canada', 0x085d: 'Inuktitut-Latin/Canada' }[name.languageID]; } else { enc = lang = undefined; } result.push(plat + ' ' + (enc || name.encodingID) + ' ' + (lang || name.languageID) + ' N' + name.nameID + ' [' + hex(encodedText) + ']'); } } return result; } describe('tables/name.js', function() { it('can parse a naming table', function() { assert.deepEqual(parseNameTable([ [1, '0057 0061 006C 0072 0075 0073', 3, 1, 0x0409], [1, '140A 1403 1555 1585', 3, 1, 0x045D], [1, '0041 0069 0076 0069 0071', 3, 1, 0x085D], [1, '6D77 99AC', 3, 1, 0x0411], [300, '42 6C 61 63 6B 20 43 6F 6E 64 65 6E 73 65 64', 1, 0, 0], [300, '4B 6F 79 75 20 53 DD 6B DD DF DD 6B', 1, 35, 17], [300, '8F EE EB F3 F7 E5 F0 20 F2 E5 F1 E5 ED', 1, 7, 44], [44444, '004C 0069 0070 0073 0074 0069 0063 006B 0020 D83D DC84', 3, 10, 0x0409] ], undefined), { fontFamily: { en: 'Walrus', iu: 'ᐊᐃᕕᖅ', 'iu-Latn': 'Aiviq', ja: '海馬' }, 300: { bg: 'Получер тесен', en: 'Black Condensed', tr: 'Koyu Sıkışık' }, 44444: { en: 'Lipstick 💄' } }); }); it('can parse a naming table which refers to an ‘ltag’ table', function() { const ltag = ['en', 'de', 'de-1901']; assert.deepEqual(parseNameTable([ [1, '0057 0061 006C 0072 0075 0073', 0, 4, 0], [1, '0057 0061 006C 0072 006F 0073 0073', 0, 4, 1], [1, '0057 0061 006C 0072 006F 00DF', 0, 4, 2], [999, '0057 0061 006C 0072 0075 0073 002D 0054 0068 0069 006E', 0, 4, 0xFFFF] ], ltag), { fontFamily: { de: 'Walross', 'de-1901': 'Walroß', en: 'Walrus' }, 999: { und: 'Walrus-Thin' } }); }); it('ignores name records for unknown platforms', function() { assert.deepEqual(parseNameTable([ [1, '01 02', 666, 1, 1] ]), {}); }); it('can make a naming table', function() { // This is an interesting test case for various reasons: // * Indonesian ('id') uses the same string as English, // so we exercise the building of string pools; const names = { fontFamily: { en: 'Walrus', de: 'Walross', id: 'Walrus' } }; const ltag = []; assert.deepEqual(getNameRecords(_name.make(names, ltag)), [ 'Mac smRoman langEnglish N1 [57 61 6C 72 75 73]', 'Mac smRoman langGerman N1 [57 61 6C 72 6F 73 73]', 'Mac smRoman langIndonesian N1 [57 61 6C 72 75 73]', 'Win UCS-2 German/Germany N1 [00 57 00 61 00 6C 00 72 00 6F 00 73 00 73]', 'Win UCS-2 English/US N1 [00 57 00 61 00 6C 00 72 00 75 00 73]', 'Win UCS-2 Indonesian/Indonesia N1 [00 57 00 61 00 6C 00 72 00 75 00 73]' ]); assert.deepEqual(ltag, []); }); it('can make a naming table that refers to a language tag table', function() { // Neither Windows nor MacOS define a numeric language code // for “German in the traditional orthography” (de-1901). // Windows has one for “Inuktitut in Latin” (iu-Latn), // but MacOS does not. const names = { fontFamily: { 'de-1901': 'Walroß', 'iu-Latn': 'Aiviq' } }; const ltag = []; assert.deepEqual(getNameRecords(_name.make(names, ltag)), [ 'Uni UTF-16 0 N1 [00 57 00 61 00 6C 00 72 00 6F 00 DF]', 'Uni UTF-16 1 N1 [00 41 00 69 00 76 00 69 00 71]', 'Win UCS-2 Inuktitut-Latin/Canada N1 [00 41 00 69 00 76 00 69 00 71]' ]); assert.deepEqual(ltag, ['de-1901', 'iu-Latn']); }); it('can make a naming table for languages in unsupported scripts', function() { // MacJapanese would need very large tables for conversion, // so we do not ship a codec for this encoding in opentype.js. // The implementation should fall back to emitting Unicode strings // with a BCP 47 language code; only newer versions of MacOS will // recognize it but this is better than stripping the string away. const names = { fontFamily: { ja: '海馬' } }; const ltag = []; assert.deepEqual(getNameRecords(_name.make(names, ltag)), [ 'Uni UTF-16 0 N1 [6D 77 99 AC]', 'Win UCS-2 Japanese/Japan N1 [6D 77 99 AC]' ]); assert.deepEqual(ltag, ['ja']); }); it('can make a naming table for English names with unusual characters', function() { // The MacRoman encoding has no interrobang character. When // building a name table, this case should be handled gracefully. const names = { fontFamily: { en: 'Hello‽' } }; const ltag = []; assert.deepEqual(getNameRecords(_name.make(names, ltag)), [ 'Uni UTF-16 0 N1 [00 48 00 65 00 6C 00 6C 00 6F 20 3D]', 'Win UCS-2 English/US N1 [00 48 00 65 00 6C 00 6C 00 6F 20 3D]' ]); assert.deepEqual(ltag, ['en']); }); it('can make a naming table for languages with unusual Mac script codes', function() { // Inuktitut ('iu') has a very unusual MacOS script code (smEthiopic) // although there are probably not too many Inuit in Ethiopia. // Apple had run out of script codes and needed a quick hack. // The implementation uses a secondary look-up table for handling such // corner cases (Inuktitut is not the only one), and this test exercises it. const names = { fontFamily: { iu: 'ᐊᐃᕕᖅ' } }; const ltag = []; assert.deepEqual(getNameRecords(_name.make(names, ltag)), [ 'Mac smEthiopic langInuktitut N1 [84 80 CD E7]', 'Win UCS-2 Inuktitut/Canada N1 [14 0A 14 03 15 55 15 85]' ]); assert.deepEqual(ltag, []); }); it('can make a naming table with custom names', function() { // Custom name for a font variation axis. const names = { 256: { en: 'Width', de: 'Breite' } }; const ltag = []; assert.deepEqual(getNameRecords(_name.make(names, ltag)), [ 'Mac smRoman langEnglish N256 [57 69 64 74 68]', 'Mac smRoman langGerman N256 [42 72 65 69 74 65]', 'Win UCS-2 German/Germany N256 [00 42 00 72 00 65 00 69 00 74 00 65]', 'Win UCS-2 English/US N256 [00 57 00 69 00 64 00 74 00 68]' ]); assert.deepEqual(ltag, []); }); });