Skip to content

Commit 2c16afe

Browse files
committed
Add Language class wrapping hb_language_t
Similar Feature.
1 parent b65c5ea commit 2c16afe

9 files changed

Lines changed: 78 additions & 47 deletions

File tree

MIGRATING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Affected APIs:
6969

7070
- `Buffer.serialize` now takes a single options object: `{ font, start, end, format, flags }` (all optional). The previous positional signature is gone.
7171
- `shape` and `shapeWithTrace` now take `Feature[]` instead of a comma-separated string. Use the new `Feature` class (e.g. `new Feature("liga", 0)` or `Feature.fromString("-liga")`).
72+
- `Buffer.setLanguage`, `Face.getName`, `Face.listNames`, and `otTagToLanguage` now use the new `Language` class instead of plain BCP 47 strings (e.g. `buffer.setLanguage(new Language("en"))`).
7273
- `Buffer.addText` / `Buffer.addCodePoints`: `itemLength` accepts `undefined` (or omission), instead of `null`.
7374
- `Face.getFeatureNameIds`: returns `undefined` on failure, instead of `null`.
7475
- `Font.glyphHOrigin` / `glyphVOrigin` / `glyphExtents` / `glyphFromName`: return `undefined` on failure, instead of `null`.

src/buffer.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "./helpers";
1111
import type { GlyphInfo, GlyphPosition } from "./types";
1212
import { Font } from "./font";
13+
import type { Language } from "./language";
1314

