Serialize layouts with serde 🔠

This commit is contained in:
Laurenz 2020-02-04 21:36:29 +01:00
parent e63ce52ae0
commit 751812f451
10 changed files with 95 additions and 106 deletions

View File

@ -3,7 +3,6 @@ name = "typstc"
version = "0.1.0" version = "0.1.0"
authors = ["Laurenz Mädje <laurmaedje@gmail.com>"] authors = ["Laurenz Mädje <laurmaedje@gmail.com>"]
edition = "2018" edition = "2018"
# build = "build.rs"
[dependencies] [dependencies]
toddle = { path = "../toddle", features = ["query"], default-features = false } toddle = { path = "../toddle", features = ["query"], default-features = false }
@ -13,10 +12,11 @@ smallvec = "1"
unicode-xid = "0.2" unicode-xid = "0.2"
async-trait = "0.1" async-trait = "0.1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", optional = true }
futures-executor = { version = "0.3", optional = true } futures-executor = { version = "0.3", optional = true }
[features] [features]
default = ["fs-provider", "futures-executor"] default = ["fs-provider", "futures-executor", "serde_json"]
fs-provider = ["toddle/fs-provider"] fs-provider = ["toddle/fs-provider"]
[[bin]] [[bin]]
@ -28,4 +28,4 @@ required-features = ["fs-provider", "futures-executor"]
name = "typeset" name = "typeset"
path = "tests/src/typeset.rs" path = "tests/src/typeset.rs"
harness = false harness = false
required-features = ["fs-provider", "futures-executor"] required-features = ["fs-provider", "futures-executor", "serde_json"]

View File

@ -53,16 +53,16 @@ pub trait ParseFunc {
/// body: Option<SyntaxModel>, /// body: Option<SyntaxModel>,
/// } /// }
/// ///
/// parse(header, body, ctx, errors, decos) { /// parse(header, body, ctx, f) {
/// let body = body!(opt: body, ctx, errors, decos); /// let body = body!(opt: body, ctx, f);
/// let hidden = header.args.pos.get::<bool>(errors) /// let hidden = header.args.pos.get::<bool>(&mut f.errors)
/// .or_missing(errors, header.name.span, "hidden") /// .or_missing(&mut f.errors, header.name.span, "hidden")
/// .unwrap_or(false); /// .unwrap_or(false);
/// ///
/// HiderFunc { body: if hidden { None } else { body } } /// HiderFunc { body: if hidden { None } else { body } }
/// } /// }
/// ///
/// layout(self, ctx, errors) { /// layout(self, ctx, f) {
/// match &self.body { /// match &self.body {
/// Some(model) => vec![LayoutSyntaxModel(model)], /// Some(model) => vec![LayoutSyntaxModel(model)],
/// None => vec![], /// None => vec![],

View File

@ -1,11 +1,11 @@
//! Drawing and configuration actions composing layouts. //! Drawing and configuration actions composing layouts.
use std::io::{self, Write};
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use serde::ser::{Serialize, Serializer, SerializeTuple};
use toddle::query::FontIndex; use toddle::query::FontIndex;
use crate::size::{Size, Size2D}; use crate::size::{Size, Size2D};
use super::{Layout, Serialize}; use super::Layout;
use self::LayoutAction::*; use self::LayoutAction::*;
@ -24,12 +24,33 @@ pub enum LayoutAction {
} }
impl Serialize for LayoutAction { impl Serialize for LayoutAction {
fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
match self { match self {
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()), LayoutAction::MoveAbsolute(pos) => {
SetFont(i, s) => write!(f, "f {} {} {}", i.id, i.variant, s.to_pt()), let mut tup = serializer.serialize_tuple(2)?;
WriteText(s) => write!(f, "w {}", s), tup.serialize_element(&0u8)?;
DebugBox(s) => write!(f, "b {} {}", s.x.to_pt(), s.y.to_pt()), tup.serialize_element(&pos)?;
tup.end()
}
LayoutAction::SetFont(index, size) => {
let mut tup = serializer.serialize_tuple(4)?;
tup.serialize_element(&1u8)?;
tup.serialize_element(index)?;
tup.serialize_element(size)?;
tup.end()
}
LayoutAction::WriteText(text) => {
let mut tup = serializer.serialize_tuple(2)?;
tup.serialize_element(&2u8)?;
tup.serialize_element(text)?;
tup.end()
}
LayoutAction::DebugBox(size) => {
let mut tup = serializer.serialize_tuple(2)?;
tup.serialize_element(&3u8)?;
tup.serialize_element(&size)?;
tup.end()
}
} }
} }
} }
@ -40,7 +61,7 @@ impl Debug for LayoutAction {
match self { match self {
MoveAbsolute(s) => write!(f, "move {} {}", s.x, s.y), MoveAbsolute(s) => write!(f, "move {} {}", s.x, s.y),
SetFont(i, s) => write!(f, "font {}_{} {}", i.id, i.variant, s), SetFont(i, s) => write!(f, "font {}_{} {}", i.id, i.variant, s),
WriteText(s) => write!(f, "write \"{}\"", s), WriteText(s) => write!(f, "write {:?}", s),
DebugBox(s) => write!(f, "box {} {}", s.x, s.y), DebugBox(s) => write!(f, "box {} {}", s.x, s.y),
} }
} }

