mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Implement context-modifying align 🧩
This commit is contained in:
parent
e87a34a4d0
commit
9a1d57a11a
29
src/func.rs
29
src/func.rs
@ -4,10 +4,9 @@ use std::any::Any;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use toddle::query::FontClass;
|
use crate::layout::{Layout, LayoutContext, Alignment, LayoutResult, MultiLayout};
|
||||||
|
|
||||||
use crate::layout::{Layout, LayoutContext, LayoutResult, MultiLayout};
|
|
||||||
use crate::parsing::{ParseContext, ParseResult};
|
use crate::parsing::{ParseContext, ParseResult};
|
||||||
|
use crate::style::TextStyle;
|
||||||
use crate::syntax::{FuncHeader, SyntaxTree};
|
use crate::syntax::{FuncHeader, SyntaxTree};
|
||||||
|
|
||||||
/// Typesetting function types.
|
/// Typesetting function types.
|
||||||
@ -27,7 +26,7 @@ pub trait Function: FunctionBounds {
|
|||||||
///
|
///
|
||||||
/// Returns optionally the resulting layout and a new context if changes to
|
/// Returns optionally the resulting layout and a new context if changes to
|
||||||
/// the context should be made.
|
/// the context should be made.
|
||||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<FuncCommands>;
|
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for dyn Function {
|
impl PartialEq for dyn Function {
|
||||||
@ -38,14 +37,14 @@ impl PartialEq for dyn Function {
|
|||||||
|
|
||||||
/// A sequence of commands requested for execution by a function.
|
/// A sequence of commands requested for execution by a function.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FuncCommands<'a> {
|
pub struct CommandList<'a> {
|
||||||
pub commands: Vec<Command<'a>>,
|
pub commands: Vec<Command<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FuncCommands<'a> {
|
impl<'a> CommandList<'a> {
|
||||||
/// Create an empty command list.
|
/// Create an empty command list.
|
||||||
pub fn new() -> FuncCommands<'a> {
|
pub fn new() -> CommandList<'a> {
|
||||||
FuncCommands { commands: vec![] }
|
CommandList { commands: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a command to the sequence.
|
/// Add a command to the sequence.
|
||||||
@ -59,7 +58,7 @@ impl<'a> FuncCommands<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for FuncCommands<'a> {
|
impl<'a> IntoIterator for CommandList<'a> {
|
||||||
type Item = Command<'a>;
|
type Item = Command<'a>;
|
||||||
type IntoIter = std::vec::IntoIter<Command<'a>>;
|
type IntoIter = std::vec::IntoIter<Command<'a>>;
|
||||||
|
|
||||||
@ -68,13 +67,23 @@ impl<'a> IntoIterator for FuncCommands<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a CommandList<'a> {
|
||||||
|
type Item = &'a Command<'a>;
|
||||||
|
type IntoIter = std::slice::Iter<'a, Command<'a>>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.commands.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Commands requested for execution by functions.
|
/// Commands requested for execution by functions.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Command<'a> {
|
pub enum Command<'a> {
|
||||||
Layout(&'a SyntaxTree),
|
Layout(&'a SyntaxTree),
|
||||||
Add(Layout),
|
Add(Layout),
|
||||||
AddMany(MultiLayout),
|
AddMany(MultiLayout),
|
||||||
ToggleStyleClass(FontClass),
|
SetAlignment(Alignment),
|
||||||
|
SetStyle(TextStyle),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A helper trait that describes requirements for types that can implement
|
/// A helper trait that describes requirements for types that can implement
|
||||||
|
@ -24,7 +24,7 @@ pub struct FlexLayouter {
|
|||||||
stack: StackLayouter,
|
stack: StackLayouter,
|
||||||
usable_width: Size,
|
usable_width: Size,
|
||||||
run: FlexRun,
|
run: FlexRun,
|
||||||
cached_glue: Option<Layout>,
|
cached_glue: Option<Size2D>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for flex layouting.
|
/// The context for flex layouting.
|
||||||
@ -70,7 +70,7 @@ enum FlexUnit {
|
|||||||
/// A unit which acts as glue between two [`FlexUnit::Boxed`] units and
|
/// A unit which acts as glue between two [`FlexUnit::Boxed`] units and
|
||||||
/// is only present if there was no flow break in between the two
|
/// is only present if there was no flow break in between the two
|
||||||
/// surrounding boxes.
|
/// surrounding boxes.
|
||||||
Glue(Layout),
|
Glue(Size2D),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FlexRun {
|
struct FlexRun {
|
||||||
@ -106,8 +106,8 @@ impl FlexLayouter {
|
|||||||
self.units.push(FlexUnit::Boxed(layout));
|
self.units.push(FlexUnit::Boxed(layout));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a glue layout which can be replaced by a line break.
|
/// Add a glue box which can be replaced by a line break.
|
||||||
pub fn add_glue(&mut self, glue: Layout) {
|
pub fn add_glue(&mut self, glue: Size2D) {
|
||||||
self.units.push(FlexUnit::Glue(glue));
|
self.units.push(FlexUnit::Glue(glue));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,12 +136,7 @@ impl FlexLayouter {
|
|||||||
/// Layout a content box into the current flex run or start a new run if
|
/// Layout a content box into the current flex run or start a new run if
|
||||||
/// it does not fit.
|
/// it does not fit.
|
||||||
fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
|
fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
|
||||||
let glue_width = self
|
let glue_width = self.cached_glue.unwrap_or(Size2D::zero()).x;
|
||||||
.cached_glue
|
|
||||||
.as_ref()
|
|
||||||
.map(|layout| layout.dimensions.x)
|
|
||||||
.unwrap_or(Size::zero());
|
|
||||||
|
|
||||||
let new_line_width = self.run.size.x + glue_width + boxed.dimensions.x;
|
let new_line_width = self.run.size.x + glue_width + boxed.dimensions.x;
|
||||||
|
|
||||||
if self.overflows_line(new_line_width) {
|
if self.overflows_line(new_line_width) {
|
||||||
@ -164,32 +159,31 @@ impl FlexLayouter {
|
|||||||
self.flush_glue();
|
self.flush_glue();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.add_to_run(boxed);
|
let dimensions = boxed.dimensions;
|
||||||
|
self.run.content.push((self.run.size.x, boxed));
|
||||||
|
|
||||||
|
self.grow_run(dimensions);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_glue(&mut self, glue: Layout) {
|
fn layout_glue(&mut self, glue: Size2D) {
|
||||||
self.flush_glue();
|
self.flush_glue();
|
||||||
self.cached_glue = Some(glue);
|
self.cached_glue = Some(glue);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush_glue(&mut self) {
|
fn flush_glue(&mut self) {
|
||||||
if let Some(glue) = self.cached_glue.take() {
|
if let Some(glue) = self.cached_glue.take() {
|
||||||
let new_line_width = self.run.size.x + glue.dimensions.x;
|
let new_line_width = self.run.size.x + glue.x;
|
||||||
if !self.overflows_line(new_line_width) {
|
if !self.overflows_line(new_line_width) {
|
||||||
self.add_to_run(glue);
|
self.grow_run(glue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_to_run(&mut self, layout: Layout) {
|
fn grow_run(&mut self, dimensions: Size2D) {
|
||||||
let x = self.run.size.x;
|
self.run.size.x += dimensions.x;
|
||||||
|
self.run.size.y = crate::size::max(self.run.size.y, dimensions.y);
|
||||||
self.run.size.x += layout.dimensions.x;
|
|
||||||
self.run.size.y = crate::size::max(self.run.size.y, layout.dimensions.y);
|
|
||||||
|
|
||||||
self.run.content.push((x, layout));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_run(&mut self) -> LayoutResult<()> {
|
fn finish_run(&mut self) -> LayoutResult<()> {
|
||||||
|
@ -12,6 +12,8 @@ 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> {
|
||||||
@ -27,6 +29,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
.. FlexContext::from_layout_ctx(ctx, flex_spacing(&ctx.style))
|
.. 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,22 +38,28 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
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 {
|
match node {
|
||||||
Node::Text(text) => self.layout_text(text, false)?,
|
Node::Text(text) => {
|
||||||
|
let layout = self.layout_text(text)?;
|
||||||
|
self.flex.add(layout);
|
||||||
|
self.set_newline = true;
|
||||||
|
}
|
||||||
|
|
||||||
Node::Space => {
|
Node::Space => {
|
||||||
// Only add a space if there was any content before.
|
// Only add a space if there was any content before.
|
||||||
if !self.flex.is_empty() {
|
if !self.flex.is_empty() {
|
||||||
self.layout_text(" ", true)?;
|
let layout = self.layout_text(" ")?;
|
||||||
|
self.flex.add_glue(layout.dimensions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finish the current flex layouting process.
|
// Finish the current flex layouting process.
|
||||||
Node::Newline => {
|
Node::Newline => {
|
||||||
self.layout_flex()?;
|
self.finish_flex()?;
|
||||||
|
|
||||||
if !self.stack.current_space_is_empty() {
|
if self.set_newline {
|
||||||
let space = paragraph_spacing(&self.style);
|
let space = paragraph_spacing(&self.style);
|
||||||
self.stack.add_space(space)?;
|
self.stack.add_space(space)?;
|
||||||
|
self.set_newline = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.start_new_flex();
|
self.start_new_flex();
|
||||||
@ -69,50 +79,10 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
|
|
||||||
/// Finish the layout.
|
/// Finish the layout.
|
||||||
fn finish(mut self) -> LayoutResult<MultiLayout> {
|
fn finish(mut self) -> LayoutResult<MultiLayout> {
|
||||||
self.layout_flex()?;
|
self.finish_flex()?;
|
||||||
self.stack.finish()
|
self.stack.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add text to the flex layout. If `glue` is true, the text will be a glue
|
|
||||||
/// part in the flex layouter. For details, see [`FlexLayouter`].
|
|
||||||
fn layout_text(&mut self, text: &str, glue: bool) -> LayoutResult<()> {
|
|
||||||
let ctx = TextContext {
|
|
||||||
loader: &self.ctx.loader,
|
|
||||||
style: &self.style,
|
|
||||||
};
|
|
||||||
|
|
||||||
let layout = layout_text(text, ctx)?;
|
|
||||||
|
|
||||||
if glue {
|
|
||||||
self.flex.add_glue(layout);
|
|
||||||
} else {
|
|
||||||
self.flex.add(layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finish the current flex layout and add it the stack.
|
|
||||||
fn layout_flex(&mut self) -> LayoutResult<()> {
|
|
||||||
if self.flex.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let layouts = self.flex.finish()?;
|
|
||||||
self.stack.add_many(layouts)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start a new flex layout.
|
|
||||||
fn start_new_flex(&mut self) {
|
|
||||||
let mut ctx = self.flex.ctx();
|
|
||||||
ctx.space.dimensions = self.stack.remaining();
|
|
||||||
ctx.flex_spacing = flex_spacing(&self.style);
|
|
||||||
|
|
||||||
self.flex = FlexLayouter::new(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Layout a function.
|
/// Layout a function.
|
||||||
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;
|
||||||
@ -130,15 +100,71 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
|
|
||||||
for command in commands {
|
for command in commands {
|
||||||
match command {
|
match command {
|
||||||
Command::Layout(tree) => self.layout(tree)?,
|
Command::Layout(tree) => {
|
||||||
Command::Add(layout) => self.stack.add(layout)?,
|
self.layout(tree)?;
|
||||||
Command::AddMany(layouts) => self.stack.add_many(layouts)?,
|
}
|
||||||
Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class),
|
|
||||||
|
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::SetAlignment(alignment) => {
|
||||||
|
self.finish_flex()?;
|
||||||
|
self.alignment = alignment;
|
||||||
|
self.start_new_flex();
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::SetStyle(style) => {
|
||||||
|
*self.style.to_mut() = style;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add text to the flex layout. If `glue` is true, the text will be a glue
|
||||||
|
/// part in the flex layouter. For details, see [`FlexLayouter`].
|
||||||
|
fn layout_text(&mut self, text: &str) -> LayoutResult<Layout> {
|
||||||
|
let ctx = TextContext {
|
||||||
|
loader: &self.ctx.loader,
|
||||||
|
style: &self.style,
|
||||||
|
};
|
||||||
|
|
||||||
|
layout_text(text, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish the current flex layout and add it the stack.
|
||||||
|
fn finish_flex(&mut self) -> LayoutResult<()> {
|
||||||
|
if self.flex.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let layouts = self.flex.finish()?;
|
||||||
|
self.stack.add_many(layouts)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a new flex layout.
|
||||||
|
fn start_new_flex(&mut self) {
|
||||||
|
let mut ctx = self.flex.ctx();
|
||||||
|
ctx.space.dimensions = self.stack.remaining();
|
||||||
|
ctx.alignment = self.alignment;
|
||||||
|
ctx.flex_spacing = flex_spacing(&self.style);
|
||||||
|
|
||||||
|
self.flex = FlexLayouter::new(ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flex_spacing(style: &TextStyle) -> Size {
|
fn flex_spacing(style: &TextStyle) -> Size {
|
||||||
|
@ -38,18 +38,21 @@ impl Function for AlignFunc {
|
|||||||
Ok(AlignFunc { alignment, body })
|
Ok(AlignFunc { alignment, body })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<FuncCommands> {
|
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
||||||
if let Some(body) = &self.body {
|
if let Some(body) = &self.body {
|
||||||
ctx.alignment = self.alignment;
|
let layouts = layout_tree(body, LayoutContext {
|
||||||
|
alignment: self.alignment,
|
||||||
|
.. ctx
|
||||||
|
})?;
|
||||||
|
|
||||||
let layouts = layout_tree(body, ctx)?;
|
let mut commands = CommandList::new();
|
||||||
|
|
||||||
let mut commands = FuncCommands::new();
|
|
||||||
commands.add(Command::AddMany(layouts));
|
commands.add(Command::AddMany(layouts));
|
||||||
|
|
||||||
Ok(commands)
|
Ok(commands)
|
||||||
} else {
|
} else {
|
||||||
unimplemented!("context-modifying align func")
|
let mut commands = CommandList::new();
|
||||||
|
commands.add(Command::SetAlignment(self.alignment));
|
||||||
|
|
||||||
|
Ok(commands)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ mod styles;
|
|||||||
|
|
||||||
/// 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, FuncCommands, Function};
|
pub use crate::func::{Command, CommandList, Function};
|
||||||
pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
|
pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
|
||||||
pub use crate::layout::{LayoutError, LayoutResult};
|
pub use crate::layout::{LayoutError, LayoutResult};
|
||||||
pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
|
pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
|
||||||
|
@ -27,12 +27,16 @@ macro_rules! style_func {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&self, _: LayoutContext) -> LayoutResult<FuncCommands> {
|
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
||||||
let mut commands = FuncCommands::new();
|
let mut commands = CommandList::new();
|
||||||
|
|
||||||
commands.add(Command::ToggleStyleClass(FontClass::$class));
|
let saved_style = ctx.style.clone();
|
||||||
|
let mut new_style = ctx.style.clone();
|
||||||
|
new_style.toggle_class(FontClass::$class);
|
||||||
|
|
||||||
|
commands.add(Command::SetStyle(new_style));
|
||||||
commands.add(Command::Layout(&self.body));
|
commands.add(Command::Layout(&self.body));
|
||||||
commands.add(Command::ToggleStyleClass(FontClass::$class));
|
commands.add(Command::SetStyle(saved_style));
|
||||||
|
|
||||||
Ok(commands)
|
Ok(commands)
|
||||||
}
|
}
|
||||||
|
@ -439,7 +439,7 @@ error_type! {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::func::{FuncCommands, Function, Scope};
|
use crate::func::{CommandList, Function, Scope};
|
||||||
use crate::layout::{LayoutContext, LayoutResult};
|
use crate::layout::{LayoutContext, LayoutResult};
|
||||||
use funcs::*;
|
use funcs::*;
|
||||||
use Node::{Func as F, Newline as N, Space as S};
|
use Node::{Func as F, Newline as N, Space as S};
|
||||||
@ -463,8 +463,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&self, _: LayoutContext) -> LayoutResult<FuncCommands> {
|
fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
|
||||||
Ok(FuncCommands::new())
|
Ok(CommandList::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,8 +482,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&self, _: LayoutContext) -> LayoutResult<FuncCommands> {
|
fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
|
||||||
Ok(FuncCommands::new())
|
Ok(CommandList::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,25 @@
|
|||||||
{size:150pt*208pt}
|
{size:150pt*208pt}
|
||||||
|
|
||||||
|
// Without newline in between
|
||||||
[align: left][Left: {lorem:20}]
|
[align: left][Left: {lorem:20}]
|
||||||
[align: right][Right: {lorem:20}]
|
[align: right][Right: {lorem:20}]
|
||||||
|
|
||||||
|
// With newline in between
|
||||||
[align: center][Center: {lorem:80}]
|
[align: center][Center: {lorem:80}]
|
||||||
|
|
||||||
[align: left][Left: {lorem:20}]
|
[align: left][Left: {lorem:20}]
|
||||||
|
|
||||||
|
// Context-modifying align
|
||||||
|
[align: right]
|
||||||
|
|
||||||
|
New Right: {lorem:30}
|
||||||
|
|
||||||
|
[align: left][Inside Left: {lorem:10}]
|
||||||
|
|
||||||
|
Right Again: {lorem:10}
|
||||||
|
|
||||||
|
// Reset context-modifier
|
||||||
|
[align: left]
|
||||||
|
|
||||||
|
// All in one line
|
||||||
|
{lorem:25} [align: right][{lorem:50}] {lorem:15}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user