summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-02-04 21:36:29 +0100
committerLaurenz <laurmaedje@gmail.com>2020-02-04 21:36:29 +0100
commit751812f45141a7b2022d0dba138457f3c21950b0 (patch)
tree8f5125f5c475313c460f4a98ec174c11cb0e9c02
parente63ce52ae0d929506a1fa238477f039d14d53813 (diff)
Serialize layouts with serde 🔠
-rw-r--r--Cargo.toml6
-rw-r--r--src/func.rs10
-rw-r--r--src/layout/actions.rs37
-rw-r--r--src/layout/mod.rs33
-rw-r--r--src/lib.rs7
-rw-r--r--src/size.rs10
-rw-r--r--src/syntax/func/values.rs6
-rw-r--r--tests/coma.typ2
-rw-r--r--tests/src/render.py59
-rw-r--r--tests/src/typeset.rs31
10 files changed, 95 insertions, 106 deletions
diff --git a/Cargo.toml b/Cargo.toml
index e2143840..d4552e3a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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]>;
diff --git a/src/lib.rs b/src/lib.rs
index b74837fe..7136b2bd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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")