all: can render basic things
This commit is contained in:
@@ -9,3 +9,4 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
getopts = "0.2.3"
|
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
|
A browser engine written in Rust. I heard that’s what the cool kids do these
|
||||||
days.
|
days.
|
||||||
|
|
||||||
Current status: can read HTML/CSS, convert to internal representation, and back
|
It can currently only render a tiny subset of HTML and CSS to PNG.
|
||||||
to (unformatted) HTML/CSS.
|
|
||||||
|
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;
|
use std;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct SimpleSelector {
|
pub struct SimpleSelector {
|
||||||
pub tag_name: Option<String>,
|
pub tag_name: Option<String>,
|
||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
pub class: Vec<String>,
|
pub class: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct ChainSelector {
|
struct ChainSelector {
|
||||||
tag_name: Option<Vec<String>>,
|
tag_name: Option<Vec<String>>,
|
||||||
id: Option<Vec<String>>,
|
id: Option<Vec<String>>,
|
||||||
class: Vec<Vec<String>>,
|
class: Vec<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum Selector {
|
pub enum Selector {
|
||||||
Simple(SimpleSelector),
|
Simple(SimpleSelector),
|
||||||
//Chain(ChainSelector),
|
//Chain(ChainSelector),
|
||||||
@@ -43,7 +46,7 @@ impl std::fmt::Display for Selector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum Unit {
|
pub enum Unit {
|
||||||
Px,
|
Px,
|
||||||
Em,
|
Em,
|
||||||
@@ -66,15 +69,15 @@ impl std::fmt::Display for Unit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
r: u8,
|
pub r: u8,
|
||||||
g: u8,
|
pub g: u8,
|
||||||
b: u8,
|
pub b: u8,
|
||||||
a: u8,
|
pub a: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
Keyword(String),
|
Keyword(String),
|
||||||
Length(f32, Unit),
|
Length(f32, Unit),
|
||||||
@@ -101,6 +104,7 @@ impl std::fmt::Display for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Declaration {
|
pub struct Declaration {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub value: Value,
|
pub value: Value,
|
||||||
@@ -112,6 +116,7 @@ impl std::fmt::Display for Declaration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Rule {
|
pub struct Rule {
|
||||||
pub selectors: Vec<Selector>,
|
pub selectors: Vec<Selector>,
|
||||||
pub declarations: Vec<Declaration>,
|
pub declarations: Vec<Declaration>,
|
||||||
@@ -136,6 +141,7 @@ impl std::fmt::Display for Rule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Stylesheet {
|
pub struct Stylesheet {
|
||||||
pub rules: Vec<Rule>,
|
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;
|
use css;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Attr {
|
pub struct Attr {
|
||||||
attrs: HashMap<String, String>,
|
attrs: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct EData {
|
pub struct EData {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub attr: Attr,
|
pub attr: Attr,
|
||||||
@@ -25,11 +27,13 @@ impl EData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct SData {
|
pub struct SData {
|
||||||
attr: Attr,
|
attr: Attr,
|
||||||
content: css::Stylesheet,
|
content: css::Stylesheet,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum NType {
|
pub enum NType {
|
||||||
Text(String),
|
Text(String),
|
||||||
Comment(String),
|
Comment(String),
|
||||||
@@ -37,6 +41,7 @@ pub enum NType {
|
|||||||
Stylesheet(SData)
|
Stylesheet(SData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
pub children: Vec<Node>,
|
pub children: Vec<Node>,
|
||||||
pub ntype: NType,
|
pub ntype: NType,
|
||||||
@@ -101,3 +106,39 @@ impl std::fmt::Display for Attr {
|
|||||||
return Result::Ok(())
|
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 {
|
use css;
|
||||||
x: f32,
|
use styling;
|
||||||
y: f32,
|
|
||||||
width: f32,
|
#[derive(Clone, Copy, Default)]
|
||||||
height: f32,
|
pub struct Rect {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub width: f32,
|
||||||
|
pub height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rect {
|
impl Rect {
|
||||||
@@ -16,46 +20,48 @@ impl Rect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Edge {
|
#[derive(Clone, Copy, Default)]
|
||||||
left: f32,
|
pub struct Edge {
|
||||||
right: f32,
|
pub left: f32,
|
||||||
top: f32,
|
pub right: f32,
|
||||||
bottom: f32,
|
pub top: f32,
|
||||||
|
pub bottom: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dimensions {
|
#[derive(Clone, Copy, Default)]
|
||||||
fn padding_box(self) -> Rect {
|
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)
|
self.content.expanded_by(self.padding)
|
||||||
}
|
}
|
||||||
fn border_box(self) -> Rect {
|
pub fn border_box(self) -> Rect {
|
||||||
self.padding_box().expanded_by(self.border)
|
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)
|
self.border_box().expanded_by(self.margin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Dim {
|
pub enum BoxType<'a> {
|
||||||
content: Rect,
|
Block(&'a styling::Node<'a>),
|
||||||
|
Inline(&'a styling::Node<'a>),
|
||||||
padding: Edge,
|
|
||||||
border: Edge,
|
|
||||||
margin: Edge,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum BoxType<'a> {
|
|
||||||
Block(&'a StyledNode<'a>),
|
|
||||||
Inline(&'a StyledNode<'a>),
|
|
||||||
Anonymous
|
Anonymous
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LayoutBox<'a> {
|
pub struct LayoutBox<'a> {
|
||||||
dimensions: Dim,
|
pub dimensions: Dim,
|
||||||
btype: BoxType<'a>,
|
pub btype: BoxType<'a>,
|
||||||
children: Vec<LayoutBox<'a>>,
|
pub children: Vec<LayoutBox<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBox {
|
impl<'a> LayoutBox<'a> {
|
||||||
fn new(btype: BoxType) -> LayoutBox {
|
fn new(btype: BoxType) -> LayoutBox {
|
||||||
LayoutBox {
|
LayoutBox {
|
||||||
btype: btype,
|
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 {
|
match self.btype {
|
||||||
Inline(_) | Anonymous => self,
|
BoxType::Block(node) | BoxType::Inline(node) => node,
|
||||||
Block(_) => {
|
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() {
|
match self.children.last() {
|
||||||
Some(&LayoutBox { btype: Anonymous,..}) => {}
|
Some(&LayoutBox { btype: BoxType::Anonymous,..}) => {}
|
||||||
_ => self.children.push(LayoutBox::new(Anonymous))
|
_ => self.children.push(LayoutBox::new(BoxType::Anonymous))
|
||||||
}
|
}
|
||||||
self.children.last_mut().unwrap()
|
self.children.last_mut().unwrap()
|
||||||
}
|
}
|
||||||
@@ -79,9 +92,9 @@ impl LayoutBox {
|
|||||||
|
|
||||||
fn layout(&mut self, containing: Dim) {
|
fn layout(&mut self, containing: Dim) {
|
||||||
match self.btype {
|
match self.btype {
|
||||||
Block(_) => self.layout_block(containing),
|
BoxType::Block(_) => self.layout_block(containing),
|
||||||
Inline(_) => {} // TODO
|
BoxType::Inline(_) => {} // TODO
|
||||||
Anonymous => {} // TODO
|
BoxType::Anonymous => {} // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,10 +111,10 @@ impl LayoutBox {
|
|||||||
fn calculate_block_width(&mut self, containing: Dim) {
|
fn calculate_block_width(&mut self, containing: Dim) {
|
||||||
let style = self.get_style_node();
|
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 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_left = style.lookup("margin-left", "margin", &zero);
|
||||||
let mut margin_right = style.lookup("margin-right", "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_left = style.lookup("padding-left", "padding", &zero);
|
||||||
let padding_right = style.lookup("padding-right", "padding", &zero);
|
let padding_right = style.lookup("padding-right", "padding", &zero);
|
||||||
|
|
||||||
let total = [&margin_left, &margin_right, &border_left, &border_right,
|
let total = sum([&margin_left, &margin_right, &border_left,
|
||||||
&padding_left, &padding_right, &width
|
&border_right, &padding_left, &padding_right,
|
||||||
].iter().map(|v| v.to_px()).sum();
|
&width
|
||||||
|
].iter().map(|v| v.to_px()));
|
||||||
|
|
||||||
if width != auto && total > containing.content.width {
|
if width != auto && total > containing.content.width {
|
||||||
if margin_left == auto {
|
if margin_left == auto {
|
||||||
margin_left = Length(0.0, Px);
|
margin_left = css::Value::Length(0.0, css::Unit::Px);
|
||||||
}
|
}
|
||||||
if margin_right == auto {
|
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) {
|
match (width == auto, margin_left == auto, margin_right == auto) {
|
||||||
(false, false, false) => {
|
(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, false, true) => { margin_right = css::Value::Length(underflow, css::Unit::Px); }
|
||||||
(false, true, false) => { margin_left = Length(underflow, Px); }
|
(false, true, false) => { margin_left = css::Value::Length(underflow, css::Unit::Px); }
|
||||||
|
|
||||||
(true, _, _) => {
|
(true, _, _) => {
|
||||||
if margin_left == auto { margin_left = Length(0.0, Px); }
|
if margin_left == auto { margin_left = css::Value::Length(0.0, css::Unit::Px); }
|
||||||
if margin_right == auto { margin_right = Length(0.0, Px); }
|
if margin_right == auto { margin_right = css::Value::Length(0.0, css::Unit::Px); }
|
||||||
|
|
||||||
if underflow >= 0.0 {
|
if underflow >= 0.0 {
|
||||||
width = Length(underflow, Px);
|
width = css::Value::Length(underflow, css::Unit::Px);
|
||||||
} else {
|
} else {
|
||||||
width = Length(0.0, Px);
|
width = css::Value::Length(0.0, css::Unit::Px);
|
||||||
margin_right = Length(margin_right.to_px() + underflow, Px);
|
margin_right = css::Value::Length(margin_right.to_px() + underflow, css::Unit::Px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(false, true, true) => {
|
(false, true, true) => {
|
||||||
margin_left = Length(underflow / 2.0, Px);
|
margin_left = css::Value::Length(underflow / 2.0, css::Unit::Px);
|
||||||
margin_right = Length(underflow / 2.0, 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 style = self.get_style_node();
|
||||||
let d = &mut self.dimensions;
|
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.top = style.lookup("margin-top", "margin", &zero).to_px();
|
||||||
d.margin.bottom = style.lookup("margin-bottom", "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;
|
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) {
|
fn layout_block_children(&mut self) {
|
||||||
let d = &mut self.dimensions;
|
let d = &mut self.dimensions;
|
||||||
for child in &mut self.children {
|
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() {
|
let mut root = LayoutBox::new(match style_node.display() {
|
||||||
css::Display::Block => Block(style_node),
|
styling::Display::Block => BoxType::Block(style_node),
|
||||||
css::Display::Inline => Inline(style_node),
|
styling::Display::Inline => BoxType::Inline(style_node),
|
||||||
css::Display::DisplayNone => panic!("Root node has display: none.")
|
styling::Display::None => panic!("Root node has display: none.")
|
||||||
});
|
});
|
||||||
|
|
||||||
for child in &style_node.children {
|
for child in &style_node.children {
|
||||||
match child.display() {
|
match child.display() {
|
||||||
css::Display::Block => root.children.push(build_layout_tree(child)),
|
styling::Display::Block => root.children.push(build_layout_tree(child)),
|
||||||
css::Display::Inline => root.get_inline_container().children.push(build_layout_tree(child)),
|
styling::Display::Inline => root.get_inline_container().children.push(build_layout_tree(child)),
|
||||||
css::Display::DisplayNone => {}
|
styling::Display::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return root;
|
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 getopts;
|
||||||
|
extern crate image;
|
||||||
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
pub mod css;
|
pub mod css;
|
||||||
|
pub mod display;
|
||||||
pub mod dom;
|
pub mod dom;
|
||||||
pub mod html;
|
pub mod html;
|
||||||
|
pub mod layout;
|
||||||
pub mod styling;
|
pub mod styling;
|
||||||
|
|
||||||
fn read_source(filename: String) -> String {
|
fn read_source(filename: String) -> String {
|
||||||
@@ -17,14 +20,40 @@ fn read_source(filename: String) -> String {
|
|||||||
fn main() {
|
fn main() {
|
||||||
let mut opts = getopts::Options::new();
|
let mut opts = getopts::Options::new();
|
||||||
opts.optopt("h", "html", "HTML document", "FILENAME");
|
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 matches = opts.parse(std::env::args().skip(1)).unwrap();
|
||||||
let str_arg = |flag: &str, default: &str| -> String {
|
let str_arg = |flag: &str, default: &str| -> String {
|
||||||
matches.opt_str(flag).unwrap_or(default.to_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 html = read_source(str_arg("h", "examples/test.html"));
|
||||||
let node = html::parse(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> {
|
pub struct Node<'a> {
|
||||||
node: & 'a dom::Node,
|
node: & 'a dom::Node,
|
||||||
values: Properties,
|
values: Properties,
|
||||||
children: Vec<Node<'a>>,
|
pub children: Vec<Node<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Display {
|
pub enum Display {
|
||||||
Inline,
|
Inline,
|
||||||
Block,
|
Block,
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Node<'a> {
|
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())
|
self.values.get(name).map(|v| v.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display(&self) -> Display {
|
pub fn display(&self) -> Display {
|
||||||
match self.value("display") {
|
match self.value("display") {
|
||||||
Some(css::Value::Keyword(s)) => match &*s {
|
Some(css::Value::Keyword(s)) => match &*s {
|
||||||
"block" => Display::Block,
|
"block" => Display::Block,
|
||||||
@@ -32,6 +32,11 @@ impl<'a> Node<'a> {
|
|||||||
_ => Display::Inline
|
_ => 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 {
|
fn matches(elem: &dom::EData, selector: &css::Selector) -> bool {
|
||||||
|
Reference in New Issue
Block a user