// 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.
///|
let dotted_circle_codepoint : UInt = 0x25CCU
fn insert_dotted_circle_for_syllables(
font : @font.Font,
buffer : Buffer,
syllables : Array[Int],
broken_type : Int,
) -> Result[Bool, ShapeError] {
if buffer.infos.length() != syllables.length() || buffer.infos.is_empty() {
return Ok(false)
}
let has_dotted_circle = match font.glyph_for_codepoint(dotted_circle_codepoint) {
Err(err) => return Err(Font(err))
Ok(None) => false
Ok(Some(_)) => true
}
if !has_dotted_circle {
return Ok(false)
}
let next_infos : Array[GlyphInfo] = []
let next_positions : Array[GlyphPosition] = []
let mut inserted = false
let mut last_syllable = -1
for i in 0.. Result[Bool, ShapeError] {
if buffer.infos.length() != syllables.length() || buffer.infos.is_empty() ||
categories.length() != buffer.infos.length() {
return Ok(false)
}
let has_dotted_circle = match font.glyph_for_codepoint(dotted_circle_codepoint) {
Err(err) => return Err(Font(err))
Ok(None) => false
Ok(Some(_)) => true
}
if !has_dotted_circle {
return Ok(false)
}
let next_infos : Array[GlyphInfo] = []
let next_positions : Array[GlyphPosition] = []
let mut inserted = false
let mut last_syllable = -1
let mut i = 0
while i < buffer.infos.length() {
let syllable = syllables[i]
if syllable != last_syllable && (syllable & 0x0f) == broken_type {
inserted = true
last_syllable = syllable
let mut j = i
while j < buffer.infos.length() &&
syllables[j] == syllable &&
categories[j] == repha_category {
next_infos.push(buffer.infos[j])
next_positions.push(buffer.positions[j])
j = j + 1
}
let cluster = if j < buffer.infos.length() && syllables[j] == syllable {
buffer.infos[j].cluster
} else {
buffer.infos[i].cluster
}
next_infos.push(GlyphInfo::{ codepoint: dotted_circle_codepoint, cluster, unicode: dotted_circle_codepoint })
next_positions.push(GlyphPosition::{ x_advance: 0, y_advance: 0, x_offset: 0, y_offset: 0 })
while j < buffer.infos.length() && syllables[j] == syllable {
next_infos.push(buffer.infos[j])
next_positions.push(buffer.positions[j])
j = j + 1
}
i = j
continue
}
next_infos.push(buffer.infos[i])
next_positions.push(buffer.positions[i])
last_syllable = syllable
i = i + 1
}
if inserted {
buffer.infos = next_infos
buffer.positions = next_positions
}
Ok(inserted)
}
///|
fn apply_gsub_features_for_syllables(
font : @font.Font,
buffer : Buffer,
gsub : @ot_tables.GsubTable,
feature_tags : Array[@common.Tag],
syllables : Array[Int],
glyph_masks? : Array[UInt] = [],
script_tag? : @common.Tag = buffer.script.to_tag(),
) -> Result[Unit, ShapeError] {
if syllables.is_empty() || syllables.length() != buffer.infos.length() {
return Ok(())
}
let mut use_masks = !glyph_masks.is_empty()
if use_masks && glyph_masks.length() != buffer.infos.length() {
use_masks = false
}
let gdef = match font.gdef() {
Err(err) => return Err(Font(err))
Ok(value) => value
}
let script_tag = script_tag
let lang_tag = language_to_ot_tag(buffer.language)
let feature_lookups : Array[Array[Int]] = []
for feature_tag in feature_tags {
let lookup_offsets = match gsub.layout().select_lookup_offsets_for_feature(
script_tag,
feature_tag,
language_tag=lang_tag,
include_required=false,
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
feature_lookups.push(lookup_offsets)
}
let mut has_lookups = false
for lookup_offsets in feature_lookups {
if !lookup_offsets.is_empty() {
has_lookups = true
break
}
}
if !has_lookups {
return Ok(())
}
let mut current_glyphs : Array[UInt] = []
let mut current_clusters : Array[Int] = []
let mut current_unicodes : Array[UInt] = []
let mut current_masks : Array[UInt] = []
for info in buffer.infos {
current_glyphs.push(info.codepoint)
current_clusters.push(info.cluster)
current_unicodes.push(info.unicode)
}
if use_masks {
for value in glyph_masks {
current_masks.push(value)
}
}
let mut changed = false
let mut delta = 0
let mut i = 0
while i < syllables.length() {
let syllable = syllables[i]
let mut j = i + 1
while j < syllables.length() && syllables[j] == syllable {
j = j + 1
}
let start = i + delta
let end = j + delta
if start < 0 || end > current_glyphs.length() || start >= end {
i = j
continue
}
let mut sub_glyphs : Array[UInt] = []
let mut sub_clusters : Array[Int] = []
let mut sub_masks : Array[UInt] = []
let sub_unicodes : Array[UInt] = []
for k in start..
match gsub.apply_with_lookups_and_masks(
sub_glyphs,
sub_clusters,
lookup_offsets[:],
masks=sub_masks,
lookup_mask=lookup_mask,
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
Some(gdef) =>
match gsub.apply_with_lookups_and_masks(
sub_glyphs,
sub_clusters,
lookup_offsets[:],
gdef=gdef,
masks=sub_masks,
lookup_mask=lookup_mask,
) {
Err(err) => return Err(Layout(err))
Ok(value) => value
}
}
let (next_glyphs, next_clusters, next_masks, did_change) = result
sub_glyphs = next_glyphs
sub_clusters = next_clusters
if use_masks {
sub_masks = next_masks
}
if did_change {
sub_changed = true
}
}
let old_len = end - start
let new_len = sub_glyphs.length()
if sub_changed || new_len != old_len {
let next_glyphs : Array[UInt] = []
let next_clusters : Array[Int] = []
let next_unicodes : Array[UInt] = []
let next_masks : Array[UInt] = []
for k in 0.. return Err(err)
Ok(value) => value
}
let unicode = if current_unicodes.length() == current_glyphs.length() {
current_unicodes[idx]
} else {
0U
}
infos.push(GlyphInfo::{ codepoint: glyph, cluster: current_clusters[idx], unicode })
positions.push(pos)
}
buffer.infos = infos
buffer.positions = positions
}
Ok(())
}