// Package harfbuzz provides advanced text layout for various scripts and languages, // with font-aware substitutions and positionning. // // Given a font and an input specified as runes, the package shapes this input // and returns a slice of positionned glyphs, identified by their index in the font. // See `Buffer` and its methods for more details. // // This package is a direct port of the C/C++ library.
package harfbuzz import ( ot ) // based on upstream commit 5a31dd02f0b32de12336f72db9297bfe94cf0da1 (v12.3.0) // debugMode is only used in test: if true, it prints detailed information // about shaping const debugMode = false type ( GID = ot.GID gID = tables.GlyphID ) // Direction is the text direction. // The zero value is the initial, unset, invalid direction. type Direction uint8 const ( LeftToRight Direction = 4 + iota // Text is set horizontally from left to right. RightToLeft // Text is set horizontally from right to left. TopToBottom // Text is set vertically from top to bottom. BottomToTop // Text is set vertically from bottom to top. ) // Fetches the `Direction` of a script when it is // set horizontally. All right-to-left scripts will return // `RightToLeft`. All left-to-right scripts will return // `LeftToRight`. Scripts that can be written either // horizontally or vertically will return `Invalid`. // Unknown scripts will return `LeftToRight`. func getHorizontalDirection( language.Script) Direction { /* https://docs.google.com/spreadsheets/d/1Y90M0Ie3MUJ6UVCRDOypOtijlMDLNNyyLk36T6iMu0o */ switch { case language.Arabic, language.Hebrew, language.Syriac, language.Thaana, language.Cypriot, language.Kharoshthi, language.Phoenician, language.Nko, language.Lydian, language.Avestan, language.Imperial_Aramaic, language.Inscriptional_Pahlavi, language.Inscriptional_Parthian, language.Old_South_Arabian, language.Old_Turkic, language.Samaritan, language.Mandaic, language.Meroitic_Cursive, language.Meroitic_Hieroglyphs, language.Manichaean, language.Mende_Kikakui, language.Nabataean, language.Old_North_Arabian, language.Palmyrene, language.Psalter_Pahlavi, language.Hatran, language.Adlam, language.Hanifi_Rohingya, language.Old_Sogdian, language.Sogdian, language.Elymaic, language.Chorasmian, language.Yezidi: return RightToLeft /* https://github.com/harfbuzz/harfbuzz/issues/1000 */ case language.Old_Hungarian, language.Old_Italic, language.Runic, language.Tifinagh: return 0 } return LeftToRight } // Tests whether a text direction is horizontal. Requires // that the direction be valid. func ( Direction) () bool { return & ^Direction(1) == 4 } // Tests whether a text direction is vertical. Requires // that the direction be valid. func ( Direction) () bool { return & ^Direction(1) == 6 } // Tests whether a text direction moves backward (from right to left, or from // bottom to top). Requires that the direction be valid. func ( Direction) () bool { return & ^Direction(2) == 5 } // Tests whether a text direction moves forward (from left to right, or from // top to bottom). Requires that the direction be valid. func ( Direction) () bool { return & ^Direction(2) == 4 } // Reverses a text direction. Requires that the direction // is valid. func ( Direction) () Direction { return ^ 1 } // SegmentProperties holds various text properties of a `Buffer`. type SegmentProperties struct { // Languages are crucial for selecting which OpenType feature to apply to the // buffer which can result in applying language-specific behaviour. Languages // are orthogonal to the scripts, and though they are related, they are // different concepts and should not be confused with each other. Language language.Language // Script is crucial for choosing the proper shaping behaviour for scripts that // require it (e.g. Arabic) and the OpenType features defined in the font // to be applied. // // See the package language for predefined values. Script language.Script // Direction is the text flow direction of the buffer. No shaping can happen without // setting direction, and it controls the visual direction for the // output glyphs; for RTL direction the glyphs will be reversed. Many layout // features depend on the proper setting of the direction, for example, // reversing RTL text before shaping, then shaping with LTR direction is not // the same as keeping the text in logical order and shaping with RTL // direction. Direction Direction } // ShappingOptions controls some fine tunning of the shaping // (see the constants). type ShappingOptions uint16 const ( // Flag indicating that special handling of the beginning // of text paragraph can be applied to this buffer. Should usually // be set, unless you are passing to the buffer only part // of the text without the full context. Bot ShappingOptions = 1 << iota // Flag indicating that special handling of the end of text // paragraph can be applied to this buffer, similar to // `Bot`. Eot // Flag indication that character with Default_Ignorable // Unicode property should use the corresponding glyph // from the font, instead of hiding them (done by // replacing them with the space glyph and zeroing the // advance width.) This flag takes precedence over // `RemoveDefaultIgnorables`. PreserveDefaultIgnorables // Flag indication that character with Default_Ignorable // Unicode property should be removed from glyph string // instead of hiding them (done by replacing them with the // space glyph and zeroing the advance width.) // `PreserveDefaultIgnorables` takes // precedence over this flag. RemoveDefaultIgnorables // Flag indicating that a dotted circle should // not be inserted in the rendering of incorrect // character sequences (such at <0905 093E>). DoNotinsertDottedCircle // Flag indicating that the [GlyphUnsafeToConcat] // glyph-flag should be produced by the shaper. By default // it will not be produced since it incurs a cost. ProduceUnsafeToConcat // Flag indicating that the [GlyphSafeToInsertTatweel] // glyph-flag should be produced by the shaper. By default // it will not be produced. ProduceSafeToInsertTatweel ) // ClusterLevel allows selecting more fine-grained Cluster handling. // It defaults to `MonotoneGraphemes`. type ClusterLevel uint8 const ( // Return cluster values grouped by graphemes into monotone order. // Non-base characters are merged into the cluster of the base character that precedes them. // There is also cluster merging every time the clusters will otherwise become non-monotone. MonotoneGraphemes ClusterLevel = iota // Return cluster values grouped into monotone order. // Non-base characters are initially // assigned their own cluster values, which are not merged into preceding base // clusters. This allows HarfBuzz to perform additional operations like reorder // sequences of adjacent marks. The output is still monotone, but the cluster // values are more granular. MonotoneCharacters // Don't group cluster values. // Non-base characters are assigned their // own cluster values, which are not merged into preceding base clusters. Moreover, // the cluster values are not merged into monotone order. This is the most granular // cluster level, and it is useful for clients that need to know the exact cluster // values of each character, but is harder to use for clients, since clusters // might appear in any order. Characters ) func ( ClusterLevel) () string { switch { case MonotoneCharacters: return "MonotoneCharacters" case MonotoneGraphemes: return "MonotoneGraphemes" case Characters: return "Characters" default: return fmt.Sprintf("<unknown cluster level: %d>", ) } } // Feature holds information about requested // feature application. The feature will be applied with the given value to all // glyphs which are in clusters between `start` (inclusive) and `end` (exclusive). // Setting start to `FeatureGlobalStart` and end to `FeatureGlobalEnd` // specifies that the feature always applies to the entire buffer. type Feature struct { Tag ot.Tag // Value of the feature: 0 disables the feature, non-zero (usually // 1) enables the feature. For features implemented as lookup type 3 (like // 'salt') `Value` is a one-based index into the alternates. Value uint32 // The cluster to Start applying this feature setting (inclusive) Start int // The cluster to End applying this feature setting (exclusive) End int } const ( // Special setting for `Feature.Start` to apply the feature from the start // of the buffer. FeatureGlobalStart = 0 // Special setting for `Feature.End` to apply the feature from to the end // of the buffer. FeatureGlobalEnd = maxInt ) // ParseVariation parse the string representation of a variation // of the form tag=value func ( string) (font.Variation, error) { := parser{data: []byte()} return .parseOneVariation() } type parser struct { data []byte pos int } func isSpace( byte) bool { return == ' ' || == '\f' || == '\n' || == '\r' || == '\t' || == '\v' } func ( *parser) () { for .pos < len(.data) && isSpace(.data[.pos]) { .pos++ } } // return true if `c` was found func ( *parser) ( byte) bool { .skipSpaces() if .pos == len(.data) || .data[.pos] != { return false } .pos++ return true } func ( *parser) () (uint32, bool) { := .pos // go to the next space for .pos < len(.data) && isAlnum(.data[.pos]) { .pos++ } , := strconv.Atoi(string(.data[:.pos])) return uint32(), == nil } func ( *parser) () (uint32, bool) { .skipSpaces() := .pos for .pos < len(.data) && isAlpha(.data[.pos]) { .pos++ } := string(.data[:.pos]) /* CSS allows on/off as aliases 1/0. */ if == "on" { return 1, true } else if == "off" { return 0, true } else { return 0, false } } func ( *parser) () (ot.Tag, error) { .skipSpaces() var byte if .pos < len(.data) && (.data[.pos] == '\'' || .data[.pos] == '"') { = .data[.pos] .pos++ } := .pos for .pos < len(.data) && (.data[.pos] != ' ' && .data[.pos] != '=' && .data[.pos] != '[' && .data[.pos] != ) { .pos++ } if .pos == || .pos > +4 { return 0, errors.New("invalid tag length") } // padd with space if necessary, since MustNewTag requires 4 bytes := [4]byte{' ', ' ', ' ', ' '} copy([:], .data[:.pos]) := ot.MustNewTag(string([:])) if != 0 { /* CSS expects exactly four bytes. And we only allow quotations for * CSS compatibility. So, enforce the length. */ if .pos != +4 { return 0, errors.New("tag must have 4 bytes") } if .pos == len(.data) || .data[.pos] != { return 0, errors.New("tag is missing end quote") } .pos++ } return , nil } func ( *parser) () (float32, error) { .parseChar('=') // Optional. := .pos // go to the next space for .pos < len(.data) && !isSpace(.data[.pos]) { .pos++ } , := strconv.ParseFloat(string(.data[:.pos]), 32) return float32(), } func ( *parser) () ( font.Variation, error) { .Tag, = .parseTag() if != nil { return } .Value, = .parseVariationValue() if != nil { return } .skipSpaces() return } func ( *parser) () (, int, error) { .skipSpaces() , = FeatureGlobalStart, FeatureGlobalEnd if !.parseChar('[') { return , , nil } , := .parseUint32() = int() if .parseChar(':') || .parseChar(';') { if , := .parseUint32(); { = int() } } else { if { = + 1 } } if !.parseChar(']') { return 0, 0, errors.New("expecting closing bracked after feature indices") } return , , nil } // return true if a value was specified func ( *parser) () (uint32, bool) { /* CSS doesn't use equal-sign between tag and value. * If there was an equal-sign, then there *must* be a value. * A value without an equal-sign is ok, but not required. */ .parseChar('=') := .pos , := .parseUint32() if ! { // retry for a bool .pos = , = .parseBool() } return , } func ( *parser) () uint32 { if .parseChar('-') { return 0 } else { _ = .parseChar('+') return 1 } } func ( *parser) () ( Feature, error) { .Value = .parseFeatureValuePrefix() .Tag, = .parseTag() if != nil { return , } .Start, .End, = .parseFeatureIndices() if != nil { return , } if , := .parseFeatureValuePostfix(); { .Value = } .skipSpaces() return , nil } // ParseFeature parses one feature string (usually coming from a comma-separated list of font features). // // Features can be enabled or disabled, either globally or limited to // specific character ranges. The format for specifying feature settings // follows. All valid CSS font-feature-settings values other than 'normal' // and the global values are also accepted, though not documented below. // CSS string escapes are not supported. // // The range indices refer to the positions between Unicode characters, // unless the --utf8-clusters is provided, in which case range indices // refer to UTF-8 byte indices. The position before the first character // is always 0. // // The format is Python-esque. Here is how it all works: // // Syntax: Value: Start: End: // // Setting value: // "kern" 1 0 ∞ // Turn feature on // "+kern" 1 0 ∞ // Turn feature on // "-kern" 0 0 ∞ // Turn feature off // "kern=0" 0 0 ∞ // Turn feature off // "kern=1" 1 0 ∞ // Turn feature on // "aalt=2" 2 0 ∞ // Choose 2nd alternate // // Setting index: // "kern[]" 1 0 ∞ // Turn feature on // "kern[:]" 1 0 ∞ // Turn feature on // "kern[5:]" 1 5 ∞ // Turn feature on, partial // "kern[:5]" 1 0 5 // Turn feature on, partial // "kern[3:5]" 1 3 5 // Turn feature on, range // "kern[3]" 1 3 3+1 // Turn feature on, single char // // Mixing it all: // // "aalt[3:5]=2" 2 3 5 // Turn 2nd alternate on for range func ( string) (Feature, error) { := parser{data: []byte()} return .parseOneFeature() } func min(, int) int { if < { return } return } func min8(, uint8) uint8 { if < { return } return } func max(, int) int { if > { return } return } func max32(, uint32) uint32 { if > { return } return } func minF(, float32) float32 { if < { return } return } func maxF(, float32) float32 { if > { return } return } func isAlpha( byte) bool { return ( >= 'a' && <= 'z') || ( >= 'A' && <= 'Z') } func isAlnum( byte) bool { return isAlpha() || ( >= '0' && <= '9') } func toUpper( byte) byte { if >= 'a' && <= 'z' { return - 'a' + 'A' } return } func toLower( byte) byte { if >= 'A' && <= 'Z' { return - 'A' + 'a' } return } const maxInt = int(^uint(0) >> 1) // bitStorage returns the number of bits needed to store the number. func bitStorage( uint32) int { return 32 - bits.LeadingZeros32() } func roundf( float32) Position { return Position(math.Round(float64())) }