New ShapeNode
Replaces `BackgroundNode` and `FixedNode`
@ -1,55 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
|
|
||||||
/// A node that places a rectangular filled background behind its child.
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
|
||||||
pub struct BackgroundNode {
|
|
||||||
/// The kind of shape to use as a background.
|
|
||||||
pub shape: BackgroundShape,
|
|
||||||
/// Background color / texture.
|
|
||||||
pub fill: Paint,
|
|
||||||
/// The child node to be filled.
|
|
||||||
pub child: LayoutNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The kind of shape to use as a background.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum BackgroundShape {
|
|
||||||
Rect,
|
|
||||||
Ellipse,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for BackgroundNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
ctx: &mut LayoutContext,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
|
||||||
let mut frames = self.child.layout(ctx, regions);
|
|
||||||
|
|
||||||
for Constrained { item: frame, .. } in &mut frames {
|
|
||||||
let (point, geometry) = match self.shape {
|
|
||||||
BackgroundShape::Rect => (Point::zero(), Geometry::Rect(frame.size)),
|
|
||||||
BackgroundShape::Ellipse => {
|
|
||||||
(frame.size.to_point() / 2.0, Geometry::Ellipse(frame.size))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a new frame with the background geometry and the child's
|
|
||||||
// frame.
|
|
||||||
let empty = Frame::new(frame.size, frame.baseline);
|
|
||||||
let prev = std::mem::replace(frame, Rc::new(empty));
|
|
||||||
let new = Rc::make_mut(frame);
|
|
||||||
new.push(point, Element::Geometry(geometry, self.fill));
|
|
||||||
new.push_frame(Point::zero(), prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
frames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<BackgroundNode> for LayoutNode {
|
|
||||||
fn from(background: BackgroundNode) -> Self {
|
|
||||||
Self::new(background)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
use decorum::N64;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// A node that can fix its child's width and height.
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
|
||||||
pub struct FixedNode {
|
|
||||||
/// The fixed width, if any.
|
|
||||||
pub width: Option<Linear>,
|
|
||||||
/// The fixed height, if any.
|
|
||||||
pub height: Option<Linear>,
|
|
||||||
/// The fixed aspect ratio between width and height.
|
|
||||||
///
|
|
||||||
/// The resulting frame will satisfy `width = aspect * height`.
|
|
||||||
pub aspect: Option<N64>,
|
|
||||||
/// The child node whose size to fix.
|
|
||||||
pub child: LayoutNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for FixedNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
ctx: &mut LayoutContext,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
|
||||||
// Fill in width or height if aspect ratio and the other is given.
|
|
||||||
let aspect = self.aspect.map(N64::into_inner);
|
|
||||||
let width = self.width.or(self.height.zip(aspect).map(|(h, a)| a * h));
|
|
||||||
let height = self.height.or(self.width.zip(aspect).map(|(w, a)| w / a));
|
|
||||||
|
|
||||||
// Resolve the linears based on the current width and height.
|
|
||||||
let mut child_size = Size::new(
|
|
||||||
width.map_or(regions.current.w, |w| w.resolve(regions.base.w)),
|
|
||||||
height.map_or(regions.current.h, |h| h.resolve(regions.base.h)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If width or height aren't set for an axis, the base should be
|
|
||||||
// inherited from the parent for that axis.
|
|
||||||
let child_base = Size::new(
|
|
||||||
if width.is_some() { child_size.w } else { regions.base.w },
|
|
||||||
if height.is_some() { child_size.h } else { regions.base.h },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Prepare constraints.
|
|
||||||
let mut constraints = Constraints::new(regions.expand);
|
|
||||||
constraints.set_base_if_linear(regions.base, Spec::new(width, height));
|
|
||||||
|
|
||||||
// If the size for one axis isn't specified, the `current` size along
|
|
||||||
// that axis needs to remain the same for the result to be reusable.
|
|
||||||
if width.is_none() {
|
|
||||||
constraints.exact.x = Some(regions.current.w);
|
|
||||||
}
|
|
||||||
if height.is_none() {
|
|
||||||
constraints.exact.y = Some(regions.current.h);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the aspect ratio.
|
|
||||||
if let Some(aspect) = aspect {
|
|
||||||
let width = child_size.w.min(aspect * child_size.h);
|
|
||||||
child_size = Size::new(width, width / aspect);
|
|
||||||
constraints.exact = regions.current.to_spec().map(Some);
|
|
||||||
constraints.min = Spec::splat(None);
|
|
||||||
constraints.max = Spec::splat(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If width or height are fixed, the child should fill the available
|
|
||||||
// space along that axis.
|
|
||||||
let child_expand = Spec::new(width.is_some(), height.is_some());
|
|
||||||
|
|
||||||
// Layout the child.
|
|
||||||
let mut regions = Regions::one(child_size, child_base, child_expand);
|
|
||||||
let mut frames = self.child.layout(ctx, ®ions);
|
|
||||||
|
|
||||||
// If we have an aspect ratio and the child is content-sized, we need to
|
|
||||||
// relayout with expansion.
|
|
||||||
if let Some(aspect) = aspect {
|
|
||||||
if width.is_none() && height.is_none() {
|
|
||||||
let needed = frames[0].item.size.cap(child_size);
|
|
||||||
let width = needed.w.max(aspect * needed.h);
|
|
||||||
regions.current = Size::new(width, width / aspect);
|
|
||||||
regions.expand = Spec::splat(true);
|
|
||||||
frames = self.child.layout(ctx, ®ions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrite the child's constraints with ours.
|
|
||||||
assert_eq!(frames.len(), 1);
|
|
||||||
frames[0].constraints = constraints;
|
|
||||||
|
|
||||||
frames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FixedNode> for LayoutNode {
|
|
||||||
fn from(fixed: FixedNode) -> Self {
|
|
||||||
Self::new(fixed)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,6 @@
|
|||||||
//! Layouting.
|
//! Layouting.
|
||||||
|
|
||||||
mod background;
|
|
||||||
mod constraints;
|
mod constraints;
|
||||||
mod fixed;
|
|
||||||
mod frame;
|
mod frame;
|
||||||
mod grid;
|
mod grid;
|
||||||
mod image;
|
mod image;
|
||||||
@ -11,14 +9,13 @@ mod incremental;
|
|||||||
mod pad;
|
mod pad;
|
||||||
mod par;
|
mod par;
|
||||||
mod regions;
|
mod regions;
|
||||||
|
mod shape;
|
||||||
mod shaping;
|
mod shaping;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
pub use self::image::*;
|
pub use self::image::*;
|
||||||
pub use background::*;
|
|
||||||
pub use constraints::*;
|
pub use constraints::*;
|
||||||
pub use fixed::*;
|
|
||||||
pub use frame::*;
|
pub use frame::*;
|
||||||
pub use grid::*;
|
pub use grid::*;
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
@ -26,6 +23,7 @@ pub use incremental::*;
|
|||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use par::*;
|
pub use par::*;
|
||||||
pub use regions::*;
|
pub use regions::*;
|
||||||
|
pub use shape::*;
|
||||||
pub use shaping::*;
|
pub use shaping::*;
|
||||||
pub use stack::*;
|
pub use stack::*;
|
||||||
pub use tree::*;
|
pub use tree::*;
|
||||||
|
141
src/layout/shape.rs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
use std::f64::consts::SQRT_2;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Places its child into a sizable and fillable shape.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||||
|
pub struct ShapeNode {
|
||||||
|
/// Which shape to place the child into.
|
||||||
|
pub shape: ShapeKind,
|
||||||
|
/// The width, if any.
|
||||||
|
pub width: Option<Linear>,
|
||||||
|
/// The height, if any.
|
||||||
|
pub height: Option<Linear>,
|
||||||
|
/// How to fill the shape, if at all.
|
||||||
|
pub fill: Option<Paint>,
|
||||||
|
/// The child node to place into the shape, if any.
|
||||||
|
pub child: Option<LayoutNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of a shape.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum ShapeKind {
|
||||||
|
/// A rectangle with equal side lengths.
|
||||||
|
Square,
|
||||||
|
/// A quadrilateral with four right angles.
|
||||||
|
Rect,
|
||||||
|
/// An ellipse with coinciding foci.
|
||||||
|
Circle,
|
||||||
|
/// A curve around two focal points.
|
||||||
|
Ellipse,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout for ShapeNode {
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
ctx: &mut LayoutContext,
|
||||||
|
regions: &Regions,
|
||||||
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
|
// Resolve width and height relative to the region's base.
|
||||||
|
let width = self.width.map(|w| w.resolve(regions.base.w));
|
||||||
|
let height = self.height.map(|h| h.resolve(regions.base.h));
|
||||||
|
|
||||||
|
// Generate constraints.
|
||||||
|
let constraints = {
|
||||||
|
let mut cts = Constraints::new(regions.expand);
|
||||||
|
cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
|
||||||
|
|
||||||
|
// Set exact and base constraint if child is automatically sized.
|
||||||
|
if self.width.is_none() {
|
||||||
|
cts.exact.x = Some(regions.current.w);
|
||||||
|
cts.base.x = Some(regions.base.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same here.
|
||||||
|
if self.height.is_none() {
|
||||||
|
cts.exact.y = Some(regions.current.h);
|
||||||
|
cts.base.y = Some(regions.base.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
cts
|
||||||
|
};
|
||||||
|
|
||||||
|
// Layout.
|
||||||
|
let mut frames = if let Some(child) = &self.child {
|
||||||
|
let mut node: &dyn Layout = child;
|
||||||
|
|
||||||
|
let padded;
|
||||||
|
if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) {
|
||||||
|
// Padding with this ratio ensures that a rectangular child fits
|
||||||
|
// perfectly into a circle / an ellipse.
|
||||||
|
padded = PadNode {
|
||||||
|
padding: Sides::splat(Relative::new(0.5 - SQRT_2 / 4.0).into()),
|
||||||
|
child: child.clone(),
|
||||||
|
};
|
||||||
|
node = &padded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "pod" is the region into which the child will be layouted.
|
||||||
|
let mut pod = {
|
||||||
|
let size = Size::new(
|
||||||
|
width.unwrap_or(regions.current.w),
|
||||||
|
height.unwrap_or(regions.current.h),
|
||||||
|
);
|
||||||
|
|
||||||
|
let base = Size::new(
|
||||||
|
if width.is_some() { size.w } else { regions.base.w },
|
||||||
|
if height.is_some() { size.h } else { regions.base.h },
|
||||||
|
);
|
||||||
|
|
||||||
|
let expand = Spec::new(width.is_some(), height.is_some());
|
||||||
|
Regions::one(size, base, expand)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now, layout the child.
|
||||||
|
let mut frames = node.layout(ctx, &pod);
|
||||||
|
|
||||||
|
if matches!(self.shape, ShapeKind::Square | ShapeKind::Circle) {
|
||||||
|
// Relayout with full expansion into square region to make sure
|
||||||
|
// the result is really a square or circle.
|
||||||
|
let size = frames[0].item.size;
|
||||||
|
pod.current.w = size.w.max(size.h).min(pod.current.w);
|
||||||
|
pod.current.h = pod.current.w;
|
||||||
|
pod.expand = Spec::splat(true);
|
||||||
|
frames = node.layout(ctx, &pod);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and set constraints.
|
||||||
|
assert_eq!(frames.len(), 1);
|
||||||
|
frames[0].constraints = constraints;
|
||||||
|
frames
|
||||||
|
} else {
|
||||||
|
// Resolve shape size.
|
||||||
|
let size = Size::new(width.unwrap_or_default(), height.unwrap_or_default());
|
||||||
|
vec![Frame::new(size, size.h).constrain(constraints)]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add background shape if desired.
|
||||||
|
if let Some(fill) = self.fill {
|
||||||
|
let frame = Rc::make_mut(&mut frames[0].item);
|
||||||
|
let (pos, geometry) = match self.shape {
|
||||||
|
ShapeKind::Square | ShapeKind::Rect => {
|
||||||
|
(Point::zero(), Geometry::Rect(frame.size))
|
||||||
|
}
|
||||||
|
ShapeKind::Circle | ShapeKind::Ellipse => {
|
||||||
|
(frame.size.to_point() / 2.0, Geometry::Ellipse(frame.size))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
frame.prepend(pos, Element::Geometry(geometry, fill));
|
||||||
|
}
|
||||||
|
|
||||||
|
frames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ShapeNode> for LayoutNode {
|
||||||
|
fn from(shape: ShapeNode) -> Self {
|
||||||
|
Self::new(shape)
|
||||||
|
}
|
||||||
|
}
|
@ -50,8 +50,9 @@ impl PageRun {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A dynamic layouting node.
|
/// A dynamic layouting node.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct LayoutNode {
|
pub struct LayoutNode {
|
||||||
node: Box<dyn Layout>,
|
node: Rc<dyn Layout>,
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
hash: u64,
|
hash: u64,
|
||||||
}
|
}
|
||||||
@ -63,7 +64,7 @@ impl LayoutNode {
|
|||||||
where
|
where
|
||||||
T: Layout + 'static,
|
T: Layout + 'static,
|
||||||
{
|
{
|
||||||
Self { node: Box::new(node) }
|
Self { node: Rc::new(node) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new instance from any node that satisifies the required bounds.
|
/// Create a new instance from any node that satisifies the required bounds.
|
||||||
@ -79,7 +80,7 @@ impl LayoutNode {
|
|||||||
state.finish()
|
state.finish()
|
||||||
};
|
};
|
||||||
|
|
||||||
Self { node: Box::new(node), hash }
|
Self { node: Rc::new(node), hash }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,10 +100,16 @@ impl Layout for LayoutNode {
|
|||||||
ctx.level -= 1;
|
ctx.level -= 1;
|
||||||
|
|
||||||
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
||||||
debug_assert!(
|
|
||||||
entry.check(regions),
|
#[cfg(debug_assertions)]
|
||||||
"constraints did not match regions they were created for",
|
if !entry.check(regions) {
|
||||||
|
eprintln!("regions: {:#?}", regions);
|
||||||
|
eprintln!(
|
||||||
|
"constraints: {:#?}",
|
||||||
|
frames.iter().map(|c| c.constraints).collect::<Vec<_>>()
|
||||||
);
|
);
|
||||||
|
panic!("constraints did not match regions they were created for");
|
||||||
|
}
|
||||||
|
|
||||||
ctx.layouts.insert(self.hash, entry);
|
ctx.layouts.insert(self.hash, entry);
|
||||||
frames
|
frames
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
use std::f64::consts::SQRT_2;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use decorum::N64;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::diag::Error;
|
use crate::diag::Error;
|
||||||
use crate::layout::{BackgroundNode, BackgroundShape, FixedNode, ImageNode, PadNode};
|
use crate::layout::{ImageNode, ShapeKind, ShapeNode};
|
||||||
|
|
||||||
/// `image`: An image.
|
/// `image`: An image.
|
||||||
pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
@ -33,52 +30,24 @@ pub fn rect(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let fill = args.named("fill")?;
|
let fill = args.named("fill")?;
|
||||||
let body = args.eat().unwrap_or_default();
|
let body = args.eat();
|
||||||
Ok(rect_impl(width, height, None, fill, body))
|
Ok(shape_impl(ShapeKind::Rect, width, height, fill, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `square`: A square with optional content.
|
/// `square`: A square with optional content.
|
||||||
pub fn square(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn square(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let size = args.named::<Length>("size")?.map(Linear::from);
|
let size = args.named::<Length>("size")?.map(Linear::from);
|
||||||
let width = match size {
|
let width = match size {
|
||||||
Some(size) => Some(size),
|
|
||||||
None => args.named("width")?,
|
None => args.named("width")?,
|
||||||
|
size => size,
|
||||||
};
|
};
|
||||||
let height = match width {
|
let height = match size {
|
||||||
Some(_) => None,
|
|
||||||
None => args.named("height")?,
|
None => args.named("height")?,
|
||||||
|
size => size,
|
||||||
};
|
};
|
||||||
let aspect = Some(N64::from(1.0));
|
|
||||||
let fill = args.named("fill")?;
|
let fill = args.named("fill")?;
|
||||||
let body = args.eat().unwrap_or_default();
|
let body = args.eat();
|
||||||
Ok(rect_impl(width, height, aspect, fill, body))
|
Ok(shape_impl(ShapeKind::Square, width, height, fill, body))
|
||||||
}
|
|
||||||
|
|
||||||
fn rect_impl(
|
|
||||||
width: Option<Linear>,
|
|
||||||
height: Option<Linear>,
|
|
||||||
aspect: Option<N64>,
|
|
||||||
fill: Option<Color>,
|
|
||||||
body: Template,
|
|
||||||
) -> Value {
|
|
||||||
Value::Template(Template::from_inline(move |style| {
|
|
||||||
let mut node = LayoutNode::new(FixedNode {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
aspect,
|
|
||||||
child: body.to_stack(style).into(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(fill) = fill {
|
|
||||||
node = LayoutNode::new(BackgroundNode {
|
|
||||||
shape: BackgroundShape::Rect,
|
|
||||||
fill: Paint::Color(fill),
|
|
||||||
child: node,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
node
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `ellipse`: An ellipse with optional content.
|
/// `ellipse`: An ellipse with optional content.
|
||||||
@ -86,8 +55,8 @@ pub fn ellipse(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let fill = args.named("fill")?;
|
let fill = args.named("fill")?;
|
||||||
let body = args.eat().unwrap_or_default();
|
let body = args.eat();
|
||||||
Ok(ellipse_impl(width, height, None, fill, body))
|
Ok(shape_impl(ShapeKind::Ellipse, width, height, fill, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `circle`: A circle with optional content.
|
/// `circle`: A circle with optional content.
|
||||||
@ -97,46 +66,39 @@ pub fn circle(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
None => args.named("width")?,
|
None => args.named("width")?,
|
||||||
diameter => diameter,
|
diameter => diameter,
|
||||||
};
|
};
|
||||||
let height = match width {
|
let height = match diameter {
|
||||||
None => args.named("height")?,
|
None => args.named("height")?,
|
||||||
width => width,
|
diameter => diameter,
|
||||||
};
|
};
|
||||||
let aspect = Some(N64::from(1.0));
|
|
||||||
let fill = args.named("fill")?;
|
let fill = args.named("fill")?;
|
||||||
let body = args.eat().unwrap_or_default();
|
let body = args.eat();
|
||||||
Ok(ellipse_impl(width, height, aspect, fill, body))
|
Ok(shape_impl(ShapeKind::Circle, width, height, fill, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ellipse_impl(
|
fn shape_impl(
|
||||||
width: Option<Linear>,
|
shape: ShapeKind,
|
||||||
height: Option<Linear>,
|
mut width: Option<Linear>,
|
||||||
aspect: Option<N64>,
|
mut height: Option<Linear>,
|
||||||
fill: Option<Color>,
|
fill: Option<Color>,
|
||||||
body: Template,
|
body: Option<Template>,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
Value::Template(Template::from_inline(move |style| {
|
// Set default shape size if there's no body.
|
||||||
// This padding ratio ensures that the rectangular padded region fits
|
if body.is_none() {
|
||||||
// perfectly into the ellipse.
|
let v = Length::pt(30.0).into();
|
||||||
const PAD: f64 = 0.5 - SQRT_2 / 4.0;
|
height.get_or_insert(v);
|
||||||
|
width.get_or_insert(match shape {
|
||||||
let mut node = LayoutNode::new(FixedNode {
|
ShapeKind::Square | ShapeKind::Circle => v,
|
||||||
width,
|
ShapeKind::Rect | ShapeKind::Ellipse => 1.5 * v,
|
||||||
height,
|
|
||||||
aspect,
|
|
||||||
child: LayoutNode::new(PadNode {
|
|
||||||
padding: Sides::splat(Relative::new(PAD).into()),
|
|
||||||
child: body.to_stack(style).into(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(fill) = fill {
|
|
||||||
node = LayoutNode::new(BackgroundNode {
|
|
||||||
shape: BackgroundShape::Ellipse,
|
|
||||||
fill: Paint::Color(fill),
|
|
||||||
child: node,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
node
|
Value::Template(Template::from_inline(move |style| ShapeNode {
|
||||||
|
shape,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
fill: Some(Paint::Color(
|
||||||
|
fill.unwrap_or(Color::Rgba(RgbaColor::new(175, 175, 175, 255))),
|
||||||
|
)),
|
||||||
|
child: body.as_ref().map(|template| template.to_stack(style).into()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::layout::{FixedNode, GridNode, PadNode, StackChild, StackNode, TrackSizing};
|
use crate::layout::{
|
||||||
|
GridNode, PadNode, ShapeKind, ShapeNode, StackChild, StackNode, TrackSizing,
|
||||||
|
};
|
||||||
use crate::style::{Paper, PaperClass};
|
use crate::style::{Paper, PaperClass};
|
||||||
|
|
||||||
/// `page`: Configure pages.
|
/// `page`: Configure pages.
|
||||||
@ -146,13 +148,15 @@ pub fn v(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
pub fn boxed(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn boxed(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
|
let fill = args.named("fill")?;
|
||||||
let body: Template = args.eat().unwrap_or_default();
|
let body: Template = args.eat().unwrap_or_default();
|
||||||
Ok(Value::Template(Template::from_inline(move |style| {
|
Ok(Value::Template(Template::from_inline(move |style| {
|
||||||
FixedNode {
|
ShapeNode {
|
||||||
|
shape: ShapeKind::Rect,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
aspect: None,
|
fill: fill.map(Paint::Color),
|
||||||
child: body.to_stack(style).into(),
|
child: Some(body.to_stack(style).into()),
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ use crate::diag::{At, TypResult};
|
|||||||
use crate::eval::{Args, Array, EvalContext, Scope, Str, Template, Value};
|
use crate::eval::{Args, Array, EvalContext, Scope, Str, Template, Value};
|
||||||
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
|
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
|
||||||
use crate::geom::*;
|
use crate::geom::*;
|
||||||
use crate::layout::LayoutNode;
|
|
||||||
use crate::style::Style;
|
use crate::style::Style;
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.8 KiB |
@ -10,18 +10,12 @@
|
|||||||
#let university = [*Technische Universität {city}*]
|
#let university = [*Technische Universität {city}*]
|
||||||
#let faculty = [*Fakultät II, Institut for Mathematik*]
|
#let faculty = [*Fakultät II, Institut for Mathematik*]
|
||||||
|
|
||||||
// The `box` function just places content into a rectangular container. When
|
// Backslashs add forced line breaks.
|
||||||
// the only argument to a function is a template, the parentheses can be omitted
|
#university #align(right)[*WiSe 2019/2020*] \
|
||||||
// (i.e. `f[a]` is the same as `f([a])`).
|
#faculty #align(right)[Woche 3] \
|
||||||
#box[
|
Sekretariat MA \
|
||||||
// Backslash adds a forced line break.
|
Dr. Max Mustermann \
|
||||||
#university \
|
Ola Nordmann, John Doe
|
||||||
#faculty \
|
|
||||||
Sekretariat MA \
|
|
||||||
Dr. Max Mustermann \
|
|
||||||
Ola Nordmann, John Doe
|
|
||||||
]
|
|
||||||
#align(right, box[*WiSe 2019/2020* \ Woche 3])
|
|
||||||
|
|
||||||
// Adds vertical spacing.
|
// Adds vertical spacing.
|
||||||
#v(6mm)
|
#v(6mm)
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
// Test the `circle` function.
|
// Test the `circle` function.
|
||||||
|
|
||||||
|
---
|
||||||
|
// Default circle.
|
||||||
|
#circle()
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test auto sizing.
|
// Test auto sizing.
|
||||||
|
|
||||||
@ -29,20 +33,15 @@ Expanded by height.
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test relative sizing.
|
// Test relative sizing.
|
||||||
#rect(width: 100%, height: 50pt, fill: rgb("aaa"))[
|
#rect(width: 100pt, height: 50pt, fill: rgb("aaa"))[
|
||||||
#align(center, center)
|
#align(center, center)
|
||||||
#font(fill: white)
|
#font(fill: white)
|
||||||
#circle(radius: 10pt, fill: eastern)[A]
|
#circle(radius: 10pt, fill: eastern)[A] // D=20pt
|
||||||
#circle(height: 60%, fill: eastern)[B]
|
#circle(height: 60%, fill: eastern)[B] // D=30pt
|
||||||
#circle(width: 20% + 20pt, fill: eastern)[C]
|
#circle(width: 20% + 20pt, fill: eastern)[C] // D=40pt
|
||||||
]
|
]
|
||||||
|
|
||||||
---
|
---
|
||||||
// Radius wins over width and height.
|
// Radius wins over width and height.
|
||||||
// Error: 23-34 unexpected argument
|
// Error: 23-34 unexpected argument
|
||||||
#circle(radius: 10pt, width: 50pt, height: 100pt, fill: eastern)
|
#circle(radius: 10pt, width: 50pt, height: 100pt, fill: eastern)
|
||||||
|
|
||||||
---
|
|
||||||
// Width wins over height.
|
|
||||||
// Error: 9-21 unexpected argument
|
|
||||||
#circle(height: 50pt, width: 20pt, fill: eastern)
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
// Test the `ellipse` function.
|
// Test the `ellipse` function.
|
||||||
|
|
||||||
|
---
|
||||||
|
// Default ellipse.
|
||||||
|
#ellipse()
|
||||||
|
|
||||||
---
|
---
|
||||||
100% rect in 100% ellipse in fixed rect. \
|
100% rect in 100% ellipse in fixed rect. \
|
||||||
#rect(width: 3cm, height: 2cm, fill: rgb("2a631a"),
|
#rect(width: 3cm, height: 2cm, fill: rgb("2a631a"),
|
||||||
|
@ -1,27 +1,25 @@
|
|||||||
// Test shapes.
|
|
||||||
|
|
||||||
---
|
|
||||||
// Test the `rect` function.
|
// Test the `rect` function.
|
||||||
|
|
||||||
|
---
|
||||||
|
// Default rectangle.
|
||||||
|
#rect()
|
||||||
|
|
||||||
|
---
|
||||||
#page(width: 150pt)
|
#page(width: 150pt)
|
||||||
|
|
||||||
// Fit to text.
|
// Fit to text.
|
||||||
#rect(fill: conifer)[Textbox]
|
#rect(fill: conifer)[Textbox]
|
||||||
|
|
||||||
// Empty with fixed width and height.
|
// Empty with fixed width and height.
|
||||||
#rect(width: 3cm, height: 12pt, fill: rgb("CB4CED"))
|
#rect(width: 3cm, height: 12pt, fill: rgb("ed8a4c"))
|
||||||
|
|
||||||
// Fixed width, text height.
|
// Fixed width, text height.
|
||||||
#rect(width: 2cm, fill: rgb("9650D6"), pad(5pt)[Fixed and padded])
|
#rect(width: 2cm, fill: rgb("9650d6"), pad(5pt)[Fixed and padded])
|
||||||
|
|
||||||
// Page width, fixed height.
|
// Page width, fixed height.
|
||||||
#rect(height: 1cm, width: 100%, fill: rgb("734CED"))[Topleft]
|
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]
|
||||||
|
|
||||||
// Not visible, but creates a gap between the boxes above and below
|
// These are inline with text.
|
||||||
// due to line spacing.
|
\{#rect(width: 0.5in, height: 7pt, fill: rgb("d6cd67"))
|
||||||
#rect(width: 1in, fill: rgb("ff0000"))
|
#rect(width: 0.5in, height: 7pt, fill: rgb("edd466"))
|
||||||
|
#rect(width: 0.5in, height: 7pt, fill: rgb("e3be62"))\}
|
||||||
// These are in a row!
|
|
||||||
#rect(width: 0.5in, height: 10pt, fill: rgb("D6CD67"))
|
|
||||||
#rect(width: 0.5in, height: 10pt, fill: rgb("EDD466"))
|
|
||||||
#rect(width: 0.5in, height: 10pt, fill: rgb("E3BE62"))
|
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
// Test the `square` function.
|
// Test the `square` function.
|
||||||
|
|
||||||
|
---
|
||||||
|
// Default square.
|
||||||
|
#square()
|
||||||
|
#square[hey!]
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test auto-sized square.
|
// Test auto-sized square.
|
||||||
#square(fill: eastern)[
|
#square(fill: eastern)[
|
||||||
@ -7,6 +12,7 @@
|
|||||||
#align(center)
|
#align(center)
|
||||||
#pad(5pt)[Typst]
|
#pad(5pt)[Typst]
|
||||||
]
|
]
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test relative-sized child.
|
// Test relative-sized child.
|
||||||
#square(fill: eastern)[
|
#square(fill: eastern)[
|
||||||
@ -15,14 +21,14 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test height overflow.
|
// Test text overflowing height.
|
||||||
#page(width: 75pt, height: 100pt)
|
#page(width: 75pt, height: 100pt)
|
||||||
#square(fill: conifer)[
|
#square(fill: conifer)[
|
||||||
But, soft! what light through yonder window breaks?
|
But, soft! what light through yonder window breaks?
|
||||||
]
|
]
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test width overflow.
|
// Test required height overflowing page.
|
||||||
#page(width: 100pt, height: 75pt)
|
#page(width: 100pt, height: 75pt)
|
||||||
#square(fill: conifer)[
|
#square(fill: conifer)[
|
||||||
But, soft! what light through yonder window breaks?
|
But, soft! what light through yonder window breaks?
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
// Test grid layouts.
|
// Test grid layouts.
|
||||||
|
|
||||||
---
|
---
|
||||||
#let rect(width, fill) = rect(width: width, height: 2cm, fill: fill)
|
#let cell(width, color) = rect(width: width, height: 2cm, fill: color)
|
||||||
|
|
||||||
#page(width: 100pt, height: 140pt)
|
#page(width: 100pt, height: 140pt)
|
||||||
#grid(
|
#grid(
|
||||||
columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%),
|
columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%),
|
||||||
rect(0.5cm, rgb("2a631a")),
|
cell(0.5cm, rgb("2a631a")),
|
||||||
rect(100%, forest),
|
cell(100%, forest),
|
||||||
rect(100%, conifer),
|
cell(100%, conifer),
|
||||||
rect(100%, rgb("ff0000")),
|
cell(100%, rgb("ff0000")),
|
||||||
rect(100%, rgb("00ff00")),
|
cell(100%, rgb("00ff00")),
|
||||||
rect(80%, rgb("00faf0")),
|
cell(80%, rgb("00faf0")),
|
||||||
rect(1cm, rgb("00ff00")),
|
cell(1cm, rgb("00ff00")),
|
||||||
rect(0.5cm, rgb("2a631a")),
|
cell(0.5cm, rgb("2a631a")),
|
||||||
rect(100%, forest),
|
cell(100%, forest),
|
||||||
rect(100%, conifer),
|
cell(100%, conifer),
|
||||||
rect(100%, rgb("ff0000")),
|
cell(100%, rgb("ff0000")),
|
||||||
rect(100%, rgb("00ff00")),
|
cell(100%, rgb("00ff00")),
|
||||||
)
|
)
|
||||||
|
|
||||||
#grid()
|
#grid()
|
||||||
@ -51,6 +51,7 @@
|
|||||||
#grid(
|
#grid(
|
||||||
columns: (1fr,),
|
columns: (1fr,),
|
||||||
rows: (1fr, auto, 2fr),
|
rows: (1fr, auto, 2fr),
|
||||||
[], rect(width: 100%)[A bit more to the top], [],
|
[],
|
||||||
|
box(width: 100%)[A bit more to the top],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
|