use std::{fs, iter};
use fdsm::{generate::generate_msdf, render::render_msdf, shape::Shape};
use image::{EncodableLayout, GrayImage, ImageBuffer, Pixel, PixelWithColorType, RgbImage};
use na::{Affine2, Similarity2, Vector2};
use skrifa::{FontRef, MetadataProvider, prelude::Size, setting::VariationSetting};
use crate::load_shape_from_face;
type Previewer<P> = fn(&ImageBuffer<P, Vec<<P as Pixel>::Subpixel>>, &mut GrayImage, f64);
fn save_bitmap_and_preview<P>(
prefix: &str,
name: &str,
suffix: &str,
image: &ImageBuffer<P, Vec<P::Subpixel>>,
px_range: f64,
previewer: Option<Previewer<P>>,
) where
[P::Subpixel]: EncodableLayout,
P: Pixel + PixelWithColorType,
{
fs::create_dir_all("output").unwrap();
image
.save(format!("output/{prefix}-{name}-{suffix}.png"))
.unwrap();
if let Some(previewer) = previewer {
let mut preview = GrayImage::new(image.width() * 10, image.height() * 10);
previewer(image, &mut preview, px_range);
preview
.save(format!("output/{prefix}-{name}-{suffix}-preview.png"))
.unwrap();
}
}
fn test_font_glyph(prefix: &str, face: &FontRef<'_>, ch: char, expected_error: f64) {
use image::{Rgb32FImage, RgbaImage, buffer::ConvertBuffer};
use fdsm::{
bezier::scanline::FillRule,
correct_error::{ErrorCorrectionConfig, correct_error_msdf},
generate::{generate_mtsdf, generate_sdf},
render::{correct_sign_msdf, correct_sign_mtsdf, render_sdf},
transform::Transform,
};
let glyph_id = face.charmap().map(ch).unwrap();
let name = face
.glyph_names()
.get(glyph_id)
.map(|name| name.as_str().into())
.unwrap_or_else(|| ch.to_string());
let axes = face.axes().location(iter::empty::<VariationSetting>());
let bbox = face
.glyph_metrics(Size::unscaled(), &axes)
.bounds(glyph_id)
.unwrap();
let (mut shape, _) = load_shape_from_face(face, glyph_id, &axes).expect("no shape?");
const RANGE: f64 = 4.0;
const SHRINKAGE: f64 = 16.0;
let transformation = na::convert::<_, Affine2<f64>>(Similarity2::new(
Vector2::new(
RANGE - bbox.x_min as f64 / SHRINKAGE,
RANGE - bbox.y_min as f64 / SHRINKAGE,
),
0.0,
1.0 / SHRINKAGE,
));
let width = ((bbox.x_max as f64 - bbox.x_min as f64) / SHRINKAGE + 2.0 * RANGE).ceil() as u32;
let height = ((bbox.y_max as f64 - bbox.y_min as f64) / SHRINKAGE + 2.0 * RANGE).ceil() as u32;
#[cfg(feature = "visualize")]
let orig_shape = shape.clone();
shape.transform(&transformation);
let colored_shape = Shape::edge_coloring_simple(shape.clone(), 0.03, 69441337420);
dbg!(&colored_shape);
let mut sdf = GrayImage::new(width, height);
let prepared_shape = shape.prepare();
generate_sdf(&prepared_shape, RANGE, &mut sdf);
save_bitmap_and_preview(prefix, &name, "sdf", &sdf, RANGE, Some(render_sdf));
let mut msdf = Rgb32FImage::new(width, height);
let prepared_colored_shape = colored_shape.prepare();
generate_msdf(&prepared_colored_shape, RANGE, &mut msdf);
correct_error_msdf(
&mut msdf,
&colored_shape,
&prepared_colored_shape,
RANGE,
&ErrorCorrectionConfig::default(),
);
correct_sign_msdf(&mut msdf, &prepared_colored_shape, FillRule::Nonzero);
let msdf = msdf.convert();
save_bitmap_and_preview(prefix, &name, "msdf", &msdf, RANGE, Some(render_msdf));
let mut mtsdf = RgbaImage::new(width, height);
let prepared_colored_shape = colored_shape.prepare();
generate_mtsdf(&prepared_colored_shape, RANGE, &mut mtsdf);
correct_sign_mtsdf(&mut mtsdf, &prepared_colored_shape, FillRule::Nonzero);
save_bitmap_and_preview(prefix, &name, "mtsdf", &mtsdf, RANGE, None);
#[cfg(feature = "visualize")]
{
use fdsm::visualize::generate_vis;
use na::Scale2;
let colored_shape = Shape::edge_coloring_simple(orig_shape, 0.03, 69441337420);
let mut vis = RgbImage::new(width * 10, height * 10);
let transformation =
na::convert::<_, Affine2<f64>>(Scale2::new(10.0, 10.0)) * transformation;
generate_vis(&colored_shape, &transformation, &mut vis);
save_bitmap_and_preview(prefix, &name, "voronoi", &vis, RANGE, None);
}
}
#[test]
fn test_glyphs_noto() {
let font = FontRef::from_index(notosans::REGULAR_TTF, 0).unwrap();
for c in 'A'..='Z' {
test_font_glyph("notosans", &font, c, 0.05);
}
}
#[test]
fn test_glyphs_inter() {
let font = FontRef::from_index(assets::INTER, 0).unwrap();
for c in 'A'..='Z' {
test_font_glyph("inter", &font, c, 0.05);
}
}
#[test]
fn test_glyphs_noto_serif_sinhala() {
let font = FontRef::from_index(assets::NOTO_SERIF_SINHALA, 0).unwrap();
for c in ['a', 'b', '+'] {
test_font_glyph("notoserif-sinhala", &font, c, 0.05);
}
}
mod assets {
pub static INTER: &[u8] = include_bytes!("../../assets/Inter-Regular.otf");
pub static NOTO_SERIF_SINHALA: &[u8] =
include_bytes!("../../assets/noto_serif_sinhala_regular.ttf");
}