1415
export const BufferContentType = {
1516
INVALID: 0,
@@ -177,15 +178,10 @@ export class Buffer {
177178

178179
/**
179180
* Set buffer language explicitly.
180-
* @param language The buffer language
181+
* @param language The buffer language.
181182
*/
182-
setLanguage(language: string): void {
183-
const str = string_to_ascii_ptr(language);
184-
exports.hb_buffer_set_language(
185-
this.ptr,
186-
exports.hb_language_from_string(str.ptr, -1),
187-
);
188-
str.free();
183+
setLanguage(language: Language): void {
184+
exports.hb_buffer_set_language(this.ptr, language.ptr);
189185
}
190186

191187
/**

src/face.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ import {
66
hb_tag,
77
hb_untag,
88
utf16_ptr_to_string,
9-
language_to_string,
10-
language_from_string,
119
typed_array_from_set,
1210
type ValueOf,
1311
} from "./helpers";
1412
import type { AxisInfo, NameEntry, FeatureNameIds } from "./types";
1513
import type { Blob } from "./blob";
14+
import { Language } from "./language";
1615

1716
const HB_OT_NAME_ID_INVALID = 0xffff;
1817

@@ -266,9 +265,7 @@ export class Face {
266265
for (let i = 0; i < numEntries; i++) {
267266
entries.push({
268267
nameId: Module.HEAPU32[entriesPtr / 4 + i * 3],
269-
language: language_to_string(
270-
Module.HEAPU32[entriesPtr / 4 + i * 3 + 2],
271-
),
268+
language: new Language(Module.HEAPU32[entriesPtr / 4 + i * 3 + 2]),
272269
});
273270
}
274271
Module.stackRestore(sp);
@@ -281,18 +278,17 @@ export class Face {
281278
* @param language The language of the name to get.
282279
* @returns The name string.
283280
*/
284-
getName(nameId: number, language: string): string {
281+
getName(nameId: number, language: Language): string {
285282
const sp = Module.stackSave();
286-
const languagePtr = language_from_string(language);
287283
const nameLen =
288-
exports.hb_ot_name_get_utf16(this.ptr, nameId, languagePtr, 0, 0) + 1;
284+
exports.hb_ot_name_get_utf16(this.ptr, nameId, language.ptr, 0, 0) + 1;
289285
const textSizePtr = Module.stackAlloc(4);
290286
const textPtr = exports.malloc(nameLen * 2);
291287
Module.HEAPU32[textSizePtr / 4] = nameLen;
292288
exports.hb_ot_name_get_utf16(
293289
this.ptr,
294290
nameId,
295-
languagePtr,
291+
language.ptr,
296292
textSizePtr,
297293
textPtr,
298294
);

src/helpers.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -119,18 +119,6 @@ export function string_to_utf16_ptr(text: string): StringPtr {
119119
};
120120
}
121121

122-
export function language_to_string(language: number): string {
123-
const ptr = exports.hb_language_to_string(language);
124-
return utf8_ptr_to_string(ptr);
125-
}
126-
127-
export function language_from_string(str: string): number {
128-
const languageStr = string_to_ascii_ptr(str);
129-
const languagePtr = exports.hb_language_from_string(languageStr.ptr, -1);
130-
languageStr.free();
131-
return languagePtr;
132-
}
133-
134122
/**
135123
* Return the typed array of HarfBuzz set contents.
136124
* @param setPtr Pointer of set

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from "./font";
88
export * from "./font-funcs";
99
export * from "./buffer";
1010
export * from "./feature";
11+
export * from "./language";
1112
export * from "./shape";
1213

1314
init(await createHarfBuzz());

src/language.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { exports, string_to_ascii_ptr, utf8_ptr_to_string } from "./helpers";
2+
3+
/**
4+
* A {@link https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-language-t | HarfBuzz language}.
5+
*
6+
* Data type for languages. Each Language corresponds to a BCP 47 language tag.
7+
*/
8+
export class Language {
9+
readonly ptr: number;
10+
11+
/**
12+
* Converts `tag` representing a BCP 47 language tag to the corresponding
13+
* Language.
14+
* @param tag A string representing a BCP 47 language tag.
15+
*/
16+
constructor(tag: string);
17+
/** @internal Wrap an existing hb_language_t. */
18+
constructor(existingPtr: number);
19+
constructor(arg: string | number) {
20+
if (typeof arg === "number") {
21+
this.ptr = arg;
22+
} else {
23+
const strPtr = string_to_ascii_ptr(arg);
24+
this.ptr = exports.hb_language_from_string(strPtr.ptr, -1);
25+
strPtr.free();
26+
}
27+
}
28+
29+
/**
30+
* Converts the language to a string.
31+
* @returns A string representing the language.
32+
*/
33+
toString(): string {
34+
return utf8_ptr_to_string(exports.hb_language_to_string(this.ptr));
35+
}
36+
}

src/shape.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import {
44
hb_tag,
55
hb_untag,
66
utf8_ptr_to_string,
7-
language_to_string,
87
type ValueOf,
98
} from "./helpers";
109
import type { TraceEntry } from "./types";
1110
import type { Font } from "./font";
1211
import type { Feature } from "./feature";
12+
import { Language } from "./language";
1313
import {
1414
Buffer,
1515
BufferContentType,
@@ -150,8 +150,6 @@ export function otTagToScript(tag: string): string {
150150
* @param tag The tag to convert.
151151
* @returns The language.
152152
*/
153-
export function otTagToLanguage(tag: string): string {
154-
const hbTag = hb_tag(tag);
155-
const language = exports.hb_ot_tag_to_language(hbTag);
156-
return language_to_string(language);
153+
export function otTagToLanguage(tag: string): Language {
154+
return new Language(exports.hb_ot_tag_to_language(hb_tag(tag)));
157155
}

src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ValueOf } from "./helpers";
2+
import type { Language } from "./language";
23

34
// Data shape types
45
export interface FontExtents {
@@ -47,7 +48,7 @@ export interface AxisInfo {
4748

4849
export interface NameEntry {
4950
nameId: number;
50-
language: string;
51+
language: Language;
5152
}
5253

5354
export interface FeatureNameIds {

test/index.test.js

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -271,17 +271,19 @@ describe("Face", function () {
271271
let face = new hb.Face(blob);
272272
let names = face.listNames();
273273
expect(names.length).to.equal(38);
274-
expect(names[0]).to.deep.equal({ nameId: 0, language: "en" });
275-
expect(names[37]).to.deep.equal({ nameId: 278, language: "en" });
274+
expect(names[0].nameId).to.equal(0);
275+
expect(names[0].language.toString()).to.equal("en");
276+
expect(names[37].nameId).to.equal(278);
277+
expect(names[37].language.toString()).to.equal("en");
276278
});
277279

278280
it("getName fetches a name", function () {
279281
let blob = new hb.Blob(
280282
fs.readFileSync(path.join(__dirname, "fonts/noto/NotoSans-Regular.ttf")),
281283
);
282284
let face = new hb.Face(blob);
283-
expect(face.getName(1, "en")).to.equal("Noto Sans");
284-
expect(face.getName(256, "en")).to.equal("florin symbol");
285+
expect(face.getName(1, new hb.Language("en"))).to.equal("Noto Sans");
286+
expect(face.getName(256, new hb.Language("en"))).to.equal("florin symbol");
285287
});
286288

287289
it("getFeatureNameIds returns valid name Ids for ssNN features", function () {
@@ -311,7 +313,7 @@ describe("Face", function () {
311313
"GSUB",
312314
face.getTableFeatureTags("GSUB").indexOf("ss03"),
313315
).uiLabelNameId,
314-
"en",
316+
new hb.Language("en"),
315317
),
316318
).to.equal("florin symbol");
317319
});
@@ -1080,6 +1082,18 @@ describe("Buffer", function () {
10801082
});
10811083
});
10821084

1085+
describe("Language", function () {
1086+
it("toString returns the BCP 47 tag", function () {
1087+
expect(new hb.Language("en").toString()).to.equal("en");
1088+
expect(new hb.Language("fa-IR").toString()).to.equal("fa-ir");
1089+
});
1090+
1091+
it("interns by tag", function () {
1092+
expect(new hb.Language("en").ptr).to.equal(new hb.Language("en").ptr);
1093+
expect(new hb.Language("en").ptr).to.not.equal(new hb.Language("fr").ptr);
1094+
});
1095+
});
1096+
10831097
describe("Feature", function () {
10841098
it("fromString parses simple tags", function () {
10851099
expect(hb.Feature.fromString("liga")).to.deep.equal(
@@ -1387,7 +1401,7 @@ describe("shape", function () {
13871401

13881402
buffer.clearContents();
13891403
buffer.addText("५ल");
1390-
buffer.setLanguage("dty");
1404+
buffer.setLanguage(new hb.Language("dty"));
13911405
buffer.guessSegmentProperties();
13921406
hb.shape(font, buffer);
13931407
var infos = buffer.getGlyphInfos();
@@ -1413,7 +1427,7 @@ describe("shape", function () {
14131427

14141428
buffer.clearContents();
14151429
buffer.addText("५ल");
1416-
buffer.setLanguage("x-hbot-4e455020"); // 'NEP '
1430+
buffer.setLanguage(new hb.Language("x-hbot-4e455020")); // 'NEP '
14171431
buffer.guessSegmentProperties();
14181432
hb.shape(font, buffer);
14191433
var infos = buffer.getGlyphInfos();
@@ -1445,10 +1459,10 @@ describe("misc", function () {
14451459
});
14461460

14471461
it("convert OpenType tag to language", function () {
1448-
expect(hb.otTagToLanguage("ARA ")).to.equal("ar");
1449-
expect(hb.otTagToLanguage("ENG ")).to.equal("en");
1450-
expect(hb.otTagToLanguage("BAD0")).to.equal("bad");
1451-
expect(hb.otTagToLanguage("SYRE")).to.equal("und-syre");
1462+
expect(hb.otTagToLanguage("ARA ").toString()).to.equal("ar");
1463+
expect(hb.otTagToLanguage("ENG ").toString()).to.equal("en");
1464+
expect(hb.otTagToLanguage("BAD0").toString()).to.equal("bad");
1465+
expect(hb.otTagToLanguage("SYRE").toString()).to.equal("und-syre");
14521466
});
14531467

14541468
it("test that calling functions repeatedly doesn't exhaust memory", function () {
@@ -1459,7 +1473,7 @@ describe("misc", function () {
14591473
let font = new hb.Font(face);
14601474
for (let i = 0; i < 10000; i++) {
14611475
expect(face.listNames()).to.not.be.undefined;
1462-
expect(face.getName(0, "en")).to.not.be.undefined;
1476+
expect(face.getName(0, new hb.Language("en"))).to.not.be.undefined;
14631477
expect(font.hExtents()).to.not.be.undefined;
14641478
expect(font.vExtents()).to.not.be.undefined;
14651479
expect(font.glyphHOrigin(0)).to.not.be.undefined;

0 commit comments

Comments
 (0)