use dxpdf::render::shape::{needs_shaping, RunDirection, Shaper};
use skia_safe::{Font, FontMgr, GlyphId, Typeface};
const BEH: char = '\u{0628}';
fn joining_face() -> Typeface {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/test-files/fonts/DxJoining.ttf"
);
let bytes = std::fs::read(path)
.unwrap_or_else(|e| panic!("{path} is missing ({e}) — run scripts/make_font_fixtures.py"));
FontMgr::new()
.new_from_data(&bytes, 0)
.expect("DxJoining.ttf is a valid SFNT")
}
fn cmap_glyphs(face: &Typeface, text: &str) -> Vec<GlyphId> {
Font::from_typeface(face.clone(), 24.0).text_to_glyphs_vec(text)
}
#[test]
fn cmap_alone_paints_one_letter_three_times() {
let face = joining_face();
let word: String = std::iter::repeat_n(BEH, 3).collect();
let ids = cmap_glyphs(&face, &word);
assert_eq!(ids.len(), 3);
assert_eq!(
ids[0], ids[1],
"a cmap lookup cannot see context, so every beh is the same glyph"
);
assert_eq!(ids[1], ids[2]);
}
#[test]
fn shaping_substitutes_a_positional_form_for_each_letter() {
let face = joining_face();
let shaper = Shaper::new().expect("skia exposes a HarfBuzz shaper");
let word: String = std::iter::repeat_n(BEH, 3).collect();
let run = shaper
.shape(&face, &word, 24.0, RunDirection::RightToLeft)
.expect("three letters shape to three glyphs");
let ids: Vec<GlyphId> = run.glyphs.iter().map(|g| g.id).collect();
assert_eq!(ids.len(), 3, "no ligature is defined; one glyph per letter");
assert_eq!(
ids.iter().collect::<std::collections::HashSet<_>>().len(),
3,
"initial, medial and final are three different glyphs: {ids:?}",
);
assert!(
!ids.contains(&cmap_glyphs(&face, "\u{0628}")[0]),
"none of them is the isolated form the cmap would have given",
);
}
#[test]
fn the_shaped_advance_differs_from_the_cmap_advance() {
let face = joining_face();
let shaper = Shaper::new().expect("shaper");
let word: String = std::iter::repeat_n(BEH, 3).collect();
let font = Font::from_typeface(face.clone(), 24.0);
let cmap_width = font.measure_str(&word, None).0;
let shaped = shaper
.shape(&face, &word, 24.0, RunDirection::RightToLeft)
.expect("shape")
.total_advance;
assert!((cmap_width - 36.0).abs() < 0.01, "cmap: {cmap_width}");
assert!(
(f32::from(shaped) - 50.4).abs() <= 1.5,
"shaped: {shaped:?} — expected ≈50.4pt from the positional forms",
);
assert!(
f32::from(shaped) > cmap_width,
"the shaped advance must be the one layout uses: {shaped:?} vs {cmap_width}",
);
}
#[test]
fn one_letter_alone_keeps_its_isolated_form() {
let face = joining_face();
let shaper = Shaper::new().expect("shaper");
let run = shaper
.shape(&face, "\u{0628}", 24.0, RunDirection::RightToLeft)
.expect("shape");
assert_eq!(run.glyphs.len(), 1);
assert_eq!(
run.glyphs[0].id,
cmap_glyphs(&face, "\u{0628}")[0],
"an isolated letter shapes to what the cmap already said",
);
}
#[test]
fn the_fixture_text_is_what_the_predicate_selects() {
assert!(needs_shaping(
&std::iter::repeat_n(BEH, 3).collect::<String>()
));
assert!(!needs_shaping("Nicht gefunden"));
}