mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Generalize tree layouter 🌲
This commit is contained in:
parent
0917d89bb8
commit
261ef9e33a
@ -249,8 +249,8 @@ impl<'d, W: Write> ExportProcess<'d, W> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
LayoutAction::SetFont(id, size) => {
|
LayoutAction::SetFont(id, size) => {
|
||||||
active_font = (self.font_remap[id], *size);
|
active_font = (self.font_remap[id], size.to_pt());
|
||||||
text.tf(active_font.0 as u32 + 1, *size);
|
text.tf(active_font.0 as u32 + 1, size.to_pt());
|
||||||
}
|
}
|
||||||
|
|
||||||
LayoutAction::WriteText(string) => {
|
LayoutAction::WriteText(string) => {
|
||||||
|
@ -12,8 +12,9 @@ pub mod helpers;
|
|||||||
/// Useful imports for creating your own functions.
|
/// Useful imports for creating your own functions.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::func::{Command, CommandList, Function};
|
pub use crate::func::{Command, CommandList, Function};
|
||||||
pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
|
pub use crate::layout::{layout_tree, Layout, MultiLayout, LayoutContext, LayoutSpace};
|
||||||
pub use crate::layout::{Flow, Alignment, LayoutError, LayoutResult};
|
pub use crate::layout::{LayoutAxes, AlignedAxis, Axis, Alignment};
|
||||||
|
pub use crate::layout::{LayoutError, LayoutResult};
|
||||||
pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span};
|
pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span};
|
||||||
pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult};
|
pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult};
|
||||||
pub use crate::size::{Size, Size2D, SizeBox};
|
pub use crate::size::{Size, Size2D, SizeBox};
|
||||||
@ -88,13 +89,16 @@ where T: Debug + PartialEq + 'static
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Command<'a> {
|
pub enum Command<'a> {
|
||||||
LayoutTree(&'a SyntaxTree),
|
LayoutTree(&'a SyntaxTree),
|
||||||
|
|
||||||
Add(Layout),
|
Add(Layout),
|
||||||
AddMany(MultiLayout),
|
AddMultiple(MultiLayout),
|
||||||
AddFlex(Layout),
|
|
||||||
SetAlignment(Alignment),
|
|
||||||
SetStyle(TextStyle),
|
|
||||||
FinishLayout,
|
|
||||||
FinishFlexRun,
|
FinishFlexRun,
|
||||||
|
FinishFlexLayout,
|
||||||
|
FinishLayout,
|
||||||
|
|
||||||
|
SetStyle(TextStyle),
|
||||||
|
SetAxes(LayoutAxes),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sequence of commands requested for execution by a function.
|
/// A sequence of commands requested for execution by a function.
|
||||||
|
@ -4,7 +4,7 @@ use std::fmt::{self, Display, Formatter};
|
|||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
use super::Layout;
|
use super::Layout;
|
||||||
use crate::size::Size2D;
|
use crate::size::{Size, Size2D};
|
||||||
use LayoutAction::*;
|
use LayoutAction::*;
|
||||||
|
|
||||||
/// A layouting action.
|
/// A layouting action.
|
||||||
@ -13,7 +13,7 @@ pub enum LayoutAction {
|
|||||||
/// Move to an absolute position.
|
/// Move to an absolute position.
|
||||||
MoveAbsolute(Size2D),
|
MoveAbsolute(Size2D),
|
||||||
/// Set the font by index and font size.
|
/// Set the font by index and font size.
|
||||||
SetFont(usize, f32),
|
SetFont(usize, Size),
|
||||||
/// Write text starting at the current position.
|
/// Write text starting at the current position.
|
||||||
WriteText(String),
|
WriteText(String),
|
||||||
/// Visualize a box for debugging purposes.
|
/// Visualize a box for debugging purposes.
|
||||||
@ -26,7 +26,7 @@ impl LayoutAction {
|
|||||||
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
|
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
|
||||||
SetFont(i, s) => write!(f, "f {} {}", i, s),
|
SetFont(i, s) => write!(f, "f {} {}", i, s.to_pt()),
|
||||||
WriteText(s) => write!(f, "w {}", s),
|
WriteText(s) => write!(f, "w {}", s),
|
||||||
DebugBox(p, s) => write!(
|
DebugBox(p, s) => write!(
|
||||||
f,
|
f,
|
||||||
@ -69,9 +69,9 @@ debug_display!(LayoutAction);
|
|||||||
pub struct LayoutActionList {
|
pub struct LayoutActionList {
|
||||||
pub origin: Size2D,
|
pub origin: Size2D,
|
||||||
actions: Vec<LayoutAction>,
|
actions: Vec<LayoutAction>,
|
||||||
active_font: (usize, f32),
|
active_font: (usize, Size),
|
||||||
next_pos: Option<Size2D>,
|
next_pos: Option<Size2D>,
|
||||||
next_font: Option<(usize, f32)>,
|
next_font: Option<(usize, Size)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutActionList {
|
impl LayoutActionList {
|
||||||
@ -80,7 +80,7 @@ impl LayoutActionList {
|
|||||||
LayoutActionList {
|
LayoutActionList {
|
||||||
actions: vec![],
|
actions: vec![],
|
||||||
origin: Size2D::zero(),
|
origin: Size2D::zero(),
|
||||||
active_font: (std::usize::MAX, 0.0),
|
active_font: (std::usize::MAX, Size::zero()),
|
||||||
next_pos: None,
|
next_pos: None,
|
||||||
next_font: None,
|
next_font: None,
|
||||||
}
|
}
|
||||||
|
@ -75,16 +75,18 @@ impl FlexLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This layouter's context.
|
|
||||||
pub fn ctx(&self) -> FlexContext {
|
|
||||||
self.ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a sublayout.
|
/// Add a sublayout.
|
||||||
pub fn add(&mut self, layout: Layout) {
|
pub fn add(&mut self, layout: Layout) {
|
||||||
self.units.push(FlexUnit::Boxed(layout));
|
self.units.push(FlexUnit::Boxed(layout));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add multiple sublayouts from a multi-layout.
|
||||||
|
pub fn add_multiple(&mut self, layouts: MultiLayout) {
|
||||||
|
for layout in layouts {
|
||||||
|
self.add(layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a space box which can be replaced by a run break.
|
/// Add a space box which can be replaced by a run break.
|
||||||
pub fn add_space(&mut self, space: Size) {
|
pub fn add_space(&mut self, space: Size) {
|
||||||
self.units.push(FlexUnit::Space(space));
|
self.units.push(FlexUnit::Space(space));
|
||||||
@ -181,6 +183,11 @@ impl FlexLayouter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This layouter's context.
|
||||||
|
pub fn ctx(&self) -> FlexContext {
|
||||||
|
self.ctx
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether this layouter contains any items.
|
/// Whether this layouter contains any items.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.units.is_empty()
|
self.units.is_empty()
|
||||||
|
@ -192,7 +192,7 @@ impl LayoutSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The axes along which the content is laid out.
|
/// The axes along which the content is laid out.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub struct LayoutAxes {
|
pub struct LayoutAxes {
|
||||||
pub primary: AlignedAxis,
|
pub primary: AlignedAxis,
|
||||||
pub secondary: AlignedAxis,
|
pub secondary: AlignedAxis,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use smallvec::smallvec;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Layouts boxes stack-like.
|
/// Layouts boxes stack-like.
|
||||||
@ -66,7 +67,7 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add multiple sublayouts from a multi-layout.
|
/// Add multiple sublayouts from a multi-layout.
|
||||||
pub fn add_many(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
|
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
|
||||||
for layout in layouts {
|
for layout in layouts {
|
||||||
self.add(layout)?;
|
self.add(layout)?;
|
||||||
}
|
}
|
||||||
@ -135,7 +136,7 @@ impl StackLayouter {
|
|||||||
/// finishing this stack. Otherwise, the new layout only appears if new
|
/// finishing this stack. Otherwise, the new layout only appears if new
|
||||||
/// content is added to it.
|
/// content is added to it.
|
||||||
fn start_new_space(&mut self, include_empty: bool) {
|
fn start_new_space(&mut self, include_empty: bool) {
|
||||||
self.active_space = (self.active_space + 1).min(self.ctx.spaces.len() - 1);
|
self.active_space = self.next_space();
|
||||||
self.usable = self.ctx.spaces[self.active_space].usable().generalized(self.ctx.axes);
|
self.usable = self.ctx.spaces[self.active_space].usable().generalized(self.ctx.axes);
|
||||||
self.dimensions = start_dimensions(self.usable, self.ctx.axes);
|
self.dimensions = start_dimensions(self.usable, self.ctx.axes);
|
||||||
self.include_empty = include_empty;
|
self.include_empty = include_empty;
|
||||||
@ -151,10 +152,20 @@ impl StackLayouter {
|
|||||||
self.usable
|
self.usable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The (specialized) remaining area for new layouts in the current space.
|
/// The remaining spaces for new layouts in the current space.
|
||||||
pub fn remaining(&self) -> Size2D {
|
pub fn remaining(&self, shrink_to_fit: bool) -> LayoutSpaces {
|
||||||
Size2D::new(self.usable.x, self.usable.y - self.dimensions.y)
|
let mut spaces = smallvec![LayoutSpace {
|
||||||
.specialized(self.ctx.axes)
|
dimensions: Size2D::new(self.usable.x, self.usable.y - self.dimensions.y)
|
||||||
|
.specialized(self.ctx.axes),
|
||||||
|
padding: SizeBox::zero(),
|
||||||
|
shrink_to_fit,
|
||||||
|
}];
|
||||||
|
|
||||||
|
for space in &self.ctx.spaces[self.next_space()..] {
|
||||||
|
spaces.push(space.usable_space(shrink_to_fit));
|
||||||
|
}
|
||||||
|
|
||||||
|
spaces
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this layouter is in its last space.
|
/// Whether this layouter is in its last space.
|
||||||
@ -162,6 +173,10 @@ impl StackLayouter {
|
|||||||
self.active_space == self.ctx.spaces.len() - 1
|
self.active_space == self.ctx.spaces.len() - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_space(&self) -> usize {
|
||||||
|
(self.active_space + 1).min(self.ctx.spaces.len() - 1)
|
||||||
|
}
|
||||||
|
|
||||||
/// The combined size of the so-far included boxes with the other size.
|
/// The combined size of the so-far included boxes with the other size.
|
||||||
fn size_with(&self, other: Size2D) -> Size2D {
|
fn size_with(&self, other: Size2D) -> Size2D {
|
||||||
Size2D {
|
Size2D {
|
||||||
|
@ -13,16 +13,6 @@ pub struct TextContext<'a, 'p> {
|
|||||||
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-
|
||||||
@ -81,7 +71,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(Layout {
|
Ok(Layout {
|
||||||
dimensions: Size2D::new(self.width, Size::pt(self.ctx.style.font_size)),
|
dimensions: Size2D::new(self.width, self.ctx.style.font_size),
|
||||||
actions: self.actions.into_vec(),
|
actions: self.actions.into_vec(),
|
||||||
debug_render: false,
|
debug_render: false,
|
||||||
})
|
})
|
||||||
@ -107,15 +97,16 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
|
|||||||
let glyph = font
|
let glyph = font
|
||||||
.read_table::<CharMap>()?
|
.read_table::<CharMap>()?
|
||||||
.get(c)
|
.get(c)
|
||||||
.expect("layout text: font should have char");
|
.expect("select_font: font should have char");
|
||||||
|
|
||||||
let glyph_width = font
|
let glyph_width = font
|
||||||
.read_table::<HorizontalMetrics>()?
|
.read_table::<HorizontalMetrics>()?
|
||||||
.get(glyph)
|
.get(glyph)
|
||||||
.expect("layout text: font should have glyph")
|
.expect("select_font: font should have glyph")
|
||||||
.advance_width as f32;
|
.advance_width as f32;
|
||||||
|
|
||||||
let char_width = font_unit_to_size(glyph_width) * self.ctx.style.font_size;
|
let char_width = font_unit_to_size(glyph_width)
|
||||||
|
* self.ctx.style.font_size.to_pt();
|
||||||
|
|
||||||
return Ok((index, char_width));
|
return Ok((index, char_width));
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,6 @@ struct TreeLayouter<'a, 'p> {
|
|||||||
stack: StackLayouter,
|
stack: StackLayouter,
|
||||||
flex: FlexLayouter,
|
flex: FlexLayouter,
|
||||||
style: Cow<'a, TextStyle>,
|
style: Cow<'a, TextStyle>,
|
||||||
alignment: Alignment,
|
|
||||||
set_newline: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'p> TreeLayouter<'a, 'p> {
|
impl<'a, 'p> TreeLayouter<'a, 'p> {
|
||||||
@ -22,51 +20,41 @@ 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::from_layout_ctx(ctx)),
|
stack: StackLayouter::new(StackContext {
|
||||||
|
spaces: ctx.spaces,
|
||||||
|
axes: ctx.axes,
|
||||||
|
}),
|
||||||
flex: FlexLayouter::new(FlexContext {
|
flex: FlexLayouter::new(FlexContext {
|
||||||
space: ctx.space.usable_space(),
|
flex_spacing: flex_spacing(&ctx.style),
|
||||||
followup_spaces: ctx.followup_spaces.map(|s| s.usable_space()),
|
spaces: ctx.spaces.iter().map(|space| space.usable_space(true)).collect(),
|
||||||
shrink_to_fit: true,
|
axes: ctx.axes,
|
||||||
.. FlexContext::from_layout_ctx(ctx, flex_spacing(&ctx.style))
|
|
||||||
}),
|
}),
|
||||||
style: Cow::Borrowed(ctx.style),
|
style: Cow::Borrowed(ctx.style),
|
||||||
alignment: ctx.alignment,
|
|
||||||
set_newline: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the tree into a box.
|
/// Layout a syntax tree.
|
||||||
fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> {
|
fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> {
|
||||||
for node in &tree.nodes {
|
for node in &tree.nodes {
|
||||||
match &node.val {
|
match &node.val {
|
||||||
Node::Text(text) => {
|
Node::Text(text) => {
|
||||||
let layout = self.layout_text(text)?;
|
self.flex.add(layout_text(text, TextContext {
|
||||||
self.flex.add(layout);
|
loader: &self.ctx.loader,
|
||||||
self.set_newline = true;
|
style: &self.style,
|
||||||
|
})?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Node::Space => {
|
Node::Space => {
|
||||||
// Only add a space if there was any content before.
|
|
||||||
if !self.flex.is_empty() {
|
if !self.flex.is_empty() {
|
||||||
let layout = self.layout_text(" ")?;
|
self.flex.add_space(self.style.word_spacing * self.style.font_size);
|
||||||
self.flex.add_glue(layout.dimensions);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finish the current flex layouting process.
|
|
||||||
Node::Newline => {
|
Node::Newline => {
|
||||||
self.finish_flex()?;
|
if !self.flex.is_empty() {
|
||||||
|
self.finish_paragraph()?;
|
||||||
if self.set_newline {
|
|
||||||
let space = paragraph_spacing(&self.style);
|
|
||||||
self.stack.add_space(space);
|
|
||||||
self.set_newline = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.start_new_flex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle the text styles.
|
|
||||||
Node::ToggleItalics => self.style.to_mut().toggle_class(FontClass::Italic),
|
Node::ToggleItalics => self.style.to_mut().toggle_class(FontClass::Italic),
|
||||||
Node::ToggleBold => self.style.to_mut().toggle_class(FontClass::Bold),
|
Node::ToggleBold => self.style.to_mut().toggle_class(FontClass::Bold),
|
||||||
Node::ToggleMonospace => self.style.to_mut().toggle_class(FontClass::Monospace),
|
Node::ToggleMonospace => self.style.to_mut().toggle_class(FontClass::Monospace),
|
||||||
@ -78,115 +66,98 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Layout a function.
|
||||||
|
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
|
||||||
|
// Finish the current flex layout on a copy to find out how
|
||||||
|
// much space would be remaining if we finished.
|
||||||
|
let mut lookahead = self.stack.clone();
|
||||||
|
lookahead.add_multiple(self.flex.clone().finish()?)?;
|
||||||
|
let spaces = lookahead.remaining(true);
|
||||||
|
|
||||||
|
let commands = func.body.val.layout(LayoutContext {
|
||||||
|
style: &self.style,
|
||||||
|
spaces,
|
||||||
|
.. self.ctx
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for command in commands {
|
||||||
|
self.execute(command)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&mut self, command: Command) -> LayoutResult<()> {
|
||||||
|
match command {
|
||||||
|
Command::LayoutTree(tree) => self.layout(tree)?,
|
||||||
|
|
||||||
|
Command::Add(layout) => self.flex.add(layout),
|
||||||
|
Command::AddMultiple(layouts) => self.flex.add_multiple(layouts),
|
||||||
|
|
||||||
|
Command::FinishFlexRun => self.flex.add_break(),
|
||||||
|
Command::FinishFlexLayout => self.finish_paragraph()?,
|
||||||
|
Command::FinishLayout => self.finish_layout(true)?,
|
||||||
|
|
||||||
|
Command::SetStyle(style) => *self.style.to_mut() = style,
|
||||||
|
Command::SetAxes(axes) => {
|
||||||
|
if axes.secondary != self.ctx.axes.secondary {
|
||||||
|
self.stack.set_axis(axes.secondary);
|
||||||
|
} else if axes.primary != self.ctx.axes.primary {
|
||||||
|
self.flex.set_axis(axes.primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ctx.axes = axes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Finish the layout.
|
/// Finish the layout.
|
||||||
fn finish(mut self) -> LayoutResult<MultiLayout> {
|
fn finish(mut self) -> LayoutResult<MultiLayout> {
|
||||||
self.finish_flex()?;
|
self.finish_flex()?;
|
||||||
Ok(self.stack.finish())
|
Ok(self.stack.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a function.
|
/// Finish the current stack layout.
|
||||||
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
|
fn finish_layout(&mut self, include_empty: bool) -> LayoutResult<()> {
|
||||||
// Finish the current flex layout on a copy to find out how
|
self.finish_flex()?;
|
||||||
// much space would be remaining if we finished.
|
self.stack.finish_layout(include_empty);
|
||||||
|
self.start_new_flex();
|
||||||
let mut lookahead_stack = self.stack.clone();
|
|
||||||
let layouts = self.flex.clone().finish()?;
|
|
||||||
lookahead_stack.add_many(layouts)?;
|
|
||||||
let remaining = lookahead_stack.remaining();
|
|
||||||
|
|
||||||
let mut ctx = self.ctx;
|
|
||||||
ctx.style = &self.style;
|
|
||||||
ctx.flow = Flow::Vertical;
|
|
||||||
ctx.shrink_to_fit = true;
|
|
||||||
ctx.space.dimensions = remaining;
|
|
||||||
ctx.space.padding = SizeBox::zero();
|
|
||||||
if let Some(space) = ctx.followup_spaces.as_mut() {
|
|
||||||
*space = space.usable_space();
|
|
||||||
}
|
|
||||||
|
|
||||||
let commands = func.body.val.layout(ctx)?;
|
|
||||||
|
|
||||||
for command in commands {
|
|
||||||
match command {
|
|
||||||
Command::LayoutTree(tree) => self.layout(tree)?,
|
|
||||||
|
|
||||||
Command::Add(layout) => {
|
|
||||||
self.finish_flex()?;
|
|
||||||
self.stack.add(layout)?;
|
|
||||||
self.set_newline = true;
|
|
||||||
self.start_new_flex();
|
|
||||||
}
|
|
||||||
|
|
||||||
Command::AddMany(layouts) => {
|
|
||||||
self.finish_flex()?;
|
|
||||||
self.stack.add_many(layouts)?;
|
|
||||||
self.set_newline = true;
|
|
||||||
self.start_new_flex();
|
|
||||||
}
|
|
||||||
|
|
||||||
Command::AddFlex(layout) => self.flex.add(layout),
|
|
||||||
|
|
||||||
Command::SetAlignment(alignment) => {
|
|
||||||
self.finish_flex()?;
|
|
||||||
self.alignment = alignment;
|
|
||||||
self.start_new_flex();
|
|
||||||
}
|
|
||||||
|
|
||||||
Command::SetStyle(style) => *self.style.to_mut() = style,
|
|
||||||
|
|
||||||
Command::FinishLayout => {
|
|
||||||
self.finish_flex()?;
|
|
||||||
self.stack.finish_layout(true);
|
|
||||||
self.start_new_flex();
|
|
||||||
}
|
|
||||||
|
|
||||||
Command::FinishFlexRun => self.flex.add_break(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add text to the flex layout. If `glue` is true, the text will be a glue
|
/// Finish the current flex layout and add space after it.
|
||||||
/// part in the flex layouter. For details, see [`FlexLayouter`].
|
fn finish_paragraph(&mut self) -> LayoutResult<()> {
|
||||||
fn layout_text(&mut self, text: &str) -> LayoutResult<Layout> {
|
self.finish_flex()?;
|
||||||
let ctx = TextContext {
|
self.stack.add_space(paragraph_spacing(&self.style));
|
||||||
loader: &self.ctx.loader,
|
self.start_new_flex();
|
||||||
style: &self.style,
|
Ok(())
|
||||||
};
|
|
||||||
|
|
||||||
layout_text(text, ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the current flex layout and add it the stack.
|
/// Finish the current flex layout and add it the stack.
|
||||||
fn finish_flex(&mut self) -> LayoutResult<()> {
|
fn finish_flex(&mut self) -> LayoutResult<()> {
|
||||||
if self.flex.is_empty() {
|
if !self.flex.is_empty() {
|
||||||
return Ok(());
|
let layouts = self.flex.finish()?;
|
||||||
|
self.stack.add_multiple(layouts)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let layouts = self.flex.finish()?;
|
|
||||||
self.stack.add_many(layouts)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a new flex layout.
|
/// Start a new flex layout.
|
||||||
fn start_new_flex(&mut self) {
|
fn start_new_flex(&mut self) {
|
||||||
let mut ctx = self.flex.ctx();
|
self.flex = FlexLayouter::new(FlexContext {
|
||||||
ctx.space.dimensions = self.stack.remaining();
|
flex_spacing: flex_spacing(&self.style),
|
||||||
ctx.alignment = self.alignment;
|
spaces: self.stack.remaining(true),
|
||||||
ctx.flex_spacing = flex_spacing(&self.style);
|
axes: self.ctx.axes,
|
||||||
|
});
|
||||||
self.flex = FlexLayouter::new(ctx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) * style.font_size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paragraph_spacing(style: &TextStyle) -> Size {
|
fn paragraph_spacing(style: &TextStyle) -> Size {
|
||||||
let line_height = Size::pt(style.font_size);
|
(style.paragraph_spacing - 1.0) * style.font_size
|
||||||
let space_factor = style.line_spacing * style.paragraph_spacing - 1.0;
|
|
||||||
line_height * space_factor
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,28 @@
|
|||||||
use crate::func::prelude::*;
|
use crate::func::prelude::*;
|
||||||
use Command::*;
|
use Command::*;
|
||||||
|
|
||||||
|
/// ↩ `line.break`, `n`: Ends the current line.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Linebreak;
|
||||||
|
|
||||||
|
function! {
|
||||||
|
data: Linebreak,
|
||||||
|
parse: plain,
|
||||||
|
layout(_, _) { Ok(commands![FinishFlexRun]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ↕ `paragraph.break`: Ends the current paragraph.
|
||||||
|
///
|
||||||
|
/// This has the same effect as two subsequent newlines.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Parbreak;
|
||||||
|
|
||||||
|
function! {
|
||||||
|
data: Parbreak,
|
||||||
|
parse: plain,
|
||||||
|
layout(_, _) { Ok(commands![FinishFlexLayout]) }
|
||||||
|
}
|
||||||
|
|
||||||
/// 📜 `page.break`: Ends the current page.
|
/// 📜 `page.break`: Ends the current page.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Pagebreak;
|
pub struct Pagebreak;
|
||||||
@ -8,23 +30,7 @@ pub struct Pagebreak;
|
|||||||
function! {
|
function! {
|
||||||
data: Pagebreak,
|
data: Pagebreak,
|
||||||
parse: plain,
|
parse: plain,
|
||||||
|
layout(_, _) { Ok(commands![FinishLayout]) }
|
||||||
layout(_, _) {
|
|
||||||
Ok(commands![FinishLayout])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 🔙 `line.break`, `n`: Ends the current line.
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct Linebreak;
|
|
||||||
|
|
||||||
function! {
|
|
||||||
data: Linebreak,
|
|
||||||
parse: plain,
|
|
||||||
|
|
||||||
layout(_, _) {
|
|
||||||
Ok(commands![FinishFlexRun])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 📐 `align`: Aligns content in different ways.
|
/// 📐 `align`: Aligns content in different ways.
|
||||||
|
@ -13,7 +13,9 @@ pub struct TextStyle {
|
|||||||
/// leftmost possible one.
|
/// leftmost possible one.
|
||||||
pub fallback: Vec<FontClass>,
|
pub fallback: Vec<FontClass>,
|
||||||
/// The font size.
|
/// The font size.
|
||||||
pub font_size: f32,
|
pub font_size: Size,
|
||||||
|
/// The word spacing (as a multiple of the font size).
|
||||||
|
pub word_spacing: f32,
|
||||||
/// The line spacing (as a multiple of the font size).
|
/// The line spacing (as a multiple of the font size).
|
||||||
pub line_spacing: f32,
|
pub line_spacing: f32,
|
||||||
/// The paragraphs spacing (as a multiple of the font size).
|
/// The paragraphs spacing (as a multiple of the font size).
|
||||||
@ -63,7 +65,8 @@ impl Default for TextStyle {
|
|||||||
TextStyle {
|
TextStyle {
|
||||||
classes: vec![Regular],
|
classes: vec![Regular],
|
||||||
fallback: vec![Serif],
|
fallback: vec![Serif],
|
||||||
font_size: 11.0,
|
font_size: Size::pt(11.0),
|
||||||
|
word_spacing: 0.25,
|
||||||
line_spacing: 1.2,
|
line_spacing: 1.2,
|
||||||
paragraph_spacing: 1.5,
|
paragraph_spacing: 1.5,
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user