diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-02-04 21:36:29 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-02-04 21:36:29 +0100 |
| commit | 751812f45141a7b2022d0dba138457f3c21950b0 (patch) | |
| tree | 8f5125f5c475313c460f4a98ec174c11cb0e9c02 | |
| parent | e63ce52ae0d929506a1fa238477f039d14d53813 (diff) | |
Serialize layouts with serde 🔠
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | src/func.rs | 10 | ||||
| -rw-r--r-- | src/layout/actions.rs | 37 | ||||
| -rw-r--r-- | src/layout/mod.rs | 33 | ||||
| -rw-r--r-- | src/lib.rs | 7 | ||||
| -rw-r--r-- | src/size.rs | 10 | ||||
| -rw-r--r-- | src/syntax/func/values.rs | 6 | ||||
| -rw-r--r-- | tests/coma.typ | 2 | ||||
| -rw-r--r-- | tests/src/render.py | 59 | ||||
| -rw-r--r-- | tests/src/typeset.rs | 31 |
10 files changed, 95 insertions, 106 deletions
@@ -3,7 +3,6 @@ name = "typstc" version = "0.1.0" authors = ["Laurenz Mädje <laurmaedje@gmail.com>"] edition = "2018" -# build = "build.rs" [dependencies] toddle = { path = "../toddle", features = ["query"], default-features = false } @@ -13,10 +12,11 @@ smallvec = "1" unicode-xid = "0.2" async-trait = "0.1" serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", optional = true } futures-executor = { version = "0.3", optional = true } [features] -default = ["fs-provider", "futures-executor"] +default = ["fs-provider", "futures-executor", "serde_json"] fs-provider = ["toddle/fs-provider"] [[bin]] @@ -28,4 +28,4 @@ required-features = ["fs-provider", "futures-executor"] name = "typeset" path = "tests/src/typeset.rs" harness = false -required-features = ["fs-provider", "futures-executor"] +required-features = ["fs-provider", "futures-executor", "serde_json"] diff --git a/src/func.rs b/src/func.rs index 215de60f..88d08d2d 100644 --- a/src/func.rs +++ b/src/func.rs @@ -53,16 +53,16 @@ pub trait ParseFunc { /// body: Option<SyntaxModel>, /// } /// -/// parse(header, body, ctx, errors, decos) { -/// let body = body!(opt: body, ctx, errors, decos); -/// let hidden = header.args.pos.get::<bool>(errors) -/// .or_missing(errors, header.name.span, "hidden") +/// parse(header, body, ctx, f) { +/// let body = body!(opt: body, ctx, f); +/// let hidden = header.args.pos.get::<bool>(&mut f.errors) +/// .or_missing(&mut f.errors, header.name.span, "hidden") /// .unwrap_or(false); /// /// HiderFunc { body: if hidden { None } else { body } } /// } /// -/// layout(self, ctx, errors) { +/// layout(self, ctx, f) { /// match &self.body { /// Some(model) => vec![LayoutSyntaxModel(model)], /// None => vec![], diff --git a/src/layout/actions.rs b/src/layout/actions.rs index 314084e5..0abef5f9 100644 --- a/src/layout/actions.rs +++ b/src/layout/actions.rs @@ -1,11 +1,11 @@ //! Drawing and configuration actions composing layouts. -use std::io::{self, Write}; use std::fmt::{self, Debug, Formatter}; +use serde::ser::{Serialize, Serializer, SerializeTuple}; use toddle::query::FontIndex; use crate::size::{Size, Size2D}; -use super::{Layout, Serialize}; +use super::Layout; use self::LayoutAction::*; @@ -24,12 +24,33 @@ pub enum 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 { - MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()), - SetFont(i, s) => write!(f, "f {} {} {}", i.id, i.variant, s.to_pt()), - WriteText(s) => write!(f, "w {}", s), - DebugBox(s) => write!(f, "b {} {}", s.x.to_pt(), s.y.to_pt()), + LayoutAction::MoveAbsolute(pos) => { + let mut tup = serializer.serialize_tuple(2)?; + tup.serialize_element(&0u8)?; + 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 { MoveAbsolute(s) => write!(f, "move {} {}", s.x, s.y), 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), } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index b29d87e3..01d402db 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,8 +1,8 @@ //! Layouting types and engines. -use std::io::{self, Write}; use std::fmt::{self, Display, Formatter}; use smallvec::SmallVec; +use serde::Serialize; use toddle::query::FontIndex; use crate::size::{Size, Size2D, SizeBox}; @@ -32,11 +32,12 @@ pub mod prelude { pub type MultiLayout = Vec<Layout>; /// A finished box with content at fixed positions. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize)] pub struct Layout { /// The size of the box. pub dimensions: Size2D, /// How to align this layout in a parent container. + #[serde(skip)] pub alignment: LayoutAlignment, /// The actions composing this layout. 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 /// contains at most 2 spaces. pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>; @@ -24,7 +24,8 @@ use async_trait::async_trait; use smallvec::smallvec; 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::layout::MultiLayout; @@ -223,8 +224,8 @@ where P: FontProvider, P::Error: Debug + 'static { type Data = P::Data; type Error = Box<dyn Debug>; - async fn load(&self, index: usize, variant: usize) -> Result<Font<P::Data>, Self::Error> { - self.provider.load(index, variant).await + async fn load(&self, index: FontIndex) -> Result<Font<P::Data>, Self::Error> { + self.provider.load(index).await .map_err(|d| Box::new(d) as Box<dyn Debug>) } } diff --git a/src/size.rs b/src/size.rs index 79b98774..e97b9248 100644 --- a/src/size.rs +++ b/src/size.rs @@ -4,12 +4,14 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::iter::Sum; use std::ops::*; use std::str::FromStr; +use serde::Serialize; use crate::layout::prelude::*; /// A general spacing type. -#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] +#[derive(Default, Copy, Clone, PartialEq, PartialOrd, Serialize)] +#[serde(transparent)] pub struct Size { /// The size in typographic points (1/72 inches). pub points: f32, @@ -137,7 +139,7 @@ pub type FSize = ScaleSize; pub type PSize = ScaleSize; /// A value in two dimensions. -#[derive(Default, Copy, Clone, Eq, PartialEq)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Serialize)] pub struct Value2D<T> { /// The horizontal component. 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 /// to a maximal value. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize)] pub struct StretchValue<T> { /// The minimum this value can be stretched to. pub min: T, @@ -320,7 +322,7 @@ impl<T> StretchValue<T> { pub type StretchSize = StretchValue<Size>; /// A value in four dimensions. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize)] pub struct ValueBox<T> { /// The left extent. pub left: T, diff --git a/src/syntax/func/values.rs b/src/syntax/func/values.rs index d2b54a0b..8e0c24b4 100644 --- a/src/syntax/func/values.rs +++ b/src/syntax/func/values.rs @@ -100,9 +100,9 @@ impl From<StringLike> for String { } } -/// A value type that matches the string `"default"` or a value type `V` and -/// returns `Option::Some(V::Output)` for a value and `Option::None` for -/// `"default"`. +/// A value type that matches the identifier `default` or a value type `V` and +/// implements `Into<Option>` yielding `Option::Some(V)` for a value and +/// `Option::None` for `default`. /// /// # Example /// ``` diff --git a/tests/coma.typ b/tests/coma.typ index 14f639b5..c69cf8fd 100644 --- a/tests/coma.typ +++ b/tests/coma.typ @@ -13,7 +13,7 @@ [v: 6mm] [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] *Alle Antworten sind zu beweisen.* ] diff --git a/tests/src/render.py b/tests/src/render.py index ac94be20..30289c3b 100644 --- a/tests/src/render.py +++ b/tests/src/render.py @@ -1,8 +1,8 @@ import sys import os -import pathlib import math import numpy +import json from PIL import Image, ImageDraw, ImageFont @@ -14,11 +14,11 @@ def main(): assert len(sys.argv) == 2, 'usage: python render.py <name>' 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: - lines = [line[:-1] for line in file.readlines()] + data = json.load(file) - renderer = MultiboxRenderer(lines) + renderer = MultiboxRenderer(data) renderer.render() image = renderer.export() @@ -26,38 +26,30 @@ def main(): class MultiboxRenderer: - def __init__(self, lines): + def __init__(self, data): self.combined = None self.fonts = {} - font_count = int(lines[0]) - for i in range(font_count): - parts = lines[i + 1].split(' ', 2) - index = int(parts[0]), int(parts[1]) - path = parts[2] - self.fonts[index] = os.path.join(BASE, '../../../fonts', path) + for entry in data["fonts"]: + index = int(entry[0]["id"]), int(entry[0]["variant"]) + self.fonts[index] = os.path.join(BASE, '../../../fonts', entry[1]) - self.content = lines[font_count + 1:] + self.layouts = data["layouts"] def render(self): images = [] - layout_count = int(self.content[0]) - horizontal = math.floor(math.sqrt(layout_count)) + horizontal = math.floor(math.sqrt(len(self.layouts))) start = 1 - for _ in range(layout_count): - width, height = (float(s) for s in self.content[start].split()) - action_count = int(self.content[start + 1]) - start += 2 + for layout in self.layouts: + size = layout["dimensions"] - renderer = BoxRenderer(self.fonts, width, height) - for i in range(action_count): - command = self.content[start + i] - renderer.execute(command) + renderer = BoxRenderer(self.fonts, size["x"], size["y"]) + for action in layout["actions"]: + renderer.execute(action) images.append(renderer.export()) - start += action_count i = 0 x = 10 @@ -128,26 +120,25 @@ class BoxRenderer: def execute(self, command): cmd = command[0] - parts = command.split()[1:] + args = command[1:] - if cmd == 'm': - x, y = (pix(float(s)) for s in parts) - self.cursor = [x, y] + if cmd == 0: + self.cursor = [pix(args[0]["x"]), pix(args[0]["y"])] - elif cmd == 'f': - index = int(parts[0]), int(parts[1]) - size = pix(float(parts[2])) + elif cmd == 1: + index = int(args[0]["id"]), int(args[0]["variant"]) + size = pix(args[1]) self.font = ImageFont.truetype(self.fonts[index], size) - elif cmd == 'w': - text = command[2:] + elif cmd == 2: + text = args[0] width = self.draw.textsize(text, font=self.font)[0] self.draw.text(self.cursor, text, (0, 0, 0, 255), font=self.font) self.cursor[0] += width - elif cmd == 'b': + elif cmd == 3: 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] forbidden_colors = set() diff --git a/tests/src/typeset.rs b/tests/src/typeset.rs index efeb5aee..b21f6b3f 100644 --- a/tests/src/typeset.rs +++ b/tests/src/typeset.rs @@ -2,19 +2,21 @@ use std::collections::HashMap; use std::error::Error; use std::ffi::OsStr; 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::process::Command; use std::time::{Instant, Duration}; +use serde::Serialize; use futures_executor::block_on; use typstc::{Typesetter, DebugErrorProvider}; -use typstc::layout::{MultiLayout, Serialize}; +use typstc::layout::MultiLayout; use typstc::size::{Size, Size2D, ValueBox}; use typstc::style::{PageStyle, PaperClass}; 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>>; @@ -86,23 +88,22 @@ fn test(name: &str, src: &str) -> DynResult<()> { for layout in &layouts { for index in layout.find_used_fonts() { 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. - let path = format!("tests/cache/{}.serialized", name); - let mut file = BufWriter::new(File::create(&path)?); - - // 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)?; + #[derive(Serialize)] + struct Document<'a> { + fonts: Vec<(FontIndex, &'a str)>, + layouts: MultiLayout, } - layouts.serialize(&mut file)?; - file.flush()?; - drop(file); + let document = Document { fonts: fonts.into_iter().collect(), layouts}; + + // 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. Command::new("python") |
