Serialize layouts with serde 🔠
This commit is contained in:
parent
e63ce52ae0
commit
751812f451
@ -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"]
|
||||
|
10
src/func.rs
10
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![],
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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>)
|
||||
}
|
||||
}
|
||||
|
10
src/size.rs
10
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,
|
||||
|
@ -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
|
||||
/// ```
|
||||
|
@ -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.*
|
||||
]
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user