// Copyright 2025 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.
///|
/// Shaping buffers and low-level glyph reordering helpers.
///
/// Ported from `swash/src/shape/buffer.rs` (swash is dual-licensed Apache-2.0 OR MIT).
///|
/// Glyph flags.
pub const SUBSTITUTED : UInt16 = 1
///|
pub const LIGATED : UInt16 = 2
///|
pub const COMPONENT : UInt16 = 4
///|
pub const MARK_ATTACH : UInt16 = 8
///|
pub const CURSIVE_ATTACH : UInt16 = 16
///|
pub const IGNORABLE : UInt16 = 64
///|
const INVALID_GLYPH_ID : UInt16 = 0xFFFF
///|
/// Per glyph shaping data.
pub struct GlyphData {
mut id : GlyphId
mut flags : UInt16
mut class : Byte
mut char_class : ShapeClass
mut mark_type : Byte
joining_type : Byte
mut mask : Byte
mut skip : Bool
mut component : Byte
mut cluster : UInt32
data : UserData
}
///|
pub fn GlyphData::default() -> GlyphData {
{
id: (0).to_uint16(),
flags: 0,
class: (0).to_byte(),
char_class: Base,
mark_type: (0).to_byte(),
joining_type: (0).to_byte(),
mask: (0).to_byte(),
skip: false,
component: (0xFF).to_byte(),
cluster: 0U,
data: 0U,
}
}
///|
pub fn GlyphData::is_component(self : GlyphData) -> Bool {
(self.flags & COMPONENT) != 0
}
///|
pub fn GlyphData::id(self : GlyphData) -> GlyphId {
self.id
}
///|
pub fn GlyphData::flags(self : GlyphData) -> UInt16 {
self.flags
}
///|
pub fn GlyphData::cluster(self : GlyphData) -> UInt32 {
self.cluster
}
///|
/// Per glyph shaping position data.
pub struct PositionData {
mut base : Byte
mut flags : UInt16
mut x : Double
mut y : Double
mut advance : Double
}
///|
pub fn PositionData::default() -> PositionData {
{ base: (0).to_byte(), flags: 0, x: 0.0, y: 0.0, advance: 0.0, }
}
///|
fn Glyph::Glyph(g : GlyphData, p : PositionData) -> Glyph {
{
id: g.id,
info: GlyphInfo(p.flags),
x: p.x,
y: p.y,
advance: p.advance,
data: g.data,
}
}
///|
pub struct IndexRange {
start : Int32
end : Int32
}
///|
///|
pub fn IndexRange::IndexRange(start : Int32, end : Int32) -> IndexRange {
{ start, end, }
}
///|
pub fn IndexRange::start(self : IndexRange) -> Int32 {
self.start
}
///|
pub fn IndexRange::end(self : IndexRange) -> Int32 {
self.end
}
///|
pub struct Buffer {
glyphs : Array[GlyphData]
positions : Array[PositionData]
infos : Array[(ClusterInfo, Bool, UserData)]
ranges : Array[SourceRange]
shaped_glyphs : Array[Glyph]
mut is_rtl : Bool
mut dotted_circle : GlyphId?
mut has_cursive : Bool
mut has_marks : Bool
mut reversed : Bool
mut next_cluster : UInt32
mut skip_state : SkipState
sub_args : Array[UInt16]
pos_args : Array[UInt16]
}
///|
///|
pub struct SkipState {
flags : Byte
mask : Byte
mark_check : Byte
mark_class : Byte
mark_set : UInt32
}
///|
pub fn SkipState::default() -> SkipState {
{
flags: (0).to_byte(),
mask: (0).to_byte(),
mark_check: (0).to_byte(),
mark_class: (0).to_byte(),
mark_set: 0U,
}
}
///|
pub fn Buffer::default() -> Buffer {
{
glyphs: ([] : Array[GlyphData]),
positions: ([] : Array[PositionData]),
infos: ([] : Array[(ClusterInfo, Bool, UserData)]),
ranges: ([] : Array[SourceRange]),
shaped_glyphs: ([] : Array[Glyph]),
is_rtl: false,
dotted_circle: None,
has_cursive: false,
has_marks: false,
reversed: false,
next_cluster: 0U,
skip_state: SkipState::default(),
sub_args: ([] : Array[UInt16]),
pos_args: ([] : Array[UInt16]),
}
}
///|
pub fn Buffer::Buffer() -> Buffer {
Buffer::default()
}
///|
pub fn Buffer::len(self : Buffer) -> Int32 {
self.glyphs.length()
}
///|
pub fn Buffer::glyphs(self : Buffer) -> ArrayView[GlyphData] {
self.glyphs[:]
}
///|
pub fn Buffer::positions(self : Buffer) -> ArrayView[PositionData] {
self.positions[:]
}
///|
pub fn Buffer::ranges(self : Buffer) -> ArrayView[SourceRange] {
self.ranges[:]
}
///|
pub fn Buffer::clear(self : Buffer) -> Unit {
self.glyphs.clear()
self.positions.clear()
self.infos.clear()
self.ranges.clear()
self.is_rtl = false
self.reversed = false
self.has_cursive = false
self.has_marks = false
self.dotted_circle = None
self.next_cluster = 0U
self.skip_state = SkipState::default()
}
///|
fn Buffer::push_cluster(self : Buffer, cluster : CharCluster) -> Unit {
self.infos.push((cluster.info(), false, cluster.user_data()))
self.ranges.push(cluster.range())
}
///|
fn Buffer::push_char(self : Buffer, ch : ClusterChar) -> Unit {
let cluster = self.next_cluster
let flags : UInt16 = if ch.is_ignorable() { IGNORABLE } else { 0 }
self.glyphs.push({
id: ch.glyph_id(),
flags,
class: (0).to_byte(),
char_class: ch.shape_class(),
joining_type: joining_type_index(ch.joining_type()).to_byte(),
mark_type: (0).to_byte(),
mask: (0).to_byte(),
skip: false,
component: (0xFF).to_byte(),
cluster,
data: ch.data(),
})
}
///|
fn Buffer::_push_hangul_char(self : Buffer, ch : ClusterChar) -> Unit {
let cluster = self.next_cluster
let c = ch.ch().to_int()
let mask = if (c >= 0x1100 && c <= 0x115F) || (c >= 0xA960 && c <= 0xA97C) {
LJMO_MASK
} else if (c >= 0x1160 && c <= 0x11A7) || (c >= 0xD7B0 && c <= 0xD7C6) {
VJMO_MASK
} else if (c >= 0x11A8 && c <= 0x11FF) || (c >= 0xD7CB && c <= 0xD7FB) {
TJMO_MASK
} else {
LJMO_MASK | VJMO_MASK | TJMO_MASK
}
let flags : UInt16 = if ch.is_ignorable() { IGNORABLE } else { 0 }
self.glyphs.push({
id: ch.glyph_id(),
flags,
class: (0).to_byte(),
char_class: ch.shape_class(),
joining_type: joining_type_index(ch.joining_type()).to_byte(),
mark_type: (0).to_byte(),
mask,
skip: false,
component: (0xFF).to_byte(),
cluster,
data: ch.data(),
})
}
///|
pub fn Buffer::push(self : Buffer, cluster : CharCluster) -> IndexRange {
let start = self.glyphs.length()
let chars = cluster.mapped_chars()
if cluster.info().is_broken() {
match self.dotted_circle {
None => ()
Some(id) => {
let first = chars[0]
self.push_char(
ClusterChar(
'\u{25CC}',
first.offset(),
Base,
U,
false,
true,
id,
first.data(),
),
)
}
}
}
for ch in chars {
self.push_char(ch)
}
self.next_cluster = self.next_cluster + 1U
self.push_cluster(cluster)
IndexRange(start, self.glyphs.length())
}
///|
pub fn Buffer::push_order(
self : Buffer,
cluster : CharCluster,
order : ArrayView[Int32],
) -> IndexRange {
let start = self.glyphs.length()
let chars = cluster.mapped_chars()
if cluster.info().is_broken() {
match self.dotted_circle {
None => ()
Some(id) => {
let first = chars[order[0]]
self.push_char(
ClusterChar(
'\u{25CC}',
first.offset(),
Base,
U,
false,
true,
id,
first.data(),
),
)
}
}
}
let len = chars.length()
for i in 0.. IndexRange {
let start = self.glyphs.length()
let chars = cluster.mapped_chars()
if cluster.info().is_broken() {
match self.dotted_circle {
None => ()
Some(id) => {
let first = chars[0]
self.push_char(
ClusterChar(
'\u{25CC}',
first.offset(),
Base,
U,
false,
true,
id,
first.data(),
),
)
}
}
}
for ch in chars {
self._push_hangul_char(ch)
}
self.next_cluster = self.next_cluster + 1U
self.push_cluster(cluster)
IndexRange(start, self.glyphs.length())
}
///|
pub fn Buffer::ensure_order(self : Buffer, reversed : Bool) -> Unit {
if reversed != self.reversed {
reverse_glyph_data(self.glyphs)
if self.positions.length() != 0 {
reverse_position_data(self.positions)
}
self.reversed = reversed
}
}
///|
pub fn Buffer::clear_flags(
self : Buffer,
flags : UInt16,
range : IndexRange?,
) -> Unit {
let mask : UInt16 = (0xFFFF).to_uint16() ^ flags
match range {
None =>
for i in 0..
for i in r.start().. Unit {
let kept : Array[GlyphData] = []
for g in self.glyphs.iter() {
if g.is_component() {
continue
}
if was_morx && g.id == INVALID_GLYPH_ID {
continue
}
kept.push(g)
}
self.glyphs.clear()
for g in kept.iter() {
self.glyphs.push(g)
}
self.positions.clear()
for _ in 0.. Unit {
self.glyphs[index].id = id
self.glyphs[index].flags = self.glyphs[index].flags | SUBSTITUTED
}
///|
pub fn Buffer::substitute_ligature(
self : Buffer,
index : Int32,
id : GlyphId,
components : ArrayView[Int32],
) -> Unit {
if components.length() == 0 {
return
}
self.glyphs[index].id = id
self.glyphs[index].flags = self.glyphs[index].flags | (SUBSTITUTED | LIGATED)
let cluster = self.glyphs[index].cluster
let mut last_index = index
let n = components.length()
for i in 0.. 1 {
let component = i.to_byte()
for j in (last_index + 1).. Unit {
let count = ids.length()
if count == 0 {
// Remove the glyph.
let out : Array[GlyphData] = []
let len = self.glyphs.length()
for i in 0.. Unit {
if index < 0 || index >= self.glyphs.length() || count <= 0 {
return
}
let g = self.glyphs[index]
let out : Array[GlyphData] = []
let len = self.glyphs.length()
for i in 0.. Unit {
self.positions[index].x = self.positions[index].x + x
self.positions[index].y = self.positions[index].y + y
self.positions[index].advance = self.positions[index].advance + xadvance
}
///|
pub fn Buffer::position_cursive(
self : Buffer,
index : Int32,
next : Int32,
_x : Double,
y : Double,
) -> Unit {
self.has_cursive = true
self.positions[index].flags = CURSIVE_ATTACH
self.positions[index].y = y
// self.positions[index].advance = self.positions[index].advance - x
self.positions[index].base = (next - index).to_byte()
}
///|
pub fn Buffer::position_mark(
self : Buffer,
index : Int32,
base : Int32,
dx : Double,
dy : Double,
) -> Unit {
self.has_marks = true
self.positions[index].flags = MARK_ATTACH
self.positions[index].base = (index - base).to_byte()
self.positions[index].x = dx
self.positions[index].y = dy
}
///|
pub fn Buffer::set_join_masks(self : Buffer) -> Unit {
let mut prev : Int32? = None
let mut state = 0
let len = self.glyphs.length()
// Transparent joining type.
for i in 0.. ()
Some(j) => if pm != NONE_MASK { self.glyphs[j].mask = pm }
}
self.glyphs[i].mask = cm
prev = Some(i)
state = next_state.to_int()
}
}
///|
fn reverse_glyph_data(arr : Array[GlyphData]) -> Unit {
if arr.length() <= 1 {
return
}
let mut i = 0
let mut j = arr.length() - 1
while i < j {
let tmp = arr[i]
arr[i] = arr[j]
arr[j] = tmp
i = i + 1
j = j - 1
}
}
///|
fn reverse_position_data(arr : Array[PositionData]) -> Unit {
if arr.length() <= 1 {
return
}
let mut i = 0
let mut j = arr.length() - 1
while i < j {
let tmp = arr[i]
arr[i] = arr[j]
arr[j] = tmp
i = i + 1
j = j - 1
}
}
///|
fn joining_type_index(t : JoiningType) -> Int32 {
match t {
U => 0
L => 1
R => 2
D => 3
Alaph => 4
DalathRish => 5
T => 6
}
}
///|
pub fn reorder_myanmar(
chars : ArrayView[ClusterChar],
order : Array[Int32],
) -> Unit {
let ignored : Array[Bool] = Array::makei(MAX_CLUSTER_SIZE, _ => false)
let mut base : Int32? = None
let mut kinzi : IndexRange? = None
let mut medial_ra : Int32? = None
let mut vpre : IndexRange? = None
let mut vblw : Int32? = None
let mut anus : IndexRange? = None
let mut i = 0
let mut last_vblw = false
let len = chars.length()
if len == 0 {
return
}
while order.length() < len {
order.push(0)
}
match chars[0].shape_class() {
Kinzi => {
kinzi = Some(IndexRange(0, 3))
ignored[0] = true
ignored[1] = true
ignored[2] = true
i = 3
}
_ => ()
}
while i < len {
let ch = chars[i]
let k = ch.shape_class()
if last_vblw {
match k {
Anusvara => {
anus = match anus {
Some(r) => Some(IndexRange(r.start(), i - r.start() + 1))
None => Some(IndexRange(i, i + 1))
}
ignored[i] = true
i = i + 1
continue
}
_ => ()
}
}
last_vblw = false
match k {
VBlw => {
if vblw is None {
vblw = Some(i)
}
last_vblw = true
}
_ => ()
}
match base {
None =>
match k {
Base => {
base = Some(i)
ignored[i] = true
}
MedialRa => {
medial_ra = Some(i)
ignored[i] = true
}
VPre => {
vpre = match vpre {
Some(r) => Some(IndexRange(r.start(), i - r.start() + 1))
None => Some(IndexRange(i, i + 1))
}
ignored[i] = true
}
_ => ()
}
Some(_) =>
match k {
MedialRa => {
medial_ra = Some(i)
ignored[i] = true
}
VPre => {
vpre = match vpre {
Some(r) => Some(IndexRange(r.start(), i - r.start() + 1))
None => Some(IndexRange(i, i + 1))
}
ignored[i] = true
}
_ => ()
}
}
i = i + 1
}
i = 0
match vpre {
None => ()
Some(r) =>
for j in r.start().. ()
Some(j) => {
order[i] = j
i = i + 1
}
}
match base {
None => ()
Some(j) => {
order[i] = j
i = i + 1
}
}
match kinzi {
None => ()
Some(r) =>
for j in r.start().. ()
Some(v) =>
if v == j {
match anus {
None => ()
Some(ar) =>
for k in ar.start().. Unit {
let mut first_base : Int32? = None
let mut last_base : Int32? = None
let mut last_halant : Int32? = None
let mut reph : Int32? = None
let mut pref : Int32? = None
let mut vpre : IndexRange? = None
let mut vmpre : IndexRange? = None
let ignored : Array[Bool] = Array::makei(64, _ => false)
let len = glyphs.length()
while buf.length() < len {
buf.push(GlyphData::default())
}
while order.length() < len {
order.push(0)
}
for i in 0.. {
match first_base {
None => {
first_base = Some(i)
ignored[i] = true
}
Some(_) => ()
}
match last_halant {
None => last_base = Some(i)
Some(_) => ()
}
}
Halant => last_halant = Some(i)
Reph =>
match reph {
None => {
reph = Some(i)
ignored[i] = true
}
Some(_) => ()
}
Pref =>
match pref {
None => {
pref = Some(i)
ignored[i] = true
}
Some(_) => ()
}
VPre => {
vpre = match vpre {
Some(r) => Some(IndexRange(r.start(), i - r.start() + 1))
None => Some(IndexRange(i, i + 1))
}
ignored[i] = true
}
VMPre => {
vmpre = match vmpre {
Some(r) => Some(IndexRange(r.start(), i - r.start() + 1))
None => Some(IndexRange(i, i + 1))
}
ignored[i] = true
}
_ => ()
}
}
let mut j = 0
match last_halant {
None => {
match vmpre {
None => ()
Some(r) =>
for i in r.start().. ()
Some(r) =>
for i in r.start().. ()
Some(i) => {
order[j] = i
j = j + 1
}
}
}
Some(_) => ()
}
match first_base {
None => ()
Some(i) => {
order[j] = i
j = j + 1
}
}
match last_base {
None =>
match reph {
None => ()
Some(i) => {
order[j] = i
j = j + 1
}
}
Some(_) => ()
}
for i in 0.. ()
Some(lb) =>
if lb == i {
match reph {
None => ()
Some(r) => {
order[j] = r
j = j + 1
}
}
}
}
match last_halant {
None => ()
Some(lh) =>
if lh == i {
match vmpre {
None => ()
Some(r) =>
for k in r.start().. ()
Some(r) =>
for k in r.start().. ()
Some(p) => {
order[j] = p
j = j + 1
}
}
}
}
}
for i in 0..