For FrameElements it would be nice to have a way to add more styles other than the color, like border thickness per side, rounded corners, a background color, etc.
Just as an possible idea on how this might be done second style struct could be added that contains non-inherited styles, say ElementStyle
. It'd track all the border and background options and could also include margins. This could possibly apply directly to other elements and replace the FrameElement
and PaddingElement
wrappers. Everything text-related would remain in the other Style
struct and would still be inherited
I tried my hand at implementing part of this for my own project. It doesn't yet handle an element spanning over multiple pages
In my fork's
genpdf::render::Area
pub fn draw_rounded_rect(&self, p: Position, w: Mm, h: Mm, r_bl: Mm, r_tl: Mm, r_tr: Mm, t_br: Mm, style: Style, has_fill: bool) { // The same code as draw_line except with 16 bezier points }Where r_* are the radii of the four rounded corners
In my project's
crate::elements
impl<E: Element> Element for RoundedFrameElement<E> { fn render( &mut self, context: &Context, area: render::Area<'_>, style: Style, ) -> Result<RenderResult, Error> { let result = self.element.render(context, area.clone(), style)?; let r = genpdf::Mm::from(5); area.draw_rounded_rect(Position::default(), area.size().width, result.size.height, r, r, r, r, style, true); Ok(result) } }Drawing code in my main program
let mut pdf_para = genpdf::elements::Paragraph::default(); pdf_para.push_styled("Rounded Frame Element"); doc.push(genpdf::elements::PaddedElement::new( genpdf::elements::StyledElement::new( crate::pdf::element::RoundedFrameElement::new( genpdf::elements::PaddedElement::new( pdf_para, genpdf::Margins::trbl(3, 3, 5, 5), ) ), genpdf::style::Color::Rgb(1, 0, 0) ), );Which works nicely (although I have to change the text color back)
But setting the fill covers the inner elements, which I can't figure out how to fix since I need to render the text first to get the height
Looks good! I think we can split this up into three aspects:
- Improve the style configuration for a
FramedElement
, namely setting a frame color that is independent of the text color and changing the line thickness. I like the idea of a separate style struct for this use case. I think I’d call itLineStyle
so that we can also use it to draw shapes (once that is implemented).- Add support for rounded borders. We could maybe have something like a
BorderStyle
struct, but I’d like to start by just adding that functionality to theFramedElement
struct.- Add support for a fill color. I think this also belongs into
FramedElement
for the time being. Regarding the covering of the inner elements, I think we have to start using multiple layers. I’ll look into that.Do you want to help with the implementation?
I can do 1 & 2. I'll put
LineStyle
andBorderStyle
ingenpdf::style
.LineStyle: color, line_thickness BorderStyle: radius_tl, radius_tr, radius_bl, radius_br, line_style: Option<LineStyle>, //fill_color: Option<Color>
(with convenience functions for a new
BorderStyle
from a uniform radius or no radius)Or it could be called
FrameStyle
. I think it makes sense to have it since there are so many fields and it'd be easier to extend to add the fill options, which I'll leave out for now. This would be another field forFramedElement
, set by a second constructorwith_frame_style
Great, thank you! Please start with just implementing
LineStyle
. I’ll have to give theBorderLayout
some more thought.
I've implemented it for
FramedElement
s here https://git.sr.ht/~ap4sc/genpdf-rs/commit/4b77bb05205f122ba92d2a6e46c90928404b731d The comments are the beizer and fill code I can remove before the final patch. I had to change the way lines are drawn across multiple pages because at large line thicknesses the corners of the lines wouldn't be connected when they were drawn separately. I assume this will be the same for Tables, which I haven't got to implementing style forAre there any other methods I should impl for
LineStyle
andFramed Element
, and am I deriving the right traits forLineStyle
. ? I just followed the example ofStyle
for these.Edit: also in the docs for printpdf
set_outline_thickness
it saysNOTE: 0.0 is a special value, it does not make the line disappear, but rather makes it appear 1px wide across all devices
I made 0 the default line thickness in
draw_line
but it looks strange because of this, should I set it to 1?
Are there any other methods I should impl for LineStyle and Framed Element, and am I deriving the right traits for LineStyle. ?
Looks good! Maybe implement
From<Color>
forLineStyle
and then changeFramedElement::wih_line_style
to acceptimpl Into<LineStyle>
?Two things I noticed:
- In
genpdf
, we always useMm
except for font sizes. I think we should also useMm
here (i. e. changethickness
toMm
inLineStyle
). You can convert that to pt usingprintpdf::Pt::from(thickness).0
.- In
Style
,color
is optional because we inherit the style, soNone
is different fromSome(black)
. This does not apply fordraw_line
as we default to black. So we should either remove theOption
and directly store the color inLineStyle
(defaulting to black), or use the text color for the line if no line color is set explicitly.I made 0 the default line thickness in draw_line but it looks strange because of this, should I set it to 1?
If we always set the thickness before drawing a shape, we don’t have to reset it in
draw_line
. Still, the question is what should be the default inLineStyle
. Why do you think that 0.0 looks strange? I think it looks okay.
I used
f64
instead ofMm
because printpdf uses it in the function for setting the thickness rather than a wrapper value likePt
orprintpdf::Mm
pub fn set_outline_thickness(&self, outline_thickness: f64)However I can still change this
defaulting to black
Will do this one
Why do you think that 0.0 looks strange?
It's always 1px no matter how far you zoom in or out. I don't know if users would expect this special behavior as a default. Mainly because I think the general impression of a PDF is a fixed document that appears the same everywhere you view it, I've never seen this feature used before.
Ok I've added all the above changes, as well as implemented the style for Tables using
FrameCellDecorator
https://git.sr.ht/~ap4sc/genpdf-rs/log/todo/34
Except I don't know how to do this:
change
FramedElement::with_line_style
to acceptimpl Into<LineStyle>
and the Table has the same issue as
FramedElement
where the borders don't connect. Here's it at line thickness 10:
This could probably be a separate ticket since it requires changing the whole way the tables are drawn to draw the outer border separately on each page
Edit:
Here's my working proposal for rounded corners on a separate branch, which supports continuity over multiple pages and different radii for each corner. I've gone ahead and implemented it since I need it for my own project so if you don't want to include it or want it done another way I can do that:
https://git.sr.ht/~ap4sc/genpdf-rs/commit/82404cda780cd02ceedc6d6ccdb69dc36a5f02ba
I had another look at the PDF specification. It says that “[s]ince the results of rendering such ‘zero-width’ lines are device-dependent, their use is not recommended.” So you are right about not using 0.0 as the default width.
It also says that the line width “is a nonnegative number expressed in user space units”, which is 1/72 inch, which is approximately 1 pt. So we should definitely use
Mm
here.Except I don't know how to do this:
change FramedElement::with_line_style to accept impl Into
You can do something like this:
impl<E: Element> FramedElement<E> { pub fn with_line_style(element: E, line_style: impl Into<LineStyle>) -> FramedElement<E> { Self { element, line_style: Some(line_style.into()), } } }
Your
LineStyle
implementation looks good! Though this might not work as intended:self.layer().set_outline_thickness(line_style.thickness().into());
line_style.thickness()
isMm
,set_outline_thickness
accepts a pt measure asf64
. But this line would pass the mm measure asf64
. So I think you have to useprintpdf::Pt::from(line_style.thickness()).0
here.Once you have changed these two things and think that everything is ready, can you please squash the commits into one and submit it? Thanks!
I’ll take a closer look at the rounded corners once the
LineStyle
patch is merged. I definitely want to include it, I just have to make up my mind about the best API.