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"
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"]

View File

@ -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![],

View File

@ -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),
}
}

View File

@ -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]>;

View File

@ -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>)
}
}

View File

@ -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,

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
/// 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
/// ```

View File

@ -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.*
]

View File

@ -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()

View File

@ -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")