diff --git a/Cargo.toml b/Cargo.toml index 3d55ec6..01206ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ serde = { version = "1" } serde_derive = { version = "1" } flate2 = "1.0.35" # for decompression of builtin fonts serde_json = { version = "1" } +smallvec = { version = "1.15.1", features = ["serde"] } # Feature-gated dependencies rust-fontconfig = { version = "2.0.0", default-features = false, features = ["std", "parsing"], optional = true } xmlparser = { version = "0.13.6", default-features = false, optional = true } diff --git a/src/deserialize.rs b/src/deserialize.rs index a24ed4e..37c98a8 100644 --- a/src/deserialize.rs +++ b/src/deserialize.rs @@ -2311,10 +2311,9 @@ pub fn parse_op( if let Some(arr_obj) = op.operands.get(0) { // parse array of numbers if let Ok(arr) = arr_obj.as_array() { - let pattern: Vec = - arr.iter().map(|item| to_f32(item) as i64).collect(); - let offset = to_f32(&op.operands[1]) as i64; - let dash = LineDashPattern::from_array(&pattern, offset); + let pattern = arr.iter().map(|item| to_f32(item)).collect(); + let offset = to_f32(&op.operands[1]); + let dash = LineDashPattern{offset, pattern}; out_ops.push(Op::SetLineDashPattern { dash }); } } @@ -3239,9 +3238,8 @@ mod extgstate { if let Some(obj) = dict.get(b"D").ok() { if let Some(arr) = obj.as_array().ok() { - // Parse the dash pattern as a flat array of integers. - let dashes: Vec = arr.iter().filter_map(|o| parse_i64(o)).collect(); - gs.line_dash_pattern = Some(LineDashPattern::from_array(&dashes, 0)); // default offset = 0 + let pattern = arr.iter().filter_map(|o| parse_f32(o)).collect(); + gs.line_dash_pattern = Some(LineDashPattern{offset: 0.0, pattern}); // default offset = 0 changed.insert(ChangedField::LineDashPattern); } } diff --git a/src/graphics.rs b/src/graphics.rs index 9de7c34..32db786 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -340,119 +340,24 @@ impl FromIterator<(Point, bool)> for Polygon { } /// Line dash pattern is made up of a total width -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, PartialOrd, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LineDashPattern { /// Offset at which the dashing pattern should start, measured from the beginning ot the line /// Default: 0 (start directly where the line starts) - pub offset: i64, - /// Length of the first dash in the dash pattern. If `None`, the line will be solid (good for - /// resetting the dash pattern) - #[serde(default)] - pub dash_1: Option, - /// Whitespace after the first dash. If `None`, whitespace will be the same as length_1st, - /// meaning that the line will have dash - whitespace - dash - whitespace in even offsets - #[serde(default)] - pub gap_1: Option, - /// Length of the second dash in the dash pattern. If None, will be equal to length_1st - #[serde(default)] - pub dash_2: Option, - /// Same as whitespace_1st, but for length_2nd - #[serde(default)] - pub gap_2: Option, - /// Length of the second dash in the dash pattern. If None, will be equal to length_1st - #[serde(default)] - pub dash_3: Option, - /// Same as whitespace_1st, but for length_3rd - #[serde(default)] - pub gap_3: Option, + pub offset: f32, + /// Dash, gap, dash, gap, ... + pub pattern: smallvec::SmallVec<[f32;8]>, } impl LineDashPattern { - pub fn as_array(&self) -> Vec { - [ - self.dash_1, - self.gap_1, - self.dash_2, - self.gap_2, - self.dash_3, - self.gap_3, - ] - .iter() - .copied() - .take_while(Option::is_some) - .flatten() - .collect() - } - pub fn get_svg_id(&self) -> String { - let dash_array = self.as_array(); - dash_array + self.pattern .iter() .map(|num| num.to_string()) .collect::>() .join(",") } - - /// Builds a `LineDashPattern` from a slice of up to 6 integers. - /// - /// - The array is interpreted in dash-gap pairs: - /// - If `dashes[0]` is present => `dash_1 = Some(...)` - /// - If `dashes[1]` is present => `gap_1 = Some(...)` - /// - If `dashes[2]` is present => `dash_2 = Some(...)` - /// - If `dashes[3]` is present => `gap_2 = Some(...)` - /// - If `dashes[4]` is present => `dash_3 = Some(...)` - /// - If `dashes[5]` is present => `gap_3 = Some(...)` - /// - /// Any extra elements beyond index 5 are ignored. If the slice is empty, - /// the line is solid (all fields `None`). - pub fn from_array(dashes: &[i64], offset: i64) -> Self { - let mut pat = LineDashPattern::default(); - pat.offset = offset; - - match dashes.len() { - 0 => { - // No dashes => solid line - // (everything is None, which is already default) - } - 1 => { - pat.dash_1 = Some(dashes[0]); - } - 2 => { - pat.dash_1 = Some(dashes[0]); - pat.gap_1 = Some(dashes[1]); - } - 3 => { - pat.dash_1 = Some(dashes[0]); - pat.gap_1 = Some(dashes[1]); - pat.dash_2 = Some(dashes[2]); - } - 4 => { - pat.dash_1 = Some(dashes[0]); - pat.gap_1 = Some(dashes[1]); - pat.dash_2 = Some(dashes[2]); - pat.gap_2 = Some(dashes[3]); - } - 5 => { - pat.dash_1 = Some(dashes[0]); - pat.gap_1 = Some(dashes[1]); - pat.dash_2 = Some(dashes[2]); - pat.gap_2 = Some(dashes[3]); - pat.dash_3 = Some(dashes[4]); - } - _ => { - // 6 or more elements => fill all 3 dash-gap pairs - pat.dash_1 = Some(dashes[0]); - pat.gap_1 = Some(dashes[1]); - pat.dash_2 = Some(dashes[2]); - pat.gap_2 = Some(dashes[3]); - pat.dash_3 = Some(dashes[4]); - pat.gap_3 = Some(dashes[5]); - } - } - - pat - } } /// __See PDF Reference Page 216__ - Line join style @@ -1216,9 +1121,9 @@ pub fn extgstate_to_dict(val: &ExtendedGraphicsState) -> LoDictionary { } // Optional parameters - if let Some(ldp) = val.line_dash_pattern { + if let Some(ldp) = &val.line_dash_pattern { if val.changed_fields.contains(&ChangedField::LineDashPattern) { - let array = ldp.as_array().into_iter().map(Integer).collect(); + let array = ldp.pattern.iter().copied().map(Real).collect(); gs_operations.push(("D".to_string(), Array(array))); } } diff --git a/src/html/border.rs b/src/html/border.rs index 1f06168..f7ae7ad 100644 --- a/src/html/border.rs +++ b/src/html/border.rs @@ -967,13 +967,11 @@ fn render_dashed_border_side( // Set dash pattern ops.push(Op::SetLineDashPattern { dash: LineDashPattern { - offset: 0, - dash_1: Some((width * 3.0) as i64), - gap_1: Some((width * 2.0) as i64), - dash_2: None, - gap_2: None, - dash_3: None, - gap_3: None, + offset: 0.0, + pattern: [ + width * 3.0, // dash + width * 2.0 // gap + ].into() }, }); @@ -982,13 +980,8 @@ fn render_dashed_border_side( // Reset dash pattern ops.push(Op::SetLineDashPattern { dash: LineDashPattern { - offset: 0, - dash_1: None, - gap_1: None, - dash_2: None, - gap_2: None, - dash_3: None, - gap_3: None, + offset: 0.0, + pattern: [].into() }, }); } @@ -1008,13 +1001,11 @@ fn render_dotted_border_side( // Set dot pattern (dash = width, gap = width) ops.push(Op::SetLineDashPattern { dash: LineDashPattern { - offset: 0, - dash_1: Some(width as i64), - gap_1: Some(width as i64), - dash_2: None, - gap_2: None, - dash_3: None, - gap_3: None, + offset: 0.0, + pattern: [ + width, // dash + width // gap + ].into() }, }); @@ -1027,13 +1018,8 @@ fn render_dotted_border_side( ops.push(Op::SetLineCapStyle { cap: LineCapStyle::Butt }); ops.push(Op::SetLineDashPattern { dash: LineDashPattern { - offset: 0, - dash_1: None, - gap_1: None, - dash_2: None, - gap_2: None, - dash_3: None, - gap_3: None, + offset: 0.0, + pattern: [].into() }, }); } diff --git a/src/render.rs b/src/render.rs index 4a3c8b6..be1eac8 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1199,13 +1199,14 @@ fn render_line_to_svg(line: &Line, gst: &GraphicsStateVec, page_height: f32) -> // Handle dash pattern if present let dash_array = match gst.get_dash_array() { Some(dash) => { - let dash_array = dash.as_array(); - if dash_array.is_empty() { + // Dispatch SmallVec storage only once, by taking slice. + let dash_pattern = dash.pattern.as_slice(); + if dash_pattern.is_empty() { String::new() } else { format!( " stroke-dasharray=\"{}\" stroke-dashoffset=\"{}\"", - dash_array + dash_pattern .iter() .map(|n| n.to_string()) .collect::>() @@ -1312,13 +1313,14 @@ fn render_polygon_to_svg(polygon: &Polygon, gst: &GraphicsStateVec, page_height: // Handle dash pattern if present let dash_array = match gst.get_dash_array() { Some(dash) => { - let dash_array = dash.as_array(); - if dash_array.is_empty() { + // Dispatch SmallVec storage only once, by taking slice. + let dash_pattern = dash.pattern.as_slice(); + if dash_pattern.is_empty() { String::new() } else { format!( " stroke-dasharray=\"{}\" stroke-dashoffset=\"{}\"", - dash_array + dash_pattern .iter() .map(|n| n.to_string()) .collect::>() diff --git a/src/serialize.rs b/src/serialize.rs index 116765f..bf1ca0e 100644 --- a/src/serialize.rs +++ b/src/serialize.rs @@ -570,10 +570,10 @@ pub(crate) fn translate_operations( content.push(LoOp::new("w", vec![Real(pt.0)])); } Op::SetLineDashPattern { dash } => { - let dash_array_ints = dash.as_array().into_iter().map(Integer).collect(); + let dash_array_floats = dash.pattern.iter().copied().map(Real).collect(); content.push(LoOp::new( "d", - vec![Array(dash_array_ints), Integer(dash.offset)], + vec![Array(dash_array_floats), Real(dash.offset)], )); } Op::SetLineJoinStyle { join } => {