refine spline parsing

This commit is contained in:
lauchmelder 2025-03-03 23:48:25 +01:00
parent e56b950cb2
commit 4ba1abe786
4 changed files with 180 additions and 52 deletions

View file

@ -17,7 +17,7 @@ use log::{debug, info};
use maximum_profile::MaximumProfile;
use table_directory::TableDirectory;
pub use glyph_data::{GlyphData, GlyphHeader, GlyphPoint, Glyph, SimpleGlyph};
pub use glyph_data::{GlyphData, GlyphHeader, GlyphPoint, Glyph, SimpleGlyph, SplineElement};
fn deserialize<R: Read, T: serde::de::DeserializeOwned>(reader: R) -> bincode::Result<T> {
bincode::options().with_big_endian().with_fixint_encoding().deserialize_from::<R, T>(reader)

View file

@ -2,7 +2,7 @@ use std::{collections::HashMap, fs::File, io::{BufReader, Read, Seek, SeekFrom},
use bincode::{ErrorKind, Result};
use bitfield::bitfield;
use log::{debug, info, warn};
use log::{debug, error, info, warn};
use serde::Deserialize;
use super::{deserialize, deserialize_vec, index_to_location::IndexToLocation, table_directory::{FontTable, TableDirectoryRecord}};
@ -252,6 +252,108 @@ pub struct Glyph {
pub data: GlyphData
}
#[derive(Debug)]
pub enum SplineElement {
Line(GlyphPoint, GlyphPoint),
Bezier(GlyphPoint, GlyphPoint, GlyphPoint)
}
pub struct SplineIter<'a> {
points: Box<dyn Iterator<Item = GlyphPoint> + 'a>,
last_point: Option<GlyphPoint>,
final_control: Option<GlyphPoint>,
first_point: Option<GlyphPoint>
}
impl<'a> SplineIter<'a> {
fn handle_spline_start(&mut self) -> Result<()> {
let Some(point) = self.points.next() else {
return Err(ErrorKind::Custom("Not enough points in contous to form a spline".into()).into());
};
if point.on_curve {
self.last_point = Some(point);
self.first_point = Some(point);
return Ok(());
} else {
self.final_control = Some(point);
}
let Some(point) = self.points.next() else {
return Err(ErrorKind::Custom("Not enough points in contous to form a spline".into()).into());
};
if !point.on_curve {
return Err(ErrorKind::Custom("Iterator returned invalid point sequence".into()).into());
}
self.last_point = Some(point);
self.first_point = Some(point);
Ok(())
}
fn handle_spline_close(&mut self) -> Option<SplineElement> {
let Some(last_point) = self.last_point else {
return None;
};
if let Some(final_control) = self.final_control {
Some(SplineElement::Bezier(last_point, final_control, self.first_point.unwrap()))
} else {
Some(SplineElement::Line(last_point, self.first_point.unwrap()))
}
}
}
impl<'a> Iterator for SplineIter<'a> {
type Item = SplineElement;
fn next(&mut self) -> Option<Self::Item> {
let Some(last_point) = self.last_point else {
debug!("spline iter: last point is None");
return None;
};
if last_point.is_endpoint {
debug!("spline iter: point is an endpoint of contour. closing spline, starting new one (if possible)");
let result = self.handle_spline_close();
if let Err(err) = self.handle_spline_start() {
debug!("spline iter: no more contours");
self.last_point = None;
}
return result;
}
let Some(point) = self.points.next() else {
self.last_point = None;
debug!("spline iter: no more points, closing spline");
return self.handle_spline_close();
};
if point.on_curve {
self.last_point = Some(point);
debug!("spline iter: point is on curve");
return Some(SplineElement::Line(last_point, point));
}
debug!("spline iter: point is control point");
let Some(next_point) = self.points.next() else {
debug!("spline iter: no more data after control point, closing spline");
self.last_point = None;
return self.handle_spline_close();
};
debug!("spline iter: returning bezier");
self.last_point = Some(next_point);
Some(SplineElement::Bezier(last_point, point, next_point))
}
}
impl Glyph {
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = GlyphPoint> + 'a> {
Box::new(
@ -261,6 +363,19 @@ impl Glyph {
}
)
}
pub fn splines<'a>(&'a self) -> Result<SplineIter<'a>> {
let mut splines = SplineIter {
points: self.iter(),
last_point: None,
final_control: None,
first_point: None
};
splines.handle_spline_start()?;
Ok(splines)
}
}
#[derive(Debug)]

View file

