Skip to content
Merged
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
2 changes: 2 additions & 0 deletions harfbuzz.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ _hb_ot_name_list_names
_hb_ot_name_get_utf16
_hb_ot_tag_to_language
_hb_ot_tag_to_script
_hb_ot_tags_from_script_and_language
_hb_ot_tags_to_script_and_language
_hb_ot_var_get_axis_infos
_hb_script_from_string
_hb_feature_from_string
Expand Down
88 changes: 86 additions & 2 deletions src/shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
hb_tag,
hb_untag,
utf8_ptr_to_string,
string_to_ascii_ptr,
language_to_string,
type ValueOf,
} from "./helpers";
Expand Down Expand Up @@ -135,7 +136,7 @@ export function versionString(): string {
}

/**
* Convert an OpenType script tag to HarfBuzz script.
* Convert an OpenType script tag to HarfBuzz script (ISO 15924).
* @param tag The tag to convert.
* @returns The script.
*/
Expand All @@ -146,7 +147,7 @@ export function otTagToScript(tag: string): string {
}

/**
* Convert an OpenType language tag to HarfBuzz language.
* Convert an OpenType language tag to HarfBuzz language (BCP 47).
* @param tag The tag to convert.
* @returns The language.
*/
Expand All @@ -155,3 +156,86 @@ export function otTagToLanguage(tag: string): string {
const language = exports.hb_ot_tag_to_language(hbTag);
return language_to_string(language);
}

/** https://harfbuzz.github.io/harfbuzz-hb-ot-layout.html#HB-OT-MAX-TAGS-PER-LANGUAGE:CAPS */
const HB_OT_MAX_TAGS_PER_LANGUAGE = 3;
/** https://harfbuzz.github.io/harfbuzz-hb-ot-layout.html#HB-OT-MAX-TAGS-PER-SCRIPT:CAPS */
const HB_OT_MAX_TAGS_PER_SCRIPT = 3;

/**
* Convert a HarfBuzz script (ISO 15924) and a HarfBuzz language (BCP 47) to OpenType script tags and OpenType language tags.
* @param script A HarfBuzz script to convert.
* @param language A HarfBuzz language to convert.
* @returns An object with the OpenType script tags and the OpenType language tags.
*/
export function otTagsFromScriptAndLanguage(
script: string,
language: string,
): {
scriptTags: string[];
languageTags: string[];
} {
const sp = Module.stackSave();
const scriptStr = string_to_ascii_ptr(script);
const languageStr = string_to_ascii_ptr(language);
const scriptCountPtr = Module.stackAlloc(4);
Module.HEAPU32[scriptCountPtr / 4] = HB_OT_MAX_TAGS_PER_SCRIPT;
const scriptTagsPtr = Module.stackAlloc(HB_OT_MAX_TAGS_PER_SCRIPT * 4);
const languageCountPtr = Module.stackAlloc(4);
Module.HEAPU32[languageCountPtr / 4] = HB_OT_MAX_TAGS_PER_LANGUAGE;
const languageTagsPtr = Module.stackAlloc(HB_OT_MAX_TAGS_PER_LANGUAGE * 4);
exports.hb_ot_tags_from_script_and_language(
exports.hb_script_from_string(scriptStr.ptr, -1),
exports.hb_language_from_string(languageStr.ptr, -1),
scriptCountPtr,
scriptTagsPtr,
languageCountPtr,
languageTagsPtr,
);
scriptStr.free();
languageStr.free();
const scriptTags = Module.HEAPU32.subarray(
scriptTagsPtr / 4,
scriptTagsPtr / 4 + Module.HEAPU32[scriptCountPtr / 4],
);
const languageTags = Module.HEAPU32.subarray(
languageTagsPtr / 4,
languageTagsPtr / 4 + Module.HEAPU32[languageCountPtr / 4],
);
Module.stackRestore(sp);
return {
scriptTags: [...scriptTags].map(hb_untag),
languageTags: [...languageTags].map(hb_untag),
};
}

/**
* Convert an OpenType script tag and an OpenType language tag to a HarfBuzz script (ISO 15924) and a HarfBuzz language (BCP 47).
* @param scriptTag An OpenType script tag.
* @param languageTag An OpenType language tag.
* @returns An object with the HarfBuzz script and the HarfBuzz language.
*/
export function otTagsToScriptAndLanguage(
scriptTag: string,
languageTag: string,
): {
script: string | undefined;
language: string | undefined;
} {
const sp = Module.stackSave();
const scriptPtr = Module.stackAlloc(4);
const languagePtr = Module.stackAlloc(4);
exports.hb_ot_tags_to_script_and_language(
hb_tag(scriptTag),
hb_tag(languageTag),
scriptPtr,
languagePtr,
);
const script = Module.HEAPU32[scriptPtr / 4];
const language = Module.HEAPU32[languagePtr / 4];
Module.stackRestore(sp);
return {
script: hb_untag(script),
language: language_to_string(language),
};
}
26 changes: 26 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2084,6 +2084,32 @@ describe("misc", function () {
expect(hb.otTagToLanguage("SYRE")).to.equal("und-syre");
});

it(hb.otTagsFromScriptAndLanguage, () => {
// Single script to multiple tags:
expect(hb.otTagsFromScriptAndLanguage("Deva", "hin")).to.deep.equal({
scriptTags: ["dev3", "dev2", "deva"],
languageTags: ["HIN "],
});
// Single language to multiple tags:
expect(hb.otTagsFromScriptAndLanguage("Syrc", "syr")).to.deep.equal({
scriptTags: ["syrc"],
languageTags: ["SYR "], // In registry: "SYR ", SYRE, SYRJ, SYRN
});
});

it(hb.otTagsToScriptAndLanguage, () => {
// Multiple tags to single script:
expect(hb.otTagsToScriptAndLanguage("dev2", "HIN ")).to.deep.equal({
script: "Deva",
language: "hi-x-hbsc-64657632", // Private disambiguation
});
// Single tag to multiple languages:
expect(hb.otTagsToScriptAndLanguage("syrc", "SYR ")).to.deep.equal({
script: "Syrc",
language: "syr", // In registry: aii, amw, cld, syc, syr, tru
});
});

it("test that calling functions repeatedly doesn't exhaust memory", function () {
let blob = new hb.Blob(
fs.readFileSync(path.join(__dirname, "fonts/noto/NotoSans-Regular.ttf")),
Expand Down
Loading