diff --git a/MIGRATING.md b/MIGRATING.md index ac4c756..63a9b24 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -70,6 +70,9 @@ Affected APIs: - `Buffer.serialize` now takes a single options object: `{ font, start, end, format, flags }` (all optional). The previous positional signature is gone. - `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")`). - `Font.setVariations` now takes `Variation[]` instead of `Record`. Use the new `Variation` class (e.g. `font.setVariations([new Variation("wght", 700)])` or `Variation.fromString("wght=700")`). +- `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"))`). +- `Buffer.setScript` and `otTagToScript` now use the new `Script` class instead of plain ISO 15924 tag strings (e.g. `buffer.setScript(new Script("Latn"))` or, for the predefined ISO 15924 scripts, `buffer.setScript(Script.LATIN)`). +- `Direction` is now a class with `INVALID`/`LTR`/`RTL`/`TTB`/`BTT` static instances and a string constructor. `Buffer.setDirection` takes a `Direction` instance; the call form `buffer.setDirection(Direction.RTL)` shown in the v0 → v1 table above keeps working. - `Buffer.addText` / `Buffer.addCodePoints`: `itemLength` accepts `undefined` (or omission), instead of `null`. - `Face.getFeatureNameIds`: returns `undefined` on failure, instead of `null`. - `Font.glyphHOrigin` / `glyphVOrigin` / `glyphExtents` / `glyphFromName`: return `undefined` on failure, instead of `null`. diff --git a/harfbuzz.symbols b/harfbuzz.symbols index 93638ef..20146a6 100644 --- a/harfbuzz.symbols +++ b/harfbuzz.symbols @@ -16,6 +16,8 @@ _hb_buffer_get_content_type _hb_buffer_guess_segment_properties _hb_buffer_set_cluster_level _hb_buffer_set_direction +_hb_direction_from_string +_hb_direction_to_string _hb_buffer_set_flags _hb_buffer_set_language _hb_buffer_set_script diff --git a/src/buffer.ts b/src/buffer.ts index 442925e..94fbdb2 100644 --- a/src/buffer.ts +++ b/src/buffer.ts @@ -4,12 +4,14 @@ import { track, hb_tag, utf8_ptr_to_string, - string_to_ascii_ptr, string_to_utf16_ptr, type ValueOf, } from "./helpers"; import type { GlyphInfo, GlyphPosition } from "./types"; import { Font } from "./font"; +import type { Language } from "./language"; +import type { Script } from "./script"; +import type { Direction } from "./direction"; export const BufferContentType = { INVALID: 0, @@ -44,15 +46,6 @@ export const BufferFlag = { } as const; export type BufferFlag = ValueOf; -export const Direction = { - INVALID: 0, - LTR: 4, - RTL: 5, - TTB: 6, - BTT: 7, -} as const; -export type Direction = ValueOf; - export const ClusterLevel = { MONOTONE_GRAPHEMES: 0, MONOTONE_CHARACTERS: 1, @@ -161,10 +154,10 @@ export class Buffer { /** * Set buffer direction explicitly. - * @param dir A {@link Direction} value. + * @param dir The buffer direction. */ setDirection(dir: Direction): void { - exports.hb_buffer_set_direction(this.ptr, dir); + exports.hb_buffer_set_direction(this.ptr, dir.value); } /** @@ -177,28 +170,18 @@ export class Buffer { /** * Set buffer language explicitly. - * @param language The buffer language + * @param language The buffer language. */ - setLanguage(language: string): void { - const str = string_to_ascii_ptr(language); - exports.hb_buffer_set_language( - this.ptr, - exports.hb_language_from_string(str.ptr, -1), - ); - str.free(); + setLanguage(language: Language): void { + exports.hb_buffer_set_language(this.ptr, language.ptr); } /** * Set buffer script explicitly. - * @param script The buffer script + * @param script The buffer script. */ - setScript(script: string): void { - const str = string_to_ascii_ptr(script); - exports.hb_buffer_set_script( - this.ptr, - exports.hb_script_from_string(str.ptr, -1), - ); - str.free(); + setScript(script: Script): void { + exports.hb_buffer_set_script(this.ptr, script.value); } /** diff --git a/src/direction.ts b/src/direction.ts new file mode 100644 index 0000000..f745bff --- /dev/null +++ b/src/direction.ts @@ -0,0 +1,49 @@ +import { exports, string_to_ascii_ptr, utf8_ptr_to_string } from "./helpers"; + +/** + * The direction of a text segment or buffer. + * + * A segment can also be tested for horizontal or vertical orientation + * (irrespective of specific direction). + */ +export class Direction { + /** Initial, unset direction. */ + static readonly INVALID = new Direction(0); + /** Text is set horizontally from left to right. */ + static readonly LTR = new Direction(4); + /** Text is set horizontally from right to left. */ + static readonly RTL = new Direction(5); + /** Text is set vertically from top to bottom. */ + static readonly TTB = new Direction(6); + /** Text is set vertically from bottom to top. */ + static readonly BTT = new Direction(7); + + readonly value: number; + + /** + * Converts a string to a Direction. Matching is loose and case-insensitive; + * the first letter determines the direction (`l`/`L`: LTR, `r`/`R`: RTL, + * `t`/`T`: TTB, `b`/`B`: BTT). Other strings yield `Direction.INVALID`. + * @param name A string like `"ltr"`, `"rtl"`, `"ttb"`, or `"btt"`. + */ + constructor(name: string); + /** @internal Wrap an existing hb_direction_t. */ + constructor(existingValue: number); + constructor(arg: string | number) { + if (typeof arg === "number") { + this.value = arg; + } else { + const strPtr = string_to_ascii_ptr(arg); + this.value = exports.hb_direction_from_string(strPtr.ptr, -1); + strPtr.free(); + } + } + + /** + * Converts the Direction to a string. + * @returns A string like `"ltr"`, `"rtl"`, `"ttb"`, `"btt"`, or `"invalid"`. + */ + toString(): string { + return utf8_ptr_to_string(exports.hb_direction_to_string(this.value)); + } +} diff --git a/src/face.ts b/src/face.ts index 29f79d1..75e1cae 100644 --- a/src/face.ts +++ b/src/face.ts @@ -6,13 +6,12 @@ import { hb_tag, hb_untag, utf16_ptr_to_string, - language_to_string, - language_from_string, typed_array_from_set, type ValueOf, } from "./helpers"; import type { AxisInfo, NameEntry, FeatureNameIds } from "./types"; import type { Blob } from "./blob"; +import { Language } from "./language"; const HB_OT_NAME_ID_INVALID = 0xffff; @@ -266,9 +265,7 @@ export class Face { for (let i = 0; i < numEntries; i++) { entries.push({ nameId: Module.HEAPU32[entriesPtr / 4 + i * 3], - language: language_to_string( - Module.HEAPU32[entriesPtr / 4 + i * 3 + 2], - ), + language: new Language(Module.HEAPU32[entriesPtr / 4 + i * 3 + 2]), }); } Module.stackRestore(sp); @@ -281,18 +278,17 @@ export class Face { * @param language The language of the name to get. * @returns The name string. */ - getName(nameId: number, language: string): string { + getName(nameId: number, language: Language): string { const sp = Module.stackSave(); - const languagePtr = language_from_string(language); const nameLen = - exports.hb_ot_name_get_utf16(this.ptr, nameId, languagePtr, 0, 0) + 1; + exports.hb_ot_name_get_utf16(this.ptr, nameId, language.ptr, 0, 0) + 1; const textSizePtr = Module.stackAlloc(4); const textPtr = exports.malloc(nameLen * 2); Module.HEAPU32[textSizePtr / 4] = nameLen; exports.hb_ot_name_get_utf16( this.ptr, nameId, - languagePtr, + language.ptr, textSizePtr, textPtr, ); diff --git a/src/helpers.ts b/src/helpers.ts index 3348935..82cc6ee 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -119,18 +119,6 @@ export function string_to_utf16_ptr(text: string): StringPtr { }; } -export function language_to_string(language: number): string { - const ptr = exports.hb_language_to_string(language); - return utf8_ptr_to_string(ptr); -} - -export function language_from_string(str: string): number { - const languageStr = string_to_ascii_ptr(str); - const languagePtr = exports.hb_language_from_string(languageStr.ptr, -1); - languageStr.free(); - return languagePtr; -} - /** * Return the typed array of HarfBuzz set contents. * @param setPtr Pointer of set diff --git a/src/index.ts b/src/index.ts index ffaa2a5..f539da3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,9 @@ export * from "./font-funcs"; export * from "./buffer"; export * from "./feature"; export * from "./variation"; +export * from "./language"; +export * from "./script"; +export * from "./direction"; export * from "./shape"; init(await createHarfBuzz()); diff --git a/src/language.ts b/src/language.ts new file mode 100644 index 0000000..cd4817f --- /dev/null +++ b/src/language.ts @@ -0,0 +1,36 @@ +import { exports, string_to_ascii_ptr, utf8_ptr_to_string } from "./helpers"; + +/** + * A {@link https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-language-t | HarfBuzz language}. + * + * Data type for languages. Each Language corresponds to a BCP 47 language tag. + */ +export class Language { + readonly ptr: number; + + /** + * Converts `tag` representing a BCP 47 language tag to the corresponding + * Language. + * @param tag A string representing a BCP 47 language tag. + */ + constructor(tag: string); + /** @internal Wrap an existing hb_language_t. */ + constructor(existingPtr: number); + constructor(arg: string | number) { + if (typeof arg === "number") { + this.ptr = arg; + } else { + const strPtr = string_to_ascii_ptr(arg); + this.ptr = exports.hb_language_from_string(strPtr.ptr, -1); + strPtr.free(); + } + } + + /** + * Converts the language to a string. + * @returns A string representing the language. + */ + toString(): string { + return utf8_ptr_to_string(exports.hb_language_to_string(this.ptr)); + } +} diff --git a/src/script.ts b/src/script.ts new file mode 100644 index 0000000..11ec977 --- /dev/null +++ b/src/script.ts @@ -0,0 +1,396 @@ +import { exports, hb_tag, hb_untag, string_to_ascii_ptr } from "./helpers"; + +/** + * A {@link https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-script-t | HarfBuzz script}. + * + * Data type for scripts. Each Script's tag is an `hb_tag_t` corresponding to + * the four-letter values defined by + * {@link https://unicode.org/iso15924/ | ISO 15924}. + * + * See also the Script (sc) property of the Unicode Character Database. + */ +export class Script { + /** `Zyyy` */ + static readonly COMMON = new Script(hb_tag("Zyyy")); + /** `Zinh` */ + static readonly INHERITED = new Script(hb_tag("Zinh")); + /** `Zzzz` */ + static readonly UNKNOWN = new Script(hb_tag("Zzzz")); + /** `Arab` */ + static readonly ARABIC = new Script(hb_tag("Arab")); + /** `Armn` */ + static readonly ARMENIAN = new Script(hb_tag("Armn")); + /** `Beng` */ + static readonly BENGALI = new Script(hb_tag("Beng")); + /** `Cyrl` */ + static readonly CYRILLIC = new Script(hb_tag("Cyrl")); + /** `Deva` */ + static readonly DEVANAGARI = new Script(hb_tag("Deva")); + /** `Geor` */ + static readonly GEORGIAN = new Script(hb_tag("Geor")); + /** `Grek` */ + static readonly GREEK = new Script(hb_tag("Grek")); + /** `Gujr` */ + static readonly GUJARATI = new Script(hb_tag("Gujr")); + /** `Guru` */ + static readonly GURMUKHI = new Script(hb_tag("Guru")); + /** `Hang` */ + static readonly HANGUL = new Script(hb_tag("Hang")); + /** `Hani` */ + static readonly HAN = new Script(hb_tag("Hani")); + /** `Hebr` */ + static readonly HEBREW = new Script(hb_tag("Hebr")); + /** `Hira` */ + static readonly HIRAGANA = new Script(hb_tag("Hira")); + /** `Knda` */ + static readonly KANNADA = new Script(hb_tag("Knda")); + /** `Kana` */ + static readonly KATAKANA = new Script(hb_tag("Kana")); + /** `Laoo` */ + static readonly LAO = new Script(hb_tag("Laoo")); + /** `Latn` */ + static readonly LATIN = new Script(hb_tag("Latn")); + /** `Mlym` */ + static readonly MALAYALAM = new Script(hb_tag("Mlym")); + /** `Orya` */ + static readonly ORIYA = new Script(hb_tag("Orya")); + /** `Taml` */ + static readonly TAMIL = new Script(hb_tag("Taml")); + /** `Telu` */ + static readonly TELUGU = new Script(hb_tag("Telu")); + /** `Thai` */ + static readonly THAI = new Script(hb_tag("Thai")); + /** `Tibt` */ + static readonly TIBETAN = new Script(hb_tag("Tibt")); + /** `Bopo` */ + static readonly BOPOMOFO = new Script(hb_tag("Bopo")); + /** `Brai` */ + static readonly BRAILLE = new Script(hb_tag("Brai")); + /** `Cans` */ + static readonly CANADIAN_SYLLABICS = new Script(hb_tag("Cans")); + /** `Cher` */ + static readonly CHEROKEE = new Script(hb_tag("Cher")); + /** `Ethi` */ + static readonly ETHIOPIC = new Script(hb_tag("Ethi")); + /** `Khmr` */ + static readonly KHMER = new Script(hb_tag("Khmr")); + /** `Mong` */ + static readonly MONGOLIAN = new Script(hb_tag("Mong")); + /** `Mymr` */ + static readonly MYANMAR = new Script(hb_tag("Mymr")); + /** `Ogam` */ + static readonly OGHAM = new Script(hb_tag("Ogam")); + /** `Runr` */ + static readonly RUNIC = new Script(hb_tag("Runr")); + /** `Sinh` */ + static readonly SINHALA = new Script(hb_tag("Sinh")); + /** `Syrc` */ + static readonly SYRIAC = new Script(hb_tag("Syrc")); + /** `Thaa` */ + static readonly THAANA = new Script(hb_tag("Thaa")); + /** `Yiii` */ + static readonly YI = new Script(hb_tag("Yiii")); + /** `Dsrt` */ + static readonly DESERET = new Script(hb_tag("Dsrt")); + /** `Goth` */ + static readonly GOTHIC = new Script(hb_tag("Goth")); + /** `Ital` */ + static readonly OLD_ITALIC = new Script(hb_tag("Ital")); + /** `Buhd` */ + static readonly BUHID = new Script(hb_tag("Buhd")); + /** `Hano` */ + static readonly HANUNOO = new Script(hb_tag("Hano")); + /** `Tglg` */ + static readonly TAGALOG = new Script(hb_tag("Tglg")); + /** `Tagb` */ + static readonly TAGBANWA = new Script(hb_tag("Tagb")); + /** `Cprt` */ + static readonly CYPRIOT = new Script(hb_tag("Cprt")); + /** `Limb` */ + static readonly LIMBU = new Script(hb_tag("Limb")); + /** `Linb` */ + static readonly LINEAR_B = new Script(hb_tag("Linb")); + /** `Osma` */ + static readonly OSMANYA = new Script(hb_tag("Osma")); + /** `Shaw` */ + static readonly SHAVIAN = new Script(hb_tag("Shaw")); + /** `Tale` */ + static readonly TAI_LE = new Script(hb_tag("Tale")); + /** `Ugar` */ + static readonly UGARITIC = new Script(hb_tag("Ugar")); + /** `Bugi` */ + static readonly BUGINESE = new Script(hb_tag("Bugi")); + /** `Copt` */ + static readonly COPTIC = new Script(hb_tag("Copt")); + /** `Glag` */ + static readonly GLAGOLITIC = new Script(hb_tag("Glag")); + /** `Khar` */ + static readonly KHAROSHTHI = new Script(hb_tag("Khar")); + /** `Talu` */ + static readonly NEW_TAI_LUE = new Script(hb_tag("Talu")); + /** `Xpeo` */ + static readonly OLD_PERSIAN = new Script(hb_tag("Xpeo")); + /** `Sylo` */ + static readonly SYLOTI_NAGRI = new Script(hb_tag("Sylo")); + /** `Tfng` */ + static readonly TIFINAGH = new Script(hb_tag("Tfng")); + /** `Bali` */ + static readonly BALINESE = new Script(hb_tag("Bali")); + /** `Xsux` */ + static readonly CUNEIFORM = new Script(hb_tag("Xsux")); + /** `Nkoo` */ + static readonly NKO = new Script(hb_tag("Nkoo")); + /** `Phag` */ + static readonly PHAGS_PA = new Script(hb_tag("Phag")); + /** `Phnx` */ + static readonly PHOENICIAN = new Script(hb_tag("Phnx")); + /** `Cari` */ + static readonly CARIAN = new Script(hb_tag("Cari")); + /** `Cham` */ + static readonly CHAM = new Script(hb_tag("Cham")); + /** `Kali` */ + static readonly KAYAH_LI = new Script(hb_tag("Kali")); + /** `Lepc` */ + static readonly LEPCHA = new Script(hb_tag("Lepc")); + /** `Lyci` */ + static readonly LYCIAN = new Script(hb_tag("Lyci")); + /** `Lydi` */ + static readonly LYDIAN = new Script(hb_tag("Lydi")); + /** `Olck` */ + static readonly OL_CHIKI = new Script(hb_tag("Olck")); + /** `Rjng` */ + static readonly REJANG = new Script(hb_tag("Rjng")); + /** `Saur` */ + static readonly SAURASHTRA = new Script(hb_tag("Saur")); + /** `Sund` */ + static readonly SUNDANESE = new Script(hb_tag("Sund")); + /** `Vaii` */ + static readonly VAI = new Script(hb_tag("Vaii")); + /** `Avst` */ + static readonly AVESTAN = new Script(hb_tag("Avst")); + /** `Bamu` */ + static readonly BAMUM = new Script(hb_tag("Bamu")); + /** `Egyp` */ + static readonly EGYPTIAN_HIEROGLYPHS = new Script(hb_tag("Egyp")); + /** `Armi` */ + static readonly IMPERIAL_ARAMAIC = new Script(hb_tag("Armi")); + /** `Phli` */ + static readonly INSCRIPTIONAL_PAHLAVI = new Script(hb_tag("Phli")); + /** `Prti` */ + static readonly INSCRIPTIONAL_PARTHIAN = new Script(hb_tag("Prti")); + /** `Java` */ + static readonly JAVANESE = new Script(hb_tag("Java")); + /** `Kthi` */ + static readonly KAITHI = new Script(hb_tag("Kthi")); + /** `Lisu` */ + static readonly LISU = new Script(hb_tag("Lisu")); + /** `Mtei` */ + static readonly MEETEI_MAYEK = new Script(hb_tag("Mtei")); + /** `Sarb` */ + static readonly OLD_SOUTH_ARABIAN = new Script(hb_tag("Sarb")); + /** `Orkh` */ + static readonly OLD_TURKIC = new Script(hb_tag("Orkh")); + /** `Samr` */ + static readonly SAMARITAN = new Script(hb_tag("Samr")); + /** `Lana` */ + static readonly TAI_THAM = new Script(hb_tag("Lana")); + /** `Tavt` */ + static readonly TAI_VIET = new Script(hb_tag("Tavt")); + /** `Batk` */ + static readonly BATAK = new Script(hb_tag("Batk")); + /** `Brah` */ + static readonly BRAHMI = new Script(hb_tag("Brah")); + /** `Mand` */ + static readonly MANDAIC = new Script(hb_tag("Mand")); + /** `Cakm` */ + static readonly CHAKMA = new Script(hb_tag("Cakm")); + /** `Merc` */ + static readonly MEROITIC_CURSIVE = new Script(hb_tag("Merc")); + /** `Mero` */ + static readonly MEROITIC_HIEROGLYPHS = new Script(hb_tag("Mero")); + /** `Plrd` */ + static readonly MIAO = new Script(hb_tag("Plrd")); + /** `Shrd` */ + static readonly SHARADA = new Script(hb_tag("Shrd")); + /** `Sora` */ + static readonly SORA_SOMPENG = new Script(hb_tag("Sora")); + /** `Takr` */ + static readonly TAKRI = new Script(hb_tag("Takr")); + /** `Bass` */ + static readonly BASSA_VAH = new Script(hb_tag("Bass")); + /** `Aghb` */ + static readonly CAUCASIAN_ALBANIAN = new Script(hb_tag("Aghb")); + /** `Dupl` */ + static readonly DUPLOYAN = new Script(hb_tag("Dupl")); + /** `Elba` */ + static readonly ELBASAN = new Script(hb_tag("Elba")); + /** `Gran` */ + static readonly GRANTHA = new Script(hb_tag("Gran")); + /** `Khoj` */ + static readonly KHOJKI = new Script(hb_tag("Khoj")); + /** `Sind` */ + static readonly KHUDAWADI = new Script(hb_tag("Sind")); + /** `Lina` */ + static readonly LINEAR_A = new Script(hb_tag("Lina")); + /** `Mahj` */ + static readonly MAHAJANI = new Script(hb_tag("Mahj")); + /** `Mani` */ + static readonly MANICHAEAN = new Script(hb_tag("Mani")); + /** `Mend` */ + static readonly MENDE_KIKAKUI = new Script(hb_tag("Mend")); + /** `Modi` */ + static readonly MODI = new Script(hb_tag("Modi")); + /** `Mroo` */ + static readonly MRO = new Script(hb_tag("Mroo")); + /** `Nbat` */ + static readonly NABATAEAN = new Script(hb_tag("Nbat")); + /** `Narb` */ + static readonly OLD_NORTH_ARABIAN = new Script(hb_tag("Narb")); + /** `Perm` */ + static readonly OLD_PERMIC = new Script(hb_tag("Perm")); + /** `Hmng` */ + static readonly PAHAWH_HMONG = new Script(hb_tag("Hmng")); + /** `Palm` */ + static readonly PALMYRENE = new Script(hb_tag("Palm")); + /** `Pauc` */ + static readonly PAU_CIN_HAU = new Script(hb_tag("Pauc")); + /** `Phlp` */ + static readonly PSALTER_PAHLAVI = new Script(hb_tag("Phlp")); + /** `Sidd` */ + static readonly SIDDHAM = new Script(hb_tag("Sidd")); + /** `Tirh` */ + static readonly TIRHUTA = new Script(hb_tag("Tirh")); + /** `Wara` */ + static readonly WARANG_CITI = new Script(hb_tag("Wara")); + /** `Ahom` */ + static readonly AHOM = new Script(hb_tag("Ahom")); + /** `Hluw` */ + static readonly ANATOLIAN_HIEROGLYPHS = new Script(hb_tag("Hluw")); + /** `Hatr` */ + static readonly HATRAN = new Script(hb_tag("Hatr")); + /** `Mult` */ + static readonly MULTANI = new Script(hb_tag("Mult")); + /** `Hung` */ + static readonly OLD_HUNGARIAN = new Script(hb_tag("Hung")); + /** `Sgnw` */ + static readonly SIGNWRITING = new Script(hb_tag("Sgnw")); + /** `Adlm` */ + static readonly ADLAM = new Script(hb_tag("Adlm")); + /** `Bhks` */ + static readonly BHAIKSUKI = new Script(hb_tag("Bhks")); + /** `Marc` */ + static readonly MARCHEN = new Script(hb_tag("Marc")); + /** `Osge` */ + static readonly OSAGE = new Script(hb_tag("Osge")); + /** `Tang` */ + static readonly TANGUT = new Script(hb_tag("Tang")); + /** `Newa` */ + static readonly NEWA = new Script(hb_tag("Newa")); + /** `Gonm` */ + static readonly MASARAM_GONDI = new Script(hb_tag("Gonm")); + /** `Nshu` */ + static readonly NUSHU = new Script(hb_tag("Nshu")); + /** `Soyo` */ + static readonly SOYOMBO = new Script(hb_tag("Soyo")); + /** `Zanb` */ + static readonly ZANABAZAR_SQUARE = new Script(hb_tag("Zanb")); + /** `Dogr` */ + static readonly DOGRA = new Script(hb_tag("Dogr")); + /** `Gong` */ + static readonly GUNJALA_GONDI = new Script(hb_tag("Gong")); + /** `Rohg` */ + static readonly HANIFI_ROHINGYA = new Script(hb_tag("Rohg")); + /** `Maka` */ + static readonly MAKASAR = new Script(hb_tag("Maka")); + /** `Medf` */ + static readonly MEDEFAIDRIN = new Script(hb_tag("Medf")); + /** `Sogo` */ + static readonly OLD_SOGDIAN = new Script(hb_tag("Sogo")); + /** `Sogd` */ + static readonly SOGDIAN = new Script(hb_tag("Sogd")); + /** `Elym` */ + static readonly ELYMAIC = new Script(hb_tag("Elym")); + /** `Nand` */ + static readonly NANDINAGARI = new Script(hb_tag("Nand")); + /** `Hmnp` */ + static readonly NYIAKENG_PUACHUE_HMONG = new Script(hb_tag("Hmnp")); + /** `Wcho` */ + static readonly WANCHO = new Script(hb_tag("Wcho")); + /** `Chrs` */ + static readonly CHORASMIAN = new Script(hb_tag("Chrs")); + /** `Diak` */ + static readonly DIVES_AKURU = new Script(hb_tag("Diak")); + /** `Kits` */ + static readonly KHITAN_SMALL_SCRIPT = new Script(hb_tag("Kits")); + /** `Yezi` */ + static readonly YEZIDI = new Script(hb_tag("Yezi")); + /** `Cpmn` */ + static readonly CYPRO_MINOAN = new Script(hb_tag("Cpmn")); + /** `Ougr` */ + static readonly OLD_UYGHUR = new Script(hb_tag("Ougr")); + /** `Tnsa` */ + static readonly TANGSA = new Script(hb_tag("Tnsa")); + /** `Toto` */ + static readonly TOTO = new Script(hb_tag("Toto")); + /** `Vith` */ + static readonly VITHKUQI = new Script(hb_tag("Vith")); + /** `Zmth` */ + static readonly MATH = new Script(hb_tag("Zmth")); + /** `Kawi` */ + static readonly KAWI = new Script(hb_tag("Kawi")); + /** `Nagm` */ + static readonly NAG_MUNDARI = new Script(hb_tag("Nagm")); + /** `Gara` */ + static readonly GARAY = new Script(hb_tag("Gara")); + /** `Gukh` */ + static readonly GURUNG_KHEMA = new Script(hb_tag("Gukh")); + /** `Krai` */ + static readonly KIRAT_RAI = new Script(hb_tag("Krai")); + /** `Onao` */ + static readonly OL_ONAL = new Script(hb_tag("Onao")); + /** `Sunu` */ + static readonly SUNUWAR = new Script(hb_tag("Sunu")); + /** `Todr` */ + static readonly TODHRI = new Script(hb_tag("Todr")); + /** `Tutg` */ + static readonly TULU_TIGALARI = new Script(hb_tag("Tutg")); + /** `Berf` */ + static readonly BERIA_ERFE = new Script(hb_tag("Berf")); + /** `Sidt` */ + static readonly SIDETIC = new Script(hb_tag("Sidt")); + /** `Tayo` */ + static readonly TAI_YO = new Script(hb_tag("Tayo")); + /** `Tols` */ + static readonly TOLONG_SIKI = new Script(hb_tag("Tols")); + + /** No script set. */ + static readonly INVALID = new Script(0); + + readonly value: number; + + /** + * Converts a string representing an ISO 15924 script tag to a corresponding + * Script. + * @param tag A string representing an ISO 15924 tag. + */ + constructor(tag: string); + /** @internal Wrap an existing hb_script_t. */ + constructor(existingTag: number); + constructor(arg: string | number) { + if (typeof arg === "number") { + this.value = arg; + } else { + const strPtr = string_to_ascii_ptr(arg); + this.value = exports.hb_script_from_string(strPtr.ptr, -1); + strPtr.free(); + } + } + + /** + * Converts the Script to a corresponding ISO 15924 script tag. + * @returns A string representing an ISO 15924 script tag. + */ + toString(): string { + return hb_untag(this.value); + } +} diff --git a/src/shape.ts b/src/shape.ts index 62ddcf6..3427b1d 100644 --- a/src/shape.ts +++ b/src/shape.ts @@ -2,14 +2,14 @@ import { Module, exports, hb_tag, - hb_untag, utf8_ptr_to_string, - language_to_string, type ValueOf, } from "./helpers"; import type { TraceEntry } from "./types"; import type { Font } from "./font"; import type { Feature } from "./feature"; +import { Language } from "./language"; +import { Script } from "./script"; import { Buffer, BufferContentType, @@ -139,10 +139,8 @@ export function versionString(): string { * @param tag The tag to convert. * @returns The script. */ -export function otTagToScript(tag: string): string { - const hbTag = hb_tag(tag); - const script = exports.hb_ot_tag_to_script(hbTag); - return hb_untag(script); +export function otTagToScript(tag: string): Script { + return new Script(exports.hb_ot_tag_to_script(hb_tag(tag))); } /** @@ -150,8 +148,6 @@ export function otTagToScript(tag: string): string { * @param tag The tag to convert. * @returns The language. */ -export function otTagToLanguage(tag: string): string { - const hbTag = hb_tag(tag); - const language = exports.hb_ot_tag_to_language(hbTag); - return language_to_string(language); +export function otTagToLanguage(tag: string): Language { + return new Language(exports.hb_ot_tag_to_language(hb_tag(tag))); } diff --git a/src/types.ts b/src/types.ts index 66c1371..648009e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import type { ValueOf } from "./helpers"; +import type { Language } from "./language"; // Data shape types export interface FontExtents { @@ -47,7 +48,7 @@ export interface AxisInfo { export interface NameEntry { nameId: number; - language: string; + language: Language; } export interface FeatureNameIds { diff --git a/test/index.test.js b/test/index.test.js index ef2a763..a2acf11 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -271,8 +271,10 @@ describe("Face", function () { let face = new hb.Face(blob); let names = face.listNames(); expect(names.length).to.equal(38); - expect(names[0]).to.deep.equal({ nameId: 0, language: "en" }); - expect(names[37]).to.deep.equal({ nameId: 278, language: "en" }); + expect(names[0].nameId).to.equal(0); + expect(names[0].language.toString()).to.equal("en"); + expect(names[37].nameId).to.equal(278); + expect(names[37].language.toString()).to.equal("en"); }); it("getName fetches a name", function () { @@ -280,8 +282,8 @@ describe("Face", function () { fs.readFileSync(path.join(__dirname, "fonts/noto/NotoSans-Regular.ttf")), ); let face = new hb.Face(blob); - expect(face.getName(1, "en")).to.equal("Noto Sans"); - expect(face.getName(256, "en")).to.equal("florin symbol"); + expect(face.getName(1, new hb.Language("en"))).to.equal("Noto Sans"); + expect(face.getName(256, new hb.Language("en"))).to.equal("florin symbol"); }); it("getFeatureNameIds returns valid name Ids for ssNN features", function () { @@ -311,7 +313,7 @@ describe("Face", function () { "GSUB", face.getTableFeatureTags("GSUB").indexOf("ss03"), ).uiLabelNameId, - "en", + new hb.Language("en"), ), ).to.equal("florin symbol"); }); @@ -1080,6 +1082,47 @@ describe("Buffer", function () { }); }); +describe("Direction", function () { + it("toString returns the lowercase direction name", function () { + expect(hb.Direction.LTR.toString()).to.equal("ltr"); + expect(hb.Direction.RTL.toString()).to.equal("rtl"); + expect(hb.Direction.TTB.toString()).to.equal("ttb"); + expect(hb.Direction.BTT.toString()).to.equal("btt"); + expect(hb.Direction.INVALID.toString()).to.equal("invalid"); + }); + + it("string constructor parses the direction name", function () { + expect(new hb.Direction("ltr")).to.deep.equal(hb.Direction.LTR); + expect(new hb.Direction("RTL")).to.deep.equal(hb.Direction.RTL); + }); +}); + +describe("Script", function () { + it("toString returns the ISO 15924 tag", function () { + expect(hb.Script.LATIN.toString()).to.equal("Latn"); + expect(hb.Script.ARABIC.toString()).to.equal("Arab"); + }); + + it("normalizes script variants to their canonical script", function () { + // ISO 15924 variants alias to a canonical script (see hb_script_from_iso15924_tag). + expect(new hb.Script("Aran")).to.deep.equal(hb.Script.ARABIC); + expect(new hb.Script("Hans")).to.deep.equal(hb.Script.HAN); + expect(new hb.Script("Hant")).to.deep.equal(hb.Script.HAN); + }); +}); + +describe("Language", function () { + it("toString returns the BCP 47 tag", function () { + expect(new hb.Language("en").toString()).to.equal("en"); + expect(new hb.Language("fa-IR").toString()).to.equal("fa-ir"); + }); + + it("interns by tag", function () { + expect(new hb.Language("en").ptr).to.equal(new hb.Language("en").ptr); + expect(new hb.Language("en").ptr).to.not.equal(new hb.Language("fr").ptr); + }); +}); + describe("Variation", function () { it("fromString parses tag=value", function () { expect(hb.Variation.fromString("wght=500")).to.deep.equal( @@ -1407,7 +1450,7 @@ describe("shape", function () { buffer.clearContents(); buffer.addText("५ल"); - buffer.setLanguage("dty"); + buffer.setLanguage(new hb.Language("dty")); buffer.guessSegmentProperties(); hb.shape(font, buffer); var infos = buffer.getGlyphInfos(); @@ -1433,7 +1476,7 @@ describe("shape", function () { buffer.clearContents(); buffer.addText("५ल"); - buffer.setLanguage("x-hbot-4e455020"); // 'NEP ' + buffer.setLanguage(new hb.Language("x-hbot-4e455020")); // 'NEP ' buffer.guessSegmentProperties(); hb.shape(font, buffer); var infos = buffer.getGlyphInfos(); @@ -1457,18 +1500,18 @@ describe("misc", function () { }); it("convert OpenType tag to script", function () { - expect(hb.otTagToScript("arab")).to.equal("Arab"); - expect(hb.otTagToScript("latn")).to.equal("Latn"); - expect(hb.otTagToScript("dev2")).to.equal("Deva"); - expect(hb.otTagToScript("nko ")).to.equal("Nkoo"); - expect(hb.otTagToScript("DFLT")).to.equal("\0\0\0\0"); + expect(hb.otTagToScript("arab").toString()).to.equal("Arab"); + expect(hb.otTagToScript("latn").toString()).to.equal("Latn"); + expect(hb.otTagToScript("dev2").toString()).to.equal("Deva"); + expect(hb.otTagToScript("nko ").toString()).to.equal("Nkoo"); + expect(hb.otTagToScript("DFLT").toString()).to.equal("\0\0\0\0"); }); it("convert OpenType tag to language", function () { - expect(hb.otTagToLanguage("ARA ")).to.equal("ar"); - expect(hb.otTagToLanguage("ENG ")).to.equal("en"); - expect(hb.otTagToLanguage("BAD0")).to.equal("bad"); - expect(hb.otTagToLanguage("SYRE")).to.equal("und-syre"); + expect(hb.otTagToLanguage("ARA ").toString()).to.equal("ar"); + expect(hb.otTagToLanguage("ENG ").toString()).to.equal("en"); + expect(hb.otTagToLanguage("BAD0").toString()).to.equal("bad"); + expect(hb.otTagToLanguage("SYRE").toString()).to.equal("und-syre"); }); it("test that calling functions repeatedly doesn't exhaust memory", function () { @@ -1479,7 +1522,7 @@ describe("misc", function () { let font = new hb.Font(face); for (let i = 0; i < 10000; i++) { expect(face.listNames()).to.not.be.undefined; - expect(face.getName(0, "en")).to.not.be.undefined; + expect(face.getName(0, new hb.Language("en"))).to.not.be.undefined; expect(font.hExtents()).to.not.be.undefined; expect(font.vExtents()).to.not.be.undefined; expect(font.glyphHOrigin(0)).to.not.be.undefined;