@ -1,18 +1,22 @@
mod svg;
use crate::font::{GlyphHeader, GlyphPoint, Glyph};
use crate::font::{Glyph, GlyphHeader, GlyphPoint, SplineElement};
use log::debug;
pub use svg::SvgWriter;
pub trait Visitor {
fn write_prefix(&mut self, header: &GlyphHeader);
fn write_point(&mut self, point: &GlyphPoint);
fn write_point(&mut self, point: &SplineElement);
fn write_suffix(&mut self);
fn write(&mut self, glyph: &Glyph) {
self.write_prefix(&glyph.header);
glyph.iter().for_each(|point| self.write_point(&point));
glyph.splines().unwrap().for_each(|spline_element| {
debug!("{spline_element:#?}");
self.write_point(&spline_element)
});
self.write_suffix();
}

View file

@ -1,25 +1,48 @@
use std::{fs::File, io::{BufWriter, Write}};
use crate::font::{GlyphHeader, GlyphPoint};
use crate::font::{GlyphHeader, GlyphPoint, SplineElement};
use super::Visitor;
pub struct SvgWriter {
first_point: bool,
last_on_curve: bool,
final_point: Option<(i32, i32)>,
final_control: Option<(i32, i32)>,
writer: BufWriter<File>
writer: BufWriter<File>,
points: Vec<(i32, i32)>,
control_points: Vec<(i32, i32)>,
virtual_points: Vec<(i32, i32)>
}
impl SvgWriter {
pub fn new(file: File) -> Self {
SvgWriter {
first_point: true,
last_on_curve: false,
final_control: None,
final_point: None,
writer: BufWriter::new(file)
writer: BufWriter::new(file),
points: vec![],
control_points: vec![],
virtual_points: vec![]
}
}
}
impl SvgWriter {
fn handle_start_point(&mut self, point: &GlyphPoint) {
if self.first_point {
write!(self.writer, "M{} {} ", point.x, -point.y);
self.first_point = false;
}
}
fn handle_end_point(&mut self, point: &GlyphPoint) {
if point.is_endpoint {
self.first_point = true;
}
}
fn write_points(writer: &mut BufWriter<File>, points: &Vec<(i32, i32)>, color: &'static str) {
for (x, y) in points {
write!(writer, "<circle cx=\"{x}\" cy=\"{y}\" r=\"10\" fill=\"{color}\" />");
}
}
}
@ -35,56 +58,42 @@ impl Visitor for SvgWriter {
);
}
fn write_point(&mut self, point: &GlyphPoint) {
if self.final_point.is_none() && point.on_curve {
self.final_point = Some((point.x, point.y));
}
fn write_point(&mut self, point: &SplineElement) {
match point {
SplineElement::Line(start, end) => {
self.handle_start_point(start);
write!(self.writer, "L{} {} ", end.x, -end.y);
if self.first_point && self.final_control.is_none() && !point.on_curve {
self.final_control = Some((point.x, point.y));
}
self.points.push((start.x, -start.y));
self.handle_end_point(start);
},
if self.first_point {
if !point.on_curve {
return;
}
SplineElement::Bezier(start, control, end) => {
self.handle_start_point(start);
write!(self.writer, "Q{} {} {} {} ", control.x, -control.y, end.x, -end.y);
write!(self.writer, "M{} {} ", point.x, -point.y);
self.first_point = false;
self.last_on_curve = true;
return;
}
let prefix = if point.on_curve { if self.last_on_curve { "L" } else { "" } } else { "Q" };
self.last_on_curve = point.on_curve;
write!(
self.writer,
"{prefix}{} {} ",
point.x, -point.y
);
if point.is_endpoint {
if let Some(final_control) = self.final_control {
if let Some(final_point) = self.final_point {
write!(self.writer, "Q{} {} {} {} ", final_control.0, -final_control.1, final_point.0, -final_point.1);
if start.is_virtual {
self.virtual_points.push((start.x, -start.y));
} else {
write!(self.writer, "Z ");
}
} else {
write!(self.writer, "Z ");
self.points.push((start.x, -start.y));
}
self.final_point = None;
self.final_control = None;
self.control_points.push((control.x, -control.y));
self.handle_end_point(start);
}
}
self.first_point = point.is_endpoint;
}
fn write_suffix(&mut self) {
write!(
self.writer,
"\" style=\"fill:none; stroke:black; stroke-width:3;\" /></svg>"
"\" style=\"fill:none; stroke:black; stroke-width:3;\" />"
);
SvgWriter::write_points(&mut self.writer, &self.points, "red");
SvgWriter::write_points(&mut self.writer, &self.control_points, "blue");
SvgWriter::write_points(&mut self.writer, &self.virtual_points, "green");
write!(self.writer, "</svg>");
}
}