|
| 1 | +import { |
| 2 | + Module, |
| 3 | + exports, |
| 4 | + hb_tag, |
| 5 | + hb_untag, |
| 6 | + string_to_ascii_ptr, |
| 7 | + utf8_ptr_to_string, |
| 8 | +} from "./helpers"; |
| 9 | + |
| 10 | +/** |
| 11 | + * A {@link https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-feature-t | HarfBuzz feature}. |
| 12 | + * |
| 13 | + * The structure that holds information about requested feature application. |
| 14 | + * The feature will be applied with the given value to all glyphs which are in |
| 15 | + * clusters between {@link Feature.start} (inclusive) and {@link Feature.end} |
| 16 | + * (exclusive). Setting `start` to `0` and `end` to `0xffffffff` specifies that |
| 17 | + * the feature always applies to the entire buffer. |
| 18 | + */ |
| 19 | +export class Feature { |
| 20 | + /** |
| 21 | + * Special setting for {@link Feature.start} to apply the feature from the |
| 22 | + * start of the buffer. |
| 23 | + */ |
| 24 | + static readonly GLOBAL_START = 0; |
| 25 | + |
| 26 | + /** |
| 27 | + * Special setting for {@link Feature.end} to apply the feature from to the |
| 28 | + * end of the buffer. |
| 29 | + */ |
| 30 | + static readonly GLOBAL_END = 0xffffffff; |
| 31 | + |
| 32 | + /** The tag of the feature. */ |
| 33 | + tag: string; |
| 34 | + /** |
| 35 | + * The value of the feature. `0` disables the feature, non-zero (usually `1`) |
| 36 | + * enables the feature. For features implemented as lookup type 3 (like |
| 37 | + * `salt`) the value is a one-based index into the alternates. |
| 38 | + */ |
| 39 | + value: number; |
| 40 | + /** The cluster to start applying this feature setting (inclusive). */ |
| 41 | + start: number; |
| 42 | + /** The cluster to end applying this feature setting (exclusive). */ |
| 43 | + end: number; |
| 44 | + |
| 45 | + constructor( |
| 46 | + tag: string, |
| 47 | + value: number = 1, |
| 48 | + start: number = Feature.GLOBAL_START, |
| 49 | + end: number = Feature.GLOBAL_END, |
| 50 | + ) { |
| 51 | + this.tag = tag; |
| 52 | + this.value = value; |
| 53 | + this.start = start; |
| 54 | + this.end = end; |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * Parses a string into a Feature. |
| 59 | + * |
| 60 | + * The format for specifying feature strings follows. All valid CSS |
| 61 | + * font-feature-settings values other than `normal` and the global values are |
| 62 | + * also accepted, though not documented below. CSS string escapes are not |
| 63 | + * supported. |
| 64 | + * |
| 65 | + * The range indices refer to the positions between Unicode characters. The |
| 66 | + * position before the first character is always 0. |
| 67 | + * |
| 68 | + * The format is Python-esque. Here is how it all works: |
| 69 | + * |
| 70 | + * | Syntax | Value | Start | End | Meaning | |
| 71 | + * | ------------- | ----- | ----- | --- | -------------------------------- | |
| 72 | + * | `kern` | 1 | 0 | ∞ | Turn feature on | |
| 73 | + * | `+kern` | 1 | 0 | ∞ | Turn feature on | |
| 74 | + * | `-kern` | 0 | 0 | ∞ | Turn feature off | |
| 75 | + * | `kern=0` | 0 | 0 | ∞ | Turn feature off | |
| 76 | + * | `kern=1` | 1 | 0 | ∞ | Turn feature on | |
| 77 | + * | `aalt=2` | 2 | 0 | ∞ | Choose 2nd alternate | |
| 78 | + * | `kern[]` | 1 | 0 | ∞ | Turn feature on | |
| 79 | + * | `kern[:]` | 1 | 0 | ∞ | Turn feature on | |
| 80 | + * | `kern[5:]` | 1 | 5 | ∞ | Turn feature on, partial | |
| 81 | + * | `kern[:5]` | 1 | 0 | 5 | Turn feature on, partial | |
| 82 | + * | `kern[3:5]` | 1 | 3 | 5 | Turn feature on, range | |
| 83 | + * | `kern[3]` | 1 | 3 | 3+1 | Turn feature on, single char | |
| 84 | + * | `aalt[3:5]=2` | 2 | 3 | 5 | Turn 2nd alternate on for range | |
| 85 | + * |
| 86 | + * @param str The string to parse. |
| 87 | + * @returns A Feature, or undefined if the string is not a valid feature. |
| 88 | + */ |
| 89 | + static fromString(str: string): Feature | undefined { |
| 90 | + const sp = Module.stackSave(); |
| 91 | + const featurePtr = Module.stackAlloc(16); |
| 92 | + const strPtr = string_to_ascii_ptr(str); |
| 93 | + let feature: Feature | undefined; |
| 94 | + if (exports.hb_feature_from_string(strPtr.ptr, -1, featurePtr)) { |
| 95 | + feature = new Feature( |
| 96 | + hb_untag(Module.HEAPU32[featurePtr / 4]), |
| 97 | + Module.HEAPU32[featurePtr / 4 + 1], |
| 98 | + Module.HEAPU32[featurePtr / 4 + 2], |
| 99 | + Module.HEAPU32[featurePtr / 4 + 3], |
| 100 | + ); |
| 101 | + } |
| 102 | + strPtr.free(); |
| 103 | + Module.stackRestore(sp); |
| 104 | + return feature; |
| 105 | + } |
| 106 | + |
| 107 | + /** |
| 108 | + * Converts the feature to a string in the format understood by |
| 109 | + * {@link Feature.fromString}. |
| 110 | + * |
| 111 | + * Note that the feature value will be omitted if it is `1`, but the string |
| 112 | + * won't include any whitespace. |
| 113 | + * |
| 114 | + * @returns The feature string. |
| 115 | + */ |
| 116 | + toString(): string { |
| 117 | + const sp = Module.stackSave(); |
| 118 | + const featurePtr = Module.stackAlloc(16); |
| 119 | + this.writeTo(featurePtr); |
| 120 | + const bufLen = 128; |
| 121 | + const bufPtr = Module.stackAlloc(bufLen); |
| 122 | + exports.hb_feature_to_string(featurePtr, bufPtr, bufLen); |
| 123 | + const result = utf8_ptr_to_string(bufPtr); |
| 124 | + Module.stackRestore(sp); |
| 125 | + return result; |
| 126 | + } |
| 127 | + |
| 128 | + /** @internal Write this feature into the given hb_feature_t pointer. */ |
| 129 | + writeTo(ptr: number): void { |
| 130 | + Module.HEAPU32[ptr / 4] = hb_tag(this.tag); |
| 131 | + Module.HEAPU32[ptr / 4 + 1] = this.value; |
| 132 | + Module.HEAPU32[ptr / 4 + 2] = this.start; |
| 133 | + Module.HEAPU32[ptr / 4 + 3] = this.end; |
| 134 | + } |
| 135 | +} |
0 commit comments