Skip to content

Commit 2dda798

Browse files
committed
[path] more space efficient display implementation for Path
1 parent 65b174c commit 2dda798

File tree

2 files changed

+133
-60
lines changed

2 files changed

+133
-60
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rasterize"
3-
version = "0.5.1"
3+
version = "0.5.2"
44
authors = ["Pavel Aslanov <asl.pavel@gmail.com>"]
55
description = "Simple and small 2D rendering library"
66
edition = "2021"

src/path.rs

Lines changed: 132 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -238,33 +238,6 @@ impl fmt::Debug for Path {
238238
}
239239
}
240240

241-
impl fmt::Display for Path {
242-
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
243-
struct FormatterWrite<'a, 'b> {
244-
fmt: &'a mut fmt::Formatter<'b>,
245-
}
246-
247-
impl std::io::Write for FormatterWrite<'_, '_> {
248-
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
249-
self.fmt
250-
.write_str(
251-
std::str::from_utf8(buf)
252-
.expect("Path generated non utf8 svg representation"),
253-
)
254-
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
255-
Ok(buf.len())
256-
}
257-
258-
fn flush(&mut self) -> std::io::Result<()> {
259-
Ok(())
260-
}
261-
}
262-
263-
self.write_svg_path(FormatterWrite { fmt })
264-
.map_err(|_| std::fmt::Error)
265-
}
266-
}
267-
268241
impl Path {
269242
pub fn new(segments: Vec<Segment>, subpaths: Vec<usize>, closed: Vec<bool>) -> Self {
270243
Self {
@@ -554,38 +527,7 @@ impl Path {
554527

555528
/// Save path in SVG path format.
556529
pub fn write_svg_path(&self, mut out: impl Write) -> std::io::Result<()> {
557-
for subpath in self.subpaths() {
558-
write!(out, "M{:?} ", subpath.start())?;
559-
let mut segment_type: Option<u8> = None;
560-
for segment in subpath.segments().iter() {
561-
match segment {
562-
Segment::Line(line) => {
563-
if segment_type.replace(b'L') != Some(b'L') {
564-
out.write_all(b"L")?;
565-
}
566-
write!(out, "{:?} ", line.end())?;
567-
}
568-
Segment::Quad(quad) => {
569-
let [_, p1, p2] = quad.points();
570-
if segment_type.replace(b'Q') != Some(b'Q') {
571-
out.write_all(b"Q")?;
572-
}
573-
write!(out, "{:?} {:?} ", p1, p2)?;
574-
}
575-
Segment::Cubic(cubic) => {
576-
let [_, p1, p2, p3] = cubic.points();
577-
if segment_type.replace(b'C') != Some(b'C') {
578-
out.write_all(b"C")?;
579-
}
580-
write!(out, "{:?} {:?} {:?} ", p1, p2, p3)?;
581-
}
582-
}
583-
}
584-
if subpath.is_closed() {
585-
out.write_all(b"Z")?;
586-
}
587-
}
588-
Ok(())
530+
write!(out, "{}", self)
589531
}
590532

591533
/// Load path from SVG path representation
@@ -597,12 +539,26 @@ impl Path {
597539
Ok(builder.build())
598540
}
599541

542+
pub fn display(&self, relative: bool, tr: Transform) -> PathSvgDisplay<'_> {
543+
PathSvgDisplay {
544+
path: self,
545+
relative,
546+
tr,
547+
}
548+
}
549+
600550
/// Returns struct that prints command per line on debug formatting.
601551
pub fn verbose_debug(&self) -> PathVerboseDebug<'_> {
602552
PathVerboseDebug { path: self }
603553
}
604554
}
605555

556+
impl fmt::Display for Path {
557+
fn fmt(&self, out: &mut fmt::Formatter<'_>) -> fmt::Result {
558+
write!(out, "{}", self.display(false, Transform::identity()))
559+
}
560+
}
561+
606562
pub struct PathVerboseDebug<'a> {
607563
path: &'a Path,
608564
}
@@ -620,6 +576,106 @@ impl fmt::Debug for PathVerboseDebug<'_> {
620576
}
621577
}
622578

