use super::{Direction, ShapeContext, Shaper};
use crate::text::cluster::{CharCluster, Parser, Token};
use crate::text::{Codepoint as _, Language, Script};
use crate::{FontRef, Setting, Synthesis};
use core::iter::Copied;
use core::slice;
pub trait Selector {
type SelectedFont: SelectedFont;
fn select_font(&mut self, cluster: &mut CharCluster) -> Option<Self::SelectedFont>;
}
pub trait SelectedFont: PartialEq {
fn font(&self) -> FontRef;
fn id_override(&self) -> Option<[u64; 2]> {
None
}
fn synthesis(&self) -> Option<Synthesis> {
None
}
}
pub trait ShapeOptions {
type Features: Iterator<Item = Setting<u16>>;
type Variations: Iterator<Item = Setting<f32>>;
fn script(&self) -> Script {
Script::Latin
}
fn language(&self) -> Option<Language> {
None
}
fn direction(&self) -> Direction {
Direction::LeftToRight
}
fn size(&self) -> f32 {
0.
}
fn features(&self) -> Self::Features;
fn variations(&self) -> Self::Variations;
fn insert_dotted_circles(&self) -> bool {
false
}
}
#[derive(Copy, Clone)]
pub struct SimpleShapeOptions<'a> {
pub script: Script,
pub language: Option<Language>,
pub direction: Direction,
pub size: f32,
pub features: &'a [Setting<u16>],
pub variations: &'a [Setting<f32>],
pub insert_dotted_circles: bool,
}
impl Default for SimpleShapeOptions<'_> {
fn default() -> Self {
Self {
script: Script::Latin,
language: None,
direction: Direction::LeftToRight,
size: 0.,
features: &[],
variations: &[],
insert_dotted_circles: false,
}
}
}
impl<'a> ShapeOptions for SimpleShapeOptions<'a> {
type Features = Copied<slice::Iter<'a, Setting<u16>>>;
type Variations = Copied<slice::Iter<'a, Setting<f32>>>;
fn script(&self) -> Script {
self.script
}
fn language(&self) -> Option<Language> {
self.language
}
fn direction(&self) -> Direction {
self.direction
}
fn size(&self) -> f32 {
self.size
}
fn features(&self) -> Self::Features {
self.features.iter().copied()
}
fn variations(&self) -> Self::Variations {
self.variations.iter().copied()
}
fn insert_dotted_circles(&self) -> bool {
self.insert_dotted_circles
}
}
pub fn shape<S, T>(
context: &mut ShapeContext,
selector: &mut S,
options: &impl ShapeOptions,
tokens: T,
mut f: impl FnMut(&S::SelectedFont, Shaper),
) where
S: Selector,
T: IntoIterator<Item = Token>,
T::IntoIter: Clone,
{
let mut cluster = CharCluster::new();
if options.direction() == Direction::RightToLeft {
let mut parser = Parser::new(
options.script(),
tokens.into_iter().map(|mut t| {
t.ch = t.ch.mirror().unwrap_or(t.ch);
t
}),
);
if !parser.next(&mut cluster) {
return;
}
let mut font = selector.select_font(&mut cluster);
while shape_clusters(
context,
selector,
options,
&mut parser,
&mut cluster,
&mut font,
&mut f,
) {}
} else {
let mut parser = Parser::new(options.script(), tokens.into_iter());
if !parser.next(&mut cluster) {
return;
}
let mut font = selector.select_font(&mut cluster);
while shape_clusters(
context,
selector,
options,
&mut parser,
&mut cluster,
&mut font,
&mut f,
) {}
}
}
fn shape_clusters<S, T>(
context: &mut ShapeContext,
selector: &mut S,
options: &impl ShapeOptions,
parser: &mut Parser<T>,
cluster: &mut CharCluster,
current_font: &mut Option<S::SelectedFont>,
f: &mut impl FnMut(&S::SelectedFont, Shaper),
) -> bool
where
S: Selector,
T: Iterator<Item = Token> + Clone,
{
if current_font.is_none() {
return false;
}
let font = current_font.as_ref().unwrap();
let font_ref = font.font();
let id = font
.id_override()
.unwrap_or([font_ref.key.value(), u64::MAX]);
let mut shaper = context
.builder_with_id(font.font(), id)
.script(options.script())
.language(options.language())
.direction(options.direction())
.size(options.size())
.features(options.features())
.variations(
font.synthesis()
.as_ref()
.map(|s| s.variations())
.unwrap_or(&[])
.iter()
.copied(),
)
.variations(options.variations())
.insert_dotted_circles(options.insert_dotted_circles())
.build();
loop {
shaper.add_cluster(cluster);
if !parser.next(cluster) {
f(font, shaper);
return false;
}
if let Some(next_font) = selector.select_font(cluster) {
if next_font != *font {
f(font, shaper);
*current_font = Some(next_font);
return true;
}
} else {
return false;
}
}
}