// 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 engine that handles the various methods available in an OpenType font.
///
/// Ported from `upstream swash/src/shape/engine.rs`.
///|
pub(all) enum EngineMode {
Simple
Myanmar
Complex
}
///|
pub impl Eq for EngineMode with fn equal(self, other) {
match self {
Simple => other is Simple
Myanmar => other is Myanmar
Complex => other is Complex
}
}
///|
pub(all) enum SubMode {
None
Gsub
Morx
}
///|
pub impl Eq for SubMode with fn equal(self, other) {
match self {
None => other is None
Gsub => other is Gsub
Morx => other is Morx
}
}
///|
pub(all) enum PosMode {
None
Gpos
Kerx
Kern
}
///|
pub impl Eq for PosMode with fn equal(self, other) {
match self {
None => other is None
Gpos => other is Gpos
Kerx => other is Kerx
Kern => other is Kern
}
}
///|
/// Metadata for creating a shaping engine.
pub struct EngineMetadata {
gdef : UInt32
gsub : UInt32
gpos : UInt32
morx : UInt32
kerx : UInt32
ankr : UInt32
kern : UInt32
sub_mode : SubMode
pos_mode : PosMode
}
///|
pub fn EngineMetadata::from_font(font : @swash.FontRef) -> EngineMetadata {
let gdef = font.table_offset(0x47444546) // "GDEF"
let gsub = font.table_offset(0x47535542) // "GSUB"
let gpos = font.table_offset(0x47504F53) // "GPOS"
let morx = font.table_offset(0x6D6F7278) // "morx"
let kerx = font.table_offset(0x6B657278) // "kerx"
let ankr = font.table_offset(0x616E6B72) // "ankr"
let kern = font.table_offset(0x6B65726E) // "kern"
let mut sub_mode = SubMode::None
if gsub != 0U {
sub_mode = Gsub
} else if morx != 0U {
sub_mode = Morx
}
let mut pos_mode = PosMode::None
if gpos != 0U {
pos_mode = Gpos
} else if kerx != 0U {
pos_mode = Kerx
} else if kern != 0U {
pos_mode = Kern
}
{ gdef, gsub, gpos, morx, kerx, ankr, kern, sub_mode, pos_mode, }
}
///|
pub struct Engine {
data : Bytes
gdef : @internal.Gdef
gsub : StageOffsets
gpos : StageOffsets
morx : UInt32
kerx : UInt32
ankr : UInt32
kern : UInt32
storage : Storage
coords : ArrayView[Int32]
script : @swash.Script
tags : (UInt32, UInt32, UInt32, UInt32)
sub_mode : SubMode
pos_mode : PosMode
use_ot : Bool
mode : EngineMode
}
///|
///|
pub fn Engine::Engine(
metadata : EngineMetadata,
font_data : Bytes,
coords : ArrayView[Int32],
script : @swash.Script,
language : @swash.Language?,
) -> Engine {
let gdef = match @internal.Gdef::from_offset(font_data, metadata.gdef) {
None => @internal.Gdef::empty()
Some(g) => g
}
let script_tag = script.to_opentype()
let lang_tag : UInt32? = match language {
None => None
Some(l) => l.to_opentype()
}
let (gsub, stags) = if metadata.sub_mode == Gsub {
match
StageOffsets::from_layout(font_data, metadata.gsub, script_tag, lang_tag) {
None => (StageOffsets::default(), (0U, 0U))
Some((off, tags)) => (off, tags)
}
} else {
(StageOffsets::default(), (0U, 0U))
}
let (gpos, ptags) = if metadata.pos_mode == Gpos {
match
StageOffsets::from_layout(font_data, metadata.gpos, script_tag, lang_tag) {
None => (StageOffsets::default(), (0U, 0U))
Some((off, tags)) => (off, tags)
}
} else {
(StageOffsets::default(), (0U, 0U))
}
let tags : (UInt32, UInt32, UInt32, UInt32) = (
stags.0,
stags.1,
ptags.0,
ptags.1,
)
let use_ot = gsub.lang != 0U || gpos.lang != 0U
let mode = if gsub.lang != 0U && script.is_complex() {
if script is Myanmar {
EngineMode::Myanmar
} else {
Complex
}
} else {
Simple
}
let sub_mode = if metadata.sub_mode == Gsub && gsub.lang == 0U {
SubMode::None
} else {
metadata.sub_mode
}
let pos_mode = if metadata.pos_mode == Gpos && gpos.lang == 0U {
PosMode::None
} else {
metadata.pos_mode
}
{
data: font_data,
gdef,
gsub,
gpos,
morx: metadata.morx,
kerx: metadata.kerx,
ankr: metadata.ankr,
kern: metadata.kern,
storage: Storage::default(),
coords,
script,
tags,
sub_mode,
pos_mode,
use_ot,
mode,
}
}
///|
/// OpenType shaping.
pub fn Engine::tags(self : Engine) -> (UInt32, UInt32, UInt32, UInt32) {
self.tags
}
///|
/// Builds a feature store for the current engine configuration.
pub fn Engine::collect_features(
self : Engine,
builder : FeatureStoreBuilder,
store : Ref[FeatureStore],
) -> Unit {
builder.build(store, self.data, self.coords, self.gdef, self.gsub, self.gpos)
store.val.groups = store.val.groups(self.script)
}
///|
/// Returns true if feature variations are supported.
pub fn Engine::has_feature_vars(self : Engine) -> Bool {
self.gsub.vars != 0U || self.gpos.vars != 0U
}
///|
/// Sets glyph and mark classes for the specified range of the buffer.
pub fn Engine::set_classes(
self : Engine,
buffer : Buffer,
range : IndexRange?,
) -> Unit {
if !self.gdef.ok() {
return
}
let (start, end) = match range {
None => (0, buffer.glyphs.length())
Some(r) => (r.start, r.end)
}
if self.gdef.has_mark_classes() {
for i in start.. Bool {
apply(
(0).to_byte(),
self.data,
self.gsub.base,
self.coords,
self.gdef,
self.storage,
store,
feature_mask,
buffer,
buffer_range,
) ==
Some(true)
}
///|
/// Applies the GPOS features to the specified range of the buffer.
pub fn Engine::gpos(
self : Engine,
store : FeatureStore,
feature_mask : FeatureMask,
buffer : Buffer,
buffer_range : IndexRange?,
) -> Bool {
apply(
(1).to_byte(),
self.data,
self.gpos.base,
self.coords,
self.gdef,
self.storage,
store,
feature_mask,
buffer,
buffer_range,
) ==
Some(true)
}
///|
/// Converts feature settings into a sorted collection of AAT selectors.
pub fn Engine::collect_selectors(
self : Engine,
features : ArrayView[@swash.FeatureSetting],
selectors : Array[(UInt16, UInt16)],
) -> Unit {
ignore(self)
selectors.clear()
for s in features.iter() {
match @internal.morx_feature_from_tag(s.tag) {
None => ()
Some((selector, (on, off))) => {
let setting = if s.value == (0).to_uint16() { off } else { on }
selectors.push((selector, setting))
}
}
}
selectors.sort_by_key(fn(p) {
let (a, b) = p
(a.to_int().reinterpret_as_uint() << 16) | b.to_int().reinterpret_as_uint()
})
}
///|
/// Applies the extended metamorphosis table.
pub fn Engine::morx(
self : Engine,
buffer : Buffer,
selectors : ArrayView[(UInt16, UInt16)],
) -> Unit {
if self.morx != 0U {
apply_morx(self.data, self.morx, buffer, selectors)
buffer.ensure_order(false)
}
}
///|
/// Applies the extended kerning table.
pub fn Engine::kerx(
self : Engine,
buffer : Buffer,
disable_kern : Bool,
) -> Unit {
if self.kerx != 0U {
apply_kerx(self.data, self.kerx, self.ankr, buffer, disable_kern)
buffer.ensure_order(false)
}
}
///|
/// Applies the kerning table.
pub fn Engine::kern(self : Engine, buffer : Buffer) -> Unit {
if self.kern != 0U {
apply_kern(self.data, self.kern, buffer)
}
}
///|
pub extend EngineMode with Eq::{not_equal, equal}
///|
pub extend PosMode with Eq::{not_equal, equal}
///|
pub extend SubMode with Eq::{not_equal, equal}