// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Glyph info entry.
pub struct GlyphInfo {
codepoint : UInt
cluster : Int
unicode : UInt
} derive(Eq, Show, ToJson)
///|
/// Glyph positioning data.
pub struct GlyphPosition {
x_advance : Int
y_advance : Int
x_offset : Int
y_offset : Int
} derive(Eq, Show, ToJson)
///|
struct FeatureValues {
tag : @common.Tag
values : Array[Int]
} derive(Show, ToJson)
///|
pub suberror ShapeError {
Font(@font.FontError)
Layout(@ot_tables.OtLayoutError)
} derive(Eq, Show, ToJson)
///|
/// Text buffer for shaping.
pub struct Buffer {
mut infos : Array[GlyphInfo]
mut positions : Array[GlyphPosition]
mut direction : @common.Direction
mut script : @common.Script
mut language : @common.Language
mut gsub_features : Array[@common.Tag]
mut gpos_features : Array[@common.Tag]
mut gsub_features_default : Bool
mut gpos_features_default : Bool
mut gsub_feature_values : FeatureValues?
mut preserve_default_ignorables : Bool
mut remove_default_ignorables : Bool
mut not_found_variation_selector : UInt?
} derive(Show, ToJson)
///|
/// Create an empty buffer.
pub fn Buffer::new() -> Buffer {
Buffer::{
infos: [],
positions: [],
direction: @common.direction_invalid,
script: @common.script_invalid,
language: @common.language_invalid,
gsub_features: default_feature_tags(@common.direction_ltr),
gpos_features: default_feature_tags(@common.direction_ltr),
gsub_features_default: true,
gpos_features_default: true,
gsub_feature_values: None,
preserve_default_ignorables: false,
remove_default_ignorables: false,
not_found_variation_selector: None,
}
}
///|
/// Clear all glyph data.
pub fn Buffer::clear(self : Buffer) -> Unit {
self.infos = []
self.positions = []
}
///|
/// Number of glyphs in the buffer.
pub fn Buffer::len(self : Buffer) -> Int {
self.infos.length()
}
///|
/// Return true when empty.
pub fn Buffer::is_empty(self : Buffer) -> Bool {
self.infos.is_empty()
}
///|
/// Append a codepoint with cluster.
pub fn Buffer::add_codepoint(
self : Buffer,
codepoint : UInt,
cluster? : Int = -1,
) -> Unit {
let actual_cluster = if cluster < 0 { self.infos.length() } else { cluster }
self.infos.push(GlyphInfo::{ codepoint, cluster: actual_cluster, unicode: codepoint })
self.positions.push(GlyphPosition::{
x_advance: 0,
y_advance: 0,
x_offset: 0,
y_offset: 0,
})
}
///|
/// Append a character with cluster.
pub fn Buffer::add_char(self : Buffer, ch : Char, cluster? : Int = -1) -> Unit {
self.add_codepoint(ch.to_uint(), cluster~)
}
///|
/// Append all characters from a string.
pub fn Buffer::add_string(self : Buffer, text : String) -> Unit {
let mut cluster = 0
for ch in text {
self.add_char(ch, cluster~)
cluster = cluster + 1
}
}
///|
/// Access glyph infos.
pub fn Buffer::infos(self : Buffer) -> ArrayView[GlyphInfo] {
self.infos[:]
}
///|
/// Access glyph positions.
pub fn Buffer::positions(self : Buffer) -> ArrayView[GlyphPosition] {
self.positions[:]
}
///|
/// Add tracking adjustment to advances.
pub fn Buffer::add_tracking(
self : Buffer,
adjustment : Int,
horizontal : Bool,
) -> Unit {
if adjustment == 0 {
return ()
}
if horizontal {
for i in 0.. Bool {
let mut next_positions = positions
if next_positions.is_empty() {
let filled : Array[GlyphPosition] = []
for _ in 0.. Unit {
if start < 0 || end > self.infos.length() || start >= end {
return ()
}
let mut min_cluster = self.infos[start].cluster
for i in (start + 1).. Unit {
let len = self.infos.length()
if len <= 1 {
return ()
}
let mut start = 0
while start < len {
let mut end = start + 1
while end < len {
let klass = @unicode.combining_class(self.infos[end].unicode)
if klass == 0U {
break
}
end = end + 1
}
if end - start > 1 {
self.merge_clusters(start, end)
}
start = end
}
}
///|
/// Set direction.
pub fn Buffer::set_direction(
self : Buffer,
direction : @common.Direction,
) -> Unit {
self.direction = direction
update_default_features(self)
}
///|
/// Get direction.
pub fn Buffer::get_direction(self : Buffer) -> @common.Direction {
self.direction
}
///|
/// Set script.
pub fn Buffer::set_script(self : Buffer, script : @common.Script) -> Unit {
self.script = script
}
///|
/// Get script.
pub fn Buffer::get_script(self : Buffer) -> @common.Script {
self.script
}
///|
/// Set language.
pub fn Buffer::set_language(self : Buffer, language : @common.Language) -> Unit {
self.language = language
}
///|
pub fn Buffer::set_gsub_features(
self : Buffer,
features : Array[@common.Tag],
) -> Unit {
self.gsub_features = features
self.gsub_features_default = false
}
///|
/// Set per-glyph GSUB feature values for a specific tag.
pub fn Buffer::set_gsub_feature_values(
self : Buffer,
tag : @common.Tag,
values : Array[Int],
) -> Unit {
self.gsub_feature_values = Some(FeatureValues::{ tag, values })
}
///|
pub fn Buffer::set_gpos_features(
self : Buffer,
features : Array[@common.Tag],
) -> Unit {
self.gpos_features = features
self.gpos_features_default = false
}
///|
pub fn Buffer::gsub_features(self : Buffer) -> ArrayView[@common.Tag] {
self.gsub_features[:]
}
///|
pub fn Buffer::gpos_features(self : Buffer) -> ArrayView[@common.Tag] {
self.gpos_features[:]
}
///|
/// Preserve default-ignorable codepoints (do not hide/zero).
pub fn Buffer::set_preserve_default_ignorables(self : Buffer, preserve : Bool) -> Unit {
self.preserve_default_ignorables = preserve
}
///|
/// Remove default-ignorable glyphs instead of zeroing advances.
pub fn Buffer::set_remove_default_ignorables(self : Buffer, remove : Bool) -> Unit {
self.remove_default_ignorables = remove
}
///|
/// Set the fallback glyph for not-found variation selectors.
pub fn Buffer::set_not_found_variation_selector(self : Buffer, glyph : UInt?) -> Unit {
self.not_found_variation_selector = glyph
}
///|
/// Get the fallback glyph for not-found variation selectors.
pub fn Buffer::get_not_found_variation_selector(self : Buffer) -> UInt? {
self.not_found_variation_selector
}
///|
fn default_feature_tags(direction : @common.Direction) -> Array[@common.Tag] {
let tags : Array[@common.Tag] = []
let dir = if direction.is_valid() { direction } else { @common.direction_ltr }
tags.push(@common.Tag::from_chars('r', 'v', 'r', 'n'))
if dir == @common.direction_ltr {
tags.push(@common.Tag::from_chars('l', 't', 'r', 'a'))
tags.push(@common.Tag::from_chars('l', 't', 'r', 'm'))
} else if dir == @common.direction_rtl {
tags.push(@common.Tag::from_chars('r', 't', 'l', 'a'))
tags.push(@common.Tag::from_chars('r', 't', 'l', 'm'))
}
tags.push(@common.Tag::from_chars('f', 'r', 'a', 'c'))
tags.push(@common.Tag::from_chars('n', 'u', 'm', 'r'))
tags.push(@common.Tag::from_chars('d', 'n', 'o', 'm'))
tags.push(@common.Tag::from_chars('r', 'a', 'n', 'd'))
tags.push(@common.Tag::from_chars('H', 'a', 'r', 'f'))
tags.push(@common.Tag::from_chars('H', 'A', 'R', 'F'))
tags.push(@common.Tag::from_chars('B', 'u', 'z', 'z'))
tags.push(@common.Tag::from_chars('B', 'U', 'Z', 'Z'))
tags.push(@common.Tag::from_chars('c', 'c', 'm', 'p'))
tags.push(@common.Tag::from_chars('l', 'o', 'c', 'l'))
tags.push(@common.Tag::from_chars('m', 'a', 'r', 'k'))
tags.push(@common.Tag::from_chars('m', 'k', 'm', 'k'))
tags.push(@common.Tag::from_chars('r', 'l', 'i', 'g'))
if dir.is_horizontal() {
tags.push(@common.Tag::from_chars('c', 'a', 'l', 't'))
tags.push(@common.Tag::from_chars('c', 'l', 'i', 'g'))
tags.push(@common.Tag::from_chars('c', 'u', 'r', 's'))
tags.push(@common.Tag::from_chars('d', 'i', 's', 't'))
tags.push(@common.Tag::from_chars('k', 'e', 'r', 'n'))
tags.push(@common.Tag::from_chars('l', 'i', 'g', 'a'))
tags.push(@common.Tag::from_chars('r', 'c', 'l', 't'))
} else {
tags.push(@common.Tag::from_chars('v', 'e', 'r', 't'))
}
tags
}
fn tag_in_list(tags : Array[@common.Tag], tag : @common.Tag) -> Bool {
for entry in tags {
if entry == tag {
return true
}
}
false
}
fn update_default_features(buffer : Buffer) -> Unit {
if buffer.gsub_features_default {
buffer.gsub_features = default_feature_tags(buffer.direction)
}
if buffer.gpos_features_default {
buffer.gpos_features = default_feature_tags(buffer.direction)
}
}
fn fallback_glyph_for_codepoint(
font : @font.Font,
codepoint : UInt,
) -> Result[(UInt?, @unicode.SpaceFallbackType?), ShapeError] {
if codepoint == 0x2011U {
return match font.glyph_for_codepoint(0x2010U) {
Err(err) => Err(Font(err))
Ok(value) => Ok((value, None))
}
}
let space_type = @unicode.space_fallback_type(codepoint)
match space_type {
@unicode.SpaceFallbackType::NotSpace => ()
_ =>
return match font.glyph_for_codepoint(0x0020U) {
Err(err) => Err(Font(err))
Ok(None) => Ok((None, None))
Ok(Some(value)) => Ok((Some(value), Some(space_type)))
}
}
Ok((None, None))
}
fn space_fallback_divisor(space_type : @unicode.SpaceFallbackType) -> Int? {
match space_type {
@unicode.SpaceFallbackType::SpaceEm => Some(1)
@unicode.SpaceFallbackType::SpaceEm2 => Some(2)
@unicode.SpaceFallbackType::SpaceEm3 => Some(3)
@unicode.SpaceFallbackType::SpaceEm4 => Some(4)
@unicode.SpaceFallbackType::SpaceEm5 => Some(5)
@unicode.SpaceFallbackType::SpaceEm6 => Some(6)
@unicode.SpaceFallbackType::SpaceEm16 => Some(16)
_ => None
}
}
fn advance_for_codepoint(
font : @font.Font,
codepoint : UInt,
horizontal : Bool,
) -> Result[Int?, ShapeError] {
match font.glyph_for_codepoint(codepoint) {
Err(err) => Err(Font(err))
Ok(None) => Ok(None)
Ok(Some(glyph)) =>
if horizontal {
match font.glyph_h_advance(glyph) {
Ok(value) => Ok(Some(value))
Err(err) => {
let glyph_count = font.get_face().get_glyph_count()
if glyph_count > 0 && glyph.reinterpret_as_int() >= glyph_count {
Ok(Some(0))
} else {
Err(Font(err))
}
}
}
} else {
match font.glyph_v_advance(glyph) {
Ok(value) => Ok(Some(value))
Err(err) => {
let glyph_count = font.get_face().get_glyph_count()
if glyph_count > 0 && glyph.reinterpret_as_int() >= glyph_count {
Ok(Some(0))
} else {
Err(Font(err))
}
}
}
}
}
}
fn adjust_space_fallback_position(
font : @font.Font,
direction : @common.Direction,
space_type : @unicode.SpaceFallbackType,
pos : GlyphPosition,
) -> Result[GlyphPosition, ShapeError] {
let horizontal = !direction.is_vertical()
match space_type {
@unicode.SpaceFallbackType::NotSpace => Ok(pos)
@unicode.SpaceFallbackType::Space => Ok(pos)
@unicode.SpaceFallbackType::SpaceNarrow => {
if horizontal {
Ok(GlyphPosition::{
x_advance: pos.x_advance / 2,
y_advance: pos.y_advance,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
})
} else {
Ok(GlyphPosition::{
x_advance: pos.x_advance,
y_advance: pos.y_advance / 2,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
})
}
}
@unicode.SpaceFallbackType::Space4Em18 => {
let (scale_x, scale_y) = font.get_scale()
let scale = if horizontal { scale_x } else { scale_y }
let advance = scale * 4 / 18
if horizontal {
Ok(GlyphPosition::{
x_advance: advance,
y_advance: pos.y_advance,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
})
} else {
Ok(GlyphPosition::{
x_advance: pos.x_advance,
y_advance: -advance,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
})
}
}
@unicode.SpaceFallbackType::SpaceFigure => {
let mut value : Int? = None
let mut digit = 0x30
while digit <= 0x39 {
match advance_for_codepoint(font, digit.reinterpret_as_uint(), horizontal) {
Err(err) => return Err(err)
Ok(None) => ()
Ok(Some(advance)) => {
value = Some(advance)
break
}
}
digit = digit + 1
}
match value {
None => Ok(pos)
Some(advance) =>
if horizontal {
Ok(GlyphPosition::{
x_advance: advance,
y_advance: pos.y_advance,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
})
} else {
Ok(GlyphPosition::{
x_advance: pos.x_advance,
y_advance: advance,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
})
}
}
}
@unicode.SpaceFallbackType::SpacePunctuation => {
let mut value = match advance_for_codepoint(font, 0x2EU, horizontal) {
Err(err) => return Err(err)
Ok(advance) => advance
}
if value is None {
value = match advance_for_codepoint(font, 0x2CU, horizontal) {
Err(err) => return Err(err)
Ok(advance) => advance
}
}
match value {
None => Ok(pos)
Some(advance) =>
if horizontal {
Ok(GlyphPosition::{
x_advance: advance,
y_advance: pos.y_advance,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
})
} else {
Ok(GlyphPosition::{
x_advance: pos.x_advance,
y_advance: advance,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
})
}
}
}
_ =>
match space_fallback_divisor(space_type) {
None => Ok(pos)
Some(divisor) => {
let (scale_x, scale_y) = font.get_scale()
let scale = if horizontal { scale_x } else { scale_y }
let base = (scale + divisor / 2) / divisor
if horizontal {
Ok(GlyphPosition::{
x_advance: base,
y_advance: pos.y_advance,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
})
} else {
Ok(GlyphPosition::{
x_advance: pos.x_advance,
y_advance: -base,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
})
}
}
}
}
}
///|
/// Get language.
pub fn Buffer::get_language(self : Buffer) -> @common.Language {
self.language
}
///|
/// Fill missing segment properties with defaults.
pub fn Buffer::guess_segment_properties(self : Buffer) -> Unit {
if self.script == @common.script_invalid {
let first_codepoint = match self.infos {
[] => None
[first, ..] => Some(first.codepoint)
}
match first_codepoint {
None => self.script = @common.script_common
Some(value) => self.script = @unicode.script(value)
}
}
if !self.direction.is_valid() {
let dir = self.script.horizontal_direction()
self.direction = if dir.is_valid() { dir } else { @common.direction_ltr }
}
update_default_features(self)
if !self.language.is_valid() {
self.language = @common.Language::from_string("und")
}
}
///|
/// Normalize buffer codepoints by reordering combining marks.
pub fn Buffer::normalize(self : Buffer) -> Unit {
let mut len = self.infos.length()
if len <= 1 {
return ()
}
let mut has_vs = false
for info in self.infos {
if @unicode.is_variation_selector(info.codepoint) {
has_vs = true
break
}
}
let vs_clusters : Map[Int, Bool] = {}
if has_vs {
merge_variation_selector_clusters(self)
for info in self.infos {
if @unicode.is_variation_selector(info.codepoint) {
vs_clusters[info.cluster] = true
}
}
}
let is_hebrew = self.script == @common.script_hebrew
let is_arabic = self.script == @common.script_arabic
let is_hangul = self.script == @common.script_hangul
let mut start = 0
while start < len {
let mut end = start + 1
let mut cluster_has_vs = @unicode.is_variation_selector(self.infos[start].codepoint)
while end < len {
let codepoint = self.infos[end].codepoint
if @unicode.is_variation_selector(codepoint) {
cluster_has_vs = true
end = end + 1
continue
}
let cc = @unicode.combining_class(codepoint)
if cc == 0U {
break
}
end = end + 1
}
if cluster_has_vs {
start = end
continue
}
let mark_start = start + 1
if end - mark_start > 1 {
let mut i = mark_start + 1
while i < end {
let current = self.infos[i]
let current_cc = @unicode.modified_combining_class(current.codepoint).reinterpret_as_int()
let current_pos = self.positions[i]
let mut j = i
while j > mark_start {
let prev = self.infos[j - 1]
let prev_cc = @unicode.modified_combining_class(prev.codepoint).reinterpret_as_int()
if prev_cc <= current_cc {
break
}
self.infos[j] = prev
self.positions[j] = self.positions[j - 1]
j = j - 1
}
self.infos[j] = current
self.positions[j] = current_pos
i = i + 1
}
}
if end > mark_start {
if is_hebrew {
reorder_marks_hebrew(self, mark_start, end)
}
if is_arabic {
reorder_marks_arabic(self, mark_start, end)
}
}
let mut i = start + 1
while i < end {
let base = self.infos[start].codepoint
let mark = self.infos[i].codepoint
let composed = match compose_for_script(self.script, base, mark) {
None => if is_hebrew { compose_hebrew(base, mark) } else { None }
Some(value) => Some(value)
}
match composed {
None => i = i + 1
Some(composed) => {
self.merge_clusters(start, i + 1)
let base = self.infos[start]
self.infos[start] = GlyphInfo::{ codepoint: composed, cluster: base.cluster, unicode: composed }
ignore(self.infos.remove(i))
ignore(self.positions.remove(i))
len = len - 1
end = end - 1
}
}
}
start = end
}
if is_hangul {
normalize_hangul(self, vs_clusters)
}
}
///|
fn merge_variation_selector_clusters(buffer : Buffer) -> Unit {
let mut last_non_vs = -1
for i in 0..= 0 {
buffer.merge_clusters(last_non_vs, i + 1)
}
} else {
last_non_vs = i
}
}
}
///|
fn reorder_marks_arabic(buffer : Buffer, start : Int, end : Int) -> Unit {
let mut i = start
let mut cc = 220
while cc <= 230 {
while i < end {
let value = @unicode.modified_combining_class(buffer.infos[i].codepoint).reinterpret_as_int()
if value >= cc {
break
}
i = i + 1
}
if i >= end {
break
}
let current_cc = @unicode.modified_combining_class(buffer.infos[i].codepoint).reinterpret_as_int()
if current_cc > cc {
cc = cc + 10
continue
}
let mut j = i
while j < end {
let value = @unicode.modified_combining_class(buffer.infos[j].codepoint).reinterpret_as_int()
if value != cc || !is_modifier_combining_mark(buffer.infos[j].codepoint) {
break
}
j = j + 1
}
if i == j {
cc = cc + 10
continue
}
buffer.merge_clusters(start, j)
let moved_len = j - i
let shift_len = i - start
let moved_infos : Array[GlyphInfo] = []
let moved_positions : Array[GlyphPosition] = []
for k in i.. Unit {
let mut i = 0
let mut len = buffer.infos.length()
while i + 1 < len {
if vs_clusters.contains(buffer.infos[i].cluster) ||
vs_clusters.contains(buffer.infos[i + 1].cluster) {
i = i + 1
continue
}
let a = buffer.infos[i].codepoint
let b = buffer.infos[i + 1].codepoint
let composed = if is_hangul_l(a) && is_hangul_v(b) {
@unicode.compose(a, b)
} else if is_hangul_lv(a) && is_hangul_t(b) {
@unicode.compose(a, b)
} else {
None
}
match composed {
None => i = i + 1
Some(value) => {
buffer.merge_clusters(i, i + 2)
let base = buffer.infos[i]
buffer.infos[i] = GlyphInfo::{ codepoint: value, cluster: base.cluster, unicode: value }
ignore(buffer.infos.remove(i + 1))
ignore(buffer.positions.remove(i + 1))
len = len - 1
if i + 1 < len {
let next = buffer.infos[i + 1].codepoint
if is_hangul_t(next) && is_hangul_lv(value) {
match @unicode.compose(value, next) {
None => ()
Some(composed) => {
buffer.merge_clusters(i, i + 2)
let base = buffer.infos[i]
buffer.infos[i] = GlyphInfo::{ codepoint: composed, cluster: base.cluster, unicode: composed }
ignore(buffer.infos.remove(i + 1))
ignore(buffer.positions.remove(i + 1))
len = len - 1
}
}
}
}
i = i + 1
}
}
}
reorder_hangul_tone_marks(buffer)
}
///|
fn reorder_hangul_tone_marks(buffer : Buffer) -> Unit {
let mut i = 1
while i < buffer.infos.length() {
let current = buffer.infos[i].codepoint
if is_hangul_tone(current) &&
is_hangul_syllable(buffer.infos[i - 1].codepoint) {
buffer.merge_clusters(i - 1, i + 1)
let info = buffer.infos[i]
let pos = buffer.positions[i]
buffer.infos[i] = buffer.infos[i - 1]
buffer.positions[i] = buffer.positions[i - 1]
buffer.infos[i - 1] = info
buffer.positions[i - 1] = pos
if i > 1 {
i = i - 1
} else {
i = i + 1
}
} else {
i = i + 1
}
}
}
///|
fn is_hangul_l(u : UInt) -> Bool {
u >= 0x1100U && u <= 0x1112U
}
///|
fn is_hangul_v(u : UInt) -> Bool {
u >= 0x1161U && u <= 0x1175U
}
///|
fn is_hangul_t(u : UInt) -> Bool {
u >= 0x11A8U && u <= 0x11C2U
}
///|
fn is_hangul_lv(u : UInt) -> Bool {
u >= 0xAC00U && u <= 0xD7A3U
}
///|
fn is_hangul_tone(u : UInt) -> Bool {
u == 0x302EU || u == 0x302FU
}
///|
fn is_hangul_syllable(u : UInt) -> Bool {
is_hangul_lv(u) || is_hangul_l(u) || is_hangul_v(u) || is_hangul_t(u)
}
///|
fn is_modifier_combining_mark(u : UInt) -> Bool {
let marks : Array[UInt] = [
0x0654U, 0x0655U, 0x0658U, 0x06DCU, 0x06E3U, 0x06E7U, 0x06E8U,
0x08CAU, 0x08CBU, 0x08CDU, 0x08CEU, 0x08CFU, 0x08D3U, 0x08F3U,
]
for mark in marks {
if mark == u {
return true
}
}
false
}
///|
fn compose_hebrew(a : UInt, b : UInt) -> UInt? {
let dagesh_forms : Array[UInt] = [
0xFB30U, 0xFB31U, 0xFB32U, 0xFB33U, 0xFB34U, 0xFB35U, 0xFB36U, 0U,
0xFB38U, 0xFB39U, 0xFB3AU, 0xFB3BU, 0xFB3CU, 0U, 0xFB3EU, 0U,
0xFB40U, 0xFB41U, 0U, 0xFB43U, 0xFB44U, 0U, 0xFB46U, 0xFB47U,
0xFB48U, 0xFB49U, 0xFB4AU,
]
match b {
0x05B4U => if a == 0x05D9U { Some(0xFB1DU) } else { None }
0x05B7U =>
if a == 0x05F2U {
Some(0xFB1FU)
} else if a == 0x05D0U {
Some(0xFB2EU)
} else {
None
}
0x05B8U => if a == 0x05D0U { Some(0xFB2FU) } else { None }
0x05B9U => if a == 0x05D5U { Some(0xFB4BU) } else { None }
0x05BCU => if a >= 0x05D0U && a <= 0x05EAU {
let form = dagesh_forms[(a - 0x05D0U).reinterpret_as_int()]
if form == 0U { None } else { Some(form) }
} else if a == 0xFB2AU {
Some(0xFB2CU)
} else if a == 0xFB2BU {
Some(0xFB2DU)
} else {
None
}
0x05BFU => if a == 0x05D1U {
Some(0xFB4CU)
} else if a == 0x05DBU {
Some(0xFB4DU)
} else if a == 0x05E4U {
Some(0xFB4EU)
} else {
None
}
0x05C1U => if a == 0x05E9U {
Some(0xFB2AU)
} else if a == 0xFB49U {
Some(0xFB2CU)
} else {
None
}
0x05C2U => if a == 0x05E9U {
Some(0xFB2BU)
} else if a == 0xFB49U {
Some(0xFB2DU)
} else {
None
}
_ => None
}
}
///|
fn reorder_marks_hebrew(buffer : Buffer, start : Int, end : Int) -> Unit {
if end - start < 3 {
return ()
}
for i in (start + 2).. @common.Tag {
match lang.to_string() {
None => @common.tag_none
Some(value) => {
let bytes : Array[Byte] = []
for c in value {
if c == '-' || c == '_' {
break
}
if c.is_ascii_alphabetic() {
bytes.push(c.to_ascii_uppercase().to_uint().to_byte())
} else if c.is_ascii_digit() {
bytes.push(c.to_uint().to_byte())
} else {
break
}
if bytes.length() == 4 {
break
}
}
if bytes.is_empty() {
@common.tag_none
} else {
for _ in bytes.length()..<4 {
bytes.push(b' ')
}
@common.Tag::from_bytes(bytes[0], bytes[1], bytes[2], bytes[3])
}
}
}
}
///|
/// Populate glyph IDs and advances using cmap + hmtx.
pub fn Buffer::shape_basic(
self : Buffer,
font : @font.Font,
) -> Result[Unit, ShapeError] {
let new_infos : Array[GlyphInfo] = []
let new_positions : Array[GlyphPosition] = []
let mut i = 0
while i < self.infos.length() {
let info = self.infos[i]
let mut skip_next = false
let mut use_variation_glyph = None
if i + 1 < self.infos.length() {
let next = self.infos[i + 1]
if @unicode.is_variation_selector(next.codepoint) {
let mapping = match font.variation_glyph_for(info.codepoint, next.codepoint) {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let (supported, glyph) = mapping
if supported {
skip_next = true
use_variation_glyph = glyph
}
}
}
let mut space_type : @unicode.SpaceFallbackType? = None
let glyph = match use_variation_glyph {
Some(value) => value
None =>
match font.glyph_for_codepoint(info.codepoint) {
Err(err) => return Err(Font(err))
Ok(Some(value)) => value
Ok(None) =>
match fallback_glyph_for_codepoint(font, info.codepoint) {
Err(err) => return Err(err)
Ok((None, _)) => 0U
Ok((Some(value), fallback_space_type)) => {
space_type = fallback_space_type
value
}
}
}
}
let mut pos = match default_position(self, font, glyph) {
Err(err) => return Err(err)
Ok(value) => value
}
match space_type {
None => ()
Some(space_type) =>
pos = match adjust_space_fallback_position(font, self.direction, space_type, pos) {
Err(err) => return Err(err)
Ok(value) => value
}
}
new_infos.push(GlyphInfo::{ codepoint: glyph, cluster: info.cluster, unicode: info.unicode })
new_positions.push(pos)
if skip_next {
i = i + 2
} else {
i = i + 1
}
}
self.infos = new_infos
self.positions = new_positions
Ok(())
}
///|
/// Apply AAT trak tracking adjustments if present.
pub fn Buffer::apply_trak(
self : Buffer,
font : @font.Font,
) -> Result[Unit, ShapeError] {
let trak = match font.trak() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
match trak {
None => Ok(())
Some(trak) => {
if !trak.has_data() {
return Ok(())
}
let horizontal = self.direction.is_horizontal()
let (ppem_x, ppem_y) = font.get_ppem()
let ptem = if horizontal {
if ppem_x > 0 { ppem_x.to_double() } else { 12.0 }
} else {
if ppem_y > 0 { ppem_y.to_double() } else { 12.0 }
}
let tracking = trak.tracking(ptem, horizontal)
let (scale_x, scale_y) = font.get_scale()
let scale = if horizontal { scale_x } else { scale_y }
let upem = font.get_face().get_upem()
let adjustment = if upem == 0 {
tracking.round().to_int()
} else {
(tracking * scale.to_double() / upem.to_double()).round().to_int()
}
self.add_tracking(adjustment, horizontal)
Ok(())
}
}
}
///|
/// Apply AAT morx/mort substitutions and rearrangements if present.
pub fn Buffer::apply_morx(
self : Buffer,
font : @font.Font,
) -> Result[Unit, ShapeError] {
let morx = match font.morx() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let use_morx = match morx {
Some(morx) => morx.has_data()
None => false
}
let mort = if use_morx {
None
} else {
match font.mort() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
}
let use_mort = match mort {
Some(mort) => mort.has_data()
None => false
}
if !use_morx && !use_mort {
return Ok(())
}
let horizontal = self.direction.is_horizontal()
let cluster_unicode : Map[Int, UInt] = {}
for info in self.infos {
if !cluster_unicode.contains(info.cluster) {
cluster_unicode[info.cluster] = info.unicode
}
}
let glyphs : Array[UInt] = []
let clusters : Array[Int] = []
for info in self.infos {
glyphs.push(info.codepoint)
clusters.push(info.cluster)
}
let changed = if use_morx {
let morx = match morx {
Some(morx) => morx
None => return Ok(())
}
morx.apply(glyphs, clusters, horizontal)
} else {
let mort = match mort {
Some(mort) => mort
None => return Ok(())
}
mort.apply(glyphs, clusters, horizontal)
}
if !changed {
return Ok(())
}
let new_infos : Array[GlyphInfo] = []
let new_positions : Array[GlyphPosition] = []
for i in 0.. return Err(err)
Ok(value) => value
}
let unicode = match cluster_unicode.get(cluster) {
None => 0U
Some(value) => value
}
new_infos.push(GlyphInfo::{ codepoint: glyph, cluster, unicode })
new_positions.push(pos)
}
self.infos = new_infos
self.positions = new_positions
Ok(())
}
///|
/// Apply AAT kerx kerning adjustments if present.
pub fn Buffer::apply_kerx(
self : Buffer,
font : @font.Font,
) -> Result[Unit, ShapeError] {
let kerx = match font.kerx() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let kerx = match kerx {
None => return Ok(())
Some(kerx) => kerx
}
if !kerx.has_data() {
return Ok(())
}
if self.infos.length() < 2 {
return Ok(())
}
let horizontal = self.direction.is_horizontal()
let (scale_x, scale_y) = font.get_scale()
let upem = font.get_face().get_upem()
let ankr = match font.ankr() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let glyphs : Array[UInt] = []
let clusters : Array[Int] = []
for info in self.infos {
glyphs.push(info.codepoint)
clusters.push(info.cluster)
}
let contour_point = (glyph, point_index) => {
match font.glyph_contour_point_for_origin(glyph, point_index) {
Err(_) => None
Ok(value) => value
}
}
let adjustments = kerx.apply_scaled(
glyphs,
horizontal,
scale_x,
scale_y,
upem,
ankr,
contour_point=contour_point,
)
if adjustments.length() != self.positions.length() {
return Ok(())
}
let positions : Array[GlyphPosition] = []
for i in 0.. Result[Bool, ShapeError] {
ignore(self)
let kerx = match font.kerx() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
Ok(
match kerx {
None => false
Some(value) => value.has_data()
},
)
}
///|
/// Apply GPOS positioning to the current glyph sequence. Returns true when GPOS was present.
pub fn Buffer::apply_gpos(
self : Buffer,
font : @font.Font,
include_all_features? : Bool = false,
adjust_mark_offsets? : Bool = true,
) -> Result[Bool, ShapeError] {
let gpos = match font.gpos() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let gpos = match gpos {
None => return Ok(false)
Some(value) => value
}
let gdef = match font.gdef() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let glyphs : Array[UInt] = []
let clusters : Array[Int] = []
for info in self.infos {
glyphs.push(info.codepoint)
clusters.push(info.cluster)
}
let script_tag = self.script.to_tag()
let lang_tag = language_to_ot_tag(self.language)
let feature_tags : Array[@common.Tag] =
if include_all_features {
[
@common.Tag::from_chars('a', 'b', 'v', 'm'),
@common.Tag::from_chars('b', 'l', 'w', 'm'),
@common.Tag::from_chars('c', 'c', 'm', 'p'),
@common.Tag::from_chars('l', 'o', 'c', 'l'),
@common.Tag::from_chars('m', 'a', 'r', 'k'),
@common.Tag::from_chars('m', 'k', 'm', 'k'),
@common.Tag::from_chars('r', 'l', 'i', 'g'),
@common.Tag::from_chars('c', 'a', 'l', 't'),
@common.Tag::from_chars('c', 'l', 'i', 'g'),
@common.Tag::from_chars('c', 'u', 'r', 's'),
@common.Tag::from_chars('d', 'i', 's', 't'),
@common.Tag::from_chars('k', 'e', 'r', 'n'),
@common.Tag::from_chars('l', 'i', 'g', 'a'),
@common.Tag::from_chars('r', 'c', 'l', 't'),
]
} else {
self.gpos_features
}
let lookup_offsets = match gpos.layout().select_lookup_offsets_with_features(
script_tag,
language_tag=lang_tag,
feature_tags=feature_tags,
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
let advances_x : Array[Int] = []
let advances_y : Array[Int] = []
for pos in self.positions {
advances_x.push(pos.x_advance)
advances_y.push(pos.y_advance)
}
let deltas = match gdef {
None =>
match gpos.position_deltas_with_lookups(
glyphs[:],
lookup_offsets[:],
clusters=clusters[:],
advances_x=advances_x[:],
advances_y=advances_y[:],
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
Some(gdef) =>
match gpos.position_deltas_with_lookups(
glyphs[:],
lookup_offsets[:],
gdef=gdef,
clusters=clusters[:],
advances_x=advances_x[:],
advances_y=advances_y[:],
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
}
if deltas.length() != self.positions.length() {
return Ok(true)
}
let new_positions : Array[GlyphPosition] = []
for i in 0.. return Err(err)
Ok(_) => ()
}
if include_all_features && adjust_mark_offsets &&
unicode_mark_flags.length() == self.positions.length() {
let mut advance_since_base = 0
let mut has_base = false
for i in 0.. Result[Unit, ShapeError] {
self.shape_ot_with_options(
font,
fallback_position=fallback_position,
zero_width_marks=ZeroWidthMarks::ByGdefLate,
)
}
///|
fn Buffer::shape_ot_with_options(
self : Buffer,
font : @font.Font,
fallback_position? : Bool = true,
zero_width_marks? : ZeroWidthMarks = ZeroWidthMarks::ByGdefLate,
) -> Result[Unit, ShapeError] {
self.guess_segment_properties()
let (unicode_mark_flags, combining_classes) = collect_mark_fallback_data(self.infos)
let gsub = match font.gsub() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let gpos = match font.gpos() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
match self.shape_basic(font) {
Err(err) => return Err(err)
Ok(_) => ()
}
let script_tag = self.script.to_tag()
let lang_tag = language_to_ot_tag(self.language)
let map = match @ot_map.OtMap::new(
gsub,
gpos,
script_tag,
language_tag=lang_tag,
gsub_features=self.gsub_features,
gpos_features=self.gpos_features,
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
let gpos_lookups = map.gpos_lookups()
let gpos_present = gpos is Some(_) && !gpos_lookups.is_empty()
let kern_tag =
if self.direction.is_horizontal() {
@common.Tag::from_chars('k', 'e', 'r', 'n')
} else {
@common.Tag::from_chars('v', 'k', 'r', 'n')
}
let wants_kern = tag_in_list(self.gpos_features, kern_tag)
let mut has_gpos_kern = false
if wants_kern && gpos is Some(gpos) {
let kern_lookups = match gpos.layout().select_lookup_offsets_for_feature(
script_tag,
kern_tag,
language_tag=lang_tag,
include_required=false,
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
has_gpos_kern = !kern_lookups.is_empty()
}
if gsub is Some(gsub) {
let gdef = match font.gdef() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let gsub_masks : Array[UInt] = []
let mut gsub_alt_values : Array[Int] = []
match self.gsub_feature_values {
None => ()
Some(feature_values) => {
if !feature_values.values.is_empty() && feature_values.values.length() == self.infos.length() {
gsub_alt_values = feature_values.values
for value in gsub_alt_values {
gsub_masks.push(if value > 0 { 1U } else { 0U })
}
}
}
}
let use_feature_values = !gsub_masks.is_empty()
let glyphs : Array[UInt] = []
let clusters : Array[Int] = []
for info in self.infos {
glyphs.push(info.codepoint)
clusters.push(info.cluster)
}
let (next_glyphs, next_clusters, changed) =
if use_feature_values {
let result = match gdef {
None =>
match gsub.apply_with_lookups_and_masks(
glyphs,
clusters,
map.gsub_lookups(),
masks=gsub_masks,
lookup_mask=1U,
alt_values=gsub_alt_values,
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
Some(gdef) =>
match gsub.apply_with_lookups_and_masks(
glyphs,
clusters,
map.gsub_lookups(),
gdef=gdef,
masks=gsub_masks,
lookup_mask=1U,
alt_values=gsub_alt_values,
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
}
let (glyphs, clusters, _, changed) = result
(glyphs, clusters, changed)
} else {
match gdef {
None =>
match gsub.apply_with_lookups(
glyphs,
clusters,
map.gsub_lookups(),
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
Some(gdef) =>
match gsub.apply_with_lookups(
glyphs,
clusters,
map.gsub_lookups(),
gdef=gdef,
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
}
}
if changed || next_glyphs.length() != self.infos.length() {
let same_len = next_glyphs.length() == self.infos.length()
let new_infos : Array[GlyphInfo] = []
let new_positions : Array[GlyphPosition] = []
for i in 0.. return Err(err)
Ok(value) => value
}
let unicode = if same_len { self.infos[i].unicode } else { 0U }
new_infos.push(GlyphInfo::{ codepoint: glyph, cluster, unicode })
new_positions.push(pos)
}
self.infos = new_infos
self.positions = new_positions
}
}
match zero_width_marks {
ZeroWidthMarks::ByGdefEarly =>
match zero_marks_by_gdef_or_unicode(font, self, unicode_mark_flags, gpos_present) {
Err(err) => return Err(err)
Ok(_) => ()
}
_ => ()
}
if gpos_present && gpos is Some(gpos) {
let gdef = match font.gdef() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let glyphs : Array[UInt] = []
let clusters : Array[Int] = []
for info in self.infos {
glyphs.push(info.codepoint)
clusters.push(info.cluster)
}
let advances_x : Array[Int] = []
let advances_y : Array[Int] = []
for pos in self.positions {
advances_x.push(pos.x_advance)
advances_y.push(pos.y_advance)
}
let deltas = match gdef {
None =>
match gpos.position_deltas_with_lookups(
glyphs[:],
gpos_lookups,
clusters=clusters[:],
advances_x=advances_x[:],
advances_y=advances_y[:],
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
Some(gdef) =>
match gpos.position_deltas_with_lookups(
glyphs[:],
gpos_lookups,
gdef=gdef,
clusters=clusters[:],
advances_x=advances_x[:],
advances_y=advances_y[:],
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
}
let new_positions : Array[GlyphPosition] = []
for i in 0.. return Err(Font(err))
Ok(value) => value
}
if kern is Some(kern) {
let new_positions : Array[GlyphPosition] = []
for pos in self.positions {
new_positions.push(pos)
}
if new_positions.length() >= 2 {
for i in 0..<(new_positions.length() - 1) {
let left = self.infos[i].codepoint
let right = self.infos[i + 1].codepoint
match kern.kern_value(left, right) {
None => ()
Some(value) => {
let pos = new_positions[i]
new_positions[i] = GlyphPosition::{
x_advance: pos.x_advance + value,
y_advance: pos.y_advance,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
}
}
}
}
}
self.positions = new_positions
}
}
match zero_width_marks {
ZeroWidthMarks::ByGdefLate =>
match zero_marks_by_gdef_or_unicode(font, self, unicode_mark_flags, gpos_present) {
Err(err) => return Err(err)
Ok(_) => ()
}
_ => ()
}
if fallback_position && !gpos_present {
match fallback_mark_position(font, self, unicode_mark_flags, combining_classes) {
Err(err) => return Err(err)
Ok(_) => ()
}
}
finalize_default_ignorables_and_variation_selectors(self)
Ok(())
}
///|
fn is_effective_default_ignorable(buffer : Buffer, info : GlyphInfo) -> Bool {
if !@unicode.is_default_ignorable(info.unicode) {
return false
}
match buffer.not_found_variation_selector {
None => true
Some(_) => !@unicode.is_variation_selector(info.unicode)
}
}
///|
fn apply_default_ignorables(buffer : Buffer) -> Unit {
if buffer.preserve_default_ignorables {
return ()
}
if buffer.remove_default_ignorables {
let new_infos : Array[GlyphInfo] = []
let new_positions : Array[GlyphPosition] = []
for i in 0.. Unit {
match buffer.not_found_variation_selector {
None => ()
Some(glyph) => {
for i in 0.. Unit {
apply_default_ignorables(buffer)
apply_variation_selector_fallback(buffer)
}
///|
fn default_position(
buffer : Buffer,
font : @font.Font,
glyph : UInt,
) -> Result[GlyphPosition, ShapeError] {
if buffer.direction.is_vertical() {
let advance = match font.glyph_v_advance(glyph) {
Ok(value) => value
Err(err) => {
let glyph_count = font.get_face().get_glyph_count()
if glyph_count > 0 && glyph.reinterpret_as_int() >= glyph_count {
0
} else {
return Err(Font(err))
}
}
}
Ok(GlyphPosition::{
x_advance: 0,
y_advance: advance,
x_offset: 0,
y_offset: 0,
})
} else {
let advance = match font.glyph_h_advance(glyph) {
Ok(value) => value
Err(err) => {
let glyph_count = font.get_face().get_glyph_count()
if glyph_count > 0 && glyph.reinterpret_as_int() >= glyph_count {
0
} else {
return Err(Font(err))
}
}
}
Ok(GlyphPosition::{
x_advance: advance,
y_advance: 0,
x_offset: 0,
y_offset: 0,
})
}
}