Render debug boxes 🧭

This commit is contained in:
Laurenz 2019-10-11 20:28:22 +02:00
parent c0e4fd55e6
commit 6f22e4f13c
14 changed files with 251 additions and 131 deletions

View File

@ -14,3 +14,8 @@ unicode-xid = "0.1.0"
[[bin]]
name = "typstc"
path = "src/bin/main.rs"
[[test]]
name = "layouting"
path = "tests/layouting.rs"
harness = false

View File

@ -1,7 +1,7 @@
//! Representation of typesetted documents.
use std::io::{self, Write};
use crate::size::{Size, Size2D};
use crate::layout::LayoutAction;
use crate::size::Size;
/// 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.
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),
}
}
}

View File

@ -14,7 +14,8 @@ use toddle::font::OwnedFont;
use toddle::query::SharedFontLoader;
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};
@ -192,16 +193,16 @@ impl<'d, W: Write> PdfEngine<'d, W> {
}
// Flush the position.
if let Some(pos) = next_pos {
if let Some(pos) = next_pos.take() {
let x = pos.x.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);
next_pos = None;
}
// Write the text.
text.tj(self.fonts[active_font.0].encode_text(&string)?);
},
LayoutAction::DebugBox(_, _) => {},
}
}

131
src/layout/actions.rs Normal file
View 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
}
}

View File

@ -1,9 +1,9 @@
//! Block-style layouting of boxes.
use std::io::{self, Write};
use crate::doc::{Document, Page, LayoutAction};
use crate::doc::{Document, Page};
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.
@ -13,6 +13,8 @@ pub struct BoxLayout {
pub dimensions: Size2D,
/// The actions composing this layout.
pub actions: Vec<LayoutAction>,
/// Whether to debug-render this box.
pub debug_render: bool,
}
impl BoxLayout {
@ -49,7 +51,7 @@ pub struct BoxContext {
#[derive(Debug)]
pub struct BoxLayouter {
pub ctx: BoxContext,
actions: ActionList,
actions: LayoutActionList,
dimensions: Size2D,
usable: Size2D,
cursor: Size2D,
@ -59,9 +61,10 @@ impl BoxLayouter {
/// Create a new box layouter.
pub fn new(ctx: BoxContext) -> BoxLayouter {
let space = ctx.space;
BoxLayouter {
ctx,
actions: ActionList::new(),
actions: LayoutActionList::new(),
dimensions: match ctx.space.alignment {
Alignment::Left => Size2D::zero(),
Alignment::Right => Size2D::with_x(space.usable().x),
@ -109,7 +112,7 @@ impl BoxLayouter {
/// Add a sublayout at an absolute position.
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.
@ -148,6 +151,7 @@ impl BoxLayouter {
self.ctx.space.dimensions
},
actions: self.actions.into_vec(),
debug_render: true,
}
}

View File

@ -1,7 +1,7 @@
//! Flexible and lazy layouting of boxes.
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.
@ -76,7 +76,7 @@ pub struct FlexContext {
struct FlexFinisher {
units: Vec<FlexUnit>,
ctx: FlexContext,
actions: ActionList,
actions: LayoutActionList,
dimensions: Size2D,
usable: Size2D,
cursor: Size2D,
@ -92,7 +92,7 @@ impl FlexFinisher {
FlexFinisher {
units: layout.units,
ctx,
actions: ActionList::new(),
actions: LayoutActionList::new(),
dimensions: match ctx.space.alignment {
Alignment::Left => Size2D::zero(),
Alignment::Right => Size2D::with_x(space.usable().x),
@ -129,6 +129,7 @@ impl FlexFinisher {
self.ctx.space.dimensions
},
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.

View File

@ -6,7 +6,6 @@ use std::mem;
use toddle::query::{SharedFontLoader, FontClass};
use toddle::Error as FontError;
use crate::doc::LayoutAction;
use crate::size::{Size, Size2D, SizeBox};
use crate::syntax::{SyntaxTree, Node, FuncCall};
use crate::style::TextStyle;
@ -18,6 +17,9 @@ use self::text::TextContext;
pub mod text;
pub mod boxed;
pub mod flex;
mod actions;
pub use actions::{LayoutAction, LayoutActionList};
/// 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.
fn layout_flex(&mut self) -> LayoutResult<()> {
if self.flex_layout.is_empty() {
return Ok(());
}
let mut layout = FlexLayout::new();
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.
pub enum LayoutError {
/// There is not enough space to add an item.

View File

@ -3,7 +3,6 @@
use toddle::query::{FontQuery, SharedFontLoader};
use toddle::tables::{Header, CharMap, HorizontalMetrics};
use crate::doc::LayoutAction;
use crate::size::{Size, Size2D};
use super::*;
@ -92,5 +91,6 @@ pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
Ok(BoxLayout {
dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)),
actions,
debug_render: false,
})
}

View File

@ -26,7 +26,7 @@ use crate::style::{PageStyle, TextStyle};
use crate::syntax::SyntaxTree;
#[macro_use]
mod error;
mod macros;
pub mod doc;
pub mod export;
pub mod func;

View File

@ -24,11 +24,7 @@ macro_rules! error_type {
}
}
impl std::fmt::Debug for $err {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
debug_display!($err);
impl std::error::Error for $err {
// 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)
}
}
};
}

View File

@ -1,7 +1,7 @@
//! General spacing types.
use std::cmp::Ordering;
use std::fmt::{self, Display, Debug, Formatter};
use std::fmt::{self, Display, Formatter};
use std::iter::Sum;
use std::ops::*;
use std::str::FromStr;
@ -133,11 +133,7 @@ impl Display for Size {
}
}
impl Debug for Size {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
debug_display!(Size);
/// An error which can be returned when parsing a size.
pub struct ParseSizeError;
@ -254,11 +250,7 @@ impl Display for Size2D {
}
}
impl Debug for Size2D {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
debug_display!(Size2D);
impl Neg for Size2D {
type Output = Size2D;
@ -349,8 +341,4 @@ impl Display for SizeBox {
}
}
impl Debug for SizeBox {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
debug_display!(SizeBox);

View File

@ -3,15 +3,25 @@ use std::io::{Write, Read, BufWriter};
use std::process::Command;
use typst::Typesetter;
use typst::layout::LayoutAction;
use typst::toddle::query::FileSystemFontProvider;
use typst::export::pdf::PdfExporter;
use typst::doc::LayoutAction;
const CACHE_DIR: &str = "test-cache";
#[test]
fn layouting() {
fn main() {
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!("{}/rendered", 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() {
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
.file_stem().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.
fn test(name: &str, src: &str) {
println!("Testing: {}", name);
let mut typesetter = Typesetter::new();
let provider = FileSystemFontProvider::from_listing("fonts/fonts.toml").unwrap();
typesetter.add_font_provider(provider.clone());

View File

@ -9,5 +9,5 @@ _Emoji:_ Hello World! 🌍
_Styles:_ This is made *bold*, that _italic_ and this one `monospace` using the
built-in syntax!
_Styles with functions:_ This [bold][word] is made bold and [italic][that] is italic
using the standard library functions [mono][bold] and `italic`!
_Styles with functions:_ This [bold][word] is made bold and [italic][that] italic
using the standard library functions `bold` and `italic`!

View File

@ -37,10 +37,25 @@ def main():
class Renderer:
def __init__(self, fonts, width, height):
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.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):
cmd = command[0]
parts = command.split()[1:]
@ -56,7 +71,33 @@ class Renderer:
elif cmd == 'w':
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:
raise Exception("invalid command")
@ -68,6 +109,9 @@ class Renderer:
def pix(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__":
main()