mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Render debug boxes 🧭
This commit is contained in:
parent
c0e4fd55e6
commit
6f22e4f13c
@ -14,3 +14,8 @@ unicode-xid = "0.1.0"
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "typstc"
|
name = "typstc"
|
||||||
path = "src/bin/main.rs"
|
path = "src/bin/main.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "layouting"
|
||||||
|
path = "tests/layouting.rs"
|
||||||
|
harness = false
|
||||||
|
27
src/doc.rs
27
src/doc.rs
@ -1,7 +1,7 @@
|
|||||||
//! Representation of typesetted documents.
|
//! Representation of typesetted documents.
|
||||||
|
|
||||||
use std::io::{self, Write};
|
use crate::layout::LayoutAction;
|
||||||
use crate::size::{Size, Size2D};
|
use crate::size::Size;
|
||||||
|
|
||||||
|
|
||||||
/// A complete typesetted document, which can be exported.
|
/// A complete typesetted document, which can be exported.
|
||||||
@ -21,26 +21,3 @@ pub struct Page {
|
|||||||
/// Layouting actions specifying how to draw content on the page.
|
/// Layouting actions specifying how to draw content on the page.
|
||||||
pub actions: Vec<LayoutAction>,
|
pub actions: Vec<LayoutAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A layouting action.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum LayoutAction {
|
|
||||||
/// Move to an absolute position.
|
|
||||||
MoveAbsolute(Size2D),
|
|
||||||
/// Set the font by index and font size.
|
|
||||||
SetFont(usize, f32),
|
|
||||||
/// Write text starting at the current position.
|
|
||||||
WriteText(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutAction {
|
|
||||||
/// Serialize this layout action into a string representation.
|
|
||||||
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
|
||||||
use LayoutAction::*;
|
|
||||||
match self {
|
|
||||||
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
|
|
||||||
SetFont(i, s) => write!(f, "f {} {}", i, s),
|
|
||||||
WriteText(s) => write!(f, "w {}", s),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -14,7 +14,8 @@ use toddle::font::OwnedFont;
|
|||||||
use toddle::query::SharedFontLoader;
|
use toddle::query::SharedFontLoader;
|
||||||
use toddle::Error as FontError;
|
use toddle::Error as FontError;
|
||||||
|
|
||||||
use crate::doc::{Document, Page as DocPage, LayoutAction};
|
use crate::layout::LayoutAction;
|
||||||
|
use crate::doc::{Document, Page as DocPage};
|
||||||
use crate::size::{Size, Size2D};
|
use crate::size::{Size, Size2D};
|
||||||
|
|
||||||
|
|
||||||
@ -192,16 +193,16 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flush the position.
|
// Flush the position.
|
||||||
if let Some(pos) = next_pos {
|
if let Some(pos) = next_pos.take() {
|
||||||
let x = pos.x.to_pt();
|
let x = pos.x.to_pt();
|
||||||
let y = (page.height - pos.y - Size::pt(active_font.1)).to_pt();
|
let y = (page.height - pos.y - Size::pt(active_font.1)).to_pt();
|
||||||
text.tm(1.0, 0.0, 0.0, 1.0, x, y);
|
text.tm(1.0, 0.0, 0.0, 1.0, x, y);
|
||||||
next_pos = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the text.
|
// Write the text.
|
||||||
text.tj(self.fonts[active_font.0].encode_text(&string)?);
|
text.tj(self.fonts[active_font.0].encode_text(&string)?);
|
||||||
},
|
},
|
||||||
|
LayoutAction::DebugBox(_, _) => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
131
src/layout/actions.rs
Normal file
131
src/layout/actions.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
//! Drawing and cofiguration actions used by layouts.
|
||||||
|
|
||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use crate::size::Size2D;
|
||||||
|
use super::boxed::BoxLayout;
|
||||||
|
use LayoutAction::*;
|
||||||
|
|
||||||
|
|
||||||
|
/// A layouting action.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum LayoutAction {
|
||||||
|
/// Move to an absolute position.
|
||||||
|
MoveAbsolute(Size2D),
|
||||||
|
/// Set the font by index and font size.
|
||||||
|
SetFont(usize, f32),
|
||||||
|
/// Write text starting at the current position.
|
||||||
|
WriteText(String),
|
||||||
|
/// Visualize a box for debugging purposes.
|
||||||
|
/// Arguments are position and size.
|
||||||
|
DebugBox(Size2D, Size2D),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutAction {
|
||||||
|
/// Serialize this layout action into a string representation.
|
||||||
|
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||||
|
use LayoutAction::*;
|
||||||
|
match self {
|
||||||
|
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
|
||||||
|
SetFont(i, s) => write!(f, "f {} {}", i, s),
|
||||||
|
WriteText(s) => write!(f, "w {}", s),
|
||||||
|
DebugBox(p, s) => write!(f, "b {} {} {} {}",
|
||||||
|
p.x.to_pt(), p.y.to_pt(), s.x.to_pt(), s.y.to_pt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LayoutAction {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
use LayoutAction::*;
|
||||||
|
match self {
|
||||||
|
MoveAbsolute(s) => write!(f, "move {} {}", s.x, s.y),
|
||||||
|
SetFont(i, s) => write!(f, "font {} {}", i, s),
|
||||||
|
WriteText(s) => write!(f, "write \"{}\"", s),
|
||||||
|
DebugBox(p, s) => write!(f, "box {} {}", p, s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_display!(LayoutAction);
|
||||||
|
|
||||||
|
/// Unifies and otimizes lists of actions.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LayoutActionList {
|
||||||
|
pub origin: Size2D,
|
||||||
|
actions: Vec<LayoutAction>,
|
||||||
|
active_font: (usize, f32),
|
||||||
|
next_pos: Option<Size2D>,
|
||||||
|
next_font: Option<(usize, f32)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutActionList {
|
||||||
|
/// Create a new action list.
|
||||||
|
pub fn new() -> LayoutActionList {
|
||||||
|
LayoutActionList {
|
||||||
|
actions: vec![],
|
||||||
|
origin: Size2D::zero(),
|
||||||
|
active_font: (std::usize::MAX, 0.0),
|
||||||
|
next_pos: None,
|
||||||
|
next_font: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an action to the list if it is not useless
|
||||||
|
/// (like changing to a font that is already active).
|
||||||
|
pub fn add(&mut self, action: LayoutAction) {
|
||||||
|
match action {
|
||||||
|
MoveAbsolute(pos) => self.next_pos = Some(self.origin + pos),
|
||||||
|
DebugBox(pos, size) => self.actions.push(DebugBox(self.origin + pos, size)),
|
||||||
|
|
||||||
|
SetFont(index, size) if (index, size) != self.active_font => {
|
||||||
|
self.next_font = Some((index, size));
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
if let Some(target) = self.next_pos.take() {
|
||||||
|
self.actions.push(MoveAbsolute(target));
|
||||||
|
}
|
||||||
|
if let Some((index, size)) = self.next_font.take() {
|
||||||
|
self.actions.push(SetFont(index, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.actions.push(action);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a series of actions.
|
||||||
|
pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item=LayoutAction> {
|
||||||
|
for action in actions.into_iter() {
|
||||||
|
self.add(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add all actions from a box layout at a position. A move to the position
|
||||||
|
/// is generated and all moves inside the box layout are translated as necessary.
|
||||||
|
pub fn add_box(&mut self, position: Size2D, layout: BoxLayout) {
|
||||||
|
if let Some(target) = self.next_pos.take() {
|
||||||
|
self.actions.push(MoveAbsolute(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.next_pos = Some(position);
|
||||||
|
self.origin = position;
|
||||||
|
|
||||||
|
if layout.debug_render {
|
||||||
|
self.actions.push(LayoutAction::DebugBox(position, layout.dimensions));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.extend(layout.actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether there are any actions in this list.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.actions.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the list of actions as a vector.
|
||||||
|
pub fn into_vec(self) -> Vec<LayoutAction> {
|
||||||
|
self.actions
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
//! Block-style layouting of boxes.
|
//! Block-style layouting of boxes.
|
||||||
|
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use crate::doc::{Document, Page, LayoutAction};
|
use crate::doc::{Document, Page};
|
||||||
use crate::size::{Size, Size2D};
|
use crate::size::{Size, Size2D};
|
||||||
use super::{ActionList, LayoutSpace, Alignment, LayoutResult, LayoutError};
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
/// A box layout has a fixed width and height and composes of actions.
|
/// A box layout has a fixed width and height and composes of actions.
|
||||||
@ -13,6 +13,8 @@ pub struct BoxLayout {
|
|||||||
pub dimensions: Size2D,
|
pub dimensions: Size2D,
|
||||||
/// The actions composing this layout.
|
/// The actions composing this layout.
|
||||||
pub actions: Vec<LayoutAction>,
|
pub actions: Vec<LayoutAction>,
|
||||||
|
/// Whether to debug-render this box.
|
||||||
|
pub debug_render: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BoxLayout {
|
impl BoxLayout {
|
||||||
@ -49,7 +51,7 @@ pub struct BoxContext {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BoxLayouter {
|
pub struct BoxLayouter {
|
||||||
pub ctx: BoxContext,
|
pub ctx: BoxContext,
|
||||||
actions: ActionList,
|
actions: LayoutActionList,
|
||||||
dimensions: Size2D,
|
dimensions: Size2D,
|
||||||
usable: Size2D,
|
usable: Size2D,
|
||||||
cursor: Size2D,
|
cursor: Size2D,
|
||||||
@ -59,9 +61,10 @@ impl BoxLayouter {
|
|||||||
/// Create a new box layouter.
|
/// Create a new box layouter.
|
||||||
pub fn new(ctx: BoxContext) -> BoxLayouter {
|
pub fn new(ctx: BoxContext) -> BoxLayouter {
|
||||||
let space = ctx.space;
|
let space = ctx.space;
|
||||||
|
|
||||||
BoxLayouter {
|
BoxLayouter {
|
||||||
ctx,
|
ctx,
|
||||||
actions: ActionList::new(),
|
actions: LayoutActionList::new(),
|
||||||
dimensions: match ctx.space.alignment {
|
dimensions: match ctx.space.alignment {
|
||||||
Alignment::Left => Size2D::zero(),
|
Alignment::Left => Size2D::zero(),
|
||||||
Alignment::Right => Size2D::with_x(space.usable().x),
|
Alignment::Right => Size2D::with_x(space.usable().x),
|
||||||
@ -109,7 +112,7 @@ impl BoxLayouter {
|
|||||||
|
|
||||||
/// Add a sublayout at an absolute position.
|
/// Add a sublayout at an absolute position.
|
||||||
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
|
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
|
||||||
self.actions.add_box_absolute(position, layout);
|
self.actions.add_box(position, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add some space in between two boxes.
|
/// Add some space in between two boxes.
|
||||||
@ -148,6 +151,7 @@ impl BoxLayouter {
|
|||||||
self.ctx.space.dimensions
|
self.ctx.space.dimensions
|
||||||
},
|
},
|
||||||
actions: self.actions.into_vec(),
|
actions: self.actions.into_vec(),
|
||||||
|
debug_render: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Flexible and lazy layouting of boxes.
|
//! Flexible and lazy layouting of boxes.
|
||||||
|
|
||||||
use crate::size::{Size, Size2D};
|
use crate::size::{Size, Size2D};
|
||||||
use super::{BoxLayout, ActionList, LayoutSpace, Alignment, LayoutResult, LayoutError};
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
/// A flex layout consists of a yet unarranged list of boxes.
|
/// A flex layout consists of a yet unarranged list of boxes.
|
||||||
@ -76,7 +76,7 @@ pub struct FlexContext {
|
|||||||
struct FlexFinisher {
|
struct FlexFinisher {
|
||||||
units: Vec<FlexUnit>,
|
units: Vec<FlexUnit>,
|
||||||
ctx: FlexContext,
|
ctx: FlexContext,
|
||||||
actions: ActionList,
|
actions: LayoutActionList,
|
||||||
dimensions: Size2D,
|
dimensions: Size2D,
|
||||||
usable: Size2D,
|
usable: Size2D,
|
||||||
cursor: Size2D,
|
cursor: Size2D,
|
||||||
@ -92,7 +92,7 @@ impl FlexFinisher {
|
|||||||
FlexFinisher {
|
FlexFinisher {
|
||||||
units: layout.units,
|
units: layout.units,
|
||||||
ctx,
|
ctx,
|
||||||
actions: ActionList::new(),
|
actions: LayoutActionList::new(),
|
||||||
dimensions: match ctx.space.alignment {
|
dimensions: match ctx.space.alignment {
|
||||||
Alignment::Left => Size2D::zero(),
|
Alignment::Left => Size2D::zero(),
|
||||||
Alignment::Right => Size2D::with_x(space.usable().x),
|
Alignment::Right => Size2D::with_x(space.usable().x),
|
||||||
@ -129,6 +129,7 @@ impl FlexFinisher {
|
|||||||
self.ctx.space.dimensions
|
self.ctx.space.dimensions
|
||||||
},
|
},
|
||||||
actions: self.actions.into_vec(),
|
actions: self.actions.into_vec(),
|
||||||
|
debug_render: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,7 +188,7 @@ impl FlexFinisher {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
self.actions.add_box_absolute(position, layout);
|
self.actions.add_box(position, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stretch the dimensions to at least the line width.
|
// Stretch the dimensions to at least the line width.
|
||||||
|
@ -6,7 +6,6 @@ use std::mem;
|
|||||||
use toddle::query::{SharedFontLoader, FontClass};
|
use toddle::query::{SharedFontLoader, FontClass};
|
||||||
use toddle::Error as FontError;
|
use toddle::Error as FontError;
|
||||||
|
|
||||||
use crate::doc::LayoutAction;
|
|
||||||
use crate::size::{Size, Size2D, SizeBox};
|
use crate::size::{Size, Size2D, SizeBox};
|
||||||
use crate::syntax::{SyntaxTree, Node, FuncCall};
|
use crate::syntax::{SyntaxTree, Node, FuncCall};
|
||||||
use crate::style::TextStyle;
|
use crate::style::TextStyle;
|
||||||
@ -18,6 +17,9 @@ use self::text::TextContext;
|
|||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod boxed;
|
pub mod boxed;
|
||||||
pub mod flex;
|
pub mod flex;
|
||||||
|
mod actions;
|
||||||
|
|
||||||
|
pub use actions::{LayoutAction, LayoutActionList};
|
||||||
|
|
||||||
|
|
||||||
/// A collection of layouted content.
|
/// A collection of layouted content.
|
||||||
@ -159,6 +161,10 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
|||||||
|
|
||||||
/// Finish the current flex run and return the resulting box.
|
/// Finish the current flex run and return the resulting box.
|
||||||
fn layout_flex(&mut self) -> LayoutResult<()> {
|
fn layout_flex(&mut self) -> LayoutResult<()> {
|
||||||
|
if self.flex_layout.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let mut layout = FlexLayout::new();
|
let mut layout = FlexLayout::new();
|
||||||
mem::swap(&mut layout, &mut self.flex_layout);
|
mem::swap(&mut layout, &mut self.flex_layout);
|
||||||
|
|
||||||
@ -204,64 +210,6 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manipulates and optimizes a list of actions.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ActionList {
|
|
||||||
pub origin: Size2D,
|
|
||||||
actions: Vec<LayoutAction>,
|
|
||||||
active_font: (usize, f32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActionList {
|
|
||||||
/// Create a new action list.
|
|
||||||
pub fn new() -> ActionList {
|
|
||||||
ActionList {
|
|
||||||
actions: vec![],
|
|
||||||
origin: Size2D::zero(),
|
|
||||||
active_font: (std::usize::MAX, 0.0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add an action to the list if it is not useless
|
|
||||||
/// (like changing to a font that is already active).
|
|
||||||
pub fn add(&mut self, action: LayoutAction) {
|
|
||||||
use LayoutAction::*;
|
|
||||||
match action {
|
|
||||||
MoveAbsolute(pos) => self.actions.push(MoveAbsolute(self.origin + pos)),
|
|
||||||
SetFont(index, size) => if (index, size) != self.active_font {
|
|
||||||
self.active_font = (index, size);
|
|
||||||
self.actions.push(action);
|
|
||||||
},
|
|
||||||
_ => self.actions.push(action),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a series of actions.
|
|
||||||
pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item=LayoutAction> {
|
|
||||||
for action in actions.into_iter() {
|
|
||||||
self.add(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add all actions from a box layout at a position. A move to the position
|
|
||||||
/// is generated and all moves inside the box layout are translated as necessary.
|
|
||||||
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
|
|
||||||
self.actions.push(LayoutAction::MoveAbsolute(position));
|
|
||||||
self.origin = position;
|
|
||||||
self.extend(layout.actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether there are any actions in this list.
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.actions.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the list of actions as a vector.
|
|
||||||
pub fn into_vec(self) -> Vec<LayoutAction> {
|
|
||||||
self.actions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error type for layouting.
|
/// The error type for layouting.
|
||||||
pub enum LayoutError {
|
pub enum LayoutError {
|
||||||
/// There is not enough space to add an item.
|
/// There is not enough space to add an item.
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
use toddle::query::{FontQuery, SharedFontLoader};
|
use toddle::query::{FontQuery, SharedFontLoader};
|
||||||
use toddle::tables::{Header, CharMap, HorizontalMetrics};
|
use toddle::tables::{Header, CharMap, HorizontalMetrics};
|
||||||
|
|
||||||
use crate::doc::LayoutAction;
|
|
||||||
use crate::size::{Size, Size2D};
|
use crate::size::{Size, Size2D};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -92,5 +91,6 @@ pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
|
|||||||
Ok(BoxLayout {
|
Ok(BoxLayout {
|
||||||
dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)),
|
dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)),
|
||||||
actions,
|
actions,
|
||||||
|
debug_render: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ use crate::style::{PageStyle, TextStyle};
|
|||||||
use crate::syntax::SyntaxTree;
|
use crate::syntax::SyntaxTree;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod error;
|
mod macros;
|
||||||
pub mod doc;
|
pub mod doc;
|
||||||
pub mod export;
|
pub mod export;
|
||||||
pub mod func;
|
pub mod func;
|
||||||
|
@ -24,11 +24,7 @@ macro_rules! error_type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for $err {
|
debug_display!($err);
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
std::fmt::Display::fmt(self, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for $err {
|
impl std::error::Error for $err {
|
||||||
// The source method is only generated if an implementation was given.
|
// The source method is only generated if an implementation was given.
|
||||||
@ -46,3 +42,14 @@ macro_rules! error_type {
|
|||||||
})*
|
})*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a `Debug` implementation from a display implementation.
|
||||||
|
macro_rules! debug_display {
|
||||||
|
($type:ident) => {
|
||||||
|
impl std::fmt::Debug for $type {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
std::fmt::Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
20
src/size.rs
20
src/size.rs
@ -1,7 +1,7 @@
|
|||||||
//! General spacing types.
|
//! General spacing types.
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::{self, Display, Debug, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::iter::Sum;
|
use std::iter::Sum;
|
||||||
use std::ops::*;
|
use std::ops::*;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@ -133,11 +133,7 @@ impl Display for Size {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Size {
|
debug_display!(Size);
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
Display::fmt(self, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error which can be returned when parsing a size.
|
/// An error which can be returned when parsing a size.
|
||||||
pub struct ParseSizeError;
|
pub struct ParseSizeError;
|
||||||
@ -254,11 +250,7 @@ impl Display for Size2D {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Size2D {
|
debug_display!(Size2D);
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
Display::fmt(self, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Neg for Size2D {
|
impl Neg for Size2D {
|
||||||
type Output = Size2D;
|
type Output = Size2D;
|
||||||
@ -349,8 +341,4 @@ impl Display for SizeBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for SizeBox {
|
debug_display!(SizeBox);
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
Display::fmt(self, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -3,15 +3,25 @@ use std::io::{Write, Read, BufWriter};
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use typst::Typesetter;
|
use typst::Typesetter;
|
||||||
|
use typst::layout::LayoutAction;
|
||||||
use typst::toddle::query::FileSystemFontProvider;
|
use typst::toddle::query::FileSystemFontProvider;
|
||||||
use typst::export::pdf::PdfExporter;
|
use typst::export::pdf::PdfExporter;
|
||||||
use typst::doc::LayoutAction;
|
|
||||||
|
|
||||||
const CACHE_DIR: &str = "test-cache";
|
const CACHE_DIR: &str = "test-cache";
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
fn main() {
|
||||||
fn layouting() {
|
let mut filter = Vec::new();
|
||||||
|
for arg in std::env::args().skip(1) {
|
||||||
|
if arg.as_str() != "--nocapture" {
|
||||||
|
filter.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filter.is_empty() {
|
||||||
|
println!("Using filter: {:?}", filter);
|
||||||
|
}
|
||||||
|
|
||||||
fs::create_dir_all(format!("{}/serialized", CACHE_DIR)).unwrap();
|
fs::create_dir_all(format!("{}/serialized", CACHE_DIR)).unwrap();
|
||||||
fs::create_dir_all(format!("{}/rendered", CACHE_DIR)).unwrap();
|
fs::create_dir_all(format!("{}/rendered", CACHE_DIR)).unwrap();
|
||||||
fs::create_dir_all(format!("{}/pdf", CACHE_DIR)).unwrap();
|
fs::create_dir_all(format!("{}/pdf", CACHE_DIR)).unwrap();
|
||||||
@ -19,20 +29,24 @@ fn layouting() {
|
|||||||
for entry in fs::read_dir("tests/layouts/").unwrap() {
|
for entry in fs::read_dir("tests/layouts/").unwrap() {
|
||||||
let path = entry.unwrap().path();
|
let path = entry.unwrap().path();
|
||||||
|
|
||||||
let mut file = File::open(&path).unwrap();
|
|
||||||
let mut src = String::new();
|
|
||||||
file.read_to_string(&mut src).unwrap();
|
|
||||||
|
|
||||||
let name = path
|
let name = path
|
||||||
.file_stem().unwrap()
|
.file_stem().unwrap()
|
||||||
.to_str().unwrap();
|
.to_str().unwrap();
|
||||||
|
|
||||||
test(name, &src);
|
if filter.is_empty() || filter.iter().any(|pattern| name.contains(pattern)) {
|
||||||
|
let mut file = File::open(&path).unwrap();
|
||||||
|
let mut src = String::new();
|
||||||
|
file.read_to_string(&mut src).unwrap();
|
||||||
|
|
||||||
|
test(name, &src);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a _PDF_ with a name from the source code.
|
/// Create a _PDF_ with a name from the source code.
|
||||||
fn test(name: &str, src: &str) {
|
fn test(name: &str, src: &str) {
|
||||||
|
println!("Testing: {}", name);
|
||||||
|
|
||||||
let mut typesetter = Typesetter::new();
|
let mut typesetter = Typesetter::new();
|
||||||
let provider = FileSystemFontProvider::from_listing("fonts/fonts.toml").unwrap();
|
let provider = FileSystemFontProvider::from_listing("fonts/fonts.toml").unwrap();
|
||||||
typesetter.add_font_provider(provider.clone());
|
typesetter.add_font_provider(provider.clone());
|
||||||
|
@ -9,5 +9,5 @@ _Emoji:_ Hello World! 🌍
|
|||||||
_Styles:_ This is made *bold*, that _italic_ and this one `monospace` using the
|
_Styles:_ This is made *bold*, that _italic_ and this one `monospace` using the
|
||||||
built-in syntax!
|
built-in syntax!
|
||||||
|
|
||||||
_Styles with functions:_ This [bold][word] is made bold and [italic][that] is italic
|
_Styles with functions:_ This [bold][word] is made bold and [italic][that] italic
|
||||||
using the standard library functions [mono][bold] and `italic`!
|
using the standard library functions `bold` and `italic`!
|
||||||
|
@ -37,10 +37,25 @@ def main():
|
|||||||
class Renderer:
|
class Renderer:
|
||||||
def __init__(self, fonts, width, height):
|
def __init__(self, fonts, width, height):
|
||||||
self.fonts = fonts
|
self.fonts = fonts
|
||||||
self.img = Image.new("RGBA", (pix(width), pix(height)), (255, 255, 255))
|
self.size = (pix(width), pix(height))
|
||||||
|
self.img = Image.new("RGBA", self.size, (255, 255, 255, 255))
|
||||||
self.draw = ImageDraw.Draw(self.img)
|
self.draw = ImageDraw.Draw(self.img)
|
||||||
self.cursor = (0, 0)
|
self.cursor = (0, 0)
|
||||||
|
|
||||||
|
self.colors = [
|
||||||
|
(176, 264, 158),
|
||||||
|
(274, 173, 207),
|
||||||
|
(158, 252, 264),
|
||||||
|
(285, 275, 187),
|
||||||
|
(132, 217, 136),
|
||||||
|
(236, 177, 246),
|
||||||
|
(174, 232, 279),
|
||||||
|
(285, 234, 158)
|
||||||
|
]
|
||||||
|
|
||||||
|
self.rects = []
|
||||||
|
self.color_index = 0
|
||||||
|
|
||||||
def execute(self, command):
|
def execute(self, command):
|
||||||
cmd = command[0]
|
cmd = command[0]
|
||||||
parts = command.split()[1:]
|
parts = command.split()[1:]
|
||||||
@ -56,7 +71,33 @@ class Renderer:
|
|||||||
|
|
||||||
elif cmd == 'w':
|
elif cmd == 'w':
|
||||||
text = command[2:]
|
text = command[2:]
|
||||||
self.draw.text(self.cursor, text, (0, 0, 0), font=self.font)
|
self.draw.text(self.cursor, text, (0, 0, 0, 255), font=self.font)
|
||||||
|
|
||||||
|
elif cmd == 'b':
|
||||||
|
x, y, w, h = (pix(float(s)) for s in parts)
|
||||||
|
rect = [x, y, x+w, y+h]
|
||||||
|
|
||||||
|
forbidden_colors = set()
|
||||||
|
for other_rect, other_color in self.rects:
|
||||||
|
if rect == other_rect:
|
||||||
|
return
|
||||||
|
|
||||||
|
if overlap(rect, other_rect) or overlap(other_rect, rect):
|
||||||
|
forbidden_colors.add(other_color)
|
||||||
|
|
||||||
|
for color in self.colors[self.color_index:] + self.colors[:self.color_index]:
|
||||||
|
self.color_index = (self.color_index + 1) % len(self.colors)
|
||||||
|
if color not in forbidden_colors:
|
||||||
|
break
|
||||||
|
|
||||||
|
overlay = Image.new("RGBA", self.size, (0, 0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(overlay)
|
||||||
|
draw.rectangle(rect, fill=color + (255,))
|
||||||
|
|
||||||
|
self.img = Image.alpha_composite(self.img, overlay)
|
||||||
|
self.draw = ImageDraw.Draw(self.img)
|
||||||
|
|
||||||
|
self.rects.append((rect, color))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("invalid command")
|
raise Exception("invalid command")
|
||||||
@ -68,6 +109,9 @@ class Renderer:
|
|||||||
def pix(points):
|
def pix(points):
|
||||||
return int(2 * points)
|
return int(2 * points)
|
||||||
|
|
||||||
|
def overlap(a, b):
|
||||||
|
return (a[0] < b[2] and b[0] < a[2]) and (a[1] < b[3] and b[1] < a[3])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user