Simple line layouter 🧾
This commit is contained in:
parent
b8620121a6
commit
269f069a4d
@ -117,6 +117,7 @@ macro_rules! function {
|
||||
}
|
||||
|
||||
/// Parse the body of a function.
|
||||
///
|
||||
/// - If the function does not expect a body, use `parse!(forbidden: body)`.
|
||||
/// - If the function can have a body, use `parse!(optional: body, ctx)`.
|
||||
/// - If the function must have a body, use `parse!(expected: body, ctx)`.
|
||||
@ -146,7 +147,7 @@ macro_rules! parse {
|
||||
}
|
||||
|
||||
/// Early-return with a formatted typesetting error or construct an error
|
||||
/// expression without returning when prefixed with `@`.
|
||||
/// expression.
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
(@unexpected_argument) => (error!(@"unexpected argument"));
|
||||
|
@ -104,7 +104,6 @@ pub enum Command<'a> {
|
||||
AddSpacing(Size, SpacingKind, GenericAxis),
|
||||
|
||||
FinishLine,
|
||||
FinishRun,
|
||||
FinishSpace,
|
||||
BreakParagraph,
|
||||
BreakPage,
|
||||
|
@ -1,198 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
/// The flex layouter first arranges boxes along a primary and if necessary also
|
||||
/// along a secondary axis.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FlexLayouter {
|
||||
axes: LayoutAxes,
|
||||
flex_spacing: Size,
|
||||
stack: StackLayouter,
|
||||
|
||||
units: Vec<FlexUnit>,
|
||||
line: FlexLine,
|
||||
part: PartialLine,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum FlexUnit {
|
||||
Boxed(Layout),
|
||||
Space(Size, SpacingKind),
|
||||
SetAxes(LayoutAxes),
|
||||
Break,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct FlexLine {
|
||||
usable: Size,
|
||||
actions: LayoutActions,
|
||||
combined_dimensions: Size2D,
|
||||
}
|
||||
|
||||
impl FlexLine {
|
||||
fn new(usable: Size) -> FlexLine {
|
||||
FlexLine {
|
||||
usable,
|
||||
actions: LayoutActions::new(),
|
||||
combined_dimensions: Size2D::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PartialLine {
|
||||
usable: Size,
|
||||
content: Vec<(Size, Layout)>,
|
||||
dimensions: Size2D,
|
||||
space: LastSpacing,
|
||||
}
|
||||
|
||||
impl PartialLine {
|
||||
fn new(usable: Size) -> PartialLine {
|
||||
PartialLine {
|
||||
usable,
|
||||
content: vec![],
|
||||
dimensions: Size2D::ZERO,
|
||||
space: LastSpacing::Hard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The context for flex layouting.
|
||||
///
|
||||
/// See [`LayoutContext`] for details about the fields.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FlexContext {
|
||||
pub spaces: LayoutSpaces,
|
||||
pub axes: LayoutAxes,
|
||||
pub alignment: LayoutAlignment,
|
||||
pub flex_spacing: Size,
|
||||
pub repeat: bool,
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
impl FlexLayouter {
|
||||
/// Create a new flex layouter.
|
||||
pub fn new(ctx: FlexContext) -> FlexLayouter {
|
||||
let stack = StackLayouter::new(StackContext {
|
||||
spaces: ctx.spaces,
|
||||
axes: ctx.axes,
|
||||
alignment: ctx.alignment,
|
||||
repeat: ctx.repeat,
|
||||
debug: ctx.debug,
|
||||
});
|
||||
|
||||
let usable = stack.primary_usable();
|
||||
|
||||
FlexLayouter {
|
||||
axes: ctx.axes,
|
||||
flex_spacing: ctx.flex_spacing,
|
||||
stack,
|
||||
|
||||
units: vec![],
|
||||
line: FlexLine::new(usable),
|
||||
part: PartialLine::new(usable),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, layout: Layout) {
|
||||
self.units.push(FlexUnit::Boxed(layout));
|
||||
}
|
||||
|
||||
pub fn add_multiple(&mut self, layouts: MultiLayout) {
|
||||
for layout in layouts {
|
||||
self.add(layout);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_break(&mut self) {
|
||||
self.units.push(FlexUnit::Break);
|
||||
}
|
||||
|
||||
pub fn add_primary_space(&mut self, space: Size, kind: SpacingKind) {
|
||||
self.units.push(FlexUnit::Space(space, kind))
|
||||
}
|
||||
|
||||
pub fn add_secondary_space(&mut self, space: Size, kind: SpacingKind) -> LayoutResult<()> {
|
||||
if !self.run_is_empty() {
|
||||
self.finish_run()?;
|
||||
}
|
||||
Ok(self.stack.add_spacing(space, kind))
|
||||
}
|
||||
|
||||
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
||||
self.units.push(FlexUnit::SetAxes(axes));
|
||||
}
|
||||
|
||||
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
|
||||
if replace_empty && self.run_is_empty() && self.stack.space_is_empty() {
|
||||
self.stack.set_spaces(spaces, true);
|
||||
self.start_line();
|
||||
} else {
|
||||
self.stack.set_spaces(spaces, false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remaining(&self) -> LayoutSpaces {
|
||||
self.stack.remaining()
|
||||
}
|
||||
|
||||
pub fn run_is_empty(&self) -> bool {
|
||||
!self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_)))
|
||||
}
|
||||
|
||||
pub fn run_last_is_space(&self) -> bool {
|
||||
matches!(self.units.last(), Some(FlexUnit::Space(_, _)))
|
||||
}
|
||||
|
||||
pub fn finish(mut self) -> LayoutResult<MultiLayout> {
|
||||
self.finish_space(false)?;
|
||||
self.stack.finish()
|
||||
}
|
||||
|
||||
pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
|
||||
if !self.run_is_empty() {
|
||||
self.finish_run()?;
|
||||
}
|
||||
|
||||
self.stack.finish_space(hard)?;
|
||||
Ok(self.start_line())
|
||||
}
|
||||
|
||||
pub fn finish_run(&mut self) -> LayoutResult<Size2D> {
|
||||
let units = std::mem::replace(&mut self.units, vec![]);
|
||||
for unit in units {
|
||||
match unit {
|
||||
FlexUnit::Boxed(boxed) => self.layout_box(boxed)?,
|
||||
FlexUnit::Space(space, kind) => self.layout_space(space, kind),
|
||||
FlexUnit::SetAxes(axes) => self.layout_set_axes(axes),
|
||||
FlexUnit::Break => { self.finish_line()?; },
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_line()
|
||||
}
|
||||
|
||||
fn finish_line(&mut self) -> LayoutResult<Size2D> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn start_line(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn finish_partial_line(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn layout_box(&mut self, _boxed: Layout) -> LayoutResult<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn layout_space(&mut self, _space: Size, _kind: SpacingKind) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn layout_set_axes(&mut self, _axes: LayoutAxes) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
283
src/layout/line.rs
Normal file
283
src/layout/line.rs
Normal file
@ -0,0 +1,283 @@
|
||||
use super::*;
|
||||
|
||||
/// The line layouter arranges boxes next to each other along a primary axis
|
||||
/// and arranges the resulting lines using an underlying stack layouter.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LineLayouter {
|
||||
/// The context for layouting.
|
||||
ctx: LineContext,
|
||||
/// The underlying stack layouter.
|
||||
stack: StackLayouter,
|
||||
/// The currently written line.
|
||||
run: LineRun,
|
||||
}
|
||||
|
||||
/// The context for line layouting.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LineContext {
|
||||
/// The spaces to layout in.
|
||||
pub spaces: LayoutSpaces,
|
||||
/// The initial layouting axes, which can be updated by the
|
||||
/// [`LineLayouter::set_axes`] method.
|
||||
pub axes: LayoutAxes,
|
||||
/// Which alignment to set on the resulting layout. This affects how it will
|
||||
/// be positioned in a parent box.
|
||||
pub alignment: LayoutAlignment,
|
||||
/// Whether to have repeated spaces or to use only the first and only once.
|
||||
pub repeat: bool,
|
||||
/// Whether to output a command which renders a debugging box showing the
|
||||
/// extent of the layout.
|
||||
pub debug: bool,
|
||||
/// The line spacing.
|
||||
pub line_spacing: Size,
|
||||
}
|
||||
|
||||
/// A simple line of boxes.
|
||||
#[derive(Debug, Clone)]
|
||||
struct LineRun {
|
||||
/// The so-far accumulated layouts in the line.
|
||||
layouts: Vec<(Size, Layout)>,
|
||||
/// The width (primary size) and maximal height (secondary size) of the
|
||||
/// line.
|
||||
size: Size2D,
|
||||
/// The alignment of all layouts in the line.
|
||||
alignment: Option<LayoutAlignment>,
|
||||
/// The remaining usable space if another differently aligned line run
|
||||
/// already took up some space.
|
||||
usable: Option<Size>,
|
||||
/// A possibly cached soft spacing or spacing state.
|
||||
last_spacing: LastSpacing,
|
||||
}
|
||||
|
||||
impl LineLayouter {
|
||||
/// Create a new line layouter.
|
||||
pub fn new(ctx: LineContext) -> LineLayouter {
|
||||
LineLayouter {
|
||||
stack: StackLayouter::new(StackContext {
|
||||
spaces: ctx.spaces.clone(),
|
||||
axes: ctx.axes,
|
||||
alignment: ctx.alignment,
|
||||
repeat: ctx.repeat,
|
||||
debug: ctx.debug,
|
||||
}),
|
||||
ctx,
|
||||
run: LineRun::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a layout to the run.
|
||||
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
|
||||
let axes = self.ctx.axes;
|
||||
|
||||
if let Some(alignment) = self.run.alignment {
|
||||
if layout.alignment.secondary != alignment.secondary {
|
||||
if self.stack.is_fitting_alignment(layout.alignment) {
|
||||
self.finish_line()?;
|
||||
} else {
|
||||
self.finish_space(true)?;
|
||||
}
|
||||
} else if layout.alignment.primary < alignment.primary {
|
||||
self.finish_line()?;
|
||||
|
||||
} else if layout.alignment.primary > alignment.primary {
|
||||
let mut rest_run = LineRun::new();
|
||||
|
||||
let usable = self.stack.usable().get_primary(axes);
|
||||
rest_run.usable = Some(match layout.alignment.primary {
|
||||
Alignment::Origin => unreachable!("origin > x"),
|
||||
Alignment::Center => usable - 2 * self.run.size.x,
|
||||
Alignment::End => usable - self.run.size.x,
|
||||
});
|
||||
|
||||
rest_run.size.y = self.run.size.y;
|
||||
|
||||
self.finish_line()?;
|
||||
self.stack.add_spacing(-rest_run.size.y, SpacingKind::Hard);
|
||||
|
||||
self.run = rest_run;
|
||||
}
|
||||
}
|
||||
|
||||
if let LastSpacing::Soft(spacing, _) = self.run.last_spacing {
|
||||
self.add_primary_spacing(spacing, SpacingKind::Hard);
|
||||
}
|
||||
|
||||
let size = layout.dimensions.generalized(axes);
|
||||
|
||||
while !self.usable().fits(size) {
|
||||
if !self.line_is_empty() {
|
||||
self.finish_line()?;
|
||||
} else {
|
||||
self.finish_space(true)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.run.alignment = Some(layout.alignment);
|
||||
self.run.layouts.push((self.run.size.x, layout));
|
||||
|
||||
self.run.size.x += size.x;
|
||||
self.run.size.y.max_eq(size.y);
|
||||
self.run.last_spacing = LastSpacing::None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add multiple layouts to the run.
|
||||
///
|
||||
/// This function simply calls `add` repeatedly for each layout.
|
||||
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
|
||||
for layout in layouts {
|
||||
self.add(layout)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The remaining usable size in the run.
|
||||
fn usable(&self) -> Size2D {
|
||||
// The base is the usable space per stack layouter.
|
||||
let mut usable = self.stack.usable().generalized(self.ctx.axes);
|
||||
|
||||
// If this is a alignment-continuing line, we override the primary
|
||||
// usable size.
|
||||
if let Some(primary) = self.run.usable {
|
||||
usable.x = primary;
|
||||
}
|
||||
|
||||
usable.x -= self.run.size.x;
|
||||
usable
|
||||
}
|
||||
|
||||
/// Add primary spacing to the line.
|
||||
pub fn add_primary_spacing(&mut self, mut spacing: Size, kind: SpacingKind) {
|
||||
match kind {
|
||||
// A hard space is simply an empty box.
|
||||
SpacingKind::Hard => {
|
||||
spacing.min_eq(self.usable().x);
|
||||
self.run.size.x += spacing;
|
||||
self.run.last_spacing = LastSpacing::Hard;
|
||||
}
|
||||
|
||||
// A soft space is cached if it is not consumed by a hard space or
|
||||
// previous soft space with higher level.
|
||||
SpacingKind::Soft(level) => {
|
||||
let consumes = match self.run.last_spacing {
|
||||
LastSpacing::None => true,
|
||||
LastSpacing::Soft(_, prev) if level < prev => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if consumes {
|
||||
self.run.last_spacing = LastSpacing::Soft(spacing, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish the run and add secondary spacing to the underlying stack.
|
||||
pub fn add_secondary_spacing(
|
||||
&mut self,
|
||||
mut spacing: Size,
|
||||
kind: SpacingKind
|
||||
) -> LayoutResult<()> {
|
||||
self.finish_line_if_not_empty()?;
|
||||
Ok(self.stack.add_spacing(spacing, kind))
|
||||
}
|
||||
|
||||
/// Change the layouting axes used by this layouter.
|
||||
pub fn set_axes(&mut self, axes: LayoutAxes) -> LayoutResult<()> {
|
||||
self.finish_line_if_not_empty()?;
|
||||
self.ctx.axes = axes;
|
||||
Ok(self.stack.set_axes(axes))
|
||||
}
|
||||
|
||||
/// Change the layouting spaces to use.
|
||||
///
|
||||
/// If `replace_empty` is true, the current space is replaced if there are
|
||||
/// no boxes laid into it yet. Otherwise, only the followup spaces are
|
||||
/// replaced.
|
||||
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
|
||||
self.stack.set_spaces(spaces, replace_empty && self.line_is_empty());
|
||||
}
|
||||
|
||||
/// Change the line spacing.
|
||||
pub fn set_line_spacing(&mut self, line_spacing: Size) {
|
||||
self.ctx.line_spacing = line_spacing;
|
||||
}
|
||||
|
||||
/// The remaining unpadded, unexpanding spaces.
|
||||
pub fn remaining(&self) -> LayoutSpaces {
|
||||
let mut spaces = self.stack.remaining();
|
||||
*spaces[0].dimensions.get_secondary_mut(self.ctx.axes)
|
||||
-= self.run.size.y;
|
||||
spaces
|
||||
}
|
||||
|
||||
/// Whether the currently set line is empty.
|
||||
pub fn line_is_empty(&self) -> bool {
|
||||
self.run.size == Size2D::ZERO && self.run.layouts.is_empty()
|
||||
}
|
||||
|
||||
/// Finish the last line and compute the final multi-layout.
|
||||
pub fn finish(mut self) -> LayoutResult<MultiLayout> {
|
||||
self.finish_line_if_not_empty()?;
|
||||
self.stack.finish()
|
||||
}
|
||||
|
||||
/// Finish the currently active space and start a new one.
|
||||
pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
|
||||
self.finish_line_if_not_empty()?;
|
||||
self.stack.finish_space(hard)
|
||||
}
|
||||
|
||||
/// Add the current line to the stack and start a new line.
|
||||
pub fn finish_line(&mut self) -> LayoutResult<()> {
|
||||
let mut actions = LayoutActions::new();
|
||||
|
||||
let layouts = std::mem::replace(&mut self.run.layouts, vec![]);
|
||||
for (offset, layout) in layouts {
|
||||
let x = match self.ctx.axes.primary.is_positive() {
|
||||
true => offset,
|
||||
false => self.run.size.x
|
||||
- offset
|
||||
- layout.dimensions.get_primary(self.ctx.axes),
|
||||
};
|
||||
|
||||
let pos = Size2D::with_x(x);
|
||||
actions.add_layout(pos, layout);
|
||||
}
|
||||
|
||||
self.stack.add(Layout {
|
||||
dimensions: self.run.size.specialized(self.ctx.axes),
|
||||
alignment: self.run.alignment
|
||||
.unwrap_or(LayoutAlignment::new(Origin, Origin)),
|
||||
actions: actions.to_vec(),
|
||||
})?;
|
||||
|
||||
self.run = LineRun::new();
|
||||
|
||||
self.stack.add_spacing(self.ctx.line_spacing, LINE_KIND);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finish the current line if it is not empty.
|
||||
fn finish_line_if_not_empty(&mut self) -> LayoutResult<()> {
|
||||
if !self.line_is_empty() {
|
||||
self.finish_line()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LineRun {
|
||||
fn new() -> LineRun {
|
||||
LineRun {
|
||||
layouts: vec![],
|
||||
size: Size2D::ZERO,
|
||||
alignment: None,
|
||||
usable: None,
|
||||
last_spacing: LastSpacing::Hard,
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ use crate::style::LayoutStyle;
|
||||
|
||||
mod actions;
|
||||
mod tree;
|
||||
mod flex;
|
||||
mod line;
|
||||
mod stack;
|
||||
mod text;
|
||||
|
||||
@ -30,7 +30,7 @@ pub mod prelude {
|
||||
/// Different kinds of layouters (fully re-exported).
|
||||
pub mod layouters {
|
||||
pub use super::tree::layout;
|
||||
pub use super::flex::{FlexLayouter, FlexContext};
|
||||
pub use super::line::{LineLayouter, LineContext};
|
||||
pub use super::stack::{StackLayouter, StackContext};
|
||||
pub use super::text::{layout_text, TextContext};
|
||||
}
|
||||
@ -174,7 +174,7 @@ impl LayoutExpansion {
|
||||
LayoutExpansion { horizontal, vertical }
|
||||
}
|
||||
|
||||
/// Borrow the spcified component mutably.
|
||||
/// Borrow the specified component mutably.
|
||||
pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool {
|
||||
match axis {
|
||||
Horizontal => &mut self.horizontal,
|
||||
@ -366,6 +366,9 @@ const PARAGRAPH_KIND: SpacingKind = SpacingKind::Soft(1);
|
||||
/// The standard spacing kind used for line spacing.
|
||||
const LINE_KIND: SpacingKind = SpacingKind::Soft(2);
|
||||
|
||||
/// The standard spacing kind used for word spacing.
|
||||
const WORD_KIND: SpacingKind = SpacingKind::Soft(1);
|
||||
|
||||
/// The last appeared spacing.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
enum LastSpacing {
|
||||
|
@ -1,11 +1,9 @@
|
||||
use smallvec::smallvec;
|
||||
use crate::size::ValueBox;
|
||||
use super::*;
|
||||
|
||||
/// The stack layouter stack boxes onto each other along the secondary layouting
|
||||
/// axis.
|
||||
///
|
||||
/// The boxes are aligned along both axes according to their requested
|
||||
/// alignment.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StackLayouter {
|
||||
/// The context for layouting.
|
||||
@ -42,30 +40,21 @@ struct Space {
|
||||
index: usize,
|
||||
/// Whether to add the layout for this space even if it would be empty.
|
||||
hard: bool,
|
||||
/// The so-far accumulated subspaces.
|
||||
/// The so-far accumulated layouts.
|
||||
layouts: Vec<(LayoutAxes, Layout)>,
|
||||
/// The specialized size of this subspace.
|
||||
/// The specialized size of this space.
|
||||
size: Size2D,
|
||||
/// The specialized remaining space.
|
||||
usable: Size2D,
|
||||
/// The specialized extra-needed dimensions to affect the size at all.
|
||||
extra: Size2D,
|
||||
/// Dictates the valid alignments for new boxes in this space.
|
||||
rulers: Rulers,
|
||||
/// The rulers of a space dictate which alignments for new boxes are still
|
||||
/// allowed and which require a new space to be started.
|
||||
rulers: ValueBox<Alignment>,
|
||||
/// The last added spacing if the last added thing was spacing.
|
||||
last_spacing: LastSpacing,
|
||||
}
|
||||
|
||||
/// The rulers of a space dictate which alignments for new boxes are still
|
||||
/// allowed and which require a new space to be started.
|
||||
#[derive(Debug, Clone)]
|
||||
struct Rulers {
|
||||
top: Alignment,
|
||||
bottom: Alignment,
|
||||
left: Alignment,
|
||||
right: Alignment,
|
||||
}
|
||||
|
||||
impl StackLayouter {
|
||||
/// Create a new stack layouter.
|
||||
pub fn new(ctx: StackContext) -> StackLayouter {
|
||||
@ -157,26 +146,6 @@ impl StackLayouter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the rulers to account for the new layout. Returns true if a
|
||||
/// space break is necessary.
|
||||
fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool {
|
||||
let axes = self.ctx.axes;
|
||||
let allowed = self.alignment_allowed(axes.primary, alignment.primary)
|
||||
&& self.alignment_allowed(axes.secondary, alignment.secondary);
|
||||
|
||||
if allowed {
|
||||
*self.space.rulers.get(axes.secondary) = alignment.secondary;
|
||||
}
|
||||
|
||||
allowed
|
||||
}
|
||||
|
||||
/// Whether the given alignment is still allowed according to the rulers.
|
||||
fn alignment_allowed(&mut self, direction: Direction, alignment: Alignment) -> bool {
|
||||
alignment >= *self.space.rulers.get(direction)
|
||||
&& alignment <= self.space.rulers.get(direction.inv()).inv()
|
||||
}
|
||||
|
||||
/// Update the size metrics to reflect that a layout or spacing with the
|
||||
/// given generalized dimensions has been added.
|
||||
fn update_metrics(&mut self, dimensions: Size2D) {
|
||||
@ -196,10 +165,31 @@ impl StackLayouter {
|
||||
*self.space.usable.get_secondary_mut(axes) -= dimensions.y;
|
||||
}
|
||||
|
||||
/// Update the rulers to account for the new layout. Returns true if a
|
||||
/// space break is necessary.
|
||||
fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool {
|
||||
let allowed = self.is_fitting_alignment(alignment);
|
||||
if allowed {
|
||||
*self.space.rulers.get_mut(self.ctx.axes.secondary, Origin)
|
||||
= alignment.secondary;
|
||||
}
|
||||
allowed
|
||||
}
|
||||
|
||||
/// Whether a layout with the given alignment can still be layouted in the
|
||||
/// active space.
|
||||
pub fn is_fitting_alignment(&mut self, alignment: LayoutAlignment) -> bool {
|
||||
self.is_fitting_axis(self.ctx.axes.primary, alignment.primary)
|
||||
&& self.is_fitting_axis(self.ctx.axes.secondary, alignment.secondary)
|
||||
}
|
||||
|
||||
/// Whether the given alignment is still allowed according to the rulers.
|
||||
fn is_fitting_axis(&mut self, direction: Direction, alignment: Alignment) -> bool {
|
||||
alignment >= *self.space.rulers.get_mut(direction, Origin)
|
||||
&& alignment <= self.space.rulers.get_mut(direction, End).inv()
|
||||
}
|
||||
|
||||
/// Change the layouting axes used by this layouter.
|
||||
///
|
||||
/// This starts a new subspace (if the axes are actually different from the
|
||||
/// current ones).
|
||||
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
||||
// Forget the spacing because it is not relevant anymore.
|
||||
if axes.secondary != self.ctx.axes.secondary {
|
||||
@ -227,9 +217,7 @@ impl StackLayouter {
|
||||
/// The remaining unpadded, unexpanding spaces. If a multi-layout is laid
|
||||
/// out into these spaces, it will fit into this stack.
|
||||
pub fn remaining(&self) -> LayoutSpaces {
|
||||
let dimensions = self.space.usable
|
||||
- Size2D::with_y(self.space.last_spacing.soft_or_zero())
|
||||
.specialized(self.ctx.axes);
|
||||
let dimensions = self.usable();
|
||||
|
||||
let mut spaces = smallvec![LayoutSpace {
|
||||
dimensions,
|
||||
@ -244,9 +232,11 @@ impl StackLayouter {
|
||||
spaces
|
||||
}
|
||||
|
||||
/// The usable size along the primary axis.
|
||||
pub fn primary_usable(&self) -> Size {
|
||||
self.space.usable.get_primary(self.ctx.axes)
|
||||
/// The remaining usable size.
|
||||
pub fn usable(&self) -> Size2D {
|
||||
self.space.usable
|
||||
- Size2D::with_y(self.space.last_spacing.soft_or_zero())
|
||||
.specialized(self.ctx.axes)
|
||||
}
|
||||
|
||||
/// Whether the current layout space (not subspace) is empty.
|
||||
@ -409,24 +399,8 @@ impl Space {
|
||||
size: Size2D::ZERO,
|
||||
usable,
|
||||
extra: Size2D::ZERO,
|
||||
rulers: Rulers {
|
||||
top: Origin,
|
||||
bottom: Origin,
|
||||
left: Origin,
|
||||
right: Origin,
|
||||
},
|
||||
rulers: ValueBox::with_all(Origin),
|
||||
last_spacing: LastSpacing::Hard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rulers {
|
||||
fn get(&mut self, direction: Direction) -> &mut Alignment {
|
||||
match direction {
|
||||
TopToBottom => &mut self.top,
|
||||
BottomToTop => &mut self.bottom,
|
||||
LeftToRight => &mut self.left,
|
||||
RightToLeft => &mut self.right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use super::*;
|
||||
pub struct TextContext<'a, 'p> {
|
||||
pub loader: &'a SharedFontLoader<'p>,
|
||||
pub style: &'a TextStyle,
|
||||
pub axes: LayoutAxes,
|
||||
pub alignment: LayoutAlignment,
|
||||
}
|
||||
|
||||
@ -50,22 +51,14 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
|
||||
|
||||
/// Layout the text
|
||||
fn layout(mut self) -> LayoutResult<Layout> {
|
||||
for c in self.text.chars() {
|
||||
let (index, char_width) = self.select_font(c)?;
|
||||
|
||||
self.width += char_width;
|
||||
|
||||
if self.active_font != index {
|
||||
if !self.buffer.is_empty() {
|
||||
self.actions.add(LayoutAction::WriteText(self.buffer));
|
||||
self.buffer = String::new();
|
||||
}
|
||||
|
||||
self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size()));
|
||||
self.active_font = index;
|
||||
if self.ctx.axes.primary.is_positive() {
|
||||
for c in self.text.chars() {
|
||||
self.layout_char(c)?;
|
||||
}
|
||||
} else {
|
||||
for c in self.text.chars().rev() {
|
||||
self.layout_char(c)?;
|
||||
}
|
||||
|
||||
self.buffer.push(c);
|
||||
}
|
||||
|
||||
if !self.buffer.is_empty() {
|
||||
@ -79,6 +72,27 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Layout an individual character.
|
||||
fn layout_char(&mut self, c: char) -> LayoutResult<()> {
|
||||
let (index, char_width) = self.select_font(c)?;
|
||||
|
||||
self.width += char_width;
|
||||
|
||||
if self.active_font != index {
|
||||
if !self.buffer.is_empty() {
|
||||
let text = std::mem::replace(&mut self.buffer, String::new());
|
||||
self.actions.add(LayoutAction::WriteText(text));
|
||||
}
|
||||
|
||||
self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size()));
|
||||
self.active_font = index;
|
||||
}
|
||||
|
||||
self.buffer.push(c);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Select the best font for a character and return its index along with
|
||||
/// the width of the char in the font.
|
||||
fn select_font(&mut self, c: char) -> LayoutResult<(usize, Size)> {
|
||||
|
@ -16,7 +16,7 @@ pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout
|
||||
#[derive(Debug, Clone)]
|
||||
struct TreeLayouter<'a, 'p> {
|
||||
ctx: LayoutContext<'a, 'p>,
|
||||
stack: StackLayouter,
|
||||
layouter: LineLayouter,
|
||||
style: LayoutStyle,
|
||||
}
|
||||
|
||||
@ -24,12 +24,13 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
||||
/// Create a new syntax tree layouter.
|
||||
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
|
||||
TreeLayouter {
|
||||
stack: StackLayouter::new(StackContext {
|
||||
layouter: LineLayouter::new(LineContext {
|
||||
spaces: ctx.spaces.clone(),
|
||||
axes: ctx.axes,
|
||||
alignment: ctx.alignment,
|
||||
repeat: ctx.repeat,
|
||||
debug: ctx.debug,
|
||||
line_spacing: ctx.style.text.line_spacing(),
|
||||
}),
|
||||
style: ctx.style.clone(),
|
||||
ctx,
|
||||
@ -59,29 +60,27 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
||||
let layout = layout_text(text, TextContext {
|
||||
loader: &self.ctx.loader,
|
||||
style: &self.style.text,
|
||||
axes: self.ctx.axes,
|
||||
alignment: self.ctx.alignment,
|
||||
})?;
|
||||
|
||||
self.stack.add(layout)
|
||||
self.layouter.add(layout)
|
||||
}
|
||||
|
||||
fn layout_space(&mut self) {
|
||||
|
||||
self.layouter.add_primary_spacing(self.style.text.word_spacing(), WORD_KIND);
|
||||
}
|
||||
|
||||
fn layout_paragraph(&mut self) -> LayoutResult<()> {
|
||||
Ok(self.stack.add_spacing(
|
||||
paragraph_spacing(&self.style.text),
|
||||
PARAGRAPH_KIND,
|
||||
))
|
||||
self.layouter.add_secondary_spacing(self.style.text.paragraph_spacing(), PARAGRAPH_KIND)
|
||||
}
|
||||
|
||||
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
|
||||
let commands = func.0.layout(LayoutContext {
|
||||
style: &self.style,
|
||||
spaces: self.stack.remaining(),
|
||||
spaces: self.layouter.remaining(),
|
||||
nested: true,
|
||||
debug: true,
|
||||
debug: false,
|
||||
.. self.ctx
|
||||
})?;
|
||||
|
||||
@ -98,26 +97,28 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
||||
match command {
|
||||
LayoutTree(tree) => self.layout(tree)?,
|
||||
|
||||
Add(layout) => self.stack.add(layout)?,
|
||||
AddMultiple(layouts) => self.stack.add_multiple(layouts)?,
|
||||
Add(layout) => self.layouter.add(layout)?,
|
||||
AddMultiple(layouts) => self.layouter.add_multiple(layouts)?,
|
||||
AddSpacing(space, kind, axis) => match axis {
|
||||
Primary => {},
|
||||
Secondary => self.stack.add_spacing(space, kind),
|
||||
Primary => self.layouter.add_primary_spacing(space, kind),
|
||||
Secondary => self.layouter.add_secondary_spacing(space, kind)?,
|
||||
}
|
||||
|
||||
FinishLine => {},
|
||||
FinishRun => {},
|
||||
FinishSpace => self.stack.finish_space(true)?,
|
||||
FinishLine => self.layouter.finish_line()?,
|
||||
FinishSpace => self.layouter.finish_space(true)?,
|
||||
BreakParagraph => self.layout_paragraph()?,
|
||||
BreakPage => {
|
||||
if self.ctx.nested {
|
||||
error!("page break cannot be issued from nested context");
|
||||
}
|
||||
|
||||
self.stack.finish_space(true)?
|
||||
self.layouter.finish_space(true)?
|
||||
}
|
||||
|
||||
SetTextStyle(style) => self.style.text = style,
|
||||
SetTextStyle(style) => {
|
||||
self.layouter.set_line_spacing(style.line_spacing());
|
||||
self.style.text = style;
|
||||
}
|
||||
SetPageStyle(style) => {
|
||||
if self.ctx.nested {
|
||||
error!("page style cannot be altered in nested context");
|
||||
@ -127,7 +128,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
||||
|
||||
let margins = style.margins();
|
||||
self.ctx.base = style.dimensions.unpadded(margins);
|
||||
self.stack.set_spaces(smallvec![
|
||||
self.layouter.set_spaces(smallvec![
|
||||
LayoutSpace {
|
||||
dimensions: style.dimensions,
|
||||
padding: margins,
|
||||
@ -137,7 +138,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
||||
}
|
||||
SetAlignment(alignment) => self.ctx.alignment = alignment,
|
||||
SetAxes(axes) => {
|
||||
self.stack.set_axes(axes);
|
||||
self.layouter.set_axes(axes);
|
||||
self.ctx.axes = axes;
|
||||
}
|
||||
}
|
||||
@ -146,18 +147,6 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
||||
}
|
||||
|
||||
fn finish(self) -> LayoutResult<MultiLayout> {
|
||||
self.stack.finish()
|
||||
self.layouter.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn word_spacing(style: &TextStyle) -> Size {
|
||||
style.word_spacing * style.font_size()
|
||||
}
|
||||
|
||||
fn flex_spacing(style: &TextStyle) -> Size {
|
||||
(style.line_spacing - 1.0) * style.font_size()
|
||||
}
|
||||
|
||||
fn paragraph_spacing(style: &TextStyle) -> Size {
|
||||
(style.paragraph_spacing - 1.0) * style.font_size()
|
||||
}
|
||||
|
@ -9,28 +9,30 @@ function! {
|
||||
pub struct Boxed {
|
||||
body: SyntaxTree,
|
||||
map: ExtentMap<PSize>,
|
||||
debug: bool,
|
||||
debug: Option<bool>,
|
||||
}
|
||||
|
||||
parse(args, body, ctx) {
|
||||
Boxed {
|
||||
body: parse!(optional: body, ctx).unwrap_or(SyntaxTree::new()),
|
||||
map: ExtentMap::new(&mut args, false)?,
|
||||
debug: args.get_key_opt::<bool>("debug")?.unwrap_or(true),
|
||||
debug: args.get_key_opt::<bool>("debug")?,
|
||||
}
|
||||
}
|
||||
|
||||
layout(self, mut ctx) {
|
||||
ctx.repeat = false;
|
||||
ctx.debug = self.debug;
|
||||
|
||||
if let Some(debug) = self.debug {
|
||||
ctx.debug = debug;
|
||||
}
|
||||
|
||||
let map = self.map.dedup(ctx.axes)?;
|
||||
|
||||
// Try to layout this box in all spaces.
|
||||
// Try to layout this box in all spaces until it fits into some space.
|
||||
let mut error = None;
|
||||
for &space in &ctx.spaces {
|
||||
for &(mut space) in &ctx.spaces {
|
||||
let mut ctx = ctx.clone();
|
||||
let mut space = space;
|
||||
|
||||
for &axis in &[Horizontal, Vertical] {
|
||||
if let Some(psize) = map.get(axis) {
|
||||
|
246
src/size.rs
246
src/size.rs
@ -1,6 +1,6 @@
|
||||
//! Different-dimensional spacing types.
|
||||
//! Different-dimensional value and spacing types.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::iter::Sum;
|
||||
use std::ops::*;
|
||||
use std::str::FromStr;
|
||||
@ -8,7 +8,7 @@ use std::str::FromStr;
|
||||
use crate::layout::prelude::*;
|
||||
|
||||
/// A general spacing type.
|
||||
#[derive(Copy, Clone, PartialEq, PartialOrd)]
|
||||
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Size {
|
||||
/// The size in typographic points (1/72 inches).
|
||||
pub points: f32,
|
||||
@ -92,64 +92,111 @@ impl Sum for Size {
|
||||
}
|
||||
}
|
||||
|
||||
/// A position or extent in 2-dimensional space.
|
||||
/// Either an absolute size or a factor of some entity.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct Size2D {
|
||||
/// The horizontal coordinate.
|
||||
pub x: Size,
|
||||
/// The vertical coordinate.
|
||||
pub y: Size,
|
||||
pub enum ScaleSize {
|
||||
Absolute(Size),
|
||||
Scaled(f32),
|
||||
}
|
||||
|
||||
impl Size2D {
|
||||
/// The zeroed 2D-size.
|
||||
pub const ZERO: Size2D = Size2D { x: Size::ZERO, y: Size::ZERO };
|
||||
impl ScaleSize {
|
||||
/// Use the absolute value or scale the entity.
|
||||
pub fn scaled(&self, entity: Size) -> Size {
|
||||
match self {
|
||||
ScaleSize::Absolute(s) => *s,
|
||||
ScaleSize::Scaled(s) => *s * entity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new 2D-size from two sizes.
|
||||
pub fn new(x: Size, y: Size) -> Size2D { Size2D { x, y } }
|
||||
impl Display for ScaleSize {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ScaleSize::Absolute(size) => write!(f, "{}", size),
|
||||
ScaleSize::Scaled(scale) => write!(f, "x{}", scale),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new 2D-size with `x` set to a value and `y` zero.
|
||||
pub fn with_x(x: Size) -> Size2D { Size2D { x, y: Size::ZERO } }
|
||||
debug_display!(ScaleSize);
|
||||
|
||||
/// Create a new 2D-size with `y` set to a value and `x` zero.
|
||||
pub fn with_y(y: Size) -> Size2D { Size2D { x: Size::ZERO, y } }
|
||||
/// A scale size that is scaled by the font size.
|
||||
pub type FSize = ScaleSize;
|
||||
|
||||
/// Create a 2D-size with `x` and `y` set to the same value `s`.
|
||||
pub fn with_all(s: Size) -> Size2D { Size2D { x: s, y: s } }
|
||||
/// A scale size that is scaled by the size of the padded parent container.
|
||||
pub type PSize = ScaleSize;
|
||||
|
||||
/// A value in two dimensions.
|
||||
#[derive(Default, Copy, Clone, PartialEq)]
|
||||
pub struct Value2D<T: Copy> {
|
||||
/// The horizontal component.
|
||||
pub x: T,
|
||||
/// The vertical component.
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
impl<T: Copy> Value2D<T> {
|
||||
/// Create a new 2D-value from two values.
|
||||
pub fn new(x: T, y: T) -> Value2D<T> { Value2D { x, y } }
|
||||
|
||||
/// Create a new 2D-value with `x` set to a value and `y` to default.
|
||||
pub fn with_x(x: T) -> Value2D<T> where T: Default {
|
||||
Value2D { x, y: T::default() }
|
||||
}
|
||||
|
||||
/// Create a new 2D-value with `y` set to a value and `x` to default.
|
||||
pub fn with_y(y: T) -> Value2D<T> where T: Default {
|
||||
Value2D { x: T::default(), y }
|
||||
}
|
||||
|
||||
/// Create a new 2D-value with the primary axis set to a value and the other
|
||||
/// one to default.
|
||||
pub fn with_primary(v: T, axes: LayoutAxes) -> Value2D<T> where T: Default {
|
||||
Value2D::with_x(v).generalized(axes)
|
||||
}
|
||||
|
||||
/// Create a new 2D-value with the secondary axis set to a value and the
|
||||
/// other one to default.
|
||||
pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2D<T> where T: Default {
|
||||
Value2D::with_y(v).generalized(axes)
|
||||
}
|
||||
|
||||
/// Create a 2D-value with `x` and `y` set to the same value `s`.
|
||||
pub fn with_all(s: T) -> Value2D<T> { Value2D { x: s, y: s } }
|
||||
|
||||
/// Get the specificed component.
|
||||
pub fn get(self, axis: SpecificAxis) -> Size {
|
||||
pub fn get(self, axis: SpecificAxis) -> T {
|
||||
match axis {
|
||||
Horizontal => self.x,
|
||||
Vertical => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the specificed component mutably.
|
||||
pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut Size {
|
||||
/// Borrow the specificed component mutably.
|
||||
pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut T {
|
||||
match axis {
|
||||
Horizontal => &mut self.x,
|
||||
Vertical => &mut self.y,
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the primary size of this specialized 2D-size.
|
||||
pub fn get_primary(self, axes: LayoutAxes) -> Size {
|
||||
/// Return the primary value of this specialized 2D-value.
|
||||
pub fn get_primary(self, axes: LayoutAxes) -> T {
|
||||
if axes.primary.axis() == Horizontal { self.x } else { self.y }
|
||||
}
|
||||
|
||||
/// Access the primary size of this specialized 2D-size mutably.
|
||||
pub fn get_primary_mut(&mut self, axes: LayoutAxes) -> &mut Size {
|
||||
/// Borrow the primary value of this specialized 2D-value mutably.
|
||||
pub fn get_primary_mut(&mut self, axes: LayoutAxes) -> &mut T {
|
||||
if axes.primary.axis() == Horizontal { &mut self.x } else { &mut self.y }
|
||||
}
|
||||
|
||||
/// Access the secondary size of this specialized 2D-size.
|
||||
pub fn get_secondary(self, axes: LayoutAxes) -> Size {
|
||||
/// Return the secondary value of this specialized 2D-value.
|
||||
pub fn get_secondary(self, axes: LayoutAxes) -> T {
|
||||
if axes.primary.axis() == Horizontal { self.y } else { self.x }
|
||||
}
|
||||
|
||||
/// Access the secondary size of this specialized 2D-size mutably.
|
||||
pub fn get_secondary_mut(&mut self, axes: LayoutAxes) -> &mut Size {
|
||||
/// Borrow the secondary value of this specialized 2D-value mutably.
|
||||
pub fn get_secondary_mut(&mut self, axes: LayoutAxes) -> &mut T {
|
||||
if axes.primary.axis() == Horizontal { &mut self.y } else { &mut self.x }
|
||||
}
|
||||
|
||||
@ -157,20 +204,40 @@ impl Size2D {
|
||||
/// axes, that is:
|
||||
/// - `x` describes the primary axis instead of the horizontal one.
|
||||
/// - `y` describes the secondary axis instead of the vertical one.
|
||||
pub fn generalized(self, axes: LayoutAxes) -> Size2D {
|
||||
pub fn generalized(self, axes: LayoutAxes) -> Value2D<T> {
|
||||
match axes.primary.axis() {
|
||||
Horizontal => self,
|
||||
Vertical => Size2D { x: self.y, y: self.x },
|
||||
Vertical => Value2D { x: self.y, y: self.x },
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the specialized version of this generalized Size2D (inverse to
|
||||
/// `generalized`).
|
||||
pub fn specialized(self, axes: LayoutAxes) -> Size2D {
|
||||
pub fn specialized(self, axes: LayoutAxes) -> Value2D<T> {
|
||||
// In fact, generalized is its own inverse. For reasons of clarity
|
||||
// at the call site, we still have this second function.
|
||||
self.generalized(axes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Display for Value2D<T> where T: Display {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "[{}, {}]", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Debug for Value2D<T> where T: Debug {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "[{:?}, {:?}]", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
/// A position or extent in 2-dimensional space.
|
||||
pub type Size2D = Value2D<Size>;
|
||||
|
||||
impl Size2D {
|
||||
/// The zeroed 2D-size.
|
||||
pub const ZERO: Size2D = Size2D { x: Size::ZERO, y: Size::ZERO };
|
||||
|
||||
/// Whether the given 2D-size fits into this one, that is, both coordinate
|
||||
/// values are smaller or equal.
|
||||
@ -207,14 +274,6 @@ impl Size2D {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Size2D {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "[{}, {}]", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
debug_display!(Size2D);
|
||||
|
||||
impl Neg for Size2D {
|
||||
type Output = Size2D;
|
||||
|
||||
@ -226,11 +285,41 @@ impl Neg for Size2D {
|
||||
}
|
||||
}
|
||||
|
||||
/// A size in four dimensions.
|
||||
pub type SizeBox = ValueBox<Size>;
|
||||
/// A value that is stretchable in an interval from a minimal through an optimal
|
||||
/// to a maximal value.
|
||||
pub struct StretchValue<T: Copy> {
|
||||
/// The minimum this value can be stretched to.
|
||||
pub min: T,
|
||||
/// The optimum for this value.
|
||||
pub opt: T,
|
||||
/// The maximum this value can be stretched to.
|
||||
pub max: T,
|
||||
}
|
||||
|
||||
impl<T: Copy> StretchValue<T> {
|
||||
/// Create a new stretch size from minimum, optimal and maximum values.
|
||||
pub fn new(min: T, opt: T, max: T) -> StretchValue<T> {
|
||||
StretchValue { min, opt, max }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Display for StretchValue<T> where T: Display {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "({}, {}, {})", self.min, self.opt, self.max)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Debug for StretchValue<T> where T: Debug {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "({:?}, {:?}, {:?})", self.min, self.opt, self.max)
|
||||
}
|
||||
}
|
||||
|
||||
/// A size that is stretchable.
|
||||
pub type StretchSize = StretchValue<Size>;
|
||||
|
||||
/// A value in four dimensions.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[derive(Default, Copy, Clone, PartialEq)]
|
||||
pub struct ValueBox<T: Copy> {
|
||||
/// The left extent.
|
||||
pub left: T,
|
||||
@ -242,16 +331,6 @@ pub struct ValueBox<T: Copy> {
|
||||
pub bottom: T,
|
||||
}
|
||||
|
||||
impl SizeBox {
|
||||
/// The zeroed size box.
|
||||
pub const ZERO: SizeBox = SizeBox {
|
||||
left: Size::ZERO,
|
||||
top: Size::ZERO,
|
||||
right: Size::ZERO,
|
||||
bottom: Size::ZERO,
|
||||
};
|
||||
}
|
||||
|
||||
impl<T: Copy> ValueBox<T> {
|
||||
/// Create a new box from four sizes.
|
||||
pub fn new(left: T, top: T, right: T, bottom: T) -> ValueBox<T> {
|
||||
@ -263,9 +342,10 @@ impl<T: Copy> ValueBox<T> {
|
||||
ValueBox { left: value, top: value, right: value, bottom: value }
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the value for the specified direction and
|
||||
/// alignment. Center alignment will be treated the same as origin
|
||||
/// Get a mutable reference to the value for the specified direction at the
|
||||
/// alignment.
|
||||
///
|
||||
/// Center alignment is treated the same as origin alignment.
|
||||
pub fn get_mut(&mut self, mut direction: Direction, alignment: Alignment) -> &mut T {
|
||||
if alignment == End {
|
||||
direction = direction.inv();
|
||||
@ -297,49 +377,33 @@ impl<T: Copy> ValueBox<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Display for ValueBox<T> where T: std::fmt::Debug {
|
||||
impl<T: Copy> Display for ValueBox<T> where T: Display {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "[left: {}, top: {}, right: {}, bottom: {}]",
|
||||
self.left, self.top, self.right, self.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Debug for ValueBox<T> where T: Debug {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "[left: {:?}, top: {:?}, right: {:?}, bottom: {:?}]",
|
||||
self.left, self.top, self.right, self.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
debug_display!(ValueBox; T where T: std::fmt::Debug + Copy);
|
||||
/// A size in four dimensions.
|
||||
pub type SizeBox = ValueBox<Size>;
|
||||
|
||||
/// Either an absolute size or a factor of some metric.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum ScaleSize {
|
||||
Absolute(Size),
|
||||
Scaled(f32),
|
||||
impl SizeBox {
|
||||
/// The zeroed size box.
|
||||
pub const ZERO: SizeBox = SizeBox {
|
||||
left: Size::ZERO,
|
||||
top: Size::ZERO,
|
||||
right: Size::ZERO,
|
||||
bottom: Size::ZERO,
|
||||
};
|
||||
}
|
||||
|
||||
/// A scale size that is scaled by the font size.
|
||||
pub type FSize = ScaleSize;
|
||||
|
||||
/// A scale size that is scaled by the size of the padded parent container.
|
||||
pub type PSize = ScaleSize;
|
||||
|
||||
impl ScaleSize {
|
||||
/// Use the absolute value or scale the entity.
|
||||
pub fn scaled(&self, entity: Size) -> Size {
|
||||
match self {
|
||||
ScaleSize::Absolute(s) => *s,
|
||||
ScaleSize::Scaled(s) => *s * entity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ScaleSize {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ScaleSize::Absolute(size) => write!(f, "{}", size),
|
||||
ScaleSize::Scaled(scale) => write!(f, "x{}", scale),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_display!(ScaleSize);
|
||||
|
||||
/// An error which can be returned when parsing a size.
|
||||
pub struct ParseSizeError;
|
||||
|
||||
|
27
src/style.rs
27
src/style.rs
@ -26,11 +26,11 @@ pub struct TextStyle {
|
||||
/// The font scale to apply on the base font size.
|
||||
pub font_scale: f32,
|
||||
/// The word spacing (as a multiple of the font size).
|
||||
pub word_spacing: f32,
|
||||
pub word_spacing_scale: f32,
|
||||
/// The line spacing (as a multiple of the font size).
|
||||
pub line_spacing: f32,
|
||||
pub line_spacing_scale: f32,
|
||||
/// The paragraphs spacing (as a multiple of the font size).
|
||||
pub paragraph_spacing: f32,
|
||||
pub paragraph_spacing_scale: f32,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
@ -39,6 +39,21 @@ impl TextStyle {
|
||||
self.base_font_size * self.font_scale
|
||||
}
|
||||
|
||||
/// The absolute word spacing.
|
||||
pub fn word_spacing(&self) -> Size {
|
||||
self.word_spacing_scale * self.font_size()
|
||||
}
|
||||
|
||||
/// The absolute line spacing.
|
||||
pub fn line_spacing(&self) -> Size {
|
||||
(self.line_spacing_scale - 1.0) * self.font_size()
|
||||
}
|
||||
|
||||
/// The absolute paragraph spacing.
|
||||
pub fn paragraph_spacing(&self) -> Size {
|
||||
(self.paragraph_spacing_scale - 1.0) * self.font_size()
|
||||
}
|
||||
|
||||
/// Toggle a class.
|
||||
///
|
||||
/// If the class was one of _italic_ or _bold_, then:
|
||||
@ -82,9 +97,9 @@ impl Default for TextStyle {
|
||||
fallback: vec![Serif],
|
||||
base_font_size: Size::pt(11.0),
|
||||
font_scale: 1.0,
|
||||
word_spacing: 0.25,
|
||||
line_spacing: 1.2,
|
||||
paragraph_spacing: 1.5,
|
||||
word_spacing_scale: 0.25,
|
||||
line_spacing_scale: 1.2,
|
||||
paragraph_spacing_scale: 1.5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
[box][
|
||||
A short sentence. [align: right][words.]
|
||||
|
||||
A short sentence. [n] [align: right][words.]
|
||||
|
||||
A short sentence. [par.break] [align: right][words.]
|
||||
|
||||
[align: bottom]
|
||||
A longer sentence with a few more words.
|
||||
]
|
||||
|
||||
[page.break]
|
||||
|
||||
[box][
|
||||
[align: vertical=top]
|
||||
Top
|
||||
|
||||
[align: vertical=center]
|
||||
Center
|
||||
|
||||
[align: vertical=bottom]
|
||||
Bottom [n] Bottom [n] Bottom
|
||||
]
|
@ -2,19 +2,19 @@
|
||||
[page.margins: 1cm]
|
||||
|
||||
[box][
|
||||
*Technical University Berlin* [n]
|
||||
*Faculty II, Institute for Mathematics* [n]
|
||||
Secretary Example [n]
|
||||
Prof. Dr. Example [n]
|
||||
Assistant #1, Assistant #2, Assistant #3
|
||||
*Technische Universität Berlin* [n]
|
||||
*Fakultät II, Institut for Mathematik* [n]
|
||||
Sekretariat MA [n]
|
||||
Dr. Max Mustermann [n]
|
||||
Ola Nordmann, John Doe
|
||||
]
|
||||
[align: right][*WiSe 2019/2020* [n] Week 1]
|
||||
[align: right][*WiSe 2019/2020* [n] Woche 3]
|
||||
|
||||
[v: 6mm]
|
||||
|
||||
[align: center][
|
||||
*3. Ubungsblatt Computerorientierte Mathematik II* [v: 0.3mm]
|
||||
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v: 0.3mm]
|
||||
*3. Ubungsblatt Computerorientierte Mathematik II* [v: 2mm]
|
||||
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v: 2mm]
|
||||
*Alle Antworten sind zu beweisen.*
|
||||
]
|
||||
|
||||
|
@ -1,14 +0,0 @@
|
||||
[page.size: height=5cm]
|
||||
|
||||
Line.
|
||||
|
||||
[box][
|
||||
Lines with [box][two] [box][boxes].
|
||||
|
||||
Lines with two boxes.
|
||||
]
|
||||
[box][
|
||||
Lines without two boxes.
|
||||
|
||||
Lines without two boxes.
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user