blob: 3488a29adf606f8806a601bae165c2982f506394 [file]
/*
* fontconfig/fc-fontations/instance_enumerate.rs
*
* Copyright 2025 Google LLC.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of the author(s) not be used in
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. The authors make no
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
use crate::InstanceMode::{self, Named};
use read_fonts::{
FileRef::{self, Collection, Font},
FontRef,
};
use skrifa::MetadataProvider;
pub fn fonts_and_indices(
file_ref: Option<FileRef>,
) -> impl Iterator<Item = (FontRef, Option<i32>)> {
let (iter_one, iter_two) = match file_ref {
Some(Font(font)) => (Some((Ok(font.clone()), None)), None),
Some(Collection(collection)) => (
None,
Some(
collection
.iter()
.enumerate()
.map(|entry| (entry.1, Some(entry.0 as i32))),
),
),
None => (None, None),
};
iter_two
.into_iter()
.flatten()
.chain(iter_one)
.filter_map(|(font_result, index)| {
if let Ok(font) = font_result {
return Some((font, index));
}
None
})
}
// Produces an iterator over the named instances of the font.
#[allow(unused)]
pub fn enumerate_named_instances<'a>(f: &'a FontRef) -> impl Iterator<Item = InstanceMode> + 'a {
let default_coords = f.axes().iter().map(|axis| axis.default_value());
f.named_instances()
.iter()
.enumerate()
.filter_map(move |(i, instance)| {
let user_coords = instance.user_coords();
if user_coords.eq(default_coords.clone()) {
None
} else {
Some(Named(i as i32))
}
})
}
/// Used for producing the expected set of patterns out of variable fonts.
/// Produces an iterator over
/// * the default instance,
/// * the named instances, and the
/// * variable instance.
#[allow(unused)]
pub fn all_instances<'a>(f: &'a FontRef) -> Box<dyn Iterator<Item = InstanceMode> + 'a> {
if f.axes().is_empty() {
Box::new(std::iter::once(InstanceMode::Default))
} else {
Box::new(
std::iter::once(InstanceMode::Default)
.chain(enumerate_named_instances(f))
.chain(std::iter::once(InstanceMode::Variable)),
)
}
}
#[cfg(test)]
mod test {
use crate::{
instance_enumerate::{all_instances, fonts_and_indices},
InstanceMode,
};
use read_fonts::FileRef;
use std::path::PathBuf;
fn testfontdir() -> PathBuf {
let default_build_dir = std::env::current_dir().unwrap().join("build");
let build_dir = std::env::var("builddir").unwrap_or_else(|_| {
println!("builddir env var not set, using current directory + \"build/\"");
default_build_dir.to_string_lossy().to_string()
});
PathBuf::from(build_dir)
.join("testfonts")
.join("roboto-flex-fonts")
.join("fonts")
.join("variable")
}
fn variable_collection_testfont() -> PathBuf {
testfontdir().join(
"RobotoFlex[GRAD,XOPQ,XTRA,YOPQ,YTAS,YTDE,YTFI,YTLC,YTUC,opsz,slnt,wdth,wght].ttf",
)
}
const EXPECTED_INSTANCES: [InstanceMode; 21] = [
InstanceMode::Default,
InstanceMode::Named(0),
InstanceMode::Named(1),
InstanceMode::Named(2),
InstanceMode::Named(4),
InstanceMode::Named(5),
InstanceMode::Named(6),
InstanceMode::Named(7),
InstanceMode::Named(8),
InstanceMode::Named(9),
InstanceMode::Named(10),
InstanceMode::Named(11),
InstanceMode::Named(12),
InstanceMode::Named(13),
InstanceMode::Named(14),
InstanceMode::Named(15),
InstanceMode::Named(16),
InstanceMode::Named(17),
InstanceMode::Named(18),
InstanceMode::Named(19),
InstanceMode::Variable,
];
#[test]
#[cfg_attr(skip_external_fonts, ignore)]
fn test_instance_iterator() {
let bytes = std::fs::read(variable_collection_testfont())
.expect("Could not read test font file");
let fileref = FileRef::new(&bytes).ok();
let fonts = fonts_and_indices(fileref);
for (font, _ttc_index) in fonts {
all_instances(&font)
.zip(EXPECTED_INSTANCES.iter())
.for_each(|(instance, expected_instance)| {
assert_eq!(instance, *expected_instance);
})
}
}
}