diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-08-02 21:17:42 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-08-02 21:17:42 +0200 |
| commit | cbbc46215fe0a0ad8a50e991ec442890b8eadc0a (patch) | |
| tree | 2efbac21cec46787f1efe0a859564b9614eefa98 /tests/src | |
| parent | d5ff97f42ed1e682a66ea8d51e5f9ed1be547b9c (diff) | |
Layout elements and pure rust rendering 🥏
Diffstat (limited to 'tests/src')
| -rw-r--r-- | tests/src/render.py | 182 | ||||
| -rw-r--r-- | tests/src/typeset.rs | 206 |
2 files changed, 0 insertions, 388 deletions
diff --git a/tests/src/render.py b/tests/src/render.py deleted file mode 100644 index 80f165a1..00000000 --- a/tests/src/render.py +++ /dev/null @@ -1,182 +0,0 @@ -import sys -import os -import math -import numpy -import json -from PIL import Image, ImageDraw, ImageFont - - -BASE = os.path.dirname(__file__) -CACHE = os.path.join(BASE, '../cache/') - - -def main(): - assert len(sys.argv) == 2, 'usage: python render.py <name>' - name = sys.argv[1] - - filename = os.path.join(CACHE, f'{name}.serde.json') - with open(filename, encoding='utf-8') as file: - data = json.load(file) - - renderer = MultiboxRenderer(data) - renderer.render() - image = renderer.export() - - image.save(os.path.join(CACHE, f'{name}.png')) - - -class MultiboxRenderer: - def __init__(self, data): - self.combined = None - - self.faces = {} - for entry in data["faces"]: - face_id = int(entry[0]["index"]), int(entry[0]["variant"]) - self.faces[face_id] = os.path.join(BASE, '../../', entry[1]) - - self.layouts = data["layouts"] - - def render(self): - images = [] - - horizontal = math.floor(math.sqrt(len(self.layouts))) - start = 1 - - for layout in self.layouts: - size = layout["dimensions"] - - renderer = BoxRenderer(self.faces, size["x"], size["y"]) - for action in layout["actions"]: - renderer.execute(action) - - images.append(renderer.export()) - - i = 0 - x = 10 - y = 10 - width = 10 - row_height = 0 - - positions = [] - - for image in images: - positions.append((x, y)) - - x += 10 + image.width - row_height = max(row_height, image.height) - - i += 1 - if i >= horizontal: - width = max(width, x) - x = 10 - y += 10 + row_height - i = 0 - row_height = 0 - - height = y - if i != 0: - height += 10 + row_height - - self.combined = Image.new('RGBA', (width, height)) - - for (position, image) in zip(positions, images): - self.combined.paste(image, position) - - def export(self): - return self.combined - - -class BoxRenderer: - def __init__(self, faces, width, height, grid=False): - self.faces = faces - self.size = (pix(width), pix(height)) - - img = Image.new('RGBA', self.size, (255, 255, 255, 255)) - pixels = numpy.array(img) - - if grid: - for i in range(0, int(height)): - for j in range(0, int(width)): - if ((i // 2) % 2 == 0) == ((j // 2) % 2 == 0): - pixels[4*i:4*(i+1), 4*j:4*(j+1)] = (225, 225, 225, 255) - - self.img = Image.fromarray(pixels, 'RGBA') - self.draw = ImageDraw.Draw(self.img) - self.cursor = (0, 0) - - self.colors = [ - (176, 264, 158), - (274, 173, 207), - (158, 252, 264), - (285, 275, 187), - (132, 217, 136), - (236, 177, 246), - (174, 232, 279), - (285, 234, 158) - ] - - self.rects = [] - self.color_index = 0 - - def execute(self, command): - cmd = command[0] - args = command[1:] - - if cmd == 0: - self.cursor = [pix(args[0]["x"]), pix(args[0]["y"])] - - elif cmd == 1: - face_id = int(args[0]["index"]), int(args[0]["variant"]) - size = pix(args[1]) - self.font = ImageFont.truetype(self.faces[face_id], size) - - 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 == 3: - x, y = self.cursor - w, h = pix(args[0]["x"]), pix(args[0]["y"]) - rect = [x, y, x+w-1, y+h-1] - - forbidden_colors = set() - for other_rect, other_color in self.rects: - if rect == other_rect: - return - - if overlap(rect, other_rect) or overlap(other_rect, rect): - forbidden_colors.add(other_color) - - for color in self.colors[self.color_index:] + self.colors[:self.color_index]: - self.color_index = (self.color_index + 1) % len(self.colors) - if color not in forbidden_colors: - break - - overlay = Image.new('RGBA', self.size, (0, 0, 0, 0)) - draw = ImageDraw.Draw(overlay) - draw.rectangle(rect, fill=color + (255,)) - - self.img = Image.alpha_composite(self.img, overlay) - self.draw = ImageDraw.Draw(self.img) - - self.rects.append((rect, color)) - - else: - raise Exception('invalid command') - - def export(self): - return self.img - - -# the number of pixels per raw unit -def pix(raw): - return int(4 * raw) - -def overlap(a, b): - return (a[0] < b[2] and b[0] < a[2]) and (a[1] < b[3] and b[1] < a[3]) - - -if __name__ == '__main__': - main() diff --git a/tests/src/typeset.rs b/tests/src/typeset.rs deleted file mode 100644 index ccce8820..00000000 --- a/tests/src/typeset.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::cell::RefCell; -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; -use std::panic; -use std::process::Command; -use std::rc::Rc; -use std::time::{Instant, Duration}; - -use serde::Serialize; -use futures_executor::block_on; - -use typstc::Typesetter; -use typstc::font::DynProvider; -use typstc::geom::{Size, Value4}; -use typstc::layout::MultiLayout; -use typstc::length::Length; -use typstc::style::PageStyle; -use typstc::paper::PaperClass; -use typstc::export::pdf; -use fontdock::{FaceId, FontLoader}; -use fontdock::fs::{FsIndex, FsProvider}; - -type DynResult<T> = Result<T, Box<dyn Error>>; - -fn main() -> DynResult<()> { - let opts = Options::parse(); - - create_dir_all("tests/cache")?; - - let tests: Vec<_> = read_dir("tests/")?.collect(); - let mut filtered = Vec::new(); - - for entry in tests { - let path = entry?.path(); - if path.extension() != Some(OsStr::new("typ")) { - continue; - } - - let name = path - .file_stem().ok_or("expected file stem")? - .to_string_lossy() - .to_string(); - - if opts.matches(&name) { - let src = read_to_string(&path)?; - filtered.push((name, src)); - } - } - - let len = filtered.len(); - if len == 0 { - return Ok(()); - } else if len == 1 { - println!("Running test ..."); - } else { - println!("Running {} tests", len); - } - - let mut index = FsIndex::new(); - index.search_dir("fonts"); - - for (name, src) in filtered { - panic::catch_unwind(|| { - if let Err(e) = test(&name, &src, &index) { - println!("error: {:?}", e); - } - }).ok(); - } - - Ok(()) -} - -/// Create a _PDF_ and render with a name from the source code. -fn test(name: &str, src: &str, index: &FsIndex) -> DynResult<()> { - println!("Testing: {}.", name); - - let (descriptors, files) = index.clone().into_vecs(); - let provider = FsProvider::new(files.clone()); - let dynamic = Box::new(provider) as Box<DynProvider>; - let loader = FontLoader::new(dynamic, descriptors); - let loader = Rc::new(RefCell::new(loader)); - let mut typesetter = Typesetter::new(loader.clone()); - - typesetter.set_page_style(PageStyle { - class: PaperClass::Custom, - dimensions: Size::with_all(Length::pt(250.0).as_raw()), - margins: Value4::with_all(None), - }); - - let layouts = compile(&typesetter, src); - - // Write the PDF file. - let path = format!("tests/cache/{}.pdf", name); - let file = BufWriter::new(File::create(path)?); - pdf::export(&layouts, &loader, file)?; - - // Compute the font's paths. - let mut faces = HashMap::new(); - for layout in &layouts { - for id in layout.find_used_fonts() { - faces.entry(id).or_insert_with(|| { - files[id.index][id.variant].0.to_str().unwrap() - }); - } - } - - #[derive(Serialize)] - struct Document<'a> { - faces: Vec<(FaceId, &'a str)>, - layouts: MultiLayout, - } - - let document = Document { faces: faces.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") - .arg("tests/src/render.py") - .arg(name) - .spawn() - .expect("failed to run python renderer") - .wait() - .expect("command did not run"); - - std::fs::remove_file(path)?; - - Ok(()) -} - -/// Compile the source code with the typesetter. -fn compile(typesetter: &Typesetter, src: &str) -> MultiLayout { - if cfg!(debug_assertions) { - let typeset = block_on(typesetter.typeset(src)); - let diagnostics = typeset.feedback.diagnostics; - - if !diagnostics.is_empty() { - for diagnostic in diagnostics { - println!(" {:?} {:?}: {}", - diagnostic.v.level, - diagnostic.span, - diagnostic.v.message - ); - } - } - - typeset.output - } else { - fn measure<T>(f: impl FnOnce() -> T) -> (T, Duration) { - let start = Instant::now(); - let output = f(); - let duration = Instant::now() - start; - (output, duration) - }; - - let (_, cold) = measure(|| block_on(typesetter.typeset(src))); - let (model, parse) = measure(|| typesetter.parse(src).output); - let (layouts, layout) = measure(|| block_on(typesetter.layout(&model)).output); - - println!(" - cold start: {:?}", cold); - println!(" - warmed up: {:?}", parse + layout); - println!(" - parsing: {:?}", parse); - println!(" - layouting: {:?}", layout); - - layouts - } -} - -/// Command line options. -struct Options { - filter: Vec<String>, - perfect: bool, -} - -impl Options { - /// Parse the options from the environment arguments. - fn parse() -> Options { - let mut perfect = false; - let mut filter = Vec::new(); - - for arg in std::env::args().skip(1) { - match arg.as_str() { - "--nocapture" => {}, - "=" => perfect = true, - _ => filter.push(arg), - } - } - - Options { filter, perfect } - } - - /// Whether a given test should be executed. - fn matches(&self, name: &str) -> bool { - match self.perfect { - true => self.filter.iter().any(|p| name == p), - false => self.filter.is_empty() - || self.filter.iter().any(|p| name.contains(p)) - } - } -} |
