Refactor layout types 🚧
This commit is contained in:
parent
6f22e4f13c
commit
463e4ebd82
23
src/doc.rs
23
src/doc.rs
@ -1,23 +0,0 @@
|
||||
//! Representation of typesetted documents.
|
||||
|
||||
use crate::layout::LayoutAction;
|
||||
use crate::size::Size;
|
||||
|
||||
|
||||
/// A complete typesetted document, which can be exported.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Document {
|
||||
/// The pages of the document.
|
||||
pub pages: Vec<Page>,
|
||||
}
|
||||
|
||||
/// A page of a document.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Page {
|
||||
/// The width of the page.
|
||||
pub width: Size,
|
||||
/// The height of the page.
|
||||
pub height: Size,
|
||||
/// Layouting actions specifying how to draw content on the page.
|
||||
pub actions: Vec<LayoutAction>,
|
||||
}
|
@ -14,12 +14,11 @@ use toddle::font::OwnedFont;
|
||||
use toddle::query::SharedFontLoader;
|
||||
use toddle::Error as FontError;
|
||||
|
||||
use crate::layout::LayoutAction;
|
||||
use crate::doc::{Document, Page as DocPage};
|
||||
use crate::layout::{MultiLayout, Layout, LayoutAction};
|
||||
use crate::size::{Size, Size2D};
|
||||
|
||||
|
||||
/// Exports documents into _PDFs_.
|
||||
/// Exports layouts into _PDFs_.
|
||||
#[derive(Debug)]
|
||||
pub struct PdfExporter {}
|
||||
|
||||
@ -30,19 +29,19 @@ impl PdfExporter {
|
||||
PdfExporter {}
|
||||
}
|
||||
|
||||
/// Export a typesetted document into a writer. Returns how many bytes were written.
|
||||
/// Export a finished layouts into a writer. Returns how many bytes were written.
|
||||
#[inline]
|
||||
pub fn export<W: Write>(&self, document: &Document, loader: &SharedFontLoader, target: W)
|
||||
pub fn export<W: Write>(&self, layout: &MultiLayout, loader: &SharedFontLoader, target: W)
|
||||
-> PdfResult<usize> {
|
||||
let mut engine = PdfEngine::new(document, loader, target)?;
|
||||
let mut engine = PdfEngine::new(layout, loader, target)?;
|
||||
engine.write()
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes documents in the _PDF_ format.
|
||||
/// Writes layouts in the _PDF_ format.
|
||||
struct PdfEngine<'d, W: Write> {
|
||||
writer: PdfWriter<W>,
|
||||
doc: &'d Document,
|
||||
layout: &'d MultiLayout,
|
||||
offsets: Offsets,
|
||||
font_remap: HashMap<usize, usize>,
|
||||
fonts: Vec<OwnedFont>,
|
||||
@ -60,16 +59,17 @@ struct Offsets {
|
||||
|
||||
impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
/// Create a new _PDF_ engine.
|
||||
fn new(doc: &'d Document, loader: &SharedFontLoader, target: W) -> PdfResult<PdfEngine<'d, W>> {
|
||||
// Create a subsetted PDF font for each font in the document.
|
||||
fn new(layout: &'d MultiLayout, loader: &SharedFontLoader, target: W)
|
||||
-> PdfResult<PdfEngine<'d, W>> {
|
||||
// Create a subsetted PDF font for each font in the layout.
|
||||
let mut font_remap = HashMap::new();
|
||||
let fonts = {
|
||||
let mut font = 0usize;
|
||||
let mut chars = HashMap::new();
|
||||
|
||||
// Find out which characters are used for each font.
|
||||
for page in &doc.pages {
|
||||
for action in &page.actions {
|
||||
for boxed in &layout.layouts {
|
||||
for action in &boxed.actions {
|
||||
match action {
|
||||
LayoutAction::WriteText(string) => {
|
||||
chars.entry(font)
|
||||
@ -108,21 +108,21 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
// Calculate a unique id for all objects that will be written.
|
||||
let catalog = 1;
|
||||
let page_tree = catalog + 1;
|
||||
let pages = (page_tree + 1, page_tree + doc.pages.len() as Ref);
|
||||
let contents = (pages.1 + 1, pages.1 + doc.pages.len() as Ref);
|
||||
let pages = (page_tree + 1, page_tree + layout.layouts.len() as Ref);
|
||||
let contents = (pages.1 + 1, pages.1 + layout.layouts.len() as Ref);
|
||||
let font_offsets = (contents.1 + 1, contents.1 + 5 * fonts.len() as Ref);
|
||||
let offsets = Offsets { catalog, page_tree, pages, contents, fonts: font_offsets };
|
||||
|
||||
Ok(PdfEngine {
|
||||
writer: PdfWriter::new(target),
|
||||
doc,
|
||||
layout,
|
||||
offsets,
|
||||
font_remap,
|
||||
fonts,
|
||||
})
|
||||
}
|
||||
|
||||
/// Write the complete document.
|
||||
/// Write the complete layout.
|
||||
fn write(&mut self) -> PdfResult<usize> {
|
||||
self.writer.write_header(Version::new(1, 7))?;
|
||||
self.write_page_tree()?;
|
||||
@ -150,9 +150,10 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
)?;
|
||||
|
||||
// The page objects
|
||||
for (id, page) in ids(self.offsets.pages).zip(&self.doc.pages) {
|
||||
for (id, page) in ids(self.offsets.pages).zip(&self.layout.layouts) {
|
||||
let rect = Rect::new(0.0, 0.0, page.dimensions.x.to_pt(), page.dimensions.y.to_pt());
|
||||
self.writer.write_obj(id, Page::new(self.offsets.page_tree)
|
||||
.media_box(Rect::new(0.0, 0.0, page.width.to_pt(), page.height.to_pt()))
|
||||
.media_box(rect)
|
||||
.contents(ids(self.offsets.contents))
|
||||
)?;
|
||||
}
|
||||
@ -162,14 +163,14 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
|
||||
/// Write the contents of all pages.
|
||||
fn write_pages(&mut self) -> PdfResult<()> {
|
||||
for (id, page) in ids(self.offsets.contents).zip(&self.doc.pages) {
|
||||
for (id, page) in ids(self.offsets.contents).zip(&self.layout.layouts) {
|
||||
self.write_page(id, &page)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the content of a page.
|
||||
fn write_page(&mut self, id: u32, page: &DocPage) -> PdfResult<()> {
|
||||
fn write_page(&mut self, id: u32, page: &Layout) -> PdfResult<()> {
|
||||
let mut text = Text::new();
|
||||
let mut active_font = (std::usize::MAX, 0.0);
|
||||
|
||||
@ -195,7 +196,7 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
// Flush the position.
|
||||
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();
|
||||
let y = (page.dimensions.y - pos.y - Size::pt(active_font.1)).to_pt();
|
||||
text.tm(1.0, 0.0, 0.0, 1.0, x, y);
|
||||
}
|
||||
|
||||
|
50
src/func.rs
50
src/func.rs
@ -4,9 +4,10 @@ use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use crate::layout::{Layout, LayoutContext, LayoutResult};
|
||||
use toddle::query::FontClass;
|
||||
use crate::layout::{Layout, MultiLayout , LayoutContext, LayoutResult};
|
||||
use crate::parsing::{ParseContext, ParseResult};
|
||||
use crate::syntax::FuncHeader;
|
||||
use crate::syntax::{SyntaxTree, FuncHeader};
|
||||
|
||||
|
||||
/// Typesetting function types.
|
||||
@ -25,7 +26,7 @@ pub trait Function: FunctionBounds {
|
||||
///
|
||||
/// Returns optionally the resulting layout and a new context if changes to the context should
|
||||
/// be made.
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<Option<Layout>>;
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<FuncCommands>;
|
||||
}
|
||||
|
||||
impl PartialEq for dyn Function {
|
||||
@ -34,6 +35,49 @@ impl PartialEq for dyn Function {
|
||||
}
|
||||
}
|
||||
|
||||
/// A sequence of commands requested for execution by a function.
|
||||
#[derive(Debug)]
|
||||
pub struct FuncCommands {
|
||||
pub commands: Vec<Command>
|
||||
}
|
||||
|
||||
impl FuncCommands {
|
||||
/// Create an empty command list.
|
||||
pub fn new() -> FuncCommands {
|
||||
FuncCommands {
|
||||
commands: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a command to the sequence.
|
||||
pub fn add_command(&mut self, command: Command) {
|
||||
self.commands.push(command);
|
||||
}
|
||||
|
||||
/// Whether there are any commands in this sequence.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.commands.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for FuncCommands {
|
||||
type Item = Command;
|
||||
type IntoIter = std::vec::IntoIter<Command>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.commands.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// Commands requested for execution by functions.
|
||||
#[derive(Debug)]
|
||||
pub enum Command {
|
||||
Layout(SyntaxTree),
|
||||
Add(Layout),
|
||||
AddMany(MultiLayout),
|
||||
ToggleStyleClass(FontClass),
|
||||
}
|
||||
|
||||
/// A helper trait that describes requirements for types that can implement [`Function`].
|
||||
///
|
||||
/// Automatically implemented for all types which fulfill to the bounds `Debug + PartialEq +
|
||||
|
@ -3,7 +3,7 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::io::{self, Write};
|
||||
use crate::size::Size2D;
|
||||
use super::boxed::BoxLayout;
|
||||
use super::Layout;
|
||||
use LayoutAction::*;
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ impl LayoutActionList {
|
||||
|
||||
/// 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) {
|
||||
pub fn add_box(&mut self, position: Size2D, layout: Layout) {
|
||||
if let Some(target) = self.next_pos.take() {
|
||||
self.actions.push(MoveAbsolute(target));
|
||||
}
|
||||
|
@ -4,62 +4,21 @@ use crate::size::{Size, Size2D};
|
||||
use super::*;
|
||||
|
||||
|
||||
/// A flex layout consists of a yet unarranged list of boxes.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FlexLayout {
|
||||
/// The sublayouts composing this layout.
|
||||
pub units: Vec<FlexUnit>,
|
||||
}
|
||||
|
||||
/// A unit in a flex layout.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FlexUnit {
|
||||
/// A content unit to be arranged flexibly.
|
||||
Boxed(BoxLayout),
|
||||
/// 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 surrounding boxes.
|
||||
Glue(BoxLayout),
|
||||
}
|
||||
/// Finishes a flex layout by justifying the positions of the individual boxes.
|
||||
#[derive(Debug)]
|
||||
pub struct FlexLayouter {
|
||||
ctx: FlexContext,
|
||||
units: Vec<FlexUnit>,
|
||||
|
||||
impl FlexLayout {
|
||||
/// Create a new flex layout.
|
||||
pub fn new() -> FlexLayout {
|
||||
FlexLayout {
|
||||
units: vec![],
|
||||
}
|
||||
}
|
||||
actions: LayoutActionList,
|
||||
dimensions: Size2D,
|
||||
usable: Size2D,
|
||||
cursor: Size2D,
|
||||
|
||||
/// Create a new flex layout containing just one box.
|
||||
pub fn from_box(boxed: BoxLayout) -> FlexLayout {
|
||||
FlexLayout {
|
||||
units: vec![FlexUnit::Boxed(boxed)],
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a sublayout.
|
||||
pub fn add_box(&mut self, layout: BoxLayout) {
|
||||
self.units.push(FlexUnit::Boxed(layout));
|
||||
}
|
||||
|
||||
/// Add a glue layout which can be replaced by a line break.
|
||||
pub fn add_glue(&mut self, glue: BoxLayout) {
|
||||
self.units.push(FlexUnit::Glue(glue));
|
||||
}
|
||||
|
||||
/// Add all sublayouts of another flex layout.
|
||||
pub fn add_flexible(&mut self, layout: FlexLayout) {
|
||||
self.units.extend(layout.units);
|
||||
}
|
||||
|
||||
/// Whether this layouter contains any items.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.units.is_empty()
|
||||
}
|
||||
|
||||
/// Compute the justified layout.
|
||||
pub fn finish(self, ctx: FlexContext) -> LayoutResult<BoxLayout> {
|
||||
FlexFinisher::new(self, ctx).finish()
|
||||
}
|
||||
line_content: Vec<(Size2D, Layout)>,
|
||||
line_metrics: Size2D,
|
||||
last_glue: Option<Layout>,
|
||||
}
|
||||
|
||||
/// The context for flex layouting.
|
||||
@ -71,42 +30,59 @@ pub struct FlexContext {
|
||||
pub flex_spacing: Size,
|
||||
}
|
||||
|
||||
/// Finishes a flex layout by justifying the positions of the individual boxes.
|
||||
#[derive(Debug)]
|
||||
struct FlexFinisher {
|
||||
units: Vec<FlexUnit>,
|
||||
ctx: FlexContext,
|
||||
actions: LayoutActionList,
|
||||
dimensions: Size2D,
|
||||
usable: Size2D,
|
||||
cursor: Size2D,
|
||||
line_metrics: Size2D,
|
||||
line_content: Vec<(Size2D, BoxLayout)>,
|
||||
glue: Option<BoxLayout>,
|
||||
/// A unit in a flex layout.
|
||||
#[derive(Debug, Clone)]
|
||||
enum FlexUnit {
|
||||
/// A content unit to be arranged flexibly.
|
||||
Boxed(Layout),
|
||||
/// 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 surrounding boxes.
|
||||
Glue(Layout),
|
||||
}
|
||||
|
||||
impl FlexFinisher {
|
||||
/// Create the finisher from the layout.
|
||||
fn new(layout: FlexLayout, ctx: FlexContext) -> FlexFinisher {
|
||||
let space = ctx.space;
|
||||
FlexFinisher {
|
||||
units: layout.units,
|
||||
impl FlexLayouter {
|
||||
/// Create a new flex layouter.
|
||||
pub fn new(ctx: FlexContext) -> FlexLayouter {
|
||||
FlexLayouter {
|
||||
ctx,
|
||||
units: vec![],
|
||||
|
||||
actions: LayoutActionList::new(),
|
||||
dimensions: match ctx.space.alignment {
|
||||
Alignment::Left => Size2D::zero(),
|
||||
Alignment::Right => Size2D::with_x(space.usable().x),
|
||||
Alignment::Right => Size2D::with_x(ctx.space.usable().x),
|
||||
},
|
||||
usable: space.usable(),
|
||||
cursor: Size2D::new(space.padding.left, space.padding.top),
|
||||
line_metrics: Size2D::zero(),
|
||||
usable: ctx.space.usable(),
|
||||
cursor: Size2D::new(ctx.space.padding.left, ctx.space.padding.top),
|
||||
|
||||
line_content: vec![],
|
||||
glue: None,
|
||||
line_metrics: Size2D::zero(),
|
||||
last_glue: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish the flex layout into the justified box layout.
|
||||
fn finish(mut self) -> LayoutResult<BoxLayout> {
|
||||
/// Get a reference to this layouter's context.
|
||||
pub fn ctx(&self) -> &FlexContext {
|
||||
&self.ctx
|
||||
}
|
||||
|
||||
/// Add a sublayout.
|
||||
pub fn add(&mut self, layout: Layout) {
|
||||
self.units.push(FlexUnit::Boxed(layout));
|
||||
}
|
||||
|
||||
/// Add a glue layout which can be replaced by a line break.
|
||||
pub fn add_glue(&mut self, glue: Layout) {
|
||||
self.units.push(FlexUnit::Glue(glue));
|
||||
}
|
||||
|
||||
/// Whether this layouter contains any items.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.units.is_empty()
|
||||
}
|
||||
|
||||
/// Compute the justified layout.
|
||||
pub fn finish(mut self) -> LayoutResult<Layout> {
|
||||
// Move the units out of the layout.
|
||||
let units = self.units;
|
||||
self.units = vec![];
|
||||
@ -122,7 +98,7 @@ impl FlexFinisher {
|
||||
// Flush everything to get the correct dimensions.
|
||||
self.newline();
|
||||
|
||||
Ok(BoxLayout {
|
||||
Ok(Layout {
|
||||
dimensions: if self.ctx.space.shrink_to_fit {
|
||||
self.dimensions.padded(self.ctx.space.padding)
|
||||
} else {
|
||||
@ -134,8 +110,8 @@ impl FlexFinisher {
|
||||
}
|
||||
|
||||
/// Layout the box.
|
||||
fn boxed(&mut self, boxed: BoxLayout) -> LayoutResult<()> {
|
||||
let last_glue_x = self.glue.as_ref()
|
||||
fn boxed(&mut self, boxed: Layout) -> LayoutResult<()> {
|
||||
let last_glue_x = self.last_glue.as_ref()
|
||||
.map(|g| g.dimensions.x)
|
||||
.unwrap_or(Size::zero());
|
||||
|
||||
@ -147,7 +123,7 @@ impl FlexFinisher {
|
||||
}
|
||||
|
||||
self.newline();
|
||||
} else if let Some(glue) = self.glue.take() {
|
||||
} else if let Some(glue) = self.last_glue.take() {
|
||||
self.append(glue);
|
||||
}
|
||||
|
||||
@ -157,15 +133,15 @@ impl FlexFinisher {
|
||||
}
|
||||
|
||||
/// Layout the glue.
|
||||
fn glue(&mut self, glue: BoxLayout) {
|
||||
if let Some(glue) = self.glue.take() {
|
||||
fn glue(&mut self, glue: Layout) {
|
||||
if let Some(glue) = self.last_glue.take() {
|
||||
self.append(glue);
|
||||
}
|
||||
self.glue = Some(glue);
|
||||
self.last_glue = Some(glue);
|
||||
}
|
||||
|
||||
/// Append a box to the layout without checking anything.
|
||||
fn append(&mut self, layout: BoxLayout) {
|
||||
fn append(&mut self, layout: Layout) {
|
||||
let dim = layout.dimensions;
|
||||
self.line_content.push((self.cursor, layout));
|
||||
|
||||
|
@ -1,50 +1,93 @@
|
||||
//! The layouting engine.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
|
||||
use toddle::query::{SharedFontLoader, FontClass};
|
||||
use toddle::Error as FontError;
|
||||
|
||||
use crate::func::Command;
|
||||
use crate::size::{Size, Size2D, SizeBox};
|
||||
use crate::syntax::{SyntaxTree, Node, FuncCall};
|
||||
use crate::style::TextStyle;
|
||||
|
||||
use self::flex::{FlexLayout, FlexContext};
|
||||
use self::boxed::{BoxLayout, BoxContext, BoxLayouter};
|
||||
use self::text::TextContext;
|
||||
|
||||
pub mod text;
|
||||
pub mod boxed;
|
||||
pub mod flex;
|
||||
mod text;
|
||||
mod stacked;
|
||||
mod flex;
|
||||
mod actions;
|
||||
|
||||
pub use actions::{LayoutAction, LayoutActionList};
|
||||
pub use text::{layout_text, TextContext};
|
||||
pub use flex::{FlexLayouter, FlexContext};
|
||||
pub use stacked::{StackLayouter, StackContext};
|
||||
|
||||
|
||||
/// A collection of layouted content.
|
||||
/// A box layout has a fixed width and height and composes of actions.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Layout {
|
||||
/// A box layout.
|
||||
Boxed(BoxLayout),
|
||||
/// A flexible layout.
|
||||
Flex(FlexLayout),
|
||||
pub struct Layout {
|
||||
/// The size of the box.
|
||||
pub dimensions: Size2D,
|
||||
/// The actions composing this layout.
|
||||
pub actions: Vec<LayoutAction>,
|
||||
/// Whether to debug-render this box.
|
||||
pub debug_render: bool,
|
||||
}
|
||||
|
||||
/// Layout a syntax tree in a given context.
|
||||
pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<BoxLayout> {
|
||||
Layouter::new(tree, ctx).layout()
|
||||
impl Layout {
|
||||
/// Serialize this layout into an output buffer.
|
||||
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||
writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?;
|
||||
for action in &self.actions {
|
||||
action.serialize(f)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of box layouts.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MultiLayout {
|
||||
pub layouts: Vec<Layout>,
|
||||
}
|
||||
|
||||
impl MultiLayout {
|
||||
/// Create an empty multibox layout.
|
||||
pub fn new() -> MultiLayout {
|
||||
MultiLayout {
|
||||
layouts: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a single sublayout and panic if this layout does not have
|
||||
/// exactly one child.
|
||||
pub fn into_single(mut self) -> Layout {
|
||||
if self.layouts.len() != 1 {
|
||||
panic!("into_single: contains not exactly one layout");
|
||||
}
|
||||
self.layouts.pop().unwrap()
|
||||
}
|
||||
|
||||
/// Add a sublayout.
|
||||
pub fn add(&mut self, layout: Layout) {
|
||||
self.layouts.push(layout);
|
||||
}
|
||||
|
||||
/// Whether this layout contains any sublayouts.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.layouts.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// The context for layouting.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct LayoutContext<'a, 'p> {
|
||||
/// Loads fonts matching queries.
|
||||
pub loader: &'a SharedFontLoader<'p>,
|
||||
/// Base style to set text with.
|
||||
pub style: &'a TextStyle,
|
||||
/// The space to layout in.
|
||||
pub space: LayoutSpace,
|
||||
pub extra_space: Option<LayoutSpace>,
|
||||
}
|
||||
|
||||
/// Spacial constraints for layouting.
|
||||
@ -61,13 +104,6 @@ pub struct LayoutSpace {
|
||||
pub shrink_to_fit: bool,
|
||||
}
|
||||
|
||||
/// Where to align content.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Alignment {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl LayoutSpace {
|
||||
/// The actually usable area.
|
||||
pub fn usable(&self) -> Size2D {
|
||||
@ -78,12 +114,23 @@ impl LayoutSpace {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where to align content.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Alignment {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> {
|
||||
Layouter::new(tree, ctx).layout()
|
||||
}
|
||||
|
||||
/// Transforms a syntax tree into a box layout.
|
||||
struct Layouter<'a, 'p> {
|
||||
ctx: LayoutContext<'a, 'p>,
|
||||
tree: &'a SyntaxTree,
|
||||
box_layouter: BoxLayouter,
|
||||
flex_layout: FlexLayout,
|
||||
loader: &'a SharedFontLoader<'p>,
|
||||
stack_layouter: StackLayouter,
|
||||
flex_layouter: FlexLayouter,
|
||||
style: Cow<'a, TextStyle>,
|
||||
}
|
||||
|
||||
@ -91,16 +138,24 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
||||
/// Create a new layouter.
|
||||
fn new(tree: &'a SyntaxTree, ctx: LayoutContext<'a, 'p>) -> Layouter<'a, 'p> {
|
||||
Layouter {
|
||||
ctx,
|
||||
tree,
|
||||
box_layouter: BoxLayouter::new(BoxContext { space: ctx.space }),
|
||||
flex_layout: FlexLayout::new(),
|
||||
loader: ctx.loader,
|
||||
stack_layouter: StackLayouter::new(StackContext { space: ctx.space }),
|
||||
flex_layouter: FlexLayouter::new(FlexContext {
|
||||
space: LayoutSpace {
|
||||
dimensions: ctx.space.usable(),
|
||||
padding: SizeBox::zero(),
|
||||
alignment: ctx.space.alignment,
|
||||
shrink_to_fit: true,
|
||||
},
|
||||
flex_spacing: (ctx.style.line_spacing - 1.0) * Size::pt(ctx.style.font_size),
|
||||
}),
|
||||
style: Cow::Borrowed(ctx.style)
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout the tree into a box.
|
||||
fn layout(mut self) -> LayoutResult<BoxLayout> {
|
||||
fn layout(mut self) -> LayoutResult<MultiLayout> {
|
||||
// Walk all nodes and layout them.
|
||||
for node in &self.tree.nodes {
|
||||
match node {
|
||||
@ -109,7 +164,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
||||
|
||||
// Add a space.
|
||||
Node::Space => {
|
||||
if !self.flex_layout.is_empty() {
|
||||
if !self.flex_layouter.is_empty() {
|
||||
self.layout_text(" ", true)?;
|
||||
}
|
||||
},
|
||||
@ -122,7 +177,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
||||
// Add some paragraph spacing.
|
||||
let size = Size::pt(self.style.font_size)
|
||||
* (self.style.line_spacing * self.style.paragraph_spacing - 1.0);
|
||||
self.box_layouter.add_space(size)?;
|
||||
self.stack_layouter.add_space(size)?;
|
||||
},
|
||||
|
||||
// Toggle the text styles.
|
||||
@ -136,24 +191,26 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
||||
}
|
||||
|
||||
// If there are remainings, add them to the layout.
|
||||
if !self.flex_layout.is_empty() {
|
||||
if !self.flex_layouter.is_empty() {
|
||||
self.layout_flex()?;
|
||||
}
|
||||
|
||||
Ok(self.box_layouter.finish())
|
||||
Ok(MultiLayout {
|
||||
layouts: vec![self.stack_layouter.finish()]
|
||||
})
|
||||
}
|
||||
|
||||
/// Layout a piece of text into a box.
|
||||
fn layout_text(&mut self, text: &str, glue: bool) -> LayoutResult<()> {
|
||||
let boxed = self::text::layout(text, TextContext {
|
||||
loader: &self.loader,
|
||||
let boxed = layout_text(text, TextContext {
|
||||
loader: &self.ctx.loader,
|
||||
style: &self.style,
|
||||
})?;
|
||||
|
||||
if glue {
|
||||
self.flex_layout.add_glue(boxed);
|
||||
self.flex_layouter.add_glue(boxed);
|
||||
} else {
|
||||
self.flex_layout.add_box(boxed);
|
||||
self.flex_layouter.add(boxed);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -161,48 +218,46 @@ 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() {
|
||||
if self.flex_layouter.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut layout = FlexLayout::new();
|
||||
mem::swap(&mut layout, &mut self.flex_layout);
|
||||
|
||||
let boxed = layout.finish(FlexContext {
|
||||
let mut layout = FlexLayouter::new(FlexContext {
|
||||
space: LayoutSpace {
|
||||
dimensions: self.box_layouter.remaining(),
|
||||
dimensions: self.stack_layouter.ctx().space.usable(),
|
||||
padding: SizeBox::zero(),
|
||||
alignment: self.box_layouter.ctx.space.alignment,
|
||||
alignment: self.ctx.space.alignment,
|
||||
shrink_to_fit: true,
|
||||
},
|
||||
flex_spacing: (self.style.line_spacing - 1.0) * Size::pt(self.style.font_size),
|
||||
})?;
|
||||
});
|
||||
mem::swap(&mut layout, &mut self.flex_layouter);
|
||||
|
||||
self.box_layouter.add_box(boxed)
|
||||
let boxed = layout.finish()?;
|
||||
|
||||
self.stack_layouter.add_box(boxed)
|
||||
}
|
||||
|
||||
/// Layout a function.
|
||||
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
|
||||
let layout = func.body.layout(LayoutContext {
|
||||
loader: &self.loader,
|
||||
let commands = func.body.layout(LayoutContext {
|
||||
loader: &self.ctx.loader,
|
||||
style: &self.style,
|
||||
space: LayoutSpace {
|
||||
dimensions: self.box_layouter.remaining(),
|
||||
dimensions: self.stack_layouter.remaining(),
|
||||
padding: SizeBox::zero(),
|
||||
alignment: self.box_layouter.ctx.space.alignment,
|
||||
alignment: self.ctx.space.alignment,
|
||||
shrink_to_fit: true,
|
||||
},
|
||||
extra_space: self.ctx.extra_space,
|
||||
})?;
|
||||
|
||||
// Add the potential layout.
|
||||
if let Some(layout) = layout {
|
||||
match layout {
|
||||
Layout::Boxed(boxed) => {
|
||||
// Finish the previous flex run before adding the box.
|
||||
self.layout_flex()?;
|
||||
self.box_layouter.add_box(boxed)?;
|
||||
},
|
||||
Layout::Flex(flex) => self.flex_layout.add_flexible(flex),
|
||||
for command in commands {
|
||||
match command {
|
||||
Command::Layout(tree) => unimplemented!(),
|
||||
Command::Add(layout) => unimplemented!(),
|
||||
Command::AddMany(layouts) => unimplemented!(),
|
||||
Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,68 +1,27 @@
|
||||
//! Block-style layouting of boxes.
|
||||
|
||||
use std::io::{self, Write};
|
||||
use crate::doc::{Document, Page};
|
||||
use crate::size::{Size, Size2D};
|
||||
use super::*;
|
||||
|
||||
|
||||
/// A box layout has a fixed width and height and composes of actions.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BoxLayout {
|
||||
/// The size of the box.
|
||||
pub dimensions: Size2D,
|
||||
/// The actions composing this layout.
|
||||
pub actions: Vec<LayoutAction>,
|
||||
/// Whether to debug-render this box.
|
||||
pub debug_render: bool,
|
||||
}
|
||||
|
||||
impl BoxLayout {
|
||||
/// Convert this layout into a document.
|
||||
pub fn into_doc(self) -> Document {
|
||||
Document {
|
||||
pages: vec![Page {
|
||||
width: self.dimensions.x,
|
||||
height: self.dimensions.y,
|
||||
actions: self.actions,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize this layout into a string representation.
|
||||
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||
writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?;
|
||||
for action in &self.actions {
|
||||
action.serialize(f)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The context for layouting boxes.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct BoxContext {
|
||||
/// The space to layout the boxes in.
|
||||
pub space: LayoutSpace,
|
||||
}
|
||||
|
||||
/// Layouts boxes block-style.
|
||||
#[derive(Debug)]
|
||||
pub struct BoxLayouter {
|
||||
pub ctx: BoxContext,
|
||||
pub struct StackLayouter {
|
||||
ctx: StackContext,
|
||||
actions: LayoutActionList,
|
||||
dimensions: Size2D,
|
||||
usable: Size2D,
|
||||
cursor: Size2D,
|
||||
}
|
||||
|
||||
impl BoxLayouter {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct StackContext {
|
||||
pub space: LayoutSpace,
|
||||
}
|
||||
|
||||
impl StackLayouter {
|
||||
/// Create a new box layouter.
|
||||
pub fn new(ctx: BoxContext) -> BoxLayouter {
|
||||
pub fn new(ctx: StackContext) -> StackLayouter {
|
||||
let space = ctx.space;
|
||||
|
||||
BoxLayouter {
|
||||
StackLayouter {
|
||||
ctx,
|
||||
actions: LayoutActionList::new(),
|
||||
dimensions: match ctx.space.alignment {
|
||||
@ -77,52 +36,54 @@ impl BoxLayouter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to this layouter's context.
|
||||
pub fn ctx(&self) -> &StackContext {
|
||||
&self.ctx
|
||||
}
|
||||
|
||||
/// Add a sublayout.
|
||||
pub fn add_box(&mut self, layout: BoxLayout) -> LayoutResult<()> {
|
||||
pub fn add_box(&mut self, layout: Layout) -> LayoutResult<()> {
|
||||
// In the flow direction (vertical) add the layout and in the second
|
||||
// direction just consider the maximal size of any child layout.
|
||||
let new = Size2D {
|
||||
let new_size = Size2D {
|
||||
x: crate::size::max(self.dimensions.x, layout.dimensions.x),
|
||||
y: self.dimensions.y + layout.dimensions.y,
|
||||
};
|
||||
|
||||
// Check whether this box fits.
|
||||
if self.overflows(new) {
|
||||
if self.overflows(new_size) {
|
||||
return Err(LayoutError::NotEnoughSpace);
|
||||
}
|
||||
|
||||
// Apply the dimensions if they fit.
|
||||
self.dimensions = new;
|
||||
let width = layout.dimensions.x;
|
||||
let height = layout.dimensions.y;
|
||||
self.dimensions = new_size;
|
||||
|
||||
// Determine where to put the box. When we right-align it, we want the
|
||||
// cursor to point to the top-right corner of the box. Therefore, the
|
||||
// position has to be moved to the left by the width of the box.
|
||||
let position = match self.ctx.space.alignment {
|
||||
Alignment::Left => self.cursor,
|
||||
Alignment::Right => self.cursor - Size2D::with_x(width),
|
||||
Alignment::Right => self.cursor - Size2D::with_x(layout.dimensions.x),
|
||||
};
|
||||
|
||||
// Add the box.
|
||||
self.add_box_absolute(position, layout);
|
||||
self.cursor.y += layout.dimensions.y;
|
||||
|
||||
// Adjust the cursor.
|
||||
self.cursor.y += height;
|
||||
self.add_box_absolute(position, layout);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a sublayout at an absolute position.
|
||||
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
|
||||
self.actions.add_box(position, layout);
|
||||
pub fn add_box_absolute(&mut self, position: Size2D, layout: Layout) -> LayoutResult<()> {
|
||||
Ok(self.actions.add_box(position, layout))
|
||||
}
|
||||
|
||||
/// Add some space in between two boxes.
|
||||
/// Add space in between two boxes.
|
||||
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
|
||||
// Check whether this space fits.
|
||||
if self.overflows(self.dimensions + Size2D::with_y(space)) {
|
||||
return Err(LayoutError::NotEnoughSpace);
|
||||
}
|
||||
|
||||
// Adjust the sizes.
|
||||
self.cursor.y += space;
|
||||
self.dimensions.y += space;
|
||||
|
||||
@ -143,8 +104,8 @@ impl BoxLayouter {
|
||||
}
|
||||
|
||||
/// Finish the layouting and create a box layout from this.
|
||||
pub fn finish(self) -> BoxLayout {
|
||||
BoxLayout {
|
||||
pub fn finish(self) -> Layout {
|
||||
Layout {
|
||||
dimensions: if self.ctx.space.shrink_to_fit {
|
||||
self.dimensions.padded(self.ctx.space.padding)
|
||||
} else {
|
@ -17,7 +17,7 @@ pub struct TextContext<'a, 'p> {
|
||||
}
|
||||
|
||||
/// Layout one piece of text without any breaks as one continous box.
|
||||
pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
|
||||
pub fn layout_text(text: &str, ctx: TextContext) -> LayoutResult<Layout> {
|
||||
let mut loader = ctx.loader.borrow_mut();
|
||||
|
||||
let mut actions = Vec::new();
|
||||
@ -88,7 +88,7 @@ pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
|
||||
actions.push(LayoutAction::WriteText(buffer));
|
||||
}
|
||||
|
||||
Ok(BoxLayout {
|
||||
Ok(Layout {
|
||||
dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)),
|
||||
actions,
|
||||
debug_render: false,
|
||||
|
42
src/lib.rs
42
src/lib.rs
@ -17,17 +17,15 @@ pub extern crate toddle;
|
||||
use std::cell::RefCell;
|
||||
use toddle::query::{FontLoader, SharedFontLoader, FontProvider};
|
||||
|
||||
use crate::doc::Document;
|
||||
use crate::func::Scope;
|
||||
use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
|
||||
use crate::layout::{layout, LayoutContext, Alignment, LayoutSpace, LayoutError, LayoutResult};
|
||||
use crate::layout::boxed::BoxLayout;
|
||||
use crate::layout::{layout_tree, LayoutContext, MultiLayout};
|
||||
use crate::layout::{LayoutSpace, Alignment, LayoutError, LayoutResult};
|
||||
use crate::style::{PageStyle, TextStyle};
|
||||
use crate::syntax::SyntaxTree;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
pub mod doc;
|
||||
pub mod export;
|
||||
pub mod func;
|
||||
pub mod layout;
|
||||
@ -79,6 +77,12 @@ impl<'p> Typesetter<'p> {
|
||||
self.loader.get_mut().add_provider(provider);
|
||||
}
|
||||
|
||||
/// A reference to the backing font loader.
|
||||
#[inline]
|
||||
pub fn loader(&self) -> &SharedFontLoader<'p> {
|
||||
&self.loader
|
||||
}
|
||||
|
||||
/// Parse source code into a syntax tree.
|
||||
pub fn parse(&self, src: &str) -> ParseResult<SyntaxTree> {
|
||||
let scope = Scope::with_std();
|
||||
@ -86,31 +90,29 @@ impl<'p> Typesetter<'p> {
|
||||
}
|
||||
|
||||
/// Layout a syntax tree and return the layout and the referenced font list.
|
||||
pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult<BoxLayout> {
|
||||
let pages = layout(&tree, LayoutContext {
|
||||
pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult<MultiLayout> {
|
||||
let space = LayoutSpace {
|
||||
dimensions: self.page_style.dimensions,
|
||||
padding: self.page_style.margins,
|
||||
alignment: Alignment::Left,
|
||||
shrink_to_fit: false,
|
||||
};
|
||||
|
||||
let pages = layout_tree(&tree, LayoutContext {
|
||||
loader: &self.loader,
|
||||
style: &self.text_style,
|
||||
space: LayoutSpace {
|
||||
dimensions: self.page_style.dimensions,
|
||||
padding: self.page_style.margins,
|
||||
alignment: Alignment::Left,
|
||||
shrink_to_fit: false,
|
||||
},
|
||||
space,
|
||||
extra_space: Some(space),
|
||||
})?;
|
||||
|
||||
Ok(pages)
|
||||
}
|
||||
|
||||
/// Typeset a portable document from source code.
|
||||
pub fn typeset(&self, src: &str) -> Result<Document, TypesetError> {
|
||||
pub fn typeset(&self, src: &str) -> Result<MultiLayout, TypesetError> {
|
||||
let tree = self.parse(src)?;
|
||||
let layout = self.layout(&tree)?;
|
||||
let document = layout.into_doc();
|
||||
Ok(document)
|
||||
}
|
||||
|
||||
/// A reference to the backing font loader.
|
||||
pub fn loader(&self) -> &SharedFontLoader<'p> {
|
||||
&self.loader
|
||||
Ok(layout)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,12 +38,13 @@ impl Function for AlignFunc {
|
||||
Ok(AlignFunc { alignment, body })
|
||||
}
|
||||
|
||||
fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<FuncCommands> {
|
||||
if let Some(body) = &self.body {
|
||||
// Override the previous alignment and do the layouting.
|
||||
ctx.space.alignment = self.alignment;
|
||||
layout(body, ctx)
|
||||
.map(|l| Some(Layout::Boxed(l)))
|
||||
// // Override the previous alignment and do the layouting.
|
||||
// ctx.space.alignment = self.alignment;
|
||||
// layout(body, ctx)
|
||||
// .map(|l| Some(Layout::Boxed(l)))
|
||||
Ok(FuncCommands::new())
|
||||
} else {
|
||||
unimplemented!("context-modifying align func")
|
||||
}
|
||||
|
@ -9,10 +9,9 @@ mod styles;
|
||||
pub mod prelude {
|
||||
pub use crate::syntax::{SyntaxTree, FuncHeader, Expression};
|
||||
pub use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
|
||||
pub use crate::layout::{layout, Layout, LayoutContext, LayoutResult, LayoutError};
|
||||
pub use crate::layout::flex::FlexLayout;
|
||||
pub use crate::layout::boxed::BoxLayout;
|
||||
pub use crate::func::Function;
|
||||
pub use crate::layout::{layout_tree, layout_text, MultiLayout, Layout, LayoutContext};
|
||||
pub use crate::layout::{LayoutResult, LayoutError};
|
||||
pub use crate::func::{Function, Command, FuncCommands};
|
||||
|
||||
pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
|
||||
Err(ParseError::new(message))
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Basic style functions: bold, italic, monospace.
|
||||
|
||||
use super::prelude::*;
|
||||
use toddle::query::FontClass;
|
||||
// use toddle::query::FontClass;
|
||||
|
||||
|
||||
|
||||
@ -27,19 +27,20 @@ macro_rules! style_func {
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
|
||||
// Change the context.
|
||||
let mut $style = ctx.style.clone();
|
||||
$style_change
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<FuncCommands> {
|
||||
// // Change the context.
|
||||
// let mut $style = ctx.style.clone();
|
||||
// $style_change
|
||||
|
||||
// Create a box and put it into a flex layout.
|
||||
let boxed = layout(&self.body, LayoutContext {
|
||||
style: &$style,
|
||||
.. ctx
|
||||
})?;
|
||||
let flex = FlexLayout::from_box(boxed);
|
||||
// // Create a box and put it into a flex layout.
|
||||
// let boxed = layout(&self.body, LayoutContext {
|
||||
// style: &$style,
|
||||
// .. ctx
|
||||
// })?;
|
||||
// let flex = FlexLayout::from_box(boxed);
|
||||
|
||||
Ok(Some(Layout::Flex(flex)))
|
||||
// Ok(Some(Layout::Flex(flex)))
|
||||
Ok(FuncCommands::new())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -432,8 +432,8 @@ error_type! {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::func::{Function, Scope};
|
||||
use crate::layout::{LayoutContext, LayoutResult, Layout};
|
||||
use crate::func::{Function, FuncCommands, Scope};
|
||||
use crate::layout::{LayoutContext, LayoutResult};
|
||||
use Node::{Space as S, Newline as N, Func as F};
|
||||
use funcs::*;
|
||||
|
||||
@ -456,7 +456,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(&self, _: LayoutContext) -> LayoutResult<Option<Layout>> { Ok(None) }
|
||||
fn layout(&self, _: LayoutContext) -> LayoutResult<FuncCommands> {
|
||||
Ok(FuncCommands::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// A testing function without a body.
|
||||
@ -473,7 +475,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(&self, _: LayoutContext) -> LayoutResult<Option<Layout>> { Ok(None) }
|
||||
fn layout(&self, _: LayoutContext) -> LayoutResult<FuncCommands> {
|
||||
Ok(FuncCommands::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,17 +11,19 @@ const CACHE_DIR: &str = "test-cache";
|
||||
|
||||
|
||||
fn main() {
|
||||
let mut perfect_match = false;
|
||||
let mut filter = Vec::new();
|
||||
|
||||
for arg in std::env::args().skip(1) {
|
||||
if arg.as_str() != "--nocapture" {
|
||||
if arg.as_str() == "--nocapture" {
|
||||
continue;
|
||||
} else if arg.as_str() == "=" {
|
||||
perfect_match = true;
|
||||
} else {
|
||||
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();
|
||||
@ -33,7 +35,13 @@ fn main() {
|
||||
.file_stem().unwrap()
|
||||
.to_str().unwrap();
|
||||
|
||||
if filter.is_empty() || filter.iter().any(|pattern| name.contains(pattern)) {
|
||||
let matches = if perfect_match {
|
||||
filter.iter().any(|pattern| name == pattern)
|
||||
} else {
|
||||
filter.is_empty() || filter.iter().any(|pattern| name.contains(pattern))
|
||||
};
|
||||
|
||||
if matches {
|
||||
let mut file = File::open(&path).unwrap();
|
||||
let mut src = String::new();
|
||||
file.read_to_string(&mut src).unwrap();
|
||||
@ -62,7 +70,8 @@ fn test(name: &str, src: &str) {
|
||||
// Find all used fonts and their filenames.
|
||||
let mut map = Vec::new();
|
||||
let mut loader = typesetter.loader().borrow_mut();
|
||||
for action in &layout.actions {
|
||||
let single = &layout.layouts[0];
|
||||
for action in &single.actions {
|
||||
if let LayoutAction::SetFont(index, _) = action {
|
||||
if map.iter().find(|(i, _)| i == index).is_none() {
|
||||
let (_, provider_index) = loader.get_provider_and_index(*index);
|
||||
@ -78,7 +87,7 @@ fn test(name: &str, src: &str) {
|
||||
for (index, path) in map {
|
||||
writeln!(file, "{} {}", index, path).unwrap();
|
||||
}
|
||||
layout.serialize(&mut file).unwrap();
|
||||
single.serialize(&mut file).unwrap();
|
||||
|
||||
// Render the layout into a PNG.
|
||||
Command::new("python")
|
||||
@ -90,7 +99,6 @@ fn test(name: &str, src: &str) {
|
||||
// Write the PDF file.
|
||||
let path = format!("{}/pdf/{}.pdf", CACHE_DIR, name);
|
||||
let file = BufWriter::new(File::create(path).unwrap());
|
||||
let document = layout.into_doc();
|
||||
let exporter = PdfExporter::new();
|
||||
exporter.export(&document, typesetter.loader(), file).unwrap();
|
||||
exporter.export(&layout, typesetter.loader(), file).unwrap();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user