panic (index out of bounds) for specific character in a specific font when rasterizing
tigregalis opened this issue · 5 comments
As per the title.
For the font "Airstrip Four", the 'v' character appears to not have a bounding box, so when the rasterizer runs, it tries to index into the Vec but it is out of bounds.
In more detail, I've done some research and did a write-up here: bevyengine/bevy#1254
I cloned this repo, made a small change to the image
example (below) to accept text as a second parameter, and downloaded airstrip.ttf into dev/fonts, and run cargo run --example image -- "dev/fonts/airstrip.ttf" "v"
. I put some dbg!()
s in places, and the output is below:
Finished dev [unoptimized + debuginfo] target(s) in
1.48s
Running `target\debug\examples\image.exe dev/fonts/airstrip.ttf v`
Using font: airstrip.ttf
[glyph\src\ttfp.rs:297] x_min = 0
[glyph\src\ttfp.rs:297] y_min = 0
[glyph\src\ttfp.rs:297] x_max = 0
[glyph\src\ttfp.rs:297] y_max = 0
[glyph\src\outlined.rs:36] &a = Rect {
min: point(20.0, 56.0),
max: point(20.0, 57.0),
}
[glyph\src\outlined.rs:101] h_factor = 0.019667832
[glyph\src\outlined.rs:101] v_factor = -0.019667832
[glyph\src\outlined.rs:101] offset = point(0.0, 0.46416092)
[glyph\src\outlined.rs:101] w = 0
[glyph\src\outlined.rs:101] h = 1
thread 'main' panicked at 'index out of bounds: the len
is 4 but the index is 9', rasterizer\src\raster.rs:128:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\examples\image.exe dev/fonts/airstrip.ttf v` (exit code:
101)
//! Draws text into `image_example.png`.
//!
//! Use a custom font file: `cargo run --example image /path/to/font.otf`
use ab_glyph::{point, Font, FontRef, FontVec, PxScale, ScaleFont};
use image::{DynamicImage, Rgba};
const TEXT: &str = "This is ab_glyph rendered into a png!";
fn main() {
let maybe_text = std::env::args().nth(2);
if let Some(font_path) = std::env::args().nth(1) {
let font_path = std::env::current_dir().unwrap().join(font_path);
let data = std::fs::read(&font_path).unwrap();
let font = FontVec::try_from_vec(data).unwrap_or_else(|_| {
panic!(format!(
"error constructing a Font from data at {:?}",
font_path
));
});
if let Some(name) = font_path.file_name().and_then(|n| n.to_str()) {
eprintln!("Using font: {}", name);
}
draw_image(font, maybe_text);
} else {
eprintln!("No font specified ... using OpenSans-Italic.ttf");
let font = FontRef::try_from_slice(include_bytes!("../fonts/OpenSans-Italic.ttf")).unwrap();
draw_image(font, maybe_text);
};
}
fn draw_image<F: Font>(font: F, maybe_text: Option<String>) {
// The font size to use
let scale = PxScale::from(45.0);
let scaled_font = font.as_scaled(scale);
let mut glyphs = Vec::new();
dev::layout_paragraph(
scaled_font,
point(20.0, 20.0),
9999.0,
maybe_text.as_deref().unwrap_or(TEXT),
&mut glyphs,
);
// Use a dark red colour
let colour = (150, 0, 0);
// work out the layout size
let glyphs_height = scaled_font.height().ceil() as u32;
let glyphs_width = {
let min_x = glyphs.first().unwrap().position.x;
let last_glyph = glyphs.last().unwrap();
let max_x = last_glyph.position.x + scaled_font.h_advance(last_glyph.id);
(max_x - min_x).ceil() as u32
};
// Create a new rgba image with some padding
let mut image = DynamicImage::new_rgba8(glyphs_width + 40, glyphs_height + 40).to_rgba();
// Loop through the glyphs in the text, positing each one on a line
for glyph in glyphs {
if let Some(outlined) = scaled_font.outline_glyph(glyph) {
let bounds = outlined.px_bounds();
// Draw the glyph into the image per-pixel by using the draw closure
outlined.draw(|x, y, v| {
// Offset the position by the glyph bounding box
let px = image.get_pixel_mut(x + bounds.min.x as u32, y + bounds.min.y as u32);
// Turn the coverage into an alpha value (blended with any previous)
*px = Rgba([
colour.0,
colour.1,
colour.2,
px.0[3].saturating_add((v * 255.0) as u8),
]);
});
}
}
// Save the image to a png file
image.save("image_example.png").unwrap();
println!("Generated: image_example.png");
}
It seems ttf-parser is returning a zero bounding box for this glyph. The rasterizer fails as it can't draw the outline within the incorrect bounds. I've raised an issue upstream: RazrFalcon/ttf-parser#49
As I'm not 100% sure if this will be fixed upstream, I'll queue up a workaround fix in the meantime (#30).
So this should be fixed in the next release that I'll publish at the end of the week.
As I'm not 100% sure if this will be fixed upstream, I'll queue up a workaround fix in the meantime (#30).
So this should be fixed in the next release that I'll publish at the end of the week.
Great, thanks for this. What approach did you take, out of curiosity?
If it did need to be calculated from the curves, I was doing some searching and found this: http://pomax.nihongoresources.com/pages/bezier/
Great, thanks for this. What approach did you take, out of curiosity?
If it did need to be calculated from the curves, I was doing some searching and found this: http://pomax.nihongoresources.com/pages/bezier/
Have a look at bbox.rs in the pr. It's just a simple bounding box covering all points, including control points, rather than a tighter calculation of curve bounds.
I did have a quick look at the maths, but decided not to bother as this is a bug-workaround rather than core functionality. I'm also not sure how often we'll see this specific ttf issue.
I've published 0.2.8
with the bbox fallback