summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
Diffstat (limited to 'src/library')
-rw-r--r--src/library/deco.rs6
-rw-r--r--src/library/grid.rs3
-rw-r--r--src/library/image.rs95
-rw-r--r--src/library/pad.rs4
-rw-r--r--src/library/shape.rs29
-rw-r--r--src/library/sized.rs14
-rw-r--r--src/library/transform.rs2
7 files changed, 88 insertions, 65 deletions
diff --git a/src/library/deco.rs b/src/library/deco.rs
index 6ef5a97b..2722fd68 100644
--- a/src/library/deco.rs
+++ b/src/library/deco.rs
@@ -102,9 +102,9 @@ pub enum LineKind {
impl LineDecoration {
/// Apply a line decoration to a all text elements in a frame.
pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) {
- for i in 0 .. frame.children.len() {
- let (pos, child) = &frame.children[i];
- if let FrameChild::Element(Element::Text(text)) = child {
+ for i in 0 .. frame.elements.len() {
+ let (pos, child) = &frame.elements[i];
+ if let Element::Text(text) = child {
let face = ctx.fonts.get(text.face_id);
let metrics = match self.kind {
LineKind::Underline => face.underline,
diff --git a/src/library/grid.rs b/src/library/grid.rs
index c9accffb..f247c7f4 100644
--- a/src/library/grid.rs
+++ b/src/library/grid.rs
@@ -167,7 +167,8 @@ impl<'a> GridLayouter<'a> {
cols.pop();
rows.pop();
- // We use the regions only for auto row measurement and constraints.
+ // We use the regions for auto row measurement. Since at that moment,
+ // columns are already sized, we can enable horizontal expansion.
let expand = regions.expand;
regions.expand = Spec::new(true, false);
diff --git a/src/library/image.rs b/src/library/image.rs
index f93d4b54..ea9a050c 100644
--- a/src/library/image.rs
+++ b/src/library/image.rs
@@ -9,7 +9,9 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
let width = args.named("width")?;
let height = args.named("height")?;
+ let fit = args.named("fit")?.unwrap_or_default();
+ // Load the image.
let full = ctx.make_path(&path.v);
let id = ctx.images.load(&full).map_err(|err| {
Error::boxed(path.span, match err.kind() {
@@ -18,10 +20,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
})
})?;
- Ok(Value::Template(Template::from_inline(move |_| ImageNode {
- id,
- width,
- height,
+ Ok(Value::Template(Template::from_inline(move |_| {
+ ImageNode { id, fit }.pack().sized(width, height)
})))
}
@@ -30,10 +30,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
pub struct ImageNode {
/// The id of the image file.
pub id: ImageId,
- /// The fixed width, if any.
- pub width: Option<Linear>,
- /// The fixed height, if any.
- pub height: Option<Linear>,
+ /// How the image should adjust itself to a given area.
+ pub fit: ImageFit,
}
impl Layout for ImageNode {
@@ -42,36 +40,77 @@ impl Layout for ImageNode {
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
+ let &Regions { current, expand, .. } = regions;
+
let img = ctx.images.get(self.id);
- let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
- let pixel_ratio = pixel_size.x / pixel_size.y;
+ let pixel_w = img.width() as f64;
+ let pixel_h = img.height() as f64;
- let width = self.width.map(|w| w.resolve(regions.base.w));
- let height = self.height.map(|w| w.resolve(regions.base.h));
+ let region_ratio = current.w / current.h;
+ let pixel_ratio = pixel_w / pixel_h;
+ let wide = region_ratio < pixel_ratio;
- let mut cts = Constraints::new(regions.expand);
- cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
+ // The space into which the image will be placed according to its fit.
+ let canvas = if expand.x && expand.y {
+ current
+ } else if expand.x || (wide && current.w.is_finite()) {
+ Size::new(current.w, current.w.safe_div(pixel_ratio))
+ } else if current.h.is_finite() {
+ Size::new(current.h * pixel_ratio, current.h)
+ } else {
+ Size::new(Length::pt(pixel_w), Length::pt(pixel_h))
+ };
- let size = match (width, height) {
- (Some(width), Some(height)) => Size::new(width, height),
- (Some(width), None) => Size::new(width, width / pixel_ratio),
- (None, Some(height)) => Size::new(height * pixel_ratio, height),
- (None, None) => {
- cts.exact.x = Some(regions.current.w);
- if regions.current.w.is_finite() {
- // Fit to width.
- Size::new(regions.current.w, regions.current.w / pixel_ratio)
+ // The actual size of the fitted image.
+ let size = match self.fit {
+ ImageFit::Contain | ImageFit::Cover => {
+ if wide == (self.fit == ImageFit::Contain) {
+ Size::new(canvas.w, canvas.w / pixel_ratio)
} else {
- // Unbounded width, we have to make up something,
- // so it is 1pt per pixel.
- pixel_size.map(Length::pt).to_size()
+ Size::new(canvas.h * pixel_ratio, canvas.h)
}
}
+ ImageFit::Stretch => canvas,
};
- let mut frame = Frame::new(size, size.h);
- frame.push(Point::zero(), Element::Image(self.id, size));
+ // The position of the image so that it is centered in the canvas.
+ let mut frame = Frame::new(canvas, canvas.h);
+ frame.clips = self.fit == ImageFit::Cover;
+ frame.push(
+ Point::new((canvas.w - size.w) / 2.0, (canvas.h - size.h) / 2.0),
+ Element::Image(self.id, size),
+ );
+ let mut cts = Constraints::new(regions.expand);
+ cts.exact = regions.current.to_spec().map(Some);
vec![frame.constrain(cts)]
}
}
+
+/// How an image should adjust itself to a given area.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum ImageFit {
+ /// The image should be fully contained in the area.
+ Contain,
+ /// The image should completely cover the area.
+ Cover,
+ /// The image should be stretched so that it exactly fills the area.
+ Stretch,
+}
+
+castable! {
+ ImageFit,
+ Expected: "string",
+ Value::Str(string) => match string.as_str() {
+ "contain" => Self::Contain,
+ "cover" => Self::Cover,
+ "stretch" => Self::Stretch,
+ _ => Err(r#"expected "contain", "cover" or "stretch""#)?,
+ },
+}
+
+impl Default for ImageFit {
+ fn default() -> Self {
+ Self::Contain
+ }
+}
diff --git a/src/library/pad.rs b/src/library/pad.rs
index 1ec1b4a2..a1c8c6f9 100644
--- a/src/library/pad.rs
+++ b/src/library/pad.rs
@@ -46,9 +46,7 @@ impl Layout for PadNode {
frames.iter_mut().zip(regions.iter())
{
fn solve_axis(length: Length, padding: Linear) -> Length {
- (length + padding.abs)
- .div_finite(1.0 - padding.rel.get())
- .unwrap_or_default()
+ (length + padding.abs).safe_div(1.0 - padding.rel.get())
}
// Solve for the size `padded` that satisfies (approximately):
diff --git a/src/library/shape.rs b/src/library/shape.rs
index 407d5974..dbd6eea7 100644
--- a/src/library/shape.rs
+++ b/src/library/shape.rs
@@ -1,7 +1,7 @@
use std::f64::consts::SQRT_2;
use super::prelude::*;
-use super::{PadNode, SizedNode};
+use super::PadNode;
use crate::util::RcExt;
/// `rect`: A rectangle with optional content.
@@ -65,20 +65,13 @@ fn shape_impl(
let fill = fill.unwrap_or(Color::Rgba(RgbaColor::gray(175)));
Value::Template(Template::from_inline(move |style| {
- let shape = Layout::pack(ShapeNode {
+ ShapeNode {
kind,
fill: Some(Paint::Color(fill)),
child: body.as_ref().map(|body| body.pack(style)),
- });
-
- if width.is_some() || height.is_some() {
- Layout::pack(SizedNode {
- sizing: Spec::new(width, height),
- child: shape,
- })
- } else {
- shape
}
+ .pack()
+ .sized(width, height)
}))
}
@@ -112,7 +105,7 @@ impl Layout for ShapeNode {
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
- // Layout.
+ // Layout, either with or without child.
let mut frame = if let Some(child) = &self.child {
let mut node: &dyn Layout = child;
@@ -141,15 +134,18 @@ impl Layout for ShapeNode {
frames = node.layout(ctx, &pod);
}
- // Validate and set constraints.
- assert_eq!(frames.len(), 1);
+ // Extract the frame.
Rc::take(frames.into_iter().next().unwrap().item)
} else {
+ // When there's no child, fill the area if expansion is on,
+ // otherwise fall back to a default size.
let default = Length::pt(30.0);
let size = Size::new(
if regions.expand.x {
regions.current.w
} else {
+ // For rectangle and ellipse, the default shape is a bit
+ // wider than high.
match self.kind {
ShapeKind::Square | ShapeKind::Circle => default,
ShapeKind::Rect | ShapeKind::Ellipse => 1.5 * default,
@@ -161,7 +157,7 @@ impl Layout for ShapeNode {
Frame::new(size, size.h)
};
- // Add background shape if desired.
+ // Add background fill if desired.
if let Some(fill) = self.fill {
let (pos, geometry) = match self.kind {
ShapeKind::Square | ShapeKind::Rect => {
@@ -175,11 +171,10 @@ impl Layout for ShapeNode {
frame.prepend(pos, Element::Geometry(geometry, fill));
}
- // Generate tight constraints for now.
+ // Return tight constraints for now.
let mut cts = Constraints::new(regions.expand);
cts.exact = regions.current.to_spec().map(Some);
cts.base = regions.base.to_spec().map(Some);
-
vec![frame.constrain(cts)]
}
}
diff --git a/src/library/sized.rs b/src/library/sized.rs
index 686d79b9..6394b0f4 100644
--- a/src/library/sized.rs
+++ b/src/library/sized.rs
@@ -6,12 +6,7 @@ pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let height = args.named("height")?;
let body: Template = args.find().unwrap_or_default();
Ok(Value::Template(Template::from_inline(move |style| {
- let child = body.pack(style);
- if width.is_some() || height.is_some() {
- Layout::pack(SizedNode { sizing: Spec::new(width, height), child })
- } else {
- child
- }
+ body.pack(style).sized(width, height)
})))
}
@@ -21,12 +16,7 @@ pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let height = args.named("height")?;
let body: Template = args.find().unwrap_or_default();
Ok(Value::Template(Template::from_block(move |style| {
- let child = body.pack(style);
- if width.is_some() || height.is_some() {
- Layout::pack(SizedNode { sizing: Spec::new(width, height), child })
- } else {
- child
- }
+ body.pack(style).sized(width, height)
})))
}
diff --git a/src/library/transform.rs b/src/library/transform.rs
index 85d65703..d0a27622 100644
--- a/src/library/transform.rs
+++ b/src/library/transform.rs
@@ -36,7 +36,7 @@ impl Layout for MoveNode {
self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(),
);
- for (point, _) in &mut Rc::make_mut(frame).children {
+ for (point, _) in &mut Rc::make_mut(frame).elements {
*point += offset;
}
}