mirror of
https://github.com/typst/typst
synced 2025-06-28 08:12:53 +08:00
Layout improvements
This commit is contained in:
parent
304d9dd110
commit
393d74f9bb
@ -6,7 +6,7 @@ use std::ops::{Add, AddAssign};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::diag::StrResult;
|
||||
use crate::geom::{Align, Dir, GenAxis, Length, Linear, Paint, Sides, Size};
|
||||
use crate::geom::{Align, Dir, Length, Linear, Paint, Sides, Size, SpecAxis};
|
||||
use crate::layout::{Layout, PackedNode};
|
||||
use crate::library::{
|
||||
Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode,
|
||||
@ -33,7 +33,7 @@ enum TemplateNode {
|
||||
/// Plain text.
|
||||
Text(EcoString),
|
||||
/// Spacing.
|
||||
Spacing(GenAxis, Spacing),
|
||||
Spacing(SpecAxis, Spacing),
|
||||
/// A decorated template.
|
||||
Decorated(Decoration, Template),
|
||||
/// An inline node builder.
|
||||
@ -108,7 +108,7 @@ impl Template {
|
||||
}
|
||||
|
||||
/// Add spacing along an axis.
|
||||
pub fn spacing(&mut self, axis: GenAxis, spacing: Spacing) {
|
||||
pub fn spacing(&mut self, axis: SpecAxis, spacing: Spacing) {
|
||||
self.make_mut().push(TemplateNode::Spacing(axis, spacing));
|
||||
}
|
||||
|
||||
@ -349,13 +349,13 @@ impl Builder {
|
||||
}
|
||||
|
||||
/// Push spacing into the active paragraph or flow depending on the `axis`.
|
||||
fn spacing(&mut self, axis: GenAxis, spacing: Spacing) {
|
||||
fn spacing(&mut self, axis: SpecAxis, spacing: Spacing) {
|
||||
match axis {
|
||||
GenAxis::Block => {
|
||||
SpecAxis::Vertical => {
|
||||
self.flow.finish_par(&self.style);
|
||||
self.flow.push_hard(FlowChild::Spacing(spacing));
|
||||
}
|
||||
GenAxis::Inline => {
|
||||
SpecAxis::Horizontal => {
|
||||
self.flow.par.push_hard(ParChild::Spacing(spacing));
|
||||
}
|
||||
}
|
||||
|
54
src/frame.rs
54
src/frame.rs
@ -6,7 +6,7 @@ use std::rc::Rc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::font::FaceId;
|
||||
use crate::geom::{Em, Length, Paint, Path, Point, Size, Transform};
|
||||
use crate::geom::{Align, Em, Length, Paint, Path, Point, Size, Spec, Transform};
|
||||
use crate::image::ImageId;
|
||||
|
||||
/// A finished layout with elements at fixed positions.
|
||||
@ -23,9 +23,9 @@ pub struct Frame {
|
||||
impl Frame {
|
||||
/// Create a new, empty frame.
|
||||
#[track_caller]
|
||||
pub fn new(size: Size, baseline: Length) -> Self {
|
||||
pub fn new(size: Size) -> Self {
|
||||
assert!(size.is_finite());
|
||||
Self { size, baseline, elements: vec![] }
|
||||
Self { size, baseline: size.h, elements: vec![] }
|
||||
}
|
||||
|
||||
/// Add an element at a position in the background.
|
||||
@ -55,12 +55,46 @@ impl Frame {
|
||||
}
|
||||
}
|
||||
|
||||
/// Move all elements in the frame by an offset.
|
||||
/// Resize the frame to a new size, distributing new space according to the
|
||||
/// given alignments.
|
||||
pub fn resize(&mut self, new: Size, aligns: Spec<Align>) {
|
||||
let offset = Point::new(
|
||||
aligns.x.resolve(new.w - self.size.w),
|
||||
aligns.y.resolve(new.h - self.size.h),
|
||||
);
|
||||
self.size = new;
|
||||
self.baseline += offset.y;
|
||||
self.translate(offset);
|
||||
}
|
||||
|
||||
/// Move the contents of the frame by an offset.
|
||||
pub fn translate(&mut self, offset: Point) {
|
||||
for (point, _) in &mut self.elements {
|
||||
*point += offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// Arbitrarily transform the contents of the frame.
|
||||
pub fn transform(&mut self, transform: Transform) {
|
||||
self.group(|g| g.transform = transform);
|
||||
}
|
||||
|
||||
/// Clip the contents of a frame to its size.
|
||||
pub fn clip(&mut self) {
|
||||
self.group(|g| g.clips = true);
|
||||
}
|
||||
|
||||
/// Wrap the frame's contents in a group and modify that group with `f`.
|
||||
pub fn group<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Group),
|
||||
{
|
||||
let mut wrapper = Frame { elements: vec![], ..*self };
|
||||
let mut group = Group::new(Rc::new(std::mem::take(self)));
|
||||
f(&mut group);
|
||||
wrapper.push(Point::zero(), Element::Group(group));
|
||||
*self = wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Frame {
|
||||
@ -116,18 +150,6 @@ impl Group {
|
||||
clips: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the group's transform.
|
||||
pub fn transform(mut self, transform: Transform) -> Self {
|
||||
self.transform = transform;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether the group should be a clipping boundary.
|
||||
pub fn clips(mut self, clips: bool) -> Self {
|
||||
self.clips = clips;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A run of shaped text.
|
||||
|
@ -1,18 +1,18 @@
|
||||
use super::*;
|
||||
|
||||
/// A container with an inline and a block component.
|
||||
/// A container with a main and cross component.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Gen<T> {
|
||||
/// The inline component.
|
||||
pub inline: T,
|
||||
/// The block component.
|
||||
pub block: T,
|
||||
/// The main component.
|
||||
pub cross: T,
|
||||
/// The cross component.
|
||||
pub main: T,
|
||||
}
|
||||
|
||||
impl<T> Gen<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub const fn new(inline: T, block: T) -> Self {
|
||||
Self { inline, block }
|
||||
pub const fn new(cross: T, main: T) -> Self {
|
||||
Self { cross, main }
|
||||
}
|
||||
|
||||
/// Create a new instance with two equal components.
|
||||
@ -20,7 +20,7 @@ impl<T> Gen<T> {
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Self { inline: value.clone(), block: value }
|
||||
Self { cross: value.clone(), main: value }
|
||||
}
|
||||
|
||||
/// Maps the individual fields with `f`.
|
||||
@ -28,17 +28,14 @@ impl<T> Gen<T> {
|
||||
where
|
||||
F: FnMut(T) -> U,
|
||||
{
|
||||
Gen {
|
||||
inline: f(self.inline),
|
||||
block: f(self.block),
|
||||
}
|
||||
Gen { cross: f(self.cross), main: f(self.main) }
|
||||
}
|
||||
|
||||
/// Convert to the specific representation, given the current block axis.
|
||||
pub fn to_spec(self, block: SpecAxis) -> Spec<T> {
|
||||
match block {
|
||||
SpecAxis::Horizontal => Spec::new(self.block, self.inline),
|
||||
SpecAxis::Vertical => Spec::new(self.inline, self.block),
|
||||
pub fn to_spec(self, main: SpecAxis) -> Spec<T> {
|
||||
match main {
|
||||
SpecAxis::Horizontal => Spec::new(self.main, self.cross),
|
||||
SpecAxis::Vertical => Spec::new(self.cross, self.main),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,19 +44,19 @@ impl Gen<Length> {
|
||||
/// The zero value.
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
inline: Length::zero(),
|
||||
block: Length::zero(),
|
||||
cross: Length::zero(),
|
||||
main: Length::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to a point.
|
||||
pub fn to_point(self, block: SpecAxis) -> Point {
|
||||
self.to_spec(block).to_point()
|
||||
pub fn to_point(self, main: SpecAxis) -> Point {
|
||||
self.to_spec(main).to_point()
|
||||
}
|
||||
|
||||
/// Convert to a size.
|
||||
pub fn to_size(self, block: SpecAxis) -> Size {
|
||||
self.to_spec(block).to_size()
|
||||
pub fn to_size(self, main: SpecAxis) -> Size {
|
||||
self.to_spec(main).to_size()
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,8 +64,8 @@ impl<T> Gen<Option<T>> {
|
||||
/// Unwrap the individual fields.
|
||||
pub fn unwrap_or(self, other: Gen<T>) -> Gen<T> {
|
||||
Gen {
|
||||
inline: self.inline.unwrap_or(other.inline),
|
||||
block: self.block.unwrap_or(other.block),
|
||||
cross: self.cross.unwrap_or(other.cross),
|
||||
main: self.main.unwrap_or(other.main),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -78,40 +75,40 @@ impl<T> Get<GenAxis> for Gen<T> {
|
||||
|
||||
fn get(self, axis: GenAxis) -> T {
|
||||
match axis {
|
||||
GenAxis::Inline => self.inline,
|
||||
GenAxis::Block => self.block,
|
||||
GenAxis::Cross => self.cross,
|
||||
GenAxis::Main => self.main,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: GenAxis) -> &mut T {
|
||||
match axis {
|
||||
GenAxis::Inline => &mut self.inline,
|
||||
GenAxis::Block => &mut self.block,
|
||||
GenAxis::Cross => &mut self.cross,
|
||||
GenAxis::Main => &mut self.main,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Gen<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "Gen({:?}, {:?})", self.inline, self.block)
|
||||
write!(f, "Gen({:?}, {:?})", self.cross, self.main)
|
||||
}
|
||||
}
|
||||
|
||||
/// The two generic layouting axes.
|
||||
/// Two generic axes of a container.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum GenAxis {
|
||||
/// The axis words and lines are set along.
|
||||
Inline,
|
||||
/// The axis paragraphs and pages are set along.
|
||||
Block,
|
||||
/// The minor axis.
|
||||
Cross,
|
||||
/// The major axis.
|
||||
Main,
|
||||
}
|
||||
|
||||
impl GenAxis {
|
||||
/// The other axis.
|
||||
pub fn other(self) -> Self {
|
||||
match self {
|
||||
Self::Inline => Self::Block,
|
||||
Self::Block => Self::Inline,
|
||||
Self::Cross => Self::Main,
|
||||
Self::Main => Self::Cross,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,8 @@ impl Point {
|
||||
}
|
||||
|
||||
/// Convert to the generic representation.
|
||||
pub const fn to_gen(self, block: SpecAxis) -> Gen<Length> {
|
||||
match block {
|
||||
pub const fn to_gen(self, main: SpecAxis) -> Gen<Length> {
|
||||
match main {
|
||||
SpecAxis::Horizontal => Gen::new(self.y, self.x),
|
||||
SpecAxis::Vertical => Gen::new(self.x, self.y),
|
||||
}
|
||||
|
@ -51,8 +51,8 @@ impl Size {
|
||||
}
|
||||
|
||||
/// Convert to the generic representation.
|
||||
pub const fn to_gen(self, block: SpecAxis) -> Gen<Length> {
|
||||
match block {
|
||||
pub const fn to_gen(self, main: SpecAxis) -> Gen<Length> {
|
||||
match main {
|
||||
SpecAxis::Horizontal => Gen::new(self.h, self.w),
|
||||
SpecAxis::Vertical => Gen::new(self.w, self.h),
|
||||
}
|
||||
|
@ -57,8 +57,8 @@ impl<T> Spec<T> {
|
||||
}
|
||||
|
||||
/// Convert to the generic representation.
|
||||
pub fn to_gen(self, block: SpecAxis) -> Gen<T> {
|
||||
match block {
|
||||
pub fn to_gen(self, main: SpecAxis) -> Gen<T> {
|
||||
match main {
|
||||
SpecAxis::Horizontal => Gen::new(self.y, self.x),
|
||||
SpecAxis::Vertical => Gen::new(self.x, self.y),
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::Regions;
|
||||
use crate::frame::Frame;
|
||||
use crate::geom::{Length, Linear, Size, Spec};
|
||||
|
||||
@ -59,6 +60,17 @@ impl Constraints {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create tight constraints for a region.
|
||||
pub fn tight(regions: &Regions) -> Self {
|
||||
Self {
|
||||
min: Spec::default(),
|
||||
max: Spec::default(),
|
||||
exact: regions.current.to_spec().map(Some),
|
||||
base: regions.base.to_spec().map(Some),
|
||||
expand: regions.expand,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether the constraints are fullfilled in a region with the given
|
||||
/// properties.
|
||||
pub fn check(&self, current: Size, base: Size, expand: Spec<bool>) -> bool {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::geom::{Size, Spec};
|
||||
use crate::geom::{Length, Size, Spec};
|
||||
|
||||
/// A sequence of regions to layout into.
|
||||
#[derive(Debug, Clone)]
|
||||
@ -13,9 +13,6 @@ pub struct Regions {
|
||||
pub last: Option<Size>,
|
||||
/// Whether nodes should expand to fill the regions instead of shrinking to
|
||||
/// fit the content.
|
||||
///
|
||||
/// This property is only handled by nodes that have the ability to control
|
||||
/// their own size.
|
||||
pub expand: Spec<bool>,
|
||||
}
|
||||
|
||||
@ -52,13 +49,26 @@ impl Regions {
|
||||
regions
|
||||
}
|
||||
|
||||
/// Whether `current` is a fully sized (untouched) copy of the last region.
|
||||
/// Whether the current region is full and a region break is called for.
|
||||
pub fn is_full(&self) -> bool {
|
||||
Length::zero().fits(self.current.h) && !self.in_last()
|
||||
}
|
||||
|
||||
/// Whether `current` is the last usable region.
|
||||
///
|
||||
/// If this is true, calling `next()` will have no effect.
|
||||
pub fn in_full_last(&self) -> bool {
|
||||
pub fn in_last(&self) -> bool {
|
||||
self.backlog.len() == 0 && self.last.map_or(true, |size| self.current == size)
|
||||
}
|
||||
|
||||
/// Advance to the next region if there is any.
|
||||
pub fn next(&mut self) {
|
||||
if let Some(size) = self.backlog.next().or(self.last) {
|
||||
self.current = size;
|
||||
self.base = size;
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator that returns pairs of `(current, base)` that are equivalent
|
||||
/// to what would be produced by calling [`next()`](Self::next) repeatedly
|
||||
/// until all regions are exhausted.
|
||||
@ -69,14 +79,6 @@ impl Regions {
|
||||
first.chain(backlog.chain(last).map(|&s| (s, s)))
|
||||
}
|
||||
|
||||
/// Advance to the next region if there is any.
|
||||
pub fn next(&mut self) {
|
||||
if let Some(size) = self.backlog.next().or(self.last) {
|
||||
self.current = size;
|
||||
self.base = size;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutate all contained sizes in place.
|
||||
pub fn mutate<F>(&mut self, mut f: F)
|
||||
where
|
||||
|
@ -34,28 +34,29 @@ impl Layout for AlignNode {
|
||||
pod.expand.x &= self.aligns.x.is_none();
|
||||
pod.expand.y &= self.aligns.y.is_none();
|
||||
|
||||
// Layout the child.
|
||||
let mut frames = self.child.layout(ctx, &pod);
|
||||
for (Constrained { item: frame, cts }, (current, _)) in
|
||||
|
||||
for (Constrained { item: frame, cts }, (current, base)) in
|
||||
frames.iter_mut().zip(regions.iter())
|
||||
{
|
||||
let canvas = Size::new(
|
||||
// The possibly larger size in which we align the frame.
|
||||
let new = Size::new(
|
||||
if regions.expand.x { current.w } else { frame.size.w },
|
||||
if regions.expand.y { current.h } else { frame.size.h },
|
||||
);
|
||||
|
||||
let aligns = self.aligns.unwrap_or(Spec::new(Align::Left, Align::Top));
|
||||
let offset = Point::new(
|
||||
aligns.x.resolve(canvas.w - frame.size.w),
|
||||
aligns.y.resolve(canvas.h - frame.size.h),
|
||||
);
|
||||
|
||||
let frame = Rc::make_mut(frame);
|
||||
frame.size = canvas;
|
||||
frame.baseline += offset.y;
|
||||
frame.translate(offset);
|
||||
Rc::make_mut(frame).resize(new, aligns);
|
||||
|
||||
// Set constraints.
|
||||
cts.expand = regions.expand;
|
||||
cts.exact = current.to_spec().map(Some);
|
||||
cts.base.x.and_set(Some(base.w));
|
||||
cts.base.y.and_set(Some(base.h));
|
||||
cts.exact = Spec::new(
|
||||
regions.expand.x.then(|| current.w),
|
||||
regions.expand.y.then(|| current.h),
|
||||
);
|
||||
}
|
||||
|
||||
frames
|
||||
|
@ -52,7 +52,7 @@ impl Layout for FlowNode {
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
FlowLayouter::new(self, regions.clone()).layout(ctx)
|
||||
FlowLayouter::new(self, regions).layout(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ struct FlowLayouter<'a> {
|
||||
children: &'a [FlowChild],
|
||||
/// Whether the flow should expand to fill the region.
|
||||
expand: Spec<bool>,
|
||||
/// The region to layout into.
|
||||
/// The regions to layout children into.
|
||||
regions: Regions,
|
||||
/// The full size of `regions.current` that was available before we started
|
||||
/// subtracting.
|
||||
@ -109,15 +109,18 @@ enum FlowItem {
|
||||
|
||||
impl<'a> FlowLayouter<'a> {
|
||||
/// Create a new flow layouter.
|
||||
fn new(flow: &'a FlowNode, mut regions: Regions) -> Self {
|
||||
// Disable vertical expansion for children.
|
||||
fn new(flow: &'a FlowNode, regions: &Regions) -> Self {
|
||||
let expand = regions.expand;
|
||||
let full = regions.current;
|
||||
|
||||
// Disable vertical expansion for children.
|
||||
let mut regions = regions.clone();
|
||||
regions.expand.y = false;
|
||||
|
||||
Self {
|
||||
children: &flow.children,
|
||||
expand,
|
||||
full: regions.current,
|
||||
full,
|
||||
regions,
|
||||
used: Size::zero(),
|
||||
fr: Fractional::zero(),
|
||||
@ -129,6 +132,10 @@ impl<'a> FlowLayouter<'a> {
|
||||
/// Layout all children.
|
||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
|
||||
for child in self.children {
|
||||
if self.regions.is_full() {
|
||||
self.finish_region();
|
||||
}
|
||||
|
||||
match *child {
|
||||
FlowChild::Spacing(Spacing::Linear(v)) => {
|
||||
self.layout_absolute(v);
|
||||
@ -215,7 +222,7 @@ impl<'a> FlowLayouter<'a> {
|
||||
size.h = self.full.h;
|
||||
}
|
||||
|
||||
let mut output = Frame::new(size, size.h);
|
||||
let mut output = Frame::new(size);
|
||||
let mut before = Length::zero();
|
||||
let mut ruler = Align::Top;
|
||||
let mut first = true;
|
||||
|
@ -99,11 +99,11 @@ struct GridLayouter<'a> {
|
||||
cols: Vec<TrackSizing>,
|
||||
/// The row tracks including gutter tracks.
|
||||
rows: Vec<TrackSizing>,
|
||||
/// The regions to layout into.
|
||||
/// The regions to layout children into.
|
||||
regions: Regions,
|
||||
/// Resolved column sizes.
|
||||
rcols: Vec<Length>,
|
||||
/// The full block size of the current region.
|
||||
/// The full height of the current region.
|
||||
full: Length,
|
||||
/// The used-up size of the current region. The horizontal size is
|
||||
/// determined once after columns are resolved and not touched again.
|
||||
@ -353,6 +353,12 @@ impl<'a> GridLayouter<'a> {
|
||||
/// Layout the grid row-by-row.
|
||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
|
||||
for y in 0 .. self.rows.len() {
|
||||
// Skip to next region if current one is full, but only for content
|
||||
// rows, not for gutter rows.
|
||||
if y % 2 == 0 && self.regions.is_full() {
|
||||
self.finish_region(ctx);
|
||||
}
|
||||
|
||||
match self.rows[y] {
|
||||
TrackSizing::Auto => self.layout_auto_row(ctx, y),
|
||||
TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y),
|
||||
@ -368,8 +374,8 @@ impl<'a> GridLayouter<'a> {
|
||||
self.finished
|
||||
}
|
||||
|
||||
/// Layout a row with automatic size along the block axis. Such a row may
|
||||
/// break across multiple regions.
|
||||
/// Layout a row with automatic height. Such a row may break across multiple
|
||||
/// regions.
|
||||
fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) {
|
||||
let mut resolved: Vec<Length> = vec![];
|
||||
|
||||
@ -388,10 +394,14 @@ impl<'a> GridLayouter<'a> {
|
||||
let mut sizes =
|
||||
node.layout(ctx, &pod).into_iter().map(|frame| frame.item.size.h);
|
||||
|
||||
// For each region, we want to know the maximum height any
|
||||
// column requires.
|
||||
for (target, size) in resolved.iter_mut().zip(&mut sizes) {
|
||||
target.set_max(size);
|
||||
}
|
||||
|
||||
// New heights are maximal by virtue of being new. Note that
|
||||
// this extend only uses the rest of the sizes iterator.
|
||||
resolved.extend(sizes);
|
||||
}
|
||||
}
|
||||
@ -431,16 +441,16 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout a row with linear sizing along the block axis. Such a row cannot
|
||||
/// break across multiple regions, but it may force a region break.
|
||||
/// Layout a row with linear height. Such a row cannot break across multiple
|
||||
/// regions, but it may force a region break.
|
||||
fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) {
|
||||
let resolved = v.resolve(self.regions.base.h);
|
||||
let frame = self.layout_single_row(ctx, resolved, y);
|
||||
|
||||
// Skip to fitting region.
|
||||
let length = frame.size.h;
|
||||
while !self.regions.current.h.fits(length) && !self.regions.in_full_last() {
|
||||
self.cts.max.y = Some(self.used.h + length);
|
||||
let height = frame.size.h;
|
||||
while !self.regions.current.h.fits(height) && !self.regions.in_last() {
|
||||
self.cts.max.y = Some(self.used.h + height);
|
||||
self.finish_region(ctx);
|
||||
|
||||
// Don't skip multiple regions for gutter and don't push a row.
|
||||
@ -452,14 +462,14 @@ impl<'a> GridLayouter<'a> {
|
||||
self.push_row(frame);
|
||||
}
|
||||
|
||||
/// Layout a row with a fixed size along the block axis and return its frame.
|
||||
/// Layout a row with fixed height and return its frame.
|
||||
fn layout_single_row(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
height: Length,
|
||||
y: usize,
|
||||
) -> Frame {
|
||||
let mut output = Frame::new(Size::new(self.used.w, height), height);
|
||||
let mut output = Frame::new(Size::new(self.used.w, height));
|
||||
let mut pos = Point::zero();
|
||||
|
||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||
@ -496,7 +506,7 @@ impl<'a> GridLayouter<'a> {
|
||||
// Prepare frames.
|
||||
let mut outputs: Vec<_> = heights
|
||||
.iter()
|
||||
.map(|&h| Frame::new(Size::new(self.used.w, h), h))
|
||||
.map(|&h| Frame::new(Size::new(self.used.w, h)))
|
||||
.collect();
|
||||
|
||||
// Prepare regions.
|
||||
@ -553,7 +563,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
// The frame for the region.
|
||||
let mut output = Frame::new(size, size.h);
|
||||
let mut output = Frame::new(size);
|
||||
let mut pos = Point::zero();
|
||||
|
||||
// Place finished rows and layout fractional rows.
|
||||
|
@ -43,12 +43,12 @@ impl Layout for ImageNode {
|
||||
let &Regions { current, expand, .. } = regions;
|
||||
|
||||
let img = ctx.images.get(self.id);
|
||||
let pixel_w = img.width() as f64;
|
||||
let pixel_h = img.height() as f64;
|
||||
let pxw = img.width() as f64;
|
||||
let pxh = img.height() as f64;
|
||||
|
||||
let region_ratio = current.w / current.h;
|
||||
let pixel_ratio = pixel_w / pixel_h;
|
||||
let wide = region_ratio < pixel_ratio;
|
||||
let pixel_ratio = pxw / pxh;
|
||||
let current_ratio = current.w / current.h;
|
||||
let wide = pixel_ratio > current_ratio;
|
||||
|
||||
// The space into which the image will be placed according to its fit.
|
||||
let canvas = if expand.x && expand.y {
|
||||
@ -58,7 +58,7 @@ impl Layout for ImageNode {
|
||||
} else if current.h.is_finite() {
|
||||
Size::new(current.w.min(current.h * pixel_ratio), current.h)
|
||||
} else {
|
||||
Size::new(Length::pt(pixel_w), Length::pt(pixel_h))
|
||||
Size::new(Length::pt(pxw), Length::pt(pxh))
|
||||
};
|
||||
|
||||
// The actual size of the fitted image.
|
||||
@ -73,26 +73,19 @@ impl Layout for ImageNode {
|
||||
ImageFit::Stretch => canvas,
|
||||
};
|
||||
|
||||
// Position the image so that it is centered in the canvas.
|
||||
let mut frame = Frame::new(canvas, canvas.h);
|
||||
frame.push(
|
||||
(canvas - size).to_point() / 2.0,
|
||||
Element::Image(self.id, size),
|
||||
);
|
||||
// First, place the image in a frame of exactly its size and then resize
|
||||
// the frame to the canvas size, center aligning the image in the
|
||||
// process.
|
||||
let mut frame = Frame::new(size);
|
||||
frame.push(Point::zero(), Element::Image(self.id, size));
|
||||
frame.resize(canvas, Spec::new(Align::Center, Align::Horizon));
|
||||
|
||||
// Create a clipping group if the image mode is `cover`.
|
||||
// Create a clipping group if the fit mode is "cover".
|
||||
if self.fit == ImageFit::Cover {
|
||||
let mut wrapper = Frame::new(canvas, canvas.h);
|
||||
wrapper.push(
|
||||
Point::zero(),
|
||||
Element::Group(Group::new(Rc::new(frame)).clips(true)),
|
||||
);
|
||||
frame = wrapper;
|
||||
frame.clip();
|
||||
}
|
||||
|
||||
let mut cts = Constraints::new(regions.expand);
|
||||
cts.exact = regions.current.to_spec().map(Some);
|
||||
vec![frame.constrain(cts)]
|
||||
vec![frame.constrain(Constraints::tight(regions))]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,12 +241,17 @@ impl<'a> ParLayouter<'a> {
|
||||
starts.push((range.start, deco));
|
||||
}
|
||||
ParChild::Undecorate => {
|
||||
let (start, deco) = starts.pop().unwrap();
|
||||
decos.push((start .. range.end, deco));
|
||||
if let Some((start, deco)) = starts.pop() {
|
||||
decos.push((start .. range.end, deco));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (start, deco) in starts {
|
||||
decos.push((start .. bidi.text.len(), deco));
|
||||
}
|
||||
|
||||
Self {
|
||||
align: par.align,
|
||||
leading: par.leading,
|
||||
@ -307,7 +312,7 @@ impl<'a> ParLayouter<'a> {
|
||||
|
||||
// If the line does not fit vertically, we start a new region.
|
||||
while !stack.regions.current.h.fits(line.size.h) {
|
||||
if stack.regions.in_full_last() {
|
||||
if stack.regions.in_last() {
|
||||
stack.overflowing = true;
|
||||
break;
|
||||
}
|
||||
@ -487,8 +492,9 @@ impl<'a> LineLayout<'a> {
|
||||
let size = Size::new(self.size.w.max(width), self.size.h);
|
||||
let remaining = size.w - self.size.w;
|
||||
|
||||
let mut output = Frame::new(size, self.baseline);
|
||||
let mut output = Frame::new(size);
|
||||
let mut offset = Length::zero();
|
||||
output.baseline = self.baseline;
|
||||
|
||||
for (range, item) in self.reordered() {
|
||||
let mut position = |mut frame: Frame| {
|
||||
@ -621,7 +627,7 @@ impl<'a> LineStack<'a> {
|
||||
self.cts.exact = self.full.to_spec().map(Some);
|
||||
}
|
||||
|
||||
let mut output = Frame::new(self.size, self.size.h);
|
||||
let mut output = Frame::new(self.size);
|
||||
let mut offset = Length::zero();
|
||||
let mut first = true;
|
||||
|
||||
|
@ -165,12 +165,16 @@ impl Layout for ShapeNode {
|
||||
if regions.expand.y { regions.current.h } else { default },
|
||||
);
|
||||
|
||||
// Don't overflow the region.
|
||||
size.w = size.w.min(regions.current.w);
|
||||
size.h = size.h.min(regions.current.h);
|
||||
|
||||
if matches!(self.kind, ShapeKind::Square | ShapeKind::Circle) {
|
||||
size.w = size.w.min(size.h);
|
||||
size.h = size.w;
|
||||
}
|
||||
|
||||
Frame::new(size, size.h)
|
||||
Frame::new(size)
|
||||
};
|
||||
|
||||
// Add fill and/or stroke.
|
||||
@ -197,9 +201,6 @@ impl Layout for ShapeNode {
|
||||
);
|
||||
|
||||
// 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)]
|
||||
vec![frame.constrain(Constraints::tight(regions))]
|
||||
}
|
||||
}
|
||||
|
@ -35,10 +35,6 @@ impl Layout for SizedNode {
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
// Resolve width and height relative to the region's base.
|
||||
let width = self.sizing.x.map(|w| w.resolve(regions.base.w));
|
||||
let height = self.sizing.y.map(|h| h.resolve(regions.base.h));
|
||||
|
||||
// Generate constraints.
|
||||
let mut cts = Constraints::new(regions.expand);
|
||||
cts.set_base_if_linear(regions.base, self.sizing);
|
||||
@ -56,6 +52,10 @@ impl Layout for SizedNode {
|
||||
cts.base.y = Some(regions.base.h);
|
||||
}
|
||||
|
||||
// Resolve width and height relative to the region's base.
|
||||
let width = self.sizing.x.map(|w| w.resolve(regions.base.w));
|
||||
let height = self.sizing.y.map(|h| h.resolve(regions.base.h));
|
||||
|
||||
// The "pod" is the region into which the child will be layouted.
|
||||
let pod = {
|
||||
let size = Size::new(
|
||||
|
@ -3,14 +3,14 @@ use super::prelude::*;
|
||||
/// `h`: Horizontal spacing.
|
||||
pub fn h(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let mut template = Template::new();
|
||||
template.spacing(GenAxis::Inline, args.expect("spacing")?);
|
||||
template.spacing(SpecAxis::Horizontal, args.expect("spacing")?);
|
||||
Ok(Value::Template(template))
|
||||
}
|
||||
|
||||
/// `v`: Vertical spacing.
|
||||
pub fn v(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let mut template = Template::new();
|
||||
template.spacing(GenAxis::Block, args.expect("spacing")?);
|
||||
template.spacing(SpecAxis::Vertical, args.expect("spacing")?);
|
||||
Ok(Value::Template(template))
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ impl Layout for StackNode {
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
StackLayouter::new(self, regions.clone()).layout(ctx)
|
||||
StackLayouter::new(self, regions).layout(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ struct StackLayouter<'a> {
|
||||
axis: SpecAxis,
|
||||
/// Whether the stack should expand to fill the region.
|
||||
expand: Spec<bool>,
|
||||
/// The region to layout into.
|
||||
/// The regions to layout children into.
|
||||
regions: Regions,
|
||||
/// The full size of `regions.current` that was available before we started
|
||||
/// subtracting.
|
||||
@ -123,17 +123,21 @@ enum StackItem {
|
||||
|
||||
impl<'a> StackLayouter<'a> {
|
||||
/// Create a new stack layouter.
|
||||
fn new(stack: &'a StackNode, mut regions: Regions) -> Self {
|
||||
// Disable expansion along the block axis for children.
|
||||
fn new(stack: &'a StackNode, regions: &Regions) -> Self {
|
||||
let axis = stack.dir.axis();
|
||||
let expand = regions.expand;
|
||||
let full = regions.current;
|
||||
|
||||
|
||||
// Disable expansion along the block axis for children.
|
||||
let mut regions = regions.clone();
|
||||
regions.expand.set(axis, false);
|
||||
|
||||
Self {
|
||||
stack,
|
||||
axis,
|
||||
expand,
|
||||
full: regions.current,
|
||||
full,
|
||||
regions,
|
||||
used: Gen::zero(),
|
||||
fr: Fractional::zero(),
|
||||
@ -145,6 +149,10 @@ impl<'a> StackLayouter<'a> {
|
||||
/// Layout all children.
|
||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
|
||||
for child in &self.stack.children {
|
||||
if self.regions.is_full() {
|
||||
self.finish_region();
|
||||
}
|
||||
|
||||
match *child {
|
||||
StackChild::Spacing(Spacing::Linear(v)) => {
|
||||
self.layout_absolute(v);
|
||||
@ -167,10 +175,10 @@ impl<'a> StackLayouter<'a> {
|
||||
fn layout_absolute(&mut self, amount: Linear) {
|
||||
// Resolve the linear, limiting it to the remaining available space.
|
||||
let remaining = self.regions.current.get_mut(self.axis);
|
||||
let resolved = amount.resolve(self.full.get(self.axis));
|
||||
let resolved = amount.resolve(self.regions.base.get(self.axis));
|
||||
let limited = resolved.min(*remaining);
|
||||
*remaining -= limited;
|
||||
self.used.block += limited;
|
||||
self.used.main += limited;
|
||||
self.items.push(StackItem::Absolute(resolved));
|
||||
}
|
||||
|
||||
@ -187,9 +195,9 @@ impl<'a> StackLayouter<'a> {
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
// Grow our size, shrink the region and save the frame for later.
|
||||
let size = frame.item.size.to_gen(self.axis);
|
||||
self.used.block += size.block;
|
||||
self.used.inline.set_max(size.inline);
|
||||
*self.regions.current.get_mut(self.axis) -= size.block;
|
||||
self.used.main += size.main;
|
||||
self.used.cross.set_max(size.cross);
|
||||
*self.regions.current.get_mut(self.axis) -= size.main;
|
||||
self.items.push(StackItem::Frame(frame.item, align));
|
||||
|
||||
if i + 1 < len {
|
||||
@ -210,13 +218,13 @@ impl<'a> StackLayouter<'a> {
|
||||
|
||||
// Expand fully if there are fr spacings.
|
||||
let full = self.full.get(self.axis);
|
||||
let remaining = full - self.used.block;
|
||||
let remaining = full - self.used.main;
|
||||
if self.fr.get() > 0.0 && full.is_finite() {
|
||||
self.used.block = full;
|
||||
self.used.main = full;
|
||||
size.set(self.axis, full);
|
||||
}
|
||||
|
||||
let mut output = Frame::new(size, size.h);
|
||||
let mut output = Frame::new(size);
|
||||
let mut before = Length::zero();
|
||||
let mut ruler: Align = self.stack.dir.start().into();
|
||||
|
||||
@ -239,11 +247,11 @@ impl<'a> StackLayouter<'a> {
|
||||
// Align along the block axis.
|
||||
let parent = size.get(self.axis);
|
||||
let child = frame.size.get(self.axis);
|
||||
let block = ruler.resolve(parent - self.used.block)
|
||||
let block = ruler.resolve(parent - self.used.main)
|
||||
+ if self.stack.dir.is_positive() {
|
||||
before
|
||||
} else {
|
||||
self.used.block - child - before
|
||||
self.used.main - child - before
|
||||
};
|
||||
|
||||
let pos = Gen::new(Length::zero(), block).to_point(self.axis);
|
||||
|
@ -305,8 +305,9 @@ pub struct ShapedGlyph {
|
||||
impl<'a> ShapedText<'a> {
|
||||
/// Build the shaped text's frame.
|
||||
pub fn build(&self) -> Frame {
|
||||
let mut frame = Frame::new(self.size, self.baseline);
|
||||
let mut frame = Frame::new(self.size);
|
||||
let mut offset = Length::zero();
|
||||
frame.baseline = self.baseline;
|
||||
|
||||
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
|
||||
let pos = Point::new(offset, self.baseline);
|
||||
|
@ -62,13 +62,7 @@ impl Layout for TransformNode {
|
||||
let transform = Transform::translation(x, y)
|
||||
.pre_concat(self.transform)
|
||||
.pre_concat(Transform::translation(-x, -y));
|
||||
|
||||
let mut wrapper = Frame::new(frame.size, frame.baseline);
|
||||
wrapper.push(
|
||||
Point::zero(),
|
||||
Element::Group(Group::new(std::mem::take(frame)).transform(transform)),
|
||||
);
|
||||
*frame = Rc::new(wrapper);
|
||||
Rc::make_mut(frame).transform(transform);
|
||||
}
|
||||
|
||||
frames
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
// Syntax sugar for function definitions.
|
||||
#let fill = conifer
|
||||
#let rect(body) = rect(width: 2cm, fill: fill, pad(5pt, body))
|
||||
#let rect(body) = rect(width: 2cm, fill: fill, padding: 5pt, body)
|
||||
#rect[Hi!]
|
||||
|
||||
---
|
||||
|
@ -7,6 +7,6 @@
|
||||
#let d = 3
|
||||
#let value = [hi]
|
||||
#let item(a, b) = a + b
|
||||
#let fn(body) = rect(fill: conifer, pad(5pt, body))
|
||||
#let fn = rect with (fill: conifer, padding: 5pt)
|
||||
|
||||
Some _includable_ text.
|
||||
|
@ -16,7 +16,7 @@ Auto-sized circle. \
|
||||
Center-aligned rect in auto-sized circle.
|
||||
#circle(fill: forest, stroke: conifer,
|
||||
align(center + horizon,
|
||||
rect(fill: conifer, pad(5pt)[But, soft!])
|
||||
rect(fill: conifer, padding: 5pt)[But, soft!]
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
))
|
||||
|
||||
// Fixed width, text height.
|
||||
#rect(width: 2cm, fill: rgb("9650d6"), pad(5pt)[Fixed and padded])
|
||||
#rect(width: 2cm, fill: rgb("9650d6"), padding: 5pt)[Fixed and padded]
|
||||
|
||||
// Page width, fixed height.
|
||||
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]
|
||||
|
@ -20,8 +20,6 @@
|
||||
cell(100%, rgb("00ff00")),
|
||||
)
|
||||
|
||||
#grid()
|
||||
|
||||
---
|
||||
#grid(
|
||||
columns: (auto, auto, 40%),
|
||||
|
Loading…
x
Reference in New Issue
Block a user