Authentication-Results: mail-b.sr.ht; dkim=pass header.d=gmx.net header.i=@gmx.net Received: from mout.gmx.net (mout.gmx.net [212.227.17.20]) by mail-b.sr.ht (Postfix) with ESMTPS id 04F8E11F2D8 for <~ireas/public-inbox@lists.sr.ht>; Thu, 27 Oct 2022 10:13:30 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=s31663417; t=1666865609; bh=HpzVx2CnrKlfrh9rhrN8o7OLR5bUqEjCIfxdfAtMh+g=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date; b=aoabYdzN9J9tUrGLBG2uNsIjLjylwok8njg55UZOswLB385U7mxhxXQ6oxRnX7Tka mkG8iPyfVgZtA3yDxXjJjlt6HmcMloa2jndwgTmMxHbTnZO50Q5bRRjiBGV0hsqaQs H+6jffHZWep9X4RK0RMtAjOoXkkSkgLwxnOogdvTyleG2oXuspD3NOCmD21jYeHqNQ ozHvYL9Llb5thx5+0MziiMxpeZu8JzcSKeiIkTATrhw6q2z28fxtdekUuy0kexSog5 ekYpJkcUgUXxjzYEJ7SirtwSVIQhgODfmVVNrYqiTIesEzmRKC8qNMqtlVAuOrXWhS CY0hCJFhNJ5UA== X-UI-Sender-Class: 724b4f7f-cbec-4199-ad4e-598c01a50d3a Received: from sovereign.thetaylors.at ([80.110.37.229]) by mail.gmx.net (mrgmx104 [212.227.17.168]) with ESMTPSA (Nemesis) id 1Mqs4f-1pS1U025mD-00mpRe; Thu, 27 Oct 2022 12:13:29 +0200 From: Thomas Taylor To: ~ireas/public-inbox@lists.sr.ht Cc: Thomas Taylor Subject: [PATCH penpdf-rs] Implemented line styling effects (underline, strikethrough, overline) Date: Thu, 27 Oct 2022 12:13:25 +0200 Message-Id: <20221027101325.15501-1-thomas.taylor@gmx.net> X-Mailer: git-send-email 2.38.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-Provags-ID: V03:K1:4XaC7dEuVZLs3YE7M4KBwYCmSLKCAI7I73CZFKb9UBqkg16aGJd KrPFpXvOdcRueJeaeUeGtYA5IOJCzCKyRfvKdWWbUwgJNNYn/vEx5qXBnMxy1Tz+sR8HZIx rE/8W8isoTKDqJNAtYGqs3bACfY7HuIQrD5vqqpj6sPGkfAIOShFlCAs6bqZyfG3PjxkSJo PMHKZKAKWGP4UD/PENJRg== X-Spam-Flag: NO UI-OutboundReport: notjunk:1;M01:P0:QooVwABkZUI=;wsKd+zIP5ZJRIBik+1A4F2DeJ1H Xw6zAZbgiLnGrpSBLQBcxr6FyGJlO9rWeztcbsB9kjSB5hDH36MZfl2biwIWa7HGKsmFUuFCt kQtqW8ZV4SlRy6RpnyC0cjcZAo9EUyu2QV261CQhKHtS/JkDzVZkX9UZr1YCZGbmyT0G/euuy hgp7VnA7zJkuZZVMoLKEMQy81ySlXUsAuq6+UZKvPE8gIjXRfZkBMfRFnlp1AnBXp6wH3rJXA ANT3IVK+UU8CqxjRC9ZChkuukvIooCs641KnLurcKJiUM9qUGNots3D3phA10LUE+SQReUocw dRY5eGL+Bz/foJ9w1pSajQzQthTNae91UKyoh1coKDMm0MHiLVnLTyB0ZS9Jxuh3d5g4t905Y 41Ss3V6uaACimfT+ChbB/2RxCap9oClteVBunawZU+K4ZGGlKI+6ogIgPTRqIaqgZwgu+NJyW tq88evW7ps7FvKfz2m8xIbiZaKEFNTiidm2n442vVJNJjxzZ1Q0+uWT41PN1yYZzCSIuzaWro VJH5Zf4KDNNBZafiorFgMBFaniEJEB1DXRJ47PTxGfnQm6GwGL1dfHDoWkh//9Mc6ZHp17OkE EuHHT1ic5LHfdMHERF6M6J9B+n9A7eFzZU2NVgZDFG1rdEtQ+Ad2+ZZiHI/7riFPGBgXajWXt YNOmqGlSkVGzItlEWxTmqJMuwGwkFkniOXVv48kzrOCK89OckK8A3/hWvAbcTLtIMf0/Gg57g L7Esqzv74isXR5GzSn89gfL8wul7cSO3U7saUmbUJAMn2v2CANcojYeVgObG1Ody7bzMz+LeN P1a4cU2oGLhxSfsaiqPMsvRhNC1Eymr73kOfwi/823JEVVKEIr7HLQGwfHgwbsMX/17QKo9Su RVWLr6nBoLkE/GwI1Xz8wdxWzWiPm71Bz425X0GXmdV4FXKVeGNTD5sRvf1VHoIJQoYEpshR8 jHLe30ie0fhJTNnm4fx6Ywzzsu0= =2D-- src/elements.rs | 8 +++- src/render.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++--- src/style.rs | 71 +++++++++++++++++++++++++++++++- 3 files changed, 176 insertions(+), 8 deletions(-) diff --git a/src/elements.rs b/src/elements.rs index 242fc1c..7f49445 100644 =2D-- a/src/elements.rs +++ b/src/elements.rs @@ -352,9 +352,14 @@ impl Element for Paragraph { .fold(fonts::Metrics::default(), |max, m| max.max(&m)); let position =3D Position::new(self.get_offset(width, area.si= ze().width), 0); + let mut posx =3D Mm(0.0); if let Some(mut section) =3D area.text_section(&context.font_= cache, position, metrics) { for s in line { - section.print_str(&s.s, s.style)?; + // section.print_str(&s.s, s.style, posx)?; + match section.print_str(&s.s, s.style, posx) { + Err(e) =3D> return Err(e), + Ok(x) =3D> posx=3Dx, + } rendered_len +=3D s.s.len(); } rendered_len -=3D delta; @@ -362,6 +367,7 @@ impl Element for Paragraph { result.has_more =3D true; break; } + result.size =3D result .size .stack_vertical(Size::new(width, metrics.line_height)); diff --git a/src/render.rs b/src/render.rs index 8e79910..397dff2 100644 =2D-- a/src/render.rs +++ b/src/render.rs @@ -19,6 +19,7 @@ //! [`TextSection`]: struct.TextSection.html use std::cell; +use std::fmt; use std::io; use std::ops; use std::rc; @@ -31,6 +32,7 @@ use crate::{Margins, Mm, Position, Size}; #[cfg(feature =3D "images")] use crate::{Rotation, Scale}; +#[derive(Debug, Clone)] /// A position relative to the top left corner of a layer. struct LayerPosition(Position); @@ -40,6 +42,7 @@ impl LayerPosition { } } +#[derive(Debug, Clone)] /// A position relative to the bottom left corner of a layer (=E2=80=9Cus= er space=E2=80=9D in PDF terms). struct UserSpacePosition(Position); @@ -200,6 +203,12 @@ pub struct Page { layers: Layers, } +impl fmt::Debug for Page { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Page").field("size", &self.size).field("layers", = &self.layers).finish() + } +} + impl Page { fn new( page: printpdf::PdfPageReference, @@ -295,7 +304,7 @@ impl Layers { /// This is a wrapper around a [`printpdf::PdfLayerReference`][]. /// /// [`printpdf::PdfLayerReference`]: https://docs.rs/printpdf/0.3.2/print= pdf/types/pdf_layer/struct.PdfLayerReference.html -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Layer<'p> { page: &'p Page, data: rc::Rc, @@ -470,6 +479,13 @@ pub struct Area<'p> { layer: Layer<'p>, origin: Position, size: Size, + lines: Vec, +} + +impl fmt::Debug for Area<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Area").field("origin", &self.origin).field("size"= , &self.size).field("lines",&self.lines).finish() + } } impl<'p> Area<'p> { @@ -478,6 +494,7 @@ impl<'p> Area<'p> { layer, origin, size, + lines: Vec::new(), } } @@ -491,6 +508,7 @@ impl<'p> Area<'p> { layer, origin: self.origin, size: self.size, + lines: Vec::new(), } } @@ -576,9 +594,9 @@ impl<'p> Area<'p> { .add_image(image, self.position(position), scale, rotation, d= pi); } - /// Draws a line with the given points and the given line style. + /// Draws a line with the given points and the given line style immed= iately. /// - /// The points are relative to the upper left corner of the area. + /// The points are relative to the upper left corner of the area. Dra= wing the line immediately will mess with the layout of following text, use= add_line instead. pub fn draw_line(&self, points: I, line_style: LineStyle) where I: IntoIterator, @@ -589,6 +607,16 @@ impl<'p> Area<'p> { .add_line_shape(points.into_iter().map(|pos| self.position(po= s))); } + /// Saves a line to be drawn later with the given points and the give= n line style. + /// + /// The points are relative to the upper left corner of the area. Dra= wing the line later is necessary to prevent side effects on text layout, t= he drawing will occur on dropping the area object. + pub fn add_line(&mut self, points: I, line_style: LineStyle) + where + I: IntoIterator, + { + self.lines.push(Line::save(points.into_iter().map(|pos| self.posi= tion(pos)).collect(), &line_style)); + } + /// Tries to draw the given string at the given position and returns = `true` if the area was /// large enough to draw the string. /// @@ -604,7 +632,7 @@ impl<'p> Area<'p> { if let Some(mut section) =3D self.text_section(font_cache, position, style.metrics(font_ca= che)) { - section.print_str(s, style)?; + section.print_str(s, style, 0)?; Ok(true) } else { Ok(false) @@ -633,6 +661,21 @@ impl<'p> Area<'p> { } } +impl Drop for Area<'_> { + /// Draw all the saved lines before drop. + /// + /// When the area goes out of scope all the saved lines will be drawn= . + fn drop(&mut self) { + { + for line in self.lines.clone() { + self.layer.set_outline_thickness(line.style.thickness()); + self.layer.set_outline_color(line.style.color()); + self.layer.add_line_shape(line.points); + } + } + } +} + /// A text section that is drawn on an area of a PDF layer. pub struct TextSection<'f, 'p> { font_cache: &'f fonts::FontCache, @@ -700,7 +743,7 @@ impl<'f, 'p> TextSection<'f, 'p> { /// Prints the given string with the given style. /// /// The font cache for this text section must contain the PDF font fo= r the given style. - pub fn print_str(&mut self, s: impl AsRef, style: Style) -> Resu= lt<(), Error> { + pub fn print_str(&mut self, s: impl AsRef, style: Style, posx: i= mpl Into) -> Result { let font =3D style.font(self.font_cache); let s =3D s.as_ref(); @@ -738,7 +781,42 @@ impl<'f, 'p> TextSection<'f, 'p> { self.area .layer .write_positioned_codepoints(positions, codepoints); - Ok(()) + + let x1 =3D posx.into(); + let x2 =3D style.str_width(&self.font_cache,s)+x1; + + // Line effects (underline, strikethrough, overline) cannot be dr= awn immediately because of side effects on the layout + if style.is_underline() || style.is_strikethrough() || style.is_o= verline() { + let yo =3D self.metrics.ascent*0.15; + let ys =3D self.metrics.ascent*0.7; + let yu =3D self.metrics.ascent*1.1; + let ls =3D LineStyle::new().with_color(Color::Rgb(0,0,0)).wit= h_thickness(Mm(0.1)); + if style.is_overline() { + // self.area.draw_line( + self.area.add_line( + vec![Position::new(x1, yo),Position::new(x2,yo)], ls)= ; + // println!("over line: ({:?},{:?})-({:?},{:?}) of {:?}",= x1,yo,x2,yo,self.metrics); + } + if style.is_strikethrough() { + // self.area.draw_line( + self.area.add_line( + vec![Position::new(x1, ys),Position::new(x2,ys)], + ls, + ); + // println!("strike line: ({:?},{:?})-({:?},{:?})", x1,ys= ,x2,ys); + } + if style.is_underline() { + // self.area.draw_line( + self.area.add_line( + vec![Position::new(x1, yu),Position::new(x2,yu)], + ls, + ); + // println!("under line: ({:?},{:?})-({:?},{:?})", x1,yu,= x2,yu); + } + // println!("string: {} at {:?} line {:?}-{:?}",s,self.area,x= 1,x2); + } + + Ok(x2) } } @@ -770,3 +848,18 @@ fn encode_win1252(s: &str) -> Result, Error>= { Ok(bytes) } } + +/// A line element. +/// +/// This struct provides storage for a line, so it can be drawn later. +#[derive(Debug, Clone)] +struct Line { + points: Vec, + style: LineStyle, +} + +impl Line { + fn save(points: Vec, style: &LineStyle) -> Line { + Line {points, style: style.clone()} + } +} diff --git a/src/style.rs b/src/style.rs index b3c12f4..94617c6 100644 =2D-- a/src/style.rs +++ b/src/style.rs @@ -80,13 +80,19 @@ impl From for printpdf::Color { } } -/// A text effect (bold or italic). +/// A text effect (bold, italic, underline, overline, strikethrough). #[derive(Clone, Copy, Debug, PartialEq)] pub enum Effect { /// Bold text. Bold, /// Italic text. Italic, + /// Underlined text. + Underline, + /// Overlined text. + Overline, + /// Stricken through text. + Strikethrough, } /// A style annotation for a string. @@ -113,6 +119,9 @@ pub struct Style { color: Option, is_bold: bool, is_italic: bool, + is_underline: bool, + is_overline: bool, + is_strikethrough: bool, } impl Style { @@ -139,6 +148,15 @@ impl Style { if style.is_italic { self.is_italic =3D true; } + if style.is_underline { + self.is_underline =3D true; + } + if style.is_overline { + self.is_overline =3D true; + } + if style.is_strikethrough { + self.is_strikethrough =3D true; + } } /// Combines this style and the given style and returns the result. @@ -167,6 +185,21 @@ impl Style { self.is_italic } + /// Returns whether the underline text effect is set. + pub fn is_underline(&self) -> bool { + self.is_underline + } + + /// Returns whether the overline text effect is set. + pub fn is_overline(&self) -> bool { + self.is_overline + } + + /// Returns whether the strikethrough text effect is set. + pub fn is_strikethrough(&self) -> bool { + self.is_strikethrough + } + /// Returns the font size for this style in points, or 12 if no font = size is set. pub fn font_size(&self) -> u8 { self.font_size.unwrap_or(12) @@ -199,6 +232,39 @@ impl Style { self } + /// Sets the underline effect for this style. + pub fn set_underline(&mut self) { + self.is_underline =3D true; + } + + /// Sets the underline effect for this style and returns it. + pub fn underline(mut self) -> Style { + self.set_underline(); + self + } + + /// Sets the overline effect for this style. + pub fn set_overline(&mut self) { + self.is_overline =3D true; + } + + /// Sets the overline effect for this style and returns it. + pub fn overline(mut self) -> Style { + self.set_overline(); + self + } + + /// Sets the strikethrough effect for this style. + pub fn set_strikethrough(&mut self) { + self.is_strikethrough =3D true; + } + + /// Sets the strikethrough effect for this style and returns it. + pub fn strikethrough(mut self) -> Style { + self.set_strikethrough(); + self + } + /// Sets the font family for this style. pub fn set_font_family(&mut self, font_family: fonts::FontFamily) { self.font_family =3D Some(font_family); @@ -330,6 +396,9 @@ impl From for Style { match effect { Effect::Bold =3D> style.bold(), Effect::Italic =3D> style.italic(), + Effect::Underline =3D> style.underline(), + Effect::Overline =3D> style.overline(), + Effect::Strikethrough =3D> style.strikethrough(), } } } =2D- 2.38.1