mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-14 22:42:46 +00:00
Add strict bounded IDML path geometry reader
This commit is contained in:
2 files changed
+53
-1
No files matched your search
@@ -5,6 +5,19 @@ use craft_xml::{El, Local};
|
||||
|
||||
use crate::value::{nums, point_str};
|
||||
|
||||
/// Why a bounded path geometry was refused.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum PathGeometryError {
|
||||
#[error("too many points")]
|
||||
TooManyPoints,
|
||||
#[error("point without an anchor")]
|
||||
MissingAnchor,
|
||||
#[error("malformed point")]
|
||||
MalformedPoint,
|
||||
#[error("non-finite point")]
|
||||
NonFinitePoint,
|
||||
}
|
||||
|
||||
/// Most points one geometry keeps by default; points past it are dropped.
|
||||
pub const MAX_PATH_POINTS: usize = 1_000_000;
|
||||
|
||||
@@ -84,6 +97,45 @@ impl PathGeometry {
|
||||
PathGeometry { paths }
|
||||
}
|
||||
|
||||
/// Parse a path geometry without silently discarding malformed points, keeping at most
|
||||
/// `max` point elements. Missing direction handles default to the anchor.
|
||||
pub fn try_from_el_capped(pg: &El, max: usize) -> Result<PathGeometry, PathGeometryError> {
|
||||
let point = |value: Option<&str>, required: bool| -> Result<Option<[f64; 2]>, PathGeometryError> {
|
||||
let Some(value) = value else {
|
||||
return if required { Err(PathGeometryError::MissingAnchor) } else { Ok(None) };
|
||||
};
|
||||
let mut nums = value.split_whitespace();
|
||||
let Some(x) = nums.next().and_then(|n| n.parse::<f64>().ok()) else { return Err(PathGeometryError::MalformedPoint) };
|
||||
let Some(y) = nums.next().and_then(|n| n.parse::<f64>().ok()) else { return Err(PathGeometryError::MalformedPoint) };
|
||||
if nums.next().is_some() {
|
||||
return Err(PathGeometryError::MalformedPoint);
|
||||
}
|
||||
if !(x.is_finite() && y.is_finite()) {
|
||||
return Err(PathGeometryError::NonFinitePoint);
|
||||
}
|
||||
Ok(Some([x, y]))
|
||||
};
|
||||
let mut count = 0usize;
|
||||
let mut paths = Vec::new();
|
||||
for geometry in pg.children(Local("GeometryPathType")) {
|
||||
let mut points = Vec::new();
|
||||
if let Some(array) = geometry.child(Local("PathPointArray")) {
|
||||
for el in array.children(Local("PathPointType")) {
|
||||
count = count.saturating_add(1);
|
||||
if count > max {
|
||||
return Err(PathGeometryError::TooManyPoints);
|
||||
}
|
||||
let anchor = point(el.attr("Anchor"), true)?.ok_or(PathGeometryError::MissingAnchor)?;
|
||||
let left = point(el.attr("LeftDirection"), false)?.unwrap_or(anchor);
|
||||
let right = point(el.attr("RightDirection"), false)?.unwrap_or(anchor);
|
||||
points.push(PathPoint { anchor, left, right });
|
||||
}
|
||||
}
|
||||
paths.push(SubPath { open: geometry.attr("PathOpen") == Some("true"), points });
|
||||
}
|
||||
Ok(PathGeometry { paths })
|
||||
}
|
||||
|
||||
/// The `PathGeometry` element.
|
||||
pub fn to_el(&self) -> El {
|
||||
let mut pg = El::new("PathGeometry");
|
||||
|
||||
@@ -17,7 +17,7 @@ mod resources;
|
||||
mod spread;
|
||||
mod story;
|
||||
|
||||
pub use geometry::{MAX_PATH_POINTS, PathGeometry, PathPoint, SubPath};
|
||||
pub use geometry::{MAX_PATH_POINTS, PathGeometry, PathGeometryError, PathPoint, SubPath};
|
||||
pub use resources::{
|
||||
ColorGroup, ColorSpace, GradientStop, StrokeStyle, StrokeStyleKind, Style, StyleGroup, StyleKind, StyleNode, Swatch, SwatchKind, color_groups,
|
||||
named, preference, stroke_styles, style_groups, swatches,
|
||||
|
||||
Reference in new issue
Block a user