579+
pub struct PathSvgDisplay<'a> {
580+
path: &'a Path,
581+
relative: bool,
582+
tr: Transform,
583+
}
584+
585+
impl PathSvgDisplay<'_> {
586+
// round floating point to only contain specified amount of digits
587+
fn round_significant(x: f64, digits: usize) -> f64 {
588+
if x == 0. || digits == 0 {
589+
0.
590+
} else {
591+
let shift = digits as i32 - x.abs().log10().ceil() as i32;
592+
let shift_factor = 10_f64.powi(shift);
593+
594+
(x * shift_factor).round() / shift_factor
595+
}
596+
}
597+
598+
fn fmt_point(
599+
&self,
600+
out: &mut fmt::Formatter<'_>,
601+
point: Point,
602+
previous: Option<Point>,
603+
precision: usize,
604+
sep: bool,
605+
) -> Result<Point, fmt::Error> {
606+
let point_tr = self.tr.apply(point);
607+
let point = previous.map_or_else(|| point_tr, |point_prev| point_tr - point_prev);
608+
609+
let x = Self::round_significant(point.x(), precision);
610+
let y = Self::round_significant(point.y(), precision);
611+
let eps = 10_f64.powi(-(precision as i32));
612+
if sep && x >= 0.0 {
613+
write!(out, " ")?;
614+
}
615+
if x.abs() < eps {
616+
write!(out, "0")?;
617+
} else {
618+
write!(out, "{x}")?;
619+
}
620+
if y >= 0.0 {
621+
write!(out, ",")?;
622+
}
623+
if y.abs() < eps {
624+
write!(out, "0")?;
625+
} else {
626+
write!(out, "{y}")?;
627+
}
628+
Ok(point_tr)
629+
}
630+
}
631+
632+
impl fmt::Display for PathSvgDisplay<'_> {
633+
fn fmt(&self, out: &mut fmt::Formatter<'_>) -> fmt::Result {
634+
let precision = out.precision().unwrap_or(4);
635+
let mut previous: Option<Point> = None;
636+
for subpath in self.path.subpaths() {
637+
out.write_str(if self.relative && previous.is_some() {
638+
"m"
639+
} else {
640+
"M"
641+
})?;
642+
let point_last = self.fmt_point(out, subpath.start(), previous, precision, false)?;
643+
if self.relative {
644+
previous = Some(point_last);
645+
}
646+
647+
for segment in subpath.segments().iter() {
648+
let point_last = match segment {
649+
Segment::Line(line) => {
650+
out.write_str(if self.relative { "l" } else { "L" })?;
651+
self.fmt_point(out, line.end(), previous, precision, false)?
652+
}
653+
Segment::Quad(quad) => {
654+
out.write_str(if self.relative { "q" } else { "Q" })?;
655+
let [_, p1, p2] = quad.points();
656+
self.fmt_point(out, p1, previous, precision, false)?;
657+
self.fmt_point(out, p2, previous, precision, true)?
658+
}
659+
Segment::Cubic(cubic) => {
660+
out.write_str(if self.relative { "c" } else { "C" })?;
661+
let [_, p1, p2, p3] = cubic.points();
662+
self.fmt_point(out, p1, previous, precision, false)?;
663+
self.fmt_point(out, p2, previous, precision, true)?;
664+
self.fmt_point(out, p3, previous, precision, true)?
665+
}
666+
};
667+
if self.relative {
668+
previous = Some(point_last);
669+
}
670+
}
671+
if subpath.is_closed() {
672+
write!(out, "Z")?;
673+
}
674+
}
675+
Ok(())
676+
}
677+
}
678+
623679
pub struct PathIter<'a> {
624680
path: &'a Path,
625681
index: usize,
@@ -1250,4 +1306,21 @@ mod tests {
12501306
assert_path_eq(&path_reference, &path);
12511307
Ok(())
12521308
}
1309+
1310+
#[test]
1311+
fn test_display() -> Result<(), SvgParserError> {
1312+
let path: Path = SQUIRREL.parse()?;
1313+
1314+
let path_display = format!("{}", path);
1315+
assert_path_eq(&path, &path_display.parse()?);
1316+
let path_reference = "M12,1C9.79,1 8,2.31 8,3.92C8,5.86 8.5,6.95 8,10C8,5.5 5.23,3.66 4,3.66C4.05,3.16 3.52,3 3.52,3C3.52,3 3.3,3.11 3.22,3.34C2.95,3.03 2.66,3.07 2.66,3.07L2.53,3.65C2.53,3.65 0.7,4.29 0.68,6.87C0.88,7.2 2.21,7.47 3.15,7.3C4.04,7.35 3.82,8.09 3.62,8.29C2.78,9.13 2,8 1,8C0,8 0,9 1,9C2,9 2,10 4,10C0.91,11.2 4,14 4,14L3,14C2,14 2,15 2,15L8,15C11,15 13,14 13,11.53C13,10.68 12.57,9.74 12,9C10.89,7.54 12.23,6.32 13,7C13.77,7.68 16,8 16,5C16,2.79 14.21,1 12,1ZM2.5,6C2.22,6 2,5.78 2,5.5C2,5.22 2.22,5 2.5,5C2.78,5 3,5.22 3,5.5C3,5.78 2.78,6 2.5,6Z";
1317+
assert_eq!(path_reference, path_display.as_str());
1318+
1319+
let path_relative = format!("{}", path.display(true, Transform::identity()));
1320+
assert_path_eq(&path, &path_relative.parse()?);
1321+
let path_reference = "M12,1c-2.21,0-4,1.31-4,2.92c0,1.94 0.5,3.03 0,6.08c0-4.5-2.77-6.34-4-6.34c0.05-0.5-0.48-0.66-0.48-0.66c0,0-0.22,0.11-0.3,0.34c-0.27-0.31-0.56-0.27-0.56-0.27l-0.13,0.58c0,0-1.83,0.64-1.85,3.22c0.2,0.33 1.53,0.6 2.47,0.43c0.89,0.05 0.67,0.79 0.47,0.99c-0.84,0.84-1.62-0.29-2.62-0.29c-1,0-1,1 0,1c1,0 1,1 3,1c-3.09,1.2 0,4 0,4l-1,0c-1,0-1,1-1,1l6,0c3,0 5-1 5-3.47c0-0.85-0.43-1.79-1-2.53c-1.11-1.46 0.23-2.68 1-2c0.77,0.68 3,1 3-2c0-2.21-1.79-4-4-4Zm-9.5,5c-0.28,0-0.5-0.22-0.5-0.5c0-0.28 0.22-0.5 0.5-0.5c0.28,0 0.5,0.22 0.5,0.5c0,0.28-0.22,0.5-0.5,0.5Z";
1322+
assert_eq!(path_reference, path_relative.as_str());
1323+
1324+
Ok(())
1325+
}
12531326
}

0 commit comments

Comments
 (0)