mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Rearrange layouting contexts ♻
This commit is contained in:
parent
58693486f9
commit
e87a34a4d0
@ -28,12 +28,40 @@ pub struct FlexLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The context for flex layouting.
|
/// The context for flex layouting.
|
||||||
|
///
|
||||||
|
/// See [`LayoutContext`] for details about the fields.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct FlexContext {
|
pub struct FlexContext {
|
||||||
pub space: LayoutSpace,
|
|
||||||
/// The spacing between two lines of boxes.
|
/// The spacing between two lines of boxes.
|
||||||
pub flex_spacing: Size,
|
pub flex_spacing: Size,
|
||||||
pub extra_space: Option<LayoutSpace>,
|
pub alignment: Alignment,
|
||||||
|
pub space: LayoutSpace,
|
||||||
|
pub followup_spaces: Option<LayoutSpace>,
|
||||||
|
pub shrink_to_fit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! reuse {
|
||||||
|
($ctx:expr, $flex_spacing:expr) => {
|
||||||
|
FlexContext {
|
||||||
|
flex_spacing: $flex_spacing,
|
||||||
|
alignment: $ctx.alignment,
|
||||||
|
space: $ctx.space,
|
||||||
|
followup_spaces: $ctx.followup_spaces,
|
||||||
|
shrink_to_fit: $ctx.shrink_to_fit,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlexContext {
|
||||||
|
/// Create a flex context from a generic layout context.
|
||||||
|
pub fn from_layout_ctx(ctx: LayoutContext, flex_spacing: Size) -> FlexContext {
|
||||||
|
reuse!(ctx, flex_spacing)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a flex context from a stack context.
|
||||||
|
pub fn from_stack_ctx(ctx: StackContext, flex_spacing: Size) -> FlexContext {
|
||||||
|
reuse!(ctx, flex_spacing)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FlexUnit {
|
enum FlexUnit {
|
||||||
@ -57,10 +85,7 @@ impl FlexLayouter {
|
|||||||
ctx,
|
ctx,
|
||||||
units: vec![],
|
units: vec![],
|
||||||
|
|
||||||
stack: StackLayouter::new(StackContext {
|
stack: StackLayouter::new(StackContext::from_flex_ctx(ctx)),
|
||||||
space: ctx.space,
|
|
||||||
extra_space: ctx.extra_space,
|
|
||||||
}),
|
|
||||||
|
|
||||||
usable_width: ctx.space.usable().x,
|
usable_width: ctx.space.usable().x,
|
||||||
run: FlexRun {
|
run: FlexRun {
|
||||||
@ -125,7 +150,7 @@ impl FlexLayouter {
|
|||||||
// If the box does not even fit on its own line, then we try
|
// If the box does not even fit on its own line, then we try
|
||||||
// it in the next space, or we have to give up if there is none.
|
// it in the next space, or we have to give up if there is none.
|
||||||
if self.overflows_line(boxed.dimensions.x) {
|
if self.overflows_line(boxed.dimensions.x) {
|
||||||
if self.ctx.extra_space.is_some() {
|
if self.ctx.followup_spaces.is_some() {
|
||||||
self.stack.finish_layout(true)?;
|
self.stack.finish_layout(true)?;
|
||||||
return self.layout_box(boxed);
|
return self.layout_box(boxed);
|
||||||
} else {
|
} else {
|
||||||
|
@ -126,10 +126,22 @@ impl<'a> IntoIterator for &'a MultiLayout {
|
|||||||
/// The general context for layouting.
|
/// The general context for layouting.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct LayoutContext<'a, 'p> {
|
pub struct LayoutContext<'a, 'p> {
|
||||||
|
/// The font loader to retrieve fonts from when typesetting text
|
||||||
|
/// using [`layout_text`].
|
||||||
pub loader: &'a SharedFontLoader<'p>,
|
pub loader: &'a SharedFontLoader<'p>,
|
||||||
|
/// The style to set text with. This includes sizes and font classes
|
||||||
|
/// which determine which font from the loaders selection is used.
|
||||||
pub style: &'a TextStyle,
|
pub style: &'a TextStyle,
|
||||||
|
/// The alignment to use for the content.
|
||||||
|
pub alignment: Alignment,
|
||||||
|
/// The primary space to layout in.
|
||||||
pub space: LayoutSpace,
|
pub space: LayoutSpace,
|
||||||
pub extra_space: Option<LayoutSpace>,
|
/// The additional spaces which are used when the primary space
|
||||||
|
/// cannot fit the whole content.
|
||||||
|
pub followup_spaces: Option<LayoutSpace>,
|
||||||
|
/// Whether to shrink the dimensions to fit the content or the keep the
|
||||||
|
/// dimensions from the layout spaces.
|
||||||
|
pub shrink_to_fit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spacial layouting constraints.
|
/// Spacial layouting constraints.
|
||||||
@ -139,11 +151,6 @@ pub struct LayoutSpace {
|
|||||||
pub dimensions: Size2D,
|
pub dimensions: Size2D,
|
||||||
/// Padding that should be respected on each side.
|
/// Padding that should be respected on each side.
|
||||||
pub padding: SizeBox,
|
pub padding: SizeBox,
|
||||||
/// The alignment to use for the content.
|
|
||||||
pub alignment: Alignment,
|
|
||||||
/// Whether to shrink the dimensions to fit the content or the keep the
|
|
||||||
/// dimensions from the layout space.
|
|
||||||
pub shrink_to_fit: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutSpace {
|
impl LayoutSpace {
|
||||||
@ -151,6 +158,14 @@ impl LayoutSpace {
|
|||||||
pub fn usable(&self) -> Size2D {
|
pub fn usable(&self) -> Size2D {
|
||||||
self.dimensions.unpadded(self.padding)
|
self.dimensions.unpadded(self.padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A layout without padding and dimensions reduced by the padding.
|
||||||
|
pub fn usable_space(&self) -> LayoutSpace {
|
||||||
|
LayoutSpace {
|
||||||
|
dimensions: self.usable(),
|
||||||
|
padding: SizeBox::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Where to align content.
|
/// Where to align content.
|
||||||
|
@ -17,10 +17,37 @@ pub struct StackLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The context for stack layouting.
|
/// The context for stack layouting.
|
||||||
|
///
|
||||||
|
/// See [`LayoutContext`] for details about the fields.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct StackContext {
|
pub struct StackContext {
|
||||||
|
pub alignment: Alignment,
|
||||||
pub space: LayoutSpace,
|
pub space: LayoutSpace,
|
||||||
pub extra_space: Option<LayoutSpace>,
|
pub followup_spaces: Option<LayoutSpace>,
|
||||||
|
pub shrink_to_fit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! reuse {
|
||||||
|
($ctx:expr) => {
|
||||||
|
StackContext {
|
||||||
|
alignment: $ctx.alignment,
|
||||||
|
space: $ctx.space,
|
||||||
|
followup_spaces: $ctx.followup_spaces,
|
||||||
|
shrink_to_fit: $ctx.shrink_to_fit
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StackContext {
|
||||||
|
/// Create a stack context from a generic layout context.
|
||||||
|
pub fn from_layout_ctx(ctx: LayoutContext) -> StackContext {
|
||||||
|
reuse!(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a stack context from a flex context.
|
||||||
|
pub fn from_flex_ctx(ctx: FlexContext) -> StackContext {
|
||||||
|
reuse!(ctx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StackLayouter {
|
impl StackLayouter {
|
||||||
@ -33,8 +60,8 @@ impl StackLayouter {
|
|||||||
|
|
||||||
space: ctx.space,
|
space: ctx.space,
|
||||||
usable: ctx.space.usable(),
|
usable: ctx.space.usable(),
|
||||||
dimensions: start_dimensions(ctx.space),
|
dimensions: start_dimensions(ctx.alignment, ctx.space),
|
||||||
cursor: start_cursor(ctx.space),
|
cursor: start_cursor(ctx.alignment, ctx.space),
|
||||||
in_extra_space: false,
|
in_extra_space: false,
|
||||||
started: true,
|
started: true,
|
||||||
}
|
}
|
||||||
@ -57,7 +84,7 @@ impl StackLayouter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if self.overflows(new_dimensions) {
|
if self.overflows(new_dimensions) {
|
||||||
if self.ctx.extra_space.is_some() &&
|
if self.ctx.followup_spaces.is_some() &&
|
||||||
!(self.in_extra_space && self.overflows(layout.dimensions))
|
!(self.in_extra_space && self.overflows(layout.dimensions))
|
||||||
{
|
{
|
||||||
self.finish_layout(true)?;
|
self.finish_layout(true)?;
|
||||||
@ -70,7 +97,7 @@ impl StackLayouter {
|
|||||||
// Determine where to put the box. When we right-align it, we want the
|
// Determine where to put the box. When we right-align it, we want the
|
||||||
// cursor to point to the top-right corner of the box. Therefore, the
|
// cursor to point to the top-right corner of the box. Therefore, the
|
||||||
// position has to be moved to the left by the width of the box.
|
// position has to be moved to the left by the width of the box.
|
||||||
let position = match self.space.alignment {
|
let position = match self.ctx.alignment {
|
||||||
Alignment::Left => self.cursor,
|
Alignment::Left => self.cursor,
|
||||||
Alignment::Right => self.cursor - Size2D::with_x(layout.dimensions.x),
|
Alignment::Right => self.cursor - Size2D::with_x(layout.dimensions.x),
|
||||||
Alignment::Center => self.cursor - Size2D::with_x(layout.dimensions.x / 2),
|
Alignment::Center => self.cursor - Size2D::with_x(layout.dimensions.x / 2),
|
||||||
@ -101,7 +128,7 @@ impl StackLayouter {
|
|||||||
let new_dimensions = self.dimensions + Size2D::with_y(space);
|
let new_dimensions = self.dimensions + Size2D::with_y(space);
|
||||||
|
|
||||||
if self.overflows(new_dimensions) {
|
if self.overflows(new_dimensions) {
|
||||||
if self.ctx.extra_space.is_some() {
|
if self.ctx.followup_spaces.is_some() {
|
||||||
self.finish_layout(false)?;
|
self.finish_layout(false)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(LayoutError::NotEnoughSpace("cannot fit space into stack"));
|
return Err(LayoutError::NotEnoughSpace("cannot fit space into stack"));
|
||||||
@ -133,7 +160,7 @@ impl StackLayouter {
|
|||||||
pub fn finish_layout(&mut self, start_new_empty: bool) -> LayoutResult<()> {
|
pub fn finish_layout(&mut self, start_new_empty: bool) -> LayoutResult<()> {
|
||||||
let actions = std::mem::replace(&mut self.actions, LayoutActionList::new());
|
let actions = std::mem::replace(&mut self.actions, LayoutActionList::new());
|
||||||
self.layouts.add(Layout {
|
self.layouts.add(Layout {
|
||||||
dimensions: if self.space.shrink_to_fit {
|
dimensions: if self.ctx.shrink_to_fit {
|
||||||
self.dimensions.padded(self.space.padding)
|
self.dimensions.padded(self.space.padding)
|
||||||
} else {
|
} else {
|
||||||
self.space.dimensions
|
self.space.dimensions
|
||||||
@ -152,12 +179,12 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_new_space(&mut self) -> LayoutResult<()> {
|
pub fn start_new_space(&mut self) -> LayoutResult<()> {
|
||||||
if let Some(space) = self.ctx.extra_space {
|
if let Some(space) = self.ctx.followup_spaces {
|
||||||
self.started = true;
|
self.started = true;
|
||||||
self.space = space;
|
self.space = space;
|
||||||
self.usable = space.usable();
|
self.usable = space.usable();
|
||||||
self.dimensions = start_dimensions(space);
|
self.dimensions = start_dimensions(self.ctx.alignment, space);
|
||||||
self.cursor = start_cursor(space);
|
self.cursor = start_cursor(self.ctx.alignment, space);
|
||||||
self.in_extra_space = true;
|
self.in_extra_space = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -183,19 +210,19 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_dimensions(space: LayoutSpace) -> Size2D {
|
fn start_dimensions(alignment: Alignment, space: LayoutSpace) -> Size2D {
|
||||||
match space.alignment {
|
match alignment {
|
||||||
Alignment::Left => Size2D::zero(),
|
Alignment::Left => Size2D::zero(),
|
||||||
Alignment::Right | Alignment::Center => Size2D::with_x(space.usable().x),
|
Alignment::Right | Alignment::Center => Size2D::with_x(space.usable().x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_cursor(space: LayoutSpace) -> Size2D {
|
fn start_cursor(alignment: Alignment, space: LayoutSpace) -> Size2D {
|
||||||
Size2D {
|
Size2D {
|
||||||
// If left-align, the cursor points to the top-left corner of
|
// If left-align, the cursor points to the top-left corner of
|
||||||
// each box. If we right-align, it points to the top-right
|
// each box. If we right-align, it points to the top-right
|
||||||
// corner.
|
// corner.
|
||||||
x: match space.alignment {
|
x: match alignment {
|
||||||
Alignment::Left => space.padding.left,
|
Alignment::Left => space.padding.left,
|
||||||
Alignment::Right => space.dimensions.x - space.padding.right,
|
Alignment::Right => space.dimensions.x - space.padding.right,
|
||||||
Alignment::Center => space.padding.left + (space.usable().x / 2),
|
Alignment::Center => space.padding.left + (space.usable().x / 2),
|
||||||
|
@ -5,14 +5,24 @@ use super::*;
|
|||||||
use crate::size::{Size, Size2D};
|
use crate::size::{Size, Size2D};
|
||||||
|
|
||||||
/// The context for text layouting.
|
/// The context for text layouting.
|
||||||
|
///
|
||||||
|
/// See [`LayoutContext`] for details about the fields.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct TextContext<'a, 'p> {
|
pub struct TextContext<'a, 'p> {
|
||||||
/// Loads fonts matching queries.
|
|
||||||
pub loader: &'a SharedFontLoader<'p>,
|
pub loader: &'a SharedFontLoader<'p>,
|
||||||
/// Base style to set text with.
|
|
||||||
pub style: &'a TextStyle,
|
pub style: &'a TextStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, 'p> TextContext<'a, 'p> {
|
||||||
|
/// Create a text context from a generic layout context.
|
||||||
|
pub fn from_layout_ctx(ctx: LayoutContext<'a, 'p>) -> TextContext<'a, 'p> {
|
||||||
|
TextContext {
|
||||||
|
loader: ctx.loader,
|
||||||
|
style: ctx.style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Layouts text into a box.
|
/// Layouts text into a box.
|
||||||
///
|
///
|
||||||
/// There is no complex layout involved. The text is simply laid out left-
|
/// There is no complex layout involved. The text is simply laid out left-
|
||||||
|
@ -19,14 +19,12 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
|
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
|
||||||
TreeLayouter {
|
TreeLayouter {
|
||||||
ctx,
|
ctx,
|
||||||
stack: StackLayouter::new(StackContext {
|
stack: StackLayouter::new(StackContext::from_layout_ctx(ctx)),
|
||||||
space: ctx.space,
|
|
||||||
extra_space: ctx.extra_space
|
|
||||||
}),
|
|
||||||
flex: FlexLayouter::new(FlexContext {
|
flex: FlexLayouter::new(FlexContext {
|
||||||
space: flex_space(ctx.space),
|
space: ctx.space.usable_space(),
|
||||||
extra_space: ctx.extra_space.map(|s| flex_space(s)),
|
followup_spaces: ctx.followup_spaces.map(|s| s.usable_space()),
|
||||||
flex_spacing: flex_spacing(&ctx.style),
|
shrink_to_fit: true,
|
||||||
|
.. FlexContext::from_layout_ctx(ctx, flex_spacing(&ctx.style))
|
||||||
}),
|
}),
|
||||||
style: Cow::Borrowed(ctx.style),
|
style: Cow::Borrowed(ctx.style),
|
||||||
}
|
}
|
||||||
@ -119,15 +117,13 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
|
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
|
||||||
let mut ctx = self.ctx;
|
let mut ctx = self.ctx;
|
||||||
ctx.style = &self.style;
|
ctx.style = &self.style;
|
||||||
|
ctx.shrink_to_fit = true;
|
||||||
|
|
||||||
ctx.space.dimensions = self.stack.remaining();
|
ctx.space.dimensions = self.stack.remaining();
|
||||||
ctx.space.padding = SizeBox::zero();
|
ctx.space.padding = SizeBox::zero();
|
||||||
ctx.space.shrink_to_fit = true;
|
|
||||||
|
|
||||||
if let Some(space) = ctx.extra_space.as_mut() {
|
if let Some(space) = ctx.followup_spaces.as_mut() {
|
||||||
space.dimensions = space.usable();
|
*space = space.usable_space();
|
||||||
space.padding = SizeBox::zero();
|
|
||||||
space.shrink_to_fit = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let commands = func.body.layout(ctx)?;
|
let commands = func.body.layout(ctx)?;
|
||||||
@ -145,15 +141,6 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flex_space(space: LayoutSpace) -> LayoutSpace {
|
|
||||||
LayoutSpace {
|
|
||||||
dimensions: space.usable(),
|
|
||||||
padding: SizeBox::zero(),
|
|
||||||
alignment: space.alignment,
|
|
||||||
shrink_to_fit: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flex_spacing(style: &TextStyle) -> Size {
|
fn flex_spacing(style: &TextStyle) -> Size {
|
||||||
(style.line_spacing - 1.0) * Size::pt(style.font_size)
|
(style.line_spacing - 1.0) * Size::pt(style.font_size)
|
||||||
}
|
}
|
||||||
|
@ -96,8 +96,6 @@ impl<'p> Typesetter<'p> {
|
|||||||
let space = LayoutSpace {
|
let space = LayoutSpace {
|
||||||
dimensions: self.page_style.dimensions,
|
dimensions: self.page_style.dimensions,
|
||||||
padding: self.page_style.margins,
|
padding: self.page_style.margins,
|
||||||
alignment: Alignment::Left,
|
|
||||||
shrink_to_fit: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let pages = layout_tree(
|
let pages = layout_tree(
|
||||||
@ -105,8 +103,10 @@ impl<'p> Typesetter<'p> {
|
|||||||
LayoutContext {
|
LayoutContext {
|
||||||
loader: &self.loader,
|
loader: &self.loader,
|
||||||
style: &self.text_style,
|
style: &self.text_style,
|
||||||
|
alignment: Alignment::Left,
|
||||||
space,
|
space,
|
||||||
extra_space: Some(space),
|
followup_spaces: Some(space),
|
||||||
|
shrink_to_fit: false,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -40,10 +40,7 @@ impl Function for AlignFunc {
|
|||||||
|
|
||||||
fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<FuncCommands> {
|
fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<FuncCommands> {
|
||||||
if let Some(body) = &self.body {
|
if let Some(body) = &self.body {
|
||||||
ctx.space.alignment = self.alignment;
|
ctx.alignment = self.alignment;
|
||||||
if let Some(space) = ctx.extra_space.as_mut() {
|
|
||||||
space.alignment = self.alignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
let layouts = layout_tree(body, ctx)?;
|
let layouts = layout_tree(body, ctx)?;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{size:150pt*206pt}
|
{size:150pt*208pt}
|
||||||
|
|
||||||
[align: left][Left: {lorem:20}]
|
[align: left][Left: {lorem:20}]
|
||||||
|
|
||||||
[align: right][Right: {lorem:20}]
|
[align: right][Right: {lorem:20}]
|
||||||
|
|
||||||
[align: center][Center: {lorem:80}]
|
[align: center][Center: {lorem:80}]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user