View File

@ -1,8 +1,8 @@
//! Layouting types and engines. //! Layouting types and engines.
use std::io::{self, Write};
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use smallvec::SmallVec; use smallvec::SmallVec;
use serde::Serialize;
use toddle::query::FontIndex; use toddle::query::FontIndex;
use crate::size::{Size, Size2D, SizeBox}; use crate::size::{Size, Size2D, SizeBox};
@ -32,11 +32,12 @@ pub mod prelude {
pub type MultiLayout = Vec<Layout>; pub type MultiLayout = Vec<Layout>;
/// A finished box with content at fixed positions. /// A finished box with content at fixed positions.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Layout { pub struct Layout {
/// The size of the box. /// The size of the box.
pub dimensions: Size2D, pub dimensions: Size2D,
/// How to align this layout in a parent container. /// How to align this layout in a parent container.
#[serde(skip)]
pub alignment: LayoutAlignment, pub alignment: LayoutAlignment,
/// The actions composing this layout. /// The actions composing this layout.
pub actions: Vec<LayoutAction>, pub actions: Vec<LayoutAction>,
@ -57,34 +58,6 @@ impl Layout {
} }
} }
/// Layout components that can be serialized.
pub trait Serialize {
/// Serialize the data structure into an output writable.
fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()>;
}
impl Serialize for Layout {
fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?;
writeln!(f, "{}", self.actions.len())?;
for action in &self.actions {
action.serialize(f)?;
writeln!(f)?;
}
Ok(())
}
}
impl Serialize for MultiLayout {
fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
writeln!(f, "{}", self.len())?;
for layout in self {
layout.serialize(f)?;
}
Ok(())
}
}
/// A vector of layout spaces, that is stack allocated as long as it only /// A vector of layout spaces, that is stack allocated as long as it only
/// contains at most 2 spaces. /// contains at most 2 spaces.
pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>; pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>;

View File

@ -24,7 +24,8 @@ use async_trait::async_trait;
use smallvec::smallvec; use smallvec::smallvec;
use toddle::{Font, OwnedData}; use toddle::{Font, OwnedData};
use toddle::query::{FontLoader, FontProvider, SharedFontLoader, FontDescriptor}; use toddle::query::{FontLoader, SharedFontLoader};
use toddle::query::{FontProvider, FontIndex, FontDescriptor};
use crate::error::Error; use crate::error::Error;
use crate::layout::MultiLayout; use crate::layout::MultiLayout;
@ -223,8 +224,8 @@ where P: FontProvider, P::Error: Debug + 'static {
type Data = P::Data; type Data = P::Data;
type Error = Box<dyn Debug>; type Error = Box<dyn Debug>;
async fn load(&self, index: usize, variant: usize) -> Result<Font<P::Data>, Self::Error> { async fn load(&self, index: FontIndex) -> Result<Font<P::Data>, Self::Error> {
self.provider.load(index, variant).await self.provider.load(index).await
.map_err(|d| Box::new(d) as Box<dyn Debug>) .map_err(|d| Box::new(d) as Box<dyn Debug>)
} }
} }

View File

@ -4,12 +4,14 @@ use std::fmt::{self, Debug, Display, Formatter};
use std::iter::Sum; use std::iter::Sum;
use std::ops::*; use std::ops::*;
use std::str::FromStr; use std::str::FromStr;
use serde::Serialize;
use crate::layout::prelude::*; use crate::layout::prelude::*;
/// A general spacing type. /// A general spacing type.
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] #[derive(Default, Copy, Clone, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct Size { pub struct Size {
/// The size in typographic points (1/72 inches). /// The size in typographic points (1/72 inches).
pub points: f32, pub points: f32,
@ -137,7 +139,7 @@ pub type FSize = ScaleSize;
pub type PSize = ScaleSize; pub type PSize = ScaleSize;
/// A value in two dimensions. /// A value in two dimensions.
#[derive(Default, Copy, Clone, Eq, PartialEq)] #[derive(Default, Copy, Clone, Eq, PartialEq, Serialize)]
pub struct Value2D<T> { pub struct Value2D<T> {
/// The horizontal component. /// The horizontal component.
pub x: T, pub x: T,
@ -299,7 +301,7 @@ impl Neg for Size2D {
/// A value that is stretchable in an interval from a minimal through an optimal /// A value that is stretchable in an interval from a minimal through an optimal
/// to a maximal value. /// to a maximal value.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize)]
pub struct StretchValue<T> { pub struct StretchValue<T> {
/// The minimum this value can be stretched to. /// The minimum this value can be stretched to.
pub min: T, pub min: T,
@ -320,7 +322,7 @@ impl<T> StretchValue<T> {
pub type StretchSize = StretchValue<Size>; pub type StretchSize = StretchValue<Size>;
/// A value in four dimensions. /// A value in four dimensions.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize)]
pub struct ValueBox<T> { pub struct ValueBox<T> {
/// The left extent. /// The left extent.
pub left: T, pub left: T,

View File

@ -100,9 +100,9 @@ impl From<StringLike> for String {
} }
} }
/// A value type that matches the string `"default"` or a value type `V` and /// A value type that matches the identifier `default` or a value type `V` and
/// returns `Option::Some(V::Output)` for a value and `Option::None` for /// implements `Into<Option>` yielding `Option::Some(V)` for a value and
/// `"default"`. /// `Option::None` for `default`.
/// ///
/// # Example /// # Example
/// ``` /// ```

View File

@ -13,7 +13,7 @@
[v: 6mm] [v: 6mm]
[align: center][ [align: center][
*3. Ubungsblatt Computerorientierte Mathematik II* [v: 2mm] *3. Übungsblatt Computerorientierte Mathematik II* [v: 2mm]
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v: 2mm] *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v: 2mm]
*Alle Antworten sind zu beweisen.* *Alle Antworten sind zu beweisen.*
] ]

View File

@ -1,8 +1,8 @@
import sys import sys
import os import os
import pathlib
import math import math
import numpy import numpy
import json
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
@ -14,11 +14,11 @@ def main():
assert len(sys.argv) == 2, 'usage: python render.py <name>' assert len(sys.argv) == 2, 'usage: python render.py <name>'
name = sys.argv[1] name = sys.argv[1]
filename = os.path.join(CACHE, f'{name}.serialized') filename = os.path.join(CACHE, f'{name}.serde.json')
with open(filename, encoding='utf-8') as file: with open(filename, encoding='utf-8') as file:
lines = [line[:-1] for line in file.readlines()] data = json.load(file)
renderer = MultiboxRenderer(lines) renderer = MultiboxRenderer(data)
renderer.render() renderer.render()
image = renderer.export() image = renderer.export()
@ -26,38 +26,30 @@ def main():
class MultiboxRenderer: class MultiboxRenderer:
def __init__(self, lines): def __init__(self, data):
self.combined = None self.combined = None
self.fonts = {} self.fonts = {}
font_count = int(lines[0]) for entry in data["fonts"]:
for i in range(font_count): index = int(entry[0]["id"]), int(entry[0]["variant"])
parts = lines[i + 1].split(' ', 2) self.fonts[index] = os.path.join(BASE, '../../../fonts', entry[1])
index = int(parts[0]), int(parts[1])
path = parts[2]
self.fonts[index] = os.path.join(BASE, '../../../fonts', path)
self.content = lines[font_count + 1:] self.layouts = data["layouts"]
def render(self): def render(self):
images = [] images = []
layout_count = int(self.content[0]) horizontal = math.floor(math.sqrt(len(self.layouts)))
horizontal = math.floor(math.sqrt(layout_count))
start = 1 start = 1
for _ in range(layout_count): for layout in self.layouts:
width, height = (float(s) for s in self.content[start].split()) size = layout["dimensions"]
action_count = int(self.content[start + 1])
start += 2
renderer = BoxRenderer(self.fonts, width, height) renderer = BoxRenderer(self.fonts, size["x"], size["y"])
for i in range(action_count): for action in layout["actions"]:
command = self.content[start + i] renderer.execute(action)
renderer.execute(command)
images.append(renderer.export()) images.append(renderer.export())
start += action_count
i = 0 i = 0
x = 10 x = 10
@ -128,26 +120,25 @@ class BoxRenderer:
def execute(self, command): def execute(self, command):
cmd = command[0] cmd = command[0]
parts = command.split()[1:] args = command[1:]
if cmd == 'm': if cmd == 0:
x, y = (pix(float(s)) for s in parts) self.cursor = [pix(args[0]["x"]), pix(args[0]["y"])]
self.cursor = [x, y]
elif cmd == 'f': elif cmd == 1:
index = int(parts[0]), int(parts[1]) index = int(args[0]["id"]), int(args[0]["variant"])
size = pix(float(parts[2])) size = pix(args[1])
self.font = ImageFont.truetype(self.fonts[index], size) self.font = ImageFont.truetype(self.fonts[index], size)
elif cmd == 'w': elif cmd == 2:
text = command[2:] text = args[0]
width = self.draw.textsize(text, font=self.font)[0] width = self.draw.textsize(text, font=self.font)[0]
self.draw.text(self.cursor, text, (0, 0, 0, 255), font=self.font) self.draw.text(self.cursor, text, (0, 0, 0, 255), font=self.font)
self.cursor[0] += width self.cursor[0] += width
elif cmd == 'b': elif cmd == 3:
x, y = self.cursor x, y = self.cursor
w, h = (pix(float(s)) for s in parts) w, h = pix(args[0]["x"]), pix(args[0]["y"])
rect = [x, y, x+w-1, y+h-1] rect = [x, y, x+w-1, y+h-1]
forbidden_colors = set() forbidden_colors = set()

View File

@ -2,19 +2,21 @@ use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs::{File, create_dir_all, read_dir, read_to_string}; use std::fs::{File, create_dir_all, read_dir, read_to_string};
use std::io::{BufWriter, Write}; use std::io::BufWriter;
use std::panic; use std::panic;
use std::process::Command; use std::process::Command;
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
use serde::Serialize;
use futures_executor::block_on; use futures_executor::block_on;
use typstc::{Typesetter, DebugErrorProvider}; use typstc::{Typesetter, DebugErrorProvider};
use typstc::layout::{MultiLayout, Serialize}; use typstc::layout::MultiLayout;
use typstc::size::{Size, Size2D, ValueBox}; use typstc::size::{Size, Size2D, ValueBox};
use typstc::style::{PageStyle, PaperClass}; use typstc::style::{PageStyle, PaperClass};
use typstc::export::pdf; use typstc::export::pdf;
use typstc::toddle::query::fs::EagerFsProvider; use toddle::query::FontIndex;
use toddle::query::fs::EagerFsProvider;
type DynResult<T> = Result<T, Box<dyn Error>>; type DynResult<T> = Result<T, Box<dyn Error>>;
@ -86,23 +88,22 @@ fn test(name: &str, src: &str) -> DynResult<()> {
for layout in &layouts { for layout in &layouts {
for index in layout.find_used_fonts() { for index in layout.find_used_fonts() {
fonts.entry(index) fonts.entry(index)
.or_insert_with(|| &files[index.id][index.variant]); .or_insert_with(|| files[index.id][index.variant].as_str());
} }
} }
// Write the serialized layout file. #[derive(Serialize)]
let path = format!("tests/cache/{}.serialized", name); struct Document<'a> {
let mut file = BufWriter::new(File::create(&path)?); fonts: Vec<(FontIndex, &'a str)>,
layouts: MultiLayout,
// Write the font mapping into the serialization file.
writeln!(file, "{}", fonts.len())?;
for (index, path) in fonts.iter() {
writeln!(file, "{} {} {}", index.id, index.variant, path)?;
} }
layouts.serialize(&mut file)?; let document = Document { fonts: fonts.into_iter().collect(), layouts};
file.flush()?;
drop(file); // Serialize the document into JSON.
let path = format!("tests/cache/{}.serde.json", name);
let file = BufWriter::new(File::create(&path)?);
serde_json::to_writer(file, &document)?;
// Render the layout into a PNG. // Render the layout into a PNG.
Command::new("python") Command::new("python")