fontdue
fontdue copied to clipboard
Wrong glyphs returned for 0x80..0x100 character range
The following code uses fontdue to create a PNG showing a grid of all characters in a given font (assumed to be monospace) up through U+01FF:
use clap::Parser;
use font_kit::source::SystemSource;
use std::path::PathBuf;
const LINES: u32 = 0x200 / 16;
#[derive(Clone, Debug, Parser, PartialEq)]
struct Arguments {
#[arg(short, long, default_value = "charchart.png")]
outfile: PathBuf,
font: String,
size: f32,
}
fn main() -> anyhow::Result<()> {
let args = Arguments::parse();
let font = SystemSource::new()
.select_by_postscript_name(&args.font)?
.load()?;
let font = fontdue::Font::from_bytes(
&**font.copy_font_data().unwrap(),
fontdue::FontSettings::default(),
)
.map_err(anyhow::Error::msg)?;
let line_metrics = font.horizontal_line_metrics(args.size).unwrap();
let baseline_height = (-line_metrics.descent).max(0.0).round() as u32;
let char_height = line_metrics.new_line_size.round() as u32;
let char_width = font.rasterize('m', args.size).0.advance_width.round() as u32;
let mut img = image::GrayImage::new(char_width * 16, char_height * LINES);
for i in 0..LINES {
let y = (i + 1) * char_height - baseline_height;
for j in 0..16 {
let chr = char::try_from(i * 16 + j).unwrap();
let (metrics, data) = font.rasterize(chr, args.size);
let ystart = (y as i32) - metrics.ymin - (metrics.height as i32);
let xstart = ((j * char_width) as i32) + metrics.xmin;
for ry in 0..metrics.height {
let Ok(py) = u32::try_from(ystart + (ry as i32)) else {
continue;
};
if py >= img.height() {
continue;
}
for rx in 0..metrics.width {
let Ok(px) = u32::try_from(xstart + (rx as i32)) else {
continue;
};
if px >= img.width() {
continue;
}
let value = data[rx + ry * metrics.width];
img.put_pixel(px, py, image::Luma([value]));
}
}
}
}
img.save(args.outfile)?;
Ok(())
}
Dependencies:
[dependencies]
anyhow = "1.0.100"
clap = { version = "4.5.52", default-features = false, features = ["derive", "error-context", "help", "std", "suggestions", "usage", "wrap_help"] }
font-kit = "0.14.3"
fontdue = "0.9.3"
image = "0.25.9"
When doing cargo run -- Courier 12 on macOS Sonoma 14.7.8, the following is produced:
Observe that the characters in the eight rows after the ASCII characters do not match the characters in the Latin-1 Supplement block. This happens with other fonts as well (I've mainly tested Monaco and Menlo-Regular), not always with the same characters in the rendered result.
I'm assuming that fontdue is at fault here because using ab_glyph instead:
Rendering code using ab_glyph
use ab_glyph::{Font, ScaleFont};
use clap::Parser;
use font_kit::source::SystemSource;
use std::path::PathBuf;
const LINES: u32 = 0x200 / 16;
#[derive(Clone, Debug, Parser, PartialEq)]
struct Arguments {
#[arg(short, long, default_value = "charchart.png")]
outfile: PathBuf,
font: String,
size: f32,
}
fn main() -> anyhow::Result<()> {
let args = Arguments::parse();
let font = SystemSource::new()
.select_by_postscript_name(&args.font)?
.load()?;
let font_data = font.copy_font_data().unwrap();
let font = ab_glyph::FontRef::try_from_slice(&font_data)?;
let font = font.as_scaled(args.size);
let baseline_height = (-font.descent()).max(0.0).round() as u32;
let char_height = font.height().round() as u32;
let char_width = font.h_advance(font.glyph_id('m')).round() as u32;
let mut img = image::GrayImage::new(char_width * 16, char_height * LINES);
for i in 0..LINES {
let y = (i + 1) * char_height - baseline_height;
for j in 0..16 {
let chr = char::try_from(i * 16 + j).unwrap();
let Some(glyph) = font.outline_glyph(font.scaled_glyph(chr)) else {
continue;
};
let metrics = glyph.px_bounds();
let ystart = (y as i32) + (metrics.min.y.round() as i32);
let xstart = ((j * char_width) as i32) + (metrics.min.x.round() as i32);
glyph.draw(|dx, dy, coverage| {
let Ok(py) = u32::try_from(ystart + (dy as i32)) else {
return;
};
if py >= img.height() {
return;
}
let Ok(px) = u32::try_from(xstart + (dx as i32)) else {
return;
};
if px >= img.width() {
return;
}
let value = (255.0 * coverage).round() as u8;
img.put_pixel(px, py, image::Luma([value]));
});
}
}
img.save(args.outfile)?;
Ok(())
}
Dependencies:
[dependencies]
ab_glyph = "0.2.32"
anyhow = "1.0.100"
clap = { version = "4.5.52", default-features = false, features = ["derive", "error-context", "help", "std", "suggestions", "usage", "wrap_help"] }
font-kit = "0.14.3"
image = "0.25.9"
produces an image with the correct Latin-1 characters: