all: can render basic things
This commit is contained in:
@@ -9,3 +9,4 @@ path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
getopts = "0.2.3"
|
||||
image = "0.14"
|
||||
|
@@ -3,5 +3,4 @@
|
||||
A browser engine written in Rust. I heard that’s what the cool kids do these
|
||||
days.
|
||||
|
||||
Current status: can read HTML/CSS, convert to internal representation, and back
|
||||
to (unformatted) HTML/CSS.
|
||||
It can currently only render a tiny subset of HTML and CSS to PNG.
|
||||
|
30
examples/test.html
Normal file
30
examples/test.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
* { display: block; padding: 12px; }
|
||||
.a { background: #ff0000; }
|
||||
.b { background: #ffa500; }
|
||||
.c { background: #ffff00; }
|
||||
.d { background: #008000; }
|
||||
.e { background: #0000ff; }
|
||||
.f { background: #4b0082; }
|
||||
.g { background: #800080; border-color: #808080; border-width: 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="a">
|
||||
<div class="b">
|
||||
<div class="c">
|
||||
<div class="d">
|
||||
<div class="e">
|
||||
<div class="f">
|
||||
<div class="g">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
20
src/css.rs
20
src/css.rs
@@ -1,17 +1,20 @@
|
||||
use std;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SimpleSelector {
|
||||
pub tag_name: Option<String>,
|
||||
pub id: Option<String>,
|
||||
pub class: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ChainSelector {
|
||||
tag_name: Option<Vec<String>>,
|
||||
id: Option<Vec<String>>,
|
||||
class: Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Selector {
|
||||
Simple(SimpleSelector),
|
||||
//Chain(ChainSelector),
|
||||
@@ -43,7 +46,7 @@ impl std::fmt::Display for Selector {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Unit {
|
||||
Px,
|
||||
Em,
|
||||
@@ -66,15 +69,15 @@ impl std::fmt::Display for Unit {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub struct Color {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
a: u8,
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
pub a: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Value {
|
||||
Keyword(String),
|
||||
Length(f32, Unit),
|
||||
@@ -101,6 +104,7 @@ impl std::fmt::Display for Value {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Declaration {
|
||||
pub name: String,
|
||||
pub value: Value,
|
||||
@@ -112,6 +116,7 @@ impl std::fmt::Display for Declaration {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Rule {
|
||||
pub selectors: Vec<Selector>,
|
||||
pub declarations: Vec<Declaration>,
|
||||
@@ -136,6 +141,7 @@ impl std::fmt::Display for Rule {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Stylesheet {
|
||||
pub rules: Vec<Rule>,
|
||||
}
|
||||
|
133
src/display.rs
Normal file
133
src/display.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use css;
|
||||
use layout;
|
||||
|
||||
enum Command {
|
||||
SolidR(css::Color, layout::Rect),
|
||||
// Text(String, layout::Rect),
|
||||
// TODO: more commands here
|
||||
}
|
||||
|
||||
type DisplayList = Vec<Command>;
|
||||
|
||||
fn build_display_list(root: &layout::LayoutBox) -> DisplayList {
|
||||
let mut list = Vec::new();
|
||||
render_layout_box(&mut list, root);
|
||||
return list;
|
||||
}
|
||||
|
||||
fn render_layout_box(list: &mut DisplayList, lbox: &layout::LayoutBox) {
|
||||
render_background(list, lbox);
|
||||
render_borders(list, lbox);
|
||||
|
||||
for child in &lbox.children {
|
||||
render_layout_box(list, child);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_background(list: &mut DisplayList, lbox: &layout::LayoutBox) {
|
||||
get_color(lbox, "background").map(|color|
|
||||
list.push(Command::SolidR(color, lbox.dimensions.border_box())));
|
||||
}
|
||||
|
||||
fn get_color(lbox: &layout::LayoutBox, name: &str) -> Option<css::Color> {
|
||||
match lbox.btype {
|
||||
layout::BoxType::Block(style) | layout::BoxType::Inline(style) =>
|
||||
match style.value(name) {
|
||||
Some(css::Value::ColorValue(color)) => Some(color),
|
||||
_ => None
|
||||
},
|
||||
layout::BoxType::Anonymous => None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_borders(list: &mut DisplayList, lbox: &layout::LayoutBox) {
|
||||
let color = match get_color(lbox, "border-color") {
|
||||
Some(color) => color,
|
||||
_ => return
|
||||
};
|
||||
|
||||
let d = &lbox.dimensions;
|
||||
let border_box = d.border_box();
|
||||
|
||||
list.push(Command::SolidR(color,layout::Rect {
|
||||
x: border_box.x,
|
||||
y: border_box.y,
|
||||
width: d.border.left,
|
||||
height: border_box.height,
|
||||
}));
|
||||
|
||||
list.push(Command::SolidR(color,layout::Rect {
|
||||
x: border_box.x + border_box.width - d.border.right,
|
||||
y: border_box.y,
|
||||
width: d.border.right,
|
||||
height: border_box.height,
|
||||
}));
|
||||
|
||||
list.push(Command::SolidR(color,layout::Rect {
|
||||
x: border_box.x,
|
||||
y: border_box.y,
|
||||
width: border_box.width,
|
||||
height: d.border.top,
|
||||
}));
|
||||
|
||||
list.push(Command::SolidR(color,layout::Rect {
|
||||
x: border_box.x,
|
||||
y: border_box.y + border_box.height - d.border.bottom,
|
||||
width: border_box.width,
|
||||
height: d.border.bottom,
|
||||
}));
|
||||
}
|
||||
|
||||
pub struct Canvas {
|
||||
pub pixels: Vec<css::Color>,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
fn new(width: usize, height: usize) -> Canvas {
|
||||
let white = css::Color { r: 255, g: 255, b: 255, a: 255 };
|
||||
return Canvas {
|
||||
pixels: vec![white; width * height],
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_item(&mut self, item: &Command) {
|
||||
match item {
|
||||
&Command::SolidR(color, rect) => {
|
||||
let x0 = rect.x.clamp(0.0, self.width as f32) as usize;
|
||||
let y0 = rect.y.clamp(0.0, self.height as f32) as usize;
|
||||
let x1 = (rect.x + rect.width).clamp(0.0, self.width as f32) as usize;
|
||||
let y1 = (rect.y + rect.height).clamp(0.0, self.height as f32) as usize;
|
||||
|
||||
for y in y0 .. y1 {
|
||||
for x in x0 .. x1 {
|
||||
// TODO: alpha compositing with existing pixel
|
||||
self.pixels[x + y * self.width] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint(root: &layout::LayoutBox, bounds: layout::Rect) -> Canvas {
|
||||
let display_list = build_display_list(root);
|
||||
let mut canvas = Canvas::new(bounds.width as usize, bounds.height as usize);
|
||||
for item in display_list {
|
||||
canvas.paint_item(&item);
|
||||
}
|
||||
return canvas;
|
||||
}
|
||||
|
||||
trait Clamp {
|
||||
fn clamp(self, lower: Self, upper: Self) -> Self;
|
||||
}
|
||||
|
||||
impl Clamp for f32 {
|
||||
fn clamp(self, lower: f32, upper: f32) -> f32 {
|
||||
self.max(lower).min(upper)
|
||||
}
|
||||
}
|
41
src/dom.rs
41
src/dom.rs
@@ -3,10 +3,12 @@ use std;
|
||||
|
||||
use css;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Attr {
|
||||
attrs: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EData {
|
||||
pub name: String,
|
||||
pub attr: Attr,
|
||||
@@ -25,11 +27,13 @@ impl EData {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SData {
|
||||
attr: Attr,
|
||||
content: css::Stylesheet,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum NType {
|
||||
Text(String),
|
||||
Comment(String),
|
||||
@@ -37,6 +41,7 @@ pub enum NType {
|
||||
Stylesheet(SData)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Node {
|
||||
pub children: Vec<Node>,
|
||||
pub ntype: NType,
|
||||
@@ -101,3 +106,39 @@ impl std::fmt::Display for Attr {
|
||||
return Result::Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn find_node(name: String, node: &Node) -> Option<Node> {
|
||||
match node.ntype {
|
||||
NType::Text(_) | NType::Comment(_) => None,
|
||||
NType::Element(ref d) => {
|
||||
if d.name == name {
|
||||
return Some(node.clone());
|
||||
}
|
||||
for ref child in &node.children {
|
||||
if let Some(n) = find_node(name.clone(), child) {
|
||||
return Some(n);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
},
|
||||
NType::Stylesheet(_) =>
|
||||
if name == "style" {
|
||||
return Some(node.clone());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_style(node: &Node) -> Option<css::Stylesheet> {
|
||||
match find_node("style".to_string(), node) {
|
||||
Some(n) => {
|
||||
match n.ntype {
|
||||
NType::Stylesheet(d) => Some(d.content),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
161
src/layout.rs
161
src/layout.rs
@@ -1,8 +1,12 @@
|
||||
struct Rect {
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
use css;
|
||||
use styling;
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Rect {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
@@ -16,46 +20,48 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
struct Edge {
|
||||
left: f32,
|
||||
right: f32,
|
||||
top: f32,
|
||||
bottom: f32,
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Edge {
|
||||
pub left: f32,
|
||||
pub right: f32,
|
||||
pub top: f32,
|
||||
pub bottom: f32,
|
||||
}
|
||||
|
||||
impl Dimensions {
|
||||
fn padding_box(self) -> Rect {
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Dim {
|
||||
pub content: Rect,
|
||||
|
||||
pub padding: Edge,
|
||||
pub border: Edge,
|
||||
pub margin: Edge,
|
||||
}
|
||||
|
||||
impl Dim {
|
||||
pub fn padding_box(self) -> Rect {
|
||||
self.content.expanded_by(self.padding)
|
||||
}
|
||||
fn border_box(self) -> Rect {
|
||||
pub fn border_box(self) -> Rect {
|
||||
self.padding_box().expanded_by(self.border)
|
||||
}
|
||||
fn margin_box(self) -> Rect {
|
||||
pub fn margin_box(self) -> Rect {
|
||||
self.border_box().expanded_by(self.margin)
|
||||
}
|
||||
}
|
||||
|
||||
struct Dim {
|
||||
content: Rect,
|
||||
|
||||
padding: Edge,
|
||||
border: Edge,
|
||||
margin: Edge,
|
||||
}
|
||||
|
||||
enum BoxType<'a> {
|
||||
Block(&'a StyledNode<'a>),
|
||||
Inline(&'a StyledNode<'a>),
|
||||
pub enum BoxType<'a> {
|
||||
Block(&'a styling::Node<'a>),
|
||||
Inline(&'a styling::Node<'a>),
|
||||
Anonymous
|
||||
}
|
||||
|
||||
struct LayoutBox<'a> {
|
||||
dimensions: Dim,
|
||||
btype: BoxType<'a>,
|
||||
children: Vec<LayoutBox<'a>>,
|
||||
pub struct LayoutBox<'a> {
|
||||
pub dimensions: Dim,
|
||||
pub btype: BoxType<'a>,
|
||||
pub children: Vec<LayoutBox<'a>>,
|
||||
}
|
||||
|
||||
impl LayoutBox {
|
||||
impl<'a> LayoutBox<'a> {
|
||||
fn new(btype: BoxType) -> LayoutBox {
|
||||
LayoutBox {
|
||||
btype: btype,
|
||||
@@ -64,13 +70,20 @@ impl LayoutBox {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inline_container(&mut self) -> &mut LayoutBox {
|
||||
fn get_style_node(&self) -> &'a styling::Node<'a> {
|
||||
match self.btype {
|
||||
Inline(_) | Anonymous => self,
|
||||
Block(_) => {
|
||||
BoxType::Block(node) | BoxType::Inline(node) => node,
|
||||
BoxType::Anonymous => panic!("Anonymous block box has no style node")
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inline_container(&mut self) -> &mut LayoutBox<'a> {
|
||||
match self.btype {
|
||||
BoxType::Inline(_) | BoxType::Anonymous => self,
|
||||
BoxType::Block(_) => {
|
||||
match self.children.last() {
|
||||
Some(&LayoutBox { btype: Anonymous,..}) => {}
|
||||
_ => self.children.push(LayoutBox::new(Anonymous))
|
||||
Some(&LayoutBox { btype: BoxType::Anonymous,..}) => {}
|
||||
_ => self.children.push(LayoutBox::new(BoxType::Anonymous))
|
||||
}
|
||||
self.children.last_mut().unwrap()
|
||||
}
|
||||
@@ -79,9 +92,9 @@ impl LayoutBox {
|
||||
|
||||
fn layout(&mut self, containing: Dim) {
|
||||
match self.btype {
|
||||
Block(_) => self.layout_block(containing),
|
||||
Inline(_) => {} // TODO
|
||||
Anonymous => {} // TODO
|
||||
BoxType::Block(_) => self.layout_block(containing),
|
||||
BoxType::Inline(_) => {} // TODO
|
||||
BoxType::Anonymous => {} // TODO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,10 +111,10 @@ impl LayoutBox {
|
||||
fn calculate_block_width(&mut self, containing: Dim) {
|
||||
let style = self.get_style_node();
|
||||
|
||||
let auto = Keyword("auto".to_string());
|
||||
let auto = css::Value::Keyword("auto".to_string());
|
||||
let mut width = style.value("width").unwrap_or(auto.clone());
|
||||
|
||||
let zero = Length(0.0, Px);
|
||||
let zero = css::Value::Length(0.0, css::Unit::Px);
|
||||
|
||||
let mut margin_left = style.lookup("margin-left", "margin", &zero);
|
||||
let mut margin_right = style.lookup("margin-right", "margin", &zero);
|
||||
@@ -112,16 +125,17 @@ impl LayoutBox {
|
||||
let padding_left = style.lookup("padding-left", "padding", &zero);
|
||||
let padding_right = style.lookup("padding-right", "padding", &zero);
|
||||
|
||||
let total = [&margin_left, &margin_right, &border_left, &border_right,
|
||||
&padding_left, &padding_right, &width
|
||||
].iter().map(|v| v.to_px()).sum();
|
||||
let total = sum([&margin_left, &margin_right, &border_left,
|
||||
&border_right, &padding_left, &padding_right,
|
||||
&width
|
||||
].iter().map(|v| v.to_px()));
|
||||
|
||||
if width != auto && total > containing.content.width {
|
||||
if margin_left == auto {
|
||||
margin_left = Length(0.0, Px);
|
||||
margin_left = css::Value::Length(0.0, css::Unit::Px);
|
||||
}
|
||||
if margin_right == auto {
|
||||
margin_right = Length(0.0, Px);
|
||||
margin_right = css::Value::Length(0.0, css::Unit::Px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,27 +143,27 @@ impl LayoutBox {
|
||||
|
||||
match (width == auto, margin_left == auto, margin_right == auto) {
|
||||
(false, false, false) => {
|
||||
margin_right = Length(margin_right.to_px() + underflow, Px);
|
||||
margin_right = css::Value::Length(margin_right.to_px() + underflow, css::Unit::Px);
|
||||
}
|
||||
|
||||
(false, false, true) => { margin_right = Length(underflow, Px); }
|
||||
(false, true, false) => { margin_left = Length(underflow, Px); }
|
||||
(false, false, true) => { margin_right = css::Value::Length(underflow, css::Unit::Px); }
|
||||
(false, true, false) => { margin_left = css::Value::Length(underflow, css::Unit::Px); }
|
||||
|
||||
(true, _, _) => {
|
||||
if margin_left == auto { margin_left = Length(0.0, Px); }
|
||||
if margin_right == auto { margin_right = Length(0.0, Px); }
|
||||
if margin_left == auto { margin_left = css::Value::Length(0.0, css::Unit::Px); }
|
||||
if margin_right == auto { margin_right = css::Value::Length(0.0, css::Unit::Px); }
|
||||
|
||||
if underflow >= 0.0 {
|
||||
width = Length(underflow, Px);
|
||||
width = css::Value::Length(underflow, css::Unit::Px);
|
||||
} else {
|
||||
width = Length(0.0, Px);
|
||||
margin_right = Length(margin_right.to_px() + underflow, Px);
|
||||
width = css::Value::Length(0.0, css::Unit::Px);
|
||||
margin_right = css::Value::Length(margin_right.to_px() + underflow, css::Unit::Px);
|
||||
}
|
||||
}
|
||||
|
||||
(false, true, true) => {
|
||||
margin_left = Length(underflow / 2.0, Px);
|
||||
margin_right = Length(underflow / 2.0, Px);
|
||||
margin_left = css::Value::Length(underflow / 2.0, css::Unit::Px);
|
||||
margin_right = css::Value::Length(underflow / 2.0, css::Unit::Px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +184,7 @@ impl LayoutBox {
|
||||
let style = self.get_style_node();
|
||||
let d = &mut self.dimensions;
|
||||
|
||||
let zero = Length(0.0, Px);
|
||||
let zero = css::Value::Length(0.0, css::Unit::Px);
|
||||
|
||||
d.margin.top = style.lookup("margin-top", "margin", &zero).to_px();
|
||||
d.margin.bottom = style.lookup("margin-bottom", "margin", &zero).to_px();
|
||||
@@ -188,6 +202,14 @@ impl LayoutBox {
|
||||
d.margin.top + d.border.top + d.padding.top;
|
||||
}
|
||||
|
||||
fn calculate_block_height(&mut self) {
|
||||
// If the height is set to an explicit length, use that exact length.
|
||||
// Otherwise, just keep the value set by `layout_block_children`.
|
||||
if let Some(css::Value::Length(h, css::Unit::Px)) = self.get_style_node().value("height") {
|
||||
self.dimensions.content.height = h;
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_block_children(&mut self) {
|
||||
let d = &mut self.dimensions;
|
||||
for child in &mut self.children {
|
||||
@@ -197,19 +219,32 @@ impl LayoutBox {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_layout_tree<'a>(style_node: &'a StyledNode<'a>) -> LayoutBox<'a> {
|
||||
pub fn layout_tree<'a>(node: &'a styling::Node<'a>, mut containing: Dim) -> LayoutBox<'a> {
|
||||
containing.content.height = 0.0;
|
||||
|
||||
let mut root_box = build_layout_tree(node);
|
||||
root_box.layout(containing);
|
||||
root_box
|
||||
}
|
||||
|
||||
|
||||
fn build_layout_tree<'a>(style_node: &'a styling::Node<'a>) -> LayoutBox<'a> {
|
||||
let mut root = LayoutBox::new(match style_node.display() {
|
||||
css::Display::Block => Block(style_node),
|
||||
css::Display::Inline => Inline(style_node),
|
||||
css::Display::DisplayNone => panic!("Root node has display: none.")
|
||||
styling::Display::Block => BoxType::Block(style_node),
|
||||
styling::Display::Inline => BoxType::Inline(style_node),
|
||||
styling::Display::None => panic!("Root node has display: none.")
|
||||
});
|
||||
|
||||
for child in &style_node.children {
|
||||
match child.display() {
|
||||
css::Display::Block => root.children.push(build_layout_tree(child)),
|
||||
css::Display::Inline => root.get_inline_container().children.push(build_layout_tree(child)),
|
||||
css::Display::DisplayNone => {}
|
||||
styling::Display::Block => root.children.push(build_layout_tree(child)),
|
||||
styling::Display::Inline => root.get_inline_container().children.push(build_layout_tree(child)),
|
||||
styling::Display::None => {}
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
fn sum<I>(iter: I) -> f32 where I: Iterator<Item=f32> {
|
||||
iter.fold(0., |a, b| a + b)
|
||||
}
|
||||
|
31
src/main.rs
31
src/main.rs
@@ -1,11 +1,14 @@
|
||||
extern crate getopts;
|
||||
extern crate image;
|
||||
|
||||
use std::io::Read;
|
||||
use std::fs::File;
|
||||
|
||||
pub mod css;
|
||||
pub mod display;
|
||||
pub mod dom;
|
||||
pub mod html;
|
||||
pub mod layout;
|
||||
pub mod styling;
|
||||
|
||||
fn read_source(filename: String) -> String {
|
||||
@@ -17,14 +20,40 @@ fn read_source(filename: String) -> String {
|
||||
fn main() {
|
||||
let mut opts = getopts::Options::new();
|
||||
opts.optopt("h", "html", "HTML document", "FILENAME");
|
||||
opts.optopt("o", "out", "PNG output", "FILENAME");
|
||||
|
||||
let matches = opts.parse(std::env::args().skip(1)).unwrap();
|
||||
let str_arg = |flag: &str, default: &str| -> String {
|
||||
matches.opt_str(flag).unwrap_or(default.to_string())
|
||||
};
|
||||
|
||||
let initial_block = layout::Dim {
|
||||
content: layout::Rect { x: 0.0, y: 0.0, width: 800.0, height: 600.0 },
|
||||
padding: Default::default(),
|
||||
border: Default::default(),
|
||||
margin: Default::default(),
|
||||
};
|
||||
|
||||
let html = read_source(str_arg("h", "examples/test.html"));
|
||||
let node = html::parse(html);
|
||||
let style = dom::find_style(&node).unwrap();
|
||||
let styled_node = styling::style_tree(&node, &style);
|
||||
|
||||
println!("{}", node)
|
||||
let layout = layout::layout_tree(&styled_node, initial_block);
|
||||
let canvas = display::paint(&layout, initial_block.content);
|
||||
|
||||
let filename = str_arg("o", "out.png");
|
||||
let mut file = File::create(&filename).unwrap();
|
||||
|
||||
let (w, h) = (canvas.width as u32, canvas.height as u32);
|
||||
let img = image::ImageBuffer::from_fn(w, h, move |x, y| {
|
||||
let color = canvas.pixels[(y * w + x) as usize];
|
||||
image::Pixel::from_channels(color.r, color.g, color.b, color.a)
|
||||
});
|
||||
|
||||
let result = image::ImageRgba8(img).save(&mut file, image::PNG);
|
||||
match result {
|
||||
Ok(_) => println!("Saved output as {}", filename),
|
||||
Err(_) => println!("Error saving output as {}", filename)
|
||||
}
|
||||
}
|
||||
|
@@ -8,21 +8,21 @@ type Properties = HashMap<String, css::Value>;
|
||||
pub struct Node<'a> {
|
||||
node: & 'a dom::Node,
|
||||
values: Properties,
|
||||
children: Vec<Node<'a>>,
|
||||
pub children: Vec<Node<'a>>,
|
||||
}
|
||||
|
||||
enum Display {
|
||||
pub enum Display {
|
||||
Inline,
|
||||
Block,
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a> Node<'a> {
|
||||
fn value(&self, name: &str) -> Option<css::Value> {
|
||||
pub fn value(&self, name: &str) -> Option<css::Value> {
|
||||
self.values.get(name).map(|v| v.clone())
|
||||
}
|
||||
|
||||
fn display(&self) -> Display {
|
||||
pub fn display(&self) -> Display {
|
||||
match self.value("display") {
|
||||
Some(css::Value::Keyword(s)) => match &*s {
|
||||
"block" => Display::Block,
|
||||
@@ -32,6 +32,11 @@ impl<'a> Node<'a> {
|
||||
_ => Display::Inline
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup(&self, name: &str, fallback: &str, default: &css::Value) -> css::Value {
|
||||
self.value(name).unwrap_or_else(|| self.value(fallback)
|
||||
.unwrap_or_else(|| default.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn matches(elem: &dom::EData, selector: &css::Selector) -> bool {
|
||||
|
Reference in New Issue
Block a user