Render debug boxes 🧭
This commit is contained in:
parent
c0e4fd55e6
commit
6f22e4f13c
@ -14,3 +14,8 @@ unicode-xid = "0.1.0"
|
||||
[[bin]]
|
||||
name = "typstc"
|
||||
path = "src/bin/main.rs"
|
||||
|
||||
[[test]]
|
||||
name = "layouting"
|
||||
path = "tests/layouting.rs"
|
||||
harness = false
|
||||
|
27
src/doc.rs
27
src/doc.rs
@ -1,7 +1,7 @@
|
||||
//! Representation of typesetted documents.
|
||||
|
||||
use std::io::{self, Write};
|
||||
use crate::size::{Size, Size2D};
|
||||
use crate::layout::LayoutAction;
|
||||
use crate::size::Size;
|
||||
|
||||
|
||||
/// A complete typesetted document, which can be exported.
|
||||
@ -21,26 +21,3 @@ pub struct Page {
|
||||
/// Layouting actions specifying how to draw content on the page.
|
||||
pub actions: Vec<LayoutAction>,
|
||||
}
|
||||
|
||||
/// A layouting action.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LayoutAction {
|
||||
/// Move to an absolute position.
|
||||
MoveAbsolute(Size2D),
|
||||
/// Set the font by index and font size.
|
||||
SetFont(usize, f32),
|
||||
/// Write text starting at the current position.
|
||||
WriteText(String),
|
||||
}
|
||||
|
||||
impl LayoutAction {
|
||||
/// Serialize this layout action into a string representation.
|
||||
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||
use LayoutAction::*;
|
||||
match self {
|
||||
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
|
||||
SetFont(i, s) => write!(f, "f {} {}", i, s),
|
||||
WriteText(s) => write!(f, "w {}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ use toddle::font::OwnedFont;
|
||||
use toddle::query::SharedFontLoader;
|
||||
use toddle::Error as FontError;
|
||||
|
||||
use crate::doc::{Document, Page as DocPage, LayoutAction};
|
||||
use crate::layout::LayoutAction;
|
||||
use crate::doc::{Document, Page as DocPage};
|
||||
use crate::size::{Size, Size2D};
|
||||
|
||||
|
||||
@ -192,16 +193,16 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
}
|
||||
|
||||
// Flush the position.
|
||||
if let Some(pos) = next_pos {
|
||||
if let Some(pos) = next_pos.take() {
|
||||
let x = pos.x.to_pt();
|
||||
let y = (page.height - pos.y - Size::pt(active_font.1)).to_pt();
|
||||
text.tm(1.0, 0.0, 0.0, 1.0, x, y);
|
||||
next_pos = None;
|
||||
}
|
||||
|
||||
// Write the text.
|
||||
text.tj(self.fonts[active_font.0].encode_text(&string)?);
|
||||
},
|
||||
LayoutAction::DebugBox(_, _) => {},
|
||||
}
|
||||
}
|
||||
|
||||
|
131
src/layout/actions.rs
Normal file
131
src/layout/actions.rs
Normal file
@ -0,0 +1,131 @@
|
||||
//! Drawing and cofiguration actions used by layouts.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::io::{self, Write};
|
||||
use crate::size::Size2D;
|
||||
use super::boxed::BoxLayout;
|
||||
use LayoutAction::*;
|
||||
|
||||
|
||||
/// A layouting action.
|
||||
#[derive(Clone)]
|
||||
pub enum LayoutAction {
|
||||
/// Move to an absolute position.
|
||||
MoveAbsolute(Size2D),
|
||||
/// Set the font by index and font size.
|
||||
SetFont(usize, f32),
|
||||
/// Write text starting at the current position.
|
||||
WriteText(String),
|
||||
/// Visualize a box for debugging purposes.
|
||||
/// Arguments are position and size.
|
||||
DebugBox(Size2D, Size2D),
|
||||
}
|
||||
|
||||
impl LayoutAction {
|
||||
/// Serialize this layout action into a string representation.
|
||||
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||
use LayoutAction::*;
|
||||
match self {
|
||||
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
|
||||
SetFont(i, s) => write!(f, "f {} {}", i, s),
|
||||
WriteText(s) => write!(f, "w {}", s),
|
||||
DebugBox(p, s) => write!(f, "b {} {} {} {}",
|
||||
p.x.to_pt(), p.y.to_pt(), s.x.to_pt(), s.y.to_pt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LayoutAction {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
use LayoutAction::*;
|
||||
match self {
|
||||
MoveAbsolute(s) => write!(f, "move {} {}", s.x, s.y),
|
||||
SetFont(i, s) => write!(f, "font {} {}", i, s),
|
||||
WriteText(s) => write!(f, "write \"{}\"", s),
|
||||
DebugBox(p, s) => write!(f, "box {} {}", p, s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_display!(LayoutAction);
|
||||
|
||||
/// Unifies and otimizes lists of actions.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LayoutActionList {
|
||||
pub origin: Size2D,
|
||||
actions: Vec<LayoutAction>,
|
||||
active_font: (usize, f32),
|
||||
next_pos: Option<Size2D>,
|
||||
next_font: Option<(usize, f32)>,
|
||||
}
|
||||
|
||||
impl LayoutActionList {
|
||||
/// Create a new action list.
|
||||
pub fn new() -> LayoutActionList {
|
||||
LayoutActionList {
|
||||
actions: vec![],
|
||||
origin: Size2D::zero(),
|
||||
active_font: (std::usize::MAX, 0.0),
|
||||
next_pos: None,
|
||||
next_font: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an action to the list if it is not useless
|
||||
/// (like changing to a font that is already active).
|
||||
pub fn add(&mut self, action: LayoutAction) {
|
||||
match action {
|
||||
MoveAbsolute(pos) => self.next_pos = Some(self.origin + pos),
|
||||
DebugBox(pos, size) => self.actions.push(DebugBox(self.origin + pos, size)),
|
||||
|
||||
SetFont(index, size) if (index, size) != self.active_font => {
|
||||
self.next_font = Some((index, size));
|
||||
},
|
||||
|
||||
_ => {
|
||||
if let Some(target) = self.next_pos.take() {
|
||||
self.actions.push(MoveAbsolute(target));
|
||||
}
|
||||
if let Some((index, size)) = self.next_font.take() {
|
||||
self.actions.push(SetFont(index, size));
|
||||
}
|
||||
|
||||
self.actions.push(action);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a series of actions.
|
||||
pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item=LayoutAction> {
|
||||
for action in actions.into_iter() {
|
||||
self.add(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add all actions from a box layout at a position. A move to the position
|
||||
/// is generated and all moves inside the box layout are translated as necessary.
|
||||
pub fn add_box(&mut self, position: Size2D, layout: BoxLayout) {
|
||||
if let Some(target) = self.next_pos.take() {
|
||||
self.actions.push(MoveAbsolute(target));
|
||||
}
|
||||
|
||||
self.next_pos = Some(position);
|
||||
self.origin = position;
|
||||
|
||||
if layout.debug_render {
|
||||
self.actions.push(LayoutAction::DebugBox(position, layout.dimensions));
|
||||
}
|
||||
|
||||
self.extend(layout.actions);
|
||||
}
|
||||
|
||||
/// Whether there are any actions in this list.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.actions.is_empty()
|
||||
}
|
||||
|
||||
/// Return the list of actions as a vector.
|
||||
pub fn into_vec(self) -> Vec<LayoutAction> {
|
||||
self.actions
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
//! Block-style layouting of boxes.
|
||||
|
||||
use std::io::{self, Write};
|
||||
use crate::doc::{Document, Page, LayoutAction};
|
||||
use crate::doc::{Document, Page};
|
||||
use crate::size::{Size, Size2D};
|
||||
use super::{ActionList, LayoutSpace, Alignment, LayoutResult, LayoutError};
|
||||
use super::*;
|
||||
|
||||
|
||||
/// A box layout has a fixed width and height and composes of actions.
|
||||
@ -13,6 +13,8 @@ pub struct BoxLayout {
|
||||
pub dimensions: Size2D,
|
||||
/// The actions composing this layout.
|
||||
pub actions: Vec<LayoutAction>,
|
||||
/// Whether to debug-render this box.
|
||||
pub debug_render: bool,
|
||||
}
|
||||
|
||||
impl BoxLayout {
|
||||
@ -49,7 +51,7 @@ pub struct BoxContext {
|
||||
#[derive(Debug)]
|
||||
pub struct BoxLayouter {
|
||||
pub ctx: BoxContext,
|
||||
actions: ActionList,
|
||||
actions: LayoutActionList,
|
||||
dimensions: Size2D,
|
||||
usable: Size2D,
|
||||
cursor: Size2D,
|
||||
@ -59,9 +61,10 @@ impl BoxLayouter {
|
||||
/// Create a new box layouter.
|
||||
pub fn new(ctx: BoxContext) -> BoxLayouter {
|
||||
let space = ctx.space;
|
||||
|
||||
BoxLayouter {
|
||||
ctx,
|
||||
actions: ActionList::new(),
|
||||
actions: LayoutActionList::new(),
|
||||
dimensions: match ctx.space.alignment {
|
||||
Alignment::Left => Size2D::zero(),
|
||||
Alignment::Right => Size2D::with_x(space.usable().x),
|
||||
@ -109,7 +112,7 @@ impl BoxLayouter {
|
||||
|
||||
/// Add a sublayout at an absolute position.
|
||||
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
|
||||
self.actions.add_box_absolute(position, layout);
|
||||
self.actions.add_box(position, layout);
|
||||
}
|
||||
|
||||
/// Add some space in between two boxes.
|
||||
@ -148,6 +151,7 @@ impl BoxLayouter {
|
||||
self.ctx.space.dimensions
|
||||
},
|
||||
actions: self.actions.into_vec(),
|
||||
debug_render: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Flexible and lazy layouting of boxes.
|
||||
|
||||
use crate::size::{Size, Size2D};
|
||||
use super::{BoxLayout, ActionList, LayoutSpace, Alignment, LayoutResult, LayoutError};
|
||||
use super::*;
|
||||
|
||||
|
||||
/// A flex layout consists of a yet unarranged list of boxes.
|
||||
@ -76,7 +76,7 @@ pub struct FlexContext {
|
||||
struct FlexFinisher {
|
||||
units: Vec<FlexUnit>,
|
||||
ctx: FlexContext,
|
||||
actions: ActionList,
|
||||
actions: LayoutActionList,
|
||||
dimensions: Size2D,
|
||||
usable: Size2D,
|
||||
cursor: Size2D,
|
||||
@ -92,7 +92,7 @@ impl FlexFinisher {
|
||||
FlexFinisher {
|
||||
units: layout.units,
|
||||
ctx,
|
||||
actions: ActionList::new(),
|
||||
actions: LayoutActionList::new(),
|
||||
dimensions: match ctx.space.alignment {
|
||||
Alignment::Left => Size2D::zero(),
|
||||
Alignment::Right => Size2D::with_x(space.usable().x),
|
||||
@ -129,6 +129,7 @@ impl FlexFinisher {
|
||||
self.ctx.space.dimensions
|
||||
},
|
||||
actions: self.actions.into_vec(),
|
||||
debug_render: true,
|
||||
})
|
||||
}
|
||||
|
||||
@ -187,7 +188,7 @@ impl FlexFinisher {
|
||||
},
|
||||
};
|
||||
|
||||
self.actions.add_box_absolute(position, layout);
|
||||
self.actions.add_box(position, layout);
|
||||
}
|
||||
|
||||
// Stretch the dimensions to at least the line width.
|
||||
|
@ -6,7 +6,6 @@ use std::mem;
|
||||
use toddle::query::{SharedFontLoader, FontClass};
|
||||
use toddle::Error as FontError;
|
||||
|
||||
use crate::doc::LayoutAction;
|
||||
use crate::size::{Size, Size2D, SizeBox};
|
||||
use crate::syntax::{SyntaxTree, Node, FuncCall};
|
||||
use crate::style::TextStyle;
|
||||
@ -18,6 +17,9 @@ use self::text::TextContext;
|
||||
pub mod text;
|
||||
pub mod boxed;
|
||||
pub mod flex;
|
||||
mod actions;
|
||||
|
||||
pub use actions::{LayoutAction, LayoutActionList};
|
||||
|
||||
|
||||
/// A collection of layouted content.
|
||||
@ -159,6 +161,10 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
||||
|
||||
/// Finish the current flex run and return the resulting box.
|
||||
fn layout_flex(&mut self) -> LayoutResult<()> {
|
||||
if self.flex_layout.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut layout = FlexLayout::new();
|
||||
mem::swap(&mut layout, &mut self.flex_layout);
|
||||
|
||||
@ -204,64 +210,6 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Manipulates and optimizes a list of actions.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ActionList {
|
||||
pub origin: Size2D,
|
||||
actions: Vec<LayoutAction>,
|
||||
active_font: (usize, f32),
|
||||
}
|
||||
|
||||
impl ActionList {
|
||||
/// Create a new action list.
|
||||
pub fn new() -> ActionList {
|
||||
ActionList {
|
||||
actions: vec![],
|
||||
origin: Size2D::zero(),
|
||||
active_font: (std::usize::MAX, 0.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an action to the list if it is not useless
|
||||
/// (like changing to a font that is already active).
|
||||
pub fn add(&mut self, action: LayoutAction) {
|
||||
use LayoutAction::*;
|
||||
match action {
|
||||
MoveAbsolute(pos) => self.actions.push(MoveAbsolute(self.origin + pos)),
|
||||
SetFont(index, size) => if (index, size) != self.active_font {
|
||||
self.active_font = (index, size);
|
||||
self.actions.push(action);
|
||||
},
|
||||
_ => self.actions.push(action),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a series of actions.
|
||||
pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item=LayoutAction> {
|
||||
for action in actions.into_iter() {
|
||||
self.add(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add all actions from a box layout at a position. A move to the position
|
||||
/// is generated and all moves inside the box layout are translated as necessary.
|
||||
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
|
||||
self.actions.push(LayoutAction::MoveAbsolute(position));
|
||||
self.origin = position;
|
||||
self.extend(layout.actions);
|
||||
}
|
||||
|
||||
/// Whether there are any actions in this list.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.actions.is_empty()
|
||||
}
|
||||
|
||||
/// Return the list of actions as a vector.
|
||||
pub fn into_vec(self) -> Vec<LayoutAction> {
|
||||
self.actions
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type for layouting.
|
||||
pub enum LayoutError {
|
||||
/// There is not enough space to add an item.
|
||||
|
@ -3,7 +3,6 @@
|
||||
use toddle::query::{FontQuery, SharedFontLoader};
|
||||
use toddle::tables::{Header, CharMap, HorizontalMetrics};
|
||||
|
||||
use crate::doc::LayoutAction;
|
||||
use crate::size::{Size, Size2D};
|
||||
use super::*;
|
||||
|
||||
@ -92,5 +91,6 @@ pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
|
||||
Ok(BoxLayout {
|
||||
dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)),
|
||||
actions,
|
||||
debug_render: false,
|
||||
})
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ use crate::style::{PageStyle, TextStyle};
|
||||
use crate::syntax::SyntaxTree;
|
||||
|
||||
#[macro_use]
|
||||
mod error;
|
||||
mod macros;
|
||||
pub mod doc;
|
||||
pub mod export;
|
||||
pub mod func;
|
||||
|
@ -24,11 +24,7 @@ macro_rules! error_type {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for $err {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
debug_display!($err);
|
||||
|
||||
impl std::error::Error for $err {
|
||||
// The source method is only generated if an implementation was given.
|
||||
@ -46,3 +42,14 @@ macro_rules! error_type {
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
/// Create a `Debug` implementation from a display implementation.
|
||||
macro_rules! debug_display {
|
||||
($type:ident) => {
|
||||
impl std::fmt::Debug for $type {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
20
src/size.rs
20
src/size.rs
@ -1,7 +1,7 @@
|
||||
//! General spacing types.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Display, Debug, Formatter};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::iter::Sum;
|
||||
use std::ops::*;
|
||||
use std::str::FromStr;
|
||||
@ -133,11 +133,7 @@ impl Display for Size {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Size {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
debug_display!(Size);
|
||||
|
||||
/// An error which can be returned when parsing a size.
|
||||
pub struct ParseSizeError;
|
||||
@ -254,11 +250,7 @@ impl Display for Size2D {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Size2D {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
debug_display!(Size2D);
|
||||
|
||||
impl Neg for Size2D {
|
||||
type Output = Size2D;
|
||||
@ -349,8 +341,4 @@ impl Display for SizeBox {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SizeBox {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
debug_display!(SizeBox);
|
||||
|
@ -3,15 +3,25 @@ use std::io::{Write, Read, BufWriter};
|
||||
use std::process::Command;
|
||||
|
||||
use typst::Typesetter;
|
||||
use typst::layout::LayoutAction;
|
||||
use typst::toddle::query::FileSystemFontProvider;
|
||||
use typst::export::pdf::PdfExporter;
|
||||
use typst::doc::LayoutAction;
|
||||
|
||||
const CACHE_DIR: &str = "test-cache";
|
||||
|
||||
|
||||
#[test]
|
||||
fn layouting() {
|
||||
fn main() {
|
||||
let mut filter = Vec::new();
|
||||
for arg in std::env::args().skip(1) {
|
||||
if arg.as_str() != "--nocapture" {
|
||||
filter.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
if !filter.is_empty() {
|
||||
println!("Using filter: {:?}", filter);
|
||||
}
|
||||
|
||||
fs::create_dir_all(format!("{}/serialized", CACHE_DIR)).unwrap();
|
||||
fs::create_dir_all(format!("{}/rendered", CACHE_DIR)).unwrap();
|
||||
fs::create_dir_all(format!("{}/pdf", CACHE_DIR)).unwrap();
|
||||
@ -19,20 +29,24 @@ fn layouting() {
|
||||
for entry in fs::read_dir("tests/layouts/").unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
|
||||
let mut file = File::open(&path).unwrap();
|
||||
let mut src = String::new();
|
||||
file.read_to_string(&mut src).unwrap();
|
||||
|
||||
let name = path
|
||||
.file_stem().unwrap()
|
||||
.to_str().unwrap();
|
||||
|
||||
test(name, &src);
|
||||
if filter.is_empty() || filter.iter().any(|pattern| name.contains(pattern)) {
|
||||
let mut file = File::open(&path).unwrap();
|
||||
let mut src = String::new();
|
||||
file.read_to_string(&mut src).unwrap();
|
||||
|
||||
test(name, &src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a _PDF_ with a name from the source code.
|
||||
fn test(name: &str, src: &str) {
|
||||
println!("Testing: {}", name);
|
||||
|
||||
let mut typesetter = Typesetter::new();
|
||||
let provider = FileSystemFontProvider::from_listing("fonts/fonts.toml").unwrap();
|
||||
typesetter.add_font_provider(provider.clone());
|
||||
|
@ -9,5 +9,5 @@ _Emoji:_ Hello World! 🌍
|
||||
_Styles:_ This is made *bold*, that _italic_ and this one `monospace` using the
|
||||
built-in syntax!
|
||||
|
||||
_Styles with functions:_ This [bold][word] is made bold and [italic][that] is italic
|
||||
using the standard library functions [mono][bold] and `italic`!
|
||||
_Styles with functions:_ This [bold][word] is made bold and [italic][that] italic
|
||||
using the standard library functions `bold` and `italic`!
|
||||
|
@ -37,10 +37,25 @@ def main():
|
||||
class Renderer:
|
||||
def __init__(self, fonts, width, height):
|
||||
self.fonts = fonts
|
||||
self.img = Image.new("RGBA", (pix(width), pix(height)), (255, 255, 255))
|
||||
self.size = (pix(width), pix(height))
|
||||
self.img = Image.new("RGBA", self.size, (255, 255, 255, 255))
|
||||
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]
|
||||
parts = command.split()[1:]
|
||||
@ -56,7 +71,33 @@ class Renderer:
|
||||
|
||||
elif cmd == 'w':
|
||||
text = command[2:]
|
||||
self.draw.text(self.cursor, text, (0, 0, 0), font=self.font)
|
||||
self.draw.text(self.cursor, text, (0, 0, 0, 255), font=self.font)
|
||||
|
||||
elif cmd == 'b':
|
||||
x, y, w, h = (pix(float(s)) for s in parts)
|
||||
rect = [x, y, x+w, y+h]
|
||||
|
||||
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")
|
||||
@ -68,6 +109,9 @@ class Renderer:
|
||||
def pix(points):
|
||||
return int(2 * points)
|
||||
|
||||
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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user