// 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.
fn is_zero_width_mark(u : UInt) -> Bool {
let cat = @unicode.general_category(u)
cat == @unicode.gc_non_spacing_mark || cat == @unicode.gc_enclosing_mark
}
fn mark_flags_from_infos(infos : Array[GlyphInfo]) -> Array[Bool] {
let flags : Array[Bool] = []
for info in infos {
flags.push(is_zero_width_mark(info.codepoint))
}
flags
}
fn mark_flags_from_gdef(
gdef : @ot_tables.GdefTable,
infos : Array[GlyphInfo],
) -> Result[Array[Bool], ShapeError] {
let flags : Array[Bool] = []
for info in infos {
let glyph_class = match gdef.glyph_class(info.codepoint) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
flags.push(glyph_class == 3)
}
Ok(flags)
}
priv enum ZeroWidthMarks {
Disabled
ByGdefEarly
ByGdefLate
}
fn collect_mark_fallback_data(infos : Array[GlyphInfo]) -> (Array[Bool], Array[Int]) {
let marks = mark_flags_from_infos(infos)
let combining_classes : Array[Int] = []
for info in infos {
let klass = @unicode.modified_combining_class(info.codepoint).reinterpret_as_int()
combining_classes.push(recategorize_combining_class(info.codepoint, klass))
}
(marks, combining_classes)
}
struct GlyphExtents {
mut x_bearing : Int
mut y_bearing : Int
mut width : Int
mut height : Int
} derive(Show)
let cc_attached_below_left : Int = 200
let cc_attached_below : Int = 202
let cc_attached_above : Int = 214
let cc_attached_above_right : Int = 216
let cc_below_left : Int = 218
let cc_below : Int = 220
let cc_below_right : Int = 222
let cc_left : Int = 224
let cc_right : Int = 226
let cc_above_left : Int = 228
let cc_above : Int = 230
let cc_above_right : Int = 232
let cc_double_below : Int = 233
let cc_double_above : Int = 234
let mcc_ccc10 : Int = 22
let mcc_ccc11 : Int = 15
let mcc_ccc12 : Int = 16
let mcc_ccc13 : Int = 17
let mcc_ccc14 : Int = 23
let mcc_ccc15 : Int = 18
let mcc_ccc16 : Int = 19
let mcc_ccc17 : Int = 20
let mcc_ccc18 : Int = 21
let mcc_ccc19 : Int = 14
let mcc_ccc20 : Int = 24
let mcc_ccc22 : Int = 25
let mcc_ccc23 : Int = 13
let mcc_ccc24 : Int = 10
let mcc_ccc25 : Int = 11
let mcc_ccc26 : Int = 26
let mcc_ccc27 : Int = 28
let mcc_ccc28 : Int = 29
let mcc_ccc29 : Int = 30
let mcc_ccc30 : Int = 31
let mcc_ccc31 : Int = 32
let mcc_ccc32 : Int = 33
let mcc_ccc33 : Int = 27
let mcc_ccc34 : Int = 34
let mcc_ccc35 : Int = 35
let mcc_ccc36 : Int = 36
let mcc_ccc103 : Int = 3
let mcc_ccc107 : Int = 107
let mcc_ccc118 : Int = 118
let mcc_ccc122 : Int = 122
let mcc_ccc129 : Int = 129
let mcc_ccc130 : Int = 132
let mcc_ccc132 : Int = 131
fn recategorize_combining_class(u : UInt, klass : Int) -> Int {
if klass >= 200 {
return klass
}
if (u & 0xFFFFFF00U) == 0x0E00U {
if klass == 0 {
match u {
0x0E31U |
0x0E34U |
0x0E35U |
0x0E36U |
0x0E37U |
0x0E47U |
0x0E4CU |
0x0E4DU |
0x0E4EU => return cc_above_right
0x0EB1U |
0x0EB4U |
0x0EB5U |
0x0EB6U |
0x0EB7U |
0x0EBBU |
0x0ECCU |
0x0ECDU => return cc_above
0x0EBCU => return cc_below
_ => ()
}
} else if u == 0x0E3AU {
return cc_below_right
}
}
if klass == mcc_ccc10 ||
klass == mcc_ccc11 ||
klass == mcc_ccc12 ||
klass == mcc_ccc13 ||
klass == mcc_ccc14 ||
klass == mcc_ccc15 ||
klass == mcc_ccc16 ||
klass == mcc_ccc17 ||
klass == mcc_ccc18 ||
klass == mcc_ccc20 ||
klass == mcc_ccc22 {
return cc_below
}
if klass == mcc_ccc23 {
return cc_attached_above
}
if klass == mcc_ccc24 {
return cc_above_right
}
if klass == mcc_ccc25 || klass == mcc_ccc19 {
return cc_above_left
}
if klass == mcc_ccc26 {
return cc_above
}
if klass == mcc_ccc27 ||
klass == mcc_ccc28 ||
klass == mcc_ccc30 ||
klass == mcc_ccc31 ||
klass == mcc_ccc33 ||
klass == mcc_ccc34 ||
klass == mcc_ccc35 ||
klass == mcc_ccc36 {
return cc_above
}
if klass == mcc_ccc29 || klass == mcc_ccc32 {
return cc_below
}
if klass == mcc_ccc103 {
return cc_below_right
}
if klass == mcc_ccc107 {
return cc_above_right
}
if klass == mcc_ccc118 {
return cc_below
}
if klass == mcc_ccc122 {
return cc_above
}
if klass == mcc_ccc129 {
return cc_below
}
if klass == mcc_ccc130 {
return cc_above
}
if klass == mcc_ccc132 {
return cc_below
}
klass
}
fn scale_value(value : Int, scale : Int, upem : Int) -> Int {
if upem == 0 {
return value
}
(value * scale) / upem
}
fn glyph_extents(
font : @font.Font,
glyph : UInt,
) -> Result[GlyphExtents?, ShapeError] {
let bounds = match font.glyph_bounds(glyph) {
Err(_) => return Ok(None)
Ok(value) => value
}
match bounds {
None => Ok(None)
Some(bounds) => {
let (scale_x, scale_y) = font.get_scale()
let upem = font.get_face().get_upem()
let x_min = scale_value(bounds.x_min, scale_x, upem)
let x_max = scale_value(bounds.x_max, scale_x, upem)
let y_min = scale_value(bounds.y_min, scale_y, upem)
let y_max = scale_value(bounds.y_max, scale_y, upem)
Ok(Some(GlyphExtents::{
x_bearing: x_min,
y_bearing: y_max,
width: x_max - x_min,
height: y_max - y_min,
}))
}
}
}
fn position_mark_fallback(
font : @font.Font,
buffer : Buffer,
extents : GlyphExtents,
mark_index : Int,
combining_class : Int,
y_gap : Int,
) -> Result[GlyphExtents, ShapeError] {
if combining_class == 0 || combining_class == cc_left || combining_class == cc_right {
return Ok(extents)
}
let mark_extents = match glyph_extents(font, buffer.infos[mark_index].codepoint) {
Err(err) => return Err(err)
Ok(None) => return Ok(extents)
Ok(Some(value)) => value
}
let extents = extents
let mut x_offset = 0
if combining_class == cc_double_below || combining_class == cc_double_above {
if buffer.direction == @common.direction_ltr {
x_offset =
extents.x_bearing + extents.width - mark_extents.width / 2 - mark_extents.x_bearing
} else if buffer.direction == @common.direction_rtl {
x_offset = extents.x_bearing - mark_extents.width / 2 - mark_extents.x_bearing
} else {
x_offset =
extents.x_bearing + (extents.width - mark_extents.width) / 2 - mark_extents.x_bearing
}
} else if combining_class == cc_attached_below_left ||
combining_class == cc_below_left ||
combining_class == cc_above_left {
x_offset = extents.x_bearing - mark_extents.x_bearing
} else if combining_class == cc_attached_above_right ||
combining_class == cc_below_right ||
combining_class == cc_above_right {
x_offset = extents.x_bearing + extents.width - mark_extents.width - mark_extents.x_bearing
} else {
x_offset = extents.x_bearing + (extents.width - mark_extents.width) / 2 - mark_extents.x_bearing
}
let mut y_offset = 0
if combining_class == cc_double_below ||
combining_class == cc_below_left ||
combining_class == cc_below ||
combining_class == cc_below_right {
extents.height = extents.height - y_gap
y_offset = extents.y_bearing + extents.height - mark_extents.y_bearing
if (y_gap > 0) == (y_offset > 0) {
extents.height = extents.height - y_offset
y_offset = 0
}
extents.height = extents.height + mark_extents.height
} else if combining_class == cc_attached_below_left ||
combining_class == cc_attached_below {
y_offset = extents.y_bearing + extents.height - mark_extents.y_bearing
if (y_gap > 0) == (y_offset > 0) {
extents.height = extents.height - y_offset
y_offset = 0
}
extents.height = extents.height + mark_extents.height
} else if combining_class == cc_double_above ||
combining_class == cc_above_left ||
combining_class == cc_above ||
combining_class == cc_above_right {
extents.y_bearing = extents.y_bearing + y_gap
extents.height = extents.height - y_gap
y_offset = extents.y_bearing - (mark_extents.y_bearing + mark_extents.height)
if (y_gap > 0) != (y_offset > 0) {
let correction = -y_offset / 2
extents.y_bearing = extents.y_bearing + correction
extents.height = extents.height - correction
y_offset = y_offset + correction
}
extents.y_bearing = extents.y_bearing - mark_extents.height
extents.height = extents.height + mark_extents.height
} else if combining_class == cc_attached_above ||
combining_class == cc_attached_above_right {
y_offset = extents.y_bearing - (mark_extents.y_bearing + mark_extents.height)
if (y_gap > 0) != (y_offset > 0) {
let correction = -y_offset / 2
extents.y_bearing = extents.y_bearing + correction
extents.height = extents.height - correction
y_offset = y_offset + correction
}
extents.y_bearing = extents.y_bearing - mark_extents.height
extents.height = extents.height + mark_extents.height
}
buffer.positions[mark_index] = GlyphPosition::{
x_advance: 0,
y_advance: 0,
x_offset,
y_offset,
}
Ok(extents)
}
fn fallback_mark_position(
font : @font.Font,
buffer : Buffer,
marks : Array[Bool],
combining_classes : Array[Int],
) -> Result[Unit, ShapeError] {
if marks.length() != buffer.infos.length() ||
combining_classes.length() != buffer.infos.length() {
return Ok(())
}
if buffer.direction.is_vertical() {
return Ok(())
}
let (_, scale_y) = font.get_scale()
let y_gap = scale_y / 16
let mut i = 0
while i < buffer.infos.length() {
if marks[i] {
i = i + 1
continue
}
let base_index = i
let mut j = i + 1
while j < buffer.infos.length() && marks[j] {
j = j + 1
}
if j - base_index <= 1 {
i = j
continue
}
let base_extents = match glyph_extents(font, buffer.infos[base_index].codepoint) {
Err(err) => return Err(err)
Ok(None) => {
i = j
continue
}
Ok(Some(value)) => value
}
let mut cluster_extents = base_extents
cluster_extents.x_bearing = 0
cluster_extents.width = buffer.positions[base_index].x_advance
let mut last_cc = -1
for k in (base_index + 1).. return Err(err)
Ok(value) => value
}
}
i = j
}
Ok(())
}
fn zero_mark_advances_with_flags(
buffer : Buffer,
marks : Array[Bool],
adjust_offsets? : Bool = false,
) -> Unit {
if buffer.infos.length() != buffer.positions.length() ||
buffer.infos.length() != marks.length() {
return ()
}
for i in 0.. Result[Unit, ShapeError] {
let adjust_offsets = !gpos_present && buffer.direction.is_forward()
let mut zeroed = false
let gdef = match font.gdef() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
match gdef {
None => ()
Some(gdef) => {
if gdef.header().glyph_class_def_offset is Some(_) {
let flags = match mark_flags_from_gdef(gdef, buffer.infos) {
Err(err) => return Err(err)
Ok(value) => value
}
zero_mark_advances_with_flags(buffer, flags, adjust_offsets=adjust_offsets)
zeroed = true
}
}
}
if !zeroed &&
!gpos_present &&
unicode_marks.length() == buffer.infos.length() {
zero_mark_advances_with_flags(buffer, unicode_marks, adjust_offsets=adjust_offsets)
}
Ok(())
}