├── .gitignore ├── output.png ├── README.md ├── examples ├── test.css └── test.html ├── Cargo.toml ├── src ├── dom.rs ├── main.rs ├── painting.rs ├── html.rs ├── css.rs ├── layout.rs └── style.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target -------------------------------------------------------------------------------- /output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UMASHIBA1/violet/HEAD/output.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # violet 2 | This is web browser for studying. 3 | 4 | ## implemented 5 | - display: block 6 | - background-color 7 | - margin, padding, border 8 | - width, height 9 | -------------------------------------------------------------------------------- /examples/test.css: -------------------------------------------------------------------------------- 1 | * { display: block; padding: 12px; } 2 | .a { background: #ff0000; } 3 | .b { background: #ffa500; } 4 | .c { background: #ffff00; } 5 | .d { background: #008000; } 6 | .e { background: #0000ff; } 7 | .f { background: #4b0082; } 8 | .g { background: #800080; } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "violet" 3 | version = "0.1.0" 4 | authors = ["UMASHIBA "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | getopts = "0.2.21" 11 | image = "0.23.14" 12 | -------------------------------------------------------------------------------- /examples/test.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
-------------------------------------------------------------------------------- /src/dom.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub struct Node { 5 | pub children: Vec, 6 | pub node_type: NodeType 7 | } 8 | 9 | #[derive(Debug, PartialEq, Clone)] 10 | pub enum NodeType { 11 | Text(String), 12 | Element(ElementData) 13 | } 14 | 15 | #[derive(Debug, PartialEq, Clone)] 16 | pub struct ElementData { 17 | pub tag_name: String, 18 | pub attributes: AttrMap 19 | } 20 | 21 | pub type AttrMap = HashMap; 22 | 23 | pub fn text(data: String) -> Node { 24 | Node {children: Vec::new(), node_type: NodeType::Text(data)} 25 | } 26 | 27 | pub fn elem(name: String, attrs: AttrMap, children: Vec) -> Node { 28 | Node { 29 | children, 30 | node_type: NodeType::Element(ElementData { 31 | tag_name: name, 32 | attributes: attrs 33 | }) 34 | } 35 | } 36 | 37 | 38 | 39 | impl ElementData { 40 | pub fn id(&self) -> Option<&String> { 41 | self.attributes.get("id") 42 | } 43 | 44 | pub fn classes(&self) -> HashSet<&str> { 45 | match self.attributes.get("class") { 46 | Some(classlist) => classlist.split(' ').collect(), 47 | None => HashSet::new() 48 | } 49 | } 50 | } 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use getopts; 2 | use image; 3 | use std::fs::File; 4 | use std::io::{Read, BufWriter}; 5 | use std::rc::Rc; 6 | use std::cell::RefCell; 7 | use crate::layout::Dimensions; 8 | 9 | mod dom; 10 | mod html; 11 | mod css; 12 | mod style; 13 | mod layout; 14 | mod painting; 15 | 16 | 17 | fn main() { 18 | let mut opts = getopts::Options::new(); 19 | opts.optopt("h", "html", "HTML document", "FILENAME"); 20 | opts.optopt("c", "css", "CSS stylesheet", "FILENAME"); 21 | opts.optopt("o", "output", "Output file", "FILENAME"); 22 | 23 | let matches = opts.parse(std::env::args().skip(1)).unwrap(); 24 | let str_arg = |flag: &str, default: &str| -> String { 25 | matches.opt_str(flag).unwrap_or(default.to_string()) 26 | }; 27 | 28 | 29 | let html = read_source(str_arg("h", "examples/test.html")); 30 | let css = read_source(str_arg("c", "examples/test.css")); 31 | 32 | let mut viewport = Rc::new(RefCell::new(Dimensions::default())); 33 | viewport.clone().borrow_mut().content.width = 800.0; 34 | viewport.clone().borrow_mut().content.height = 600.0; 35 | println!("{:?}", viewport.borrow().content.height); 36 | 37 | let root_node = html::parse(html); 38 | let stylesheet = css::parse(css); 39 | let style_root = style::style_tree(&root_node, &stylesheet); 40 | println!("before layout: {:?}", viewport.borrow().content.height); 41 | let layout_root = layout::layout_tree(&style_root, viewport.clone()); 42 | 43 | let filename = str_arg("o", "output.png"); 44 | // let mut file = BufWriter::new(File::create(&filename).unwrap()); 45 | viewport.clone().borrow_mut().content.height = 600.0; 46 | 47 | let canvas = painting::paint(&layout_root, viewport.borrow().content.clone()); 48 | let (width, height) = (canvas.width as u32, canvas.height as u32); 49 | println!("{:?}", canvas.height); 50 | println!("{:?}, {:?}", width, height); 51 | let mut imgbuf = image::ImageBuffer::new(width, height); 52 | // let imgbuf2 = image::ImageBuffer::from_fn(width, height, |x,y| { 53 | // let color = canvas.pixels.get((y * width + x) as usize).unwrap(); 54 | // image::Rgb([color.r, color.g, color.b]) 55 | // }); 56 | for (x,y,pixel) in imgbuf.enumerate_pixels_mut() { 57 | let color = canvas.pixels.get((y * width + x) as usize).unwrap(); 58 | // println!("{:?}", color); 59 | *pixel = image::Rgb([color.r, color.g, color.b]); 60 | } 61 | let ok = imgbuf.save_with_format("output.png", image::ImageFormat::Png).is_ok(); 62 | if ok { 63 | println!("success"); 64 | } else { 65 | println!("failed"); 66 | } 67 | 68 | 69 | // imgbuf.save_with_format("output.png", image::ImageFormat::Png); 70 | // imgbuf.save("output.png"); 71 | 72 | // let ok = { 73 | // let content = viewport.clone().borrow().content.clone(); 74 | // let canvas = painting::paint(&layout_root, content); 75 | // let (w,h) = (canvas.width as u32, canvas.height as u32); 76 | // let img = image::ImageBuffer::from_fn(w,h, move|x,y| { 77 | // let color = canvas.pixels.get((y * w + x) as usize).unwrap(); 78 | // image::Pixels::from_channels(color.r,color.g,color.b,color.a) 79 | // }); 80 | // img.save(filename).is_ok() 81 | // }; 82 | // 83 | // if ok { 84 | // println!("Saved output as {}", filename); 85 | // } else { 86 | // println!("Error saving output as {}", filename); 87 | // } 88 | 89 | } 90 | 91 | fn read_source(filename: String) -> String { 92 | let mut str = String::new(); 93 | File::open(filename).unwrap().read_to_string(&mut str).unwrap(); 94 | str 95 | } -------------------------------------------------------------------------------- /src/painting.rs: -------------------------------------------------------------------------------- 1 | // 参考: https://limpet.net/mbrubeck/2014/11/05/toy-layout-engine-7-painting.html 2 | use crate::css::{Color, Value}; 3 | use crate::layout::{Rect, BoxType, LayoutBox}; 4 | use std::io::{repeat, Read}; 5 | 6 | type DisplayList = Vec; 7 | 8 | enum DisplayCommand { 9 | SolidColor(Color, Rect) 10 | } 11 | 12 | pub struct Canvas { 13 | pub pixels: Vec, 14 | pub width: usize, 15 | pub height: usize 16 | } 17 | 18 | pub fn paint(layout_root: &LayoutBox, bounds: Rect) -> Canvas { 19 | let display_list = build_display_list(layout_root); 20 | println!("paint: {:?}", bounds.height); 21 | let mut canvas = Canvas::new(bounds.width as usize, bounds.height as usize); 22 | for item in display_list { 23 | canvas.paint_item(&item); 24 | } 25 | canvas 26 | } 27 | 28 | fn build_display_list(layout_root: &LayoutBox) -> DisplayList { 29 | let mut list = DisplayList::new(); 30 | render_layout_box(&mut list, layout_root); 31 | list 32 | } 33 | 34 | fn render_layout_box(list: &mut DisplayList, layout_box: &LayoutBox) { 35 | // NOTE: 36 | render_background(list, layout_box); 37 | render_borders(list, layout_box); 38 | // TODO: render text 39 | 40 | for child in &layout_box.children { 41 | render_layout_box(list, child); 42 | } 43 | } 44 | 45 | fn render_background(list: &mut DisplayList, layout_box: &LayoutBox) { 46 | get_color(layout_box, "background").map(|color| 47 | list.push(DisplayCommand::SolidColor(color, layout_box.dimensions.borrow().border_box())) 48 | ); 49 | } 50 | 51 | // NOTE: LayoutBoxが持っている色のプロパティを取得 52 | fn get_color(layout_box: &LayoutBox, name: &str) -> Option { 53 | match layout_box.box_type { 54 | BoxType::BlockNode(style) | BoxType::InlineNode(style) => match style.value(name) { 55 | Some(Value::ColorValue(color)) => Some(color), 56 | _ => None 57 | }, 58 | BoxType::AnonymousBlock => None 59 | } 60 | } 61 | 62 | fn render_borders(list: &mut DisplayList, layout_box: &LayoutBox) { 63 | let color = match get_color(layout_box, "border-color") { 64 | Some(color) => color, 65 | _ => return 66 | }; 67 | 68 | let this_dimension = &layout_box.dimensions; 69 | let border_box = &this_dimension.clone().borrow().border_box(); 70 | let border = &this_dimension.borrow().border; 71 | 72 | // left border 73 | list.push(DisplayCommand::SolidColor(color.clone(), Rect { 74 | x: border_box.x, 75 | y: border_box.y, 76 | width: border.left.clone(), 77 | height: border_box.height 78 | })); 79 | 80 | // right border 81 | list.push(DisplayCommand::SolidColor(color.clone(), Rect { 82 | x: border_box.x + border_box.width - border.right.clone(), 83 | y: border_box.y, 84 | width: border.right.clone(), 85 | height: border_box.height 86 | })); 87 | 88 | // top border 89 | list.push(DisplayCommand::SolidColor(color, Rect { 90 | x: border_box.x, 91 | y: border_box.y + border_box.height - border.bottom.clone(), 92 | width: border_box.width, 93 | height: border.bottom.clone() 94 | })); 95 | 96 | } 97 | 98 | 99 | 100 | impl Canvas { 101 | fn new(width: usize, height: usize) -> Canvas { 102 | let white = Color {r: 255, g: 255, b: 255, a: 255}; 103 | Canvas { 104 | pixels: vec![white; width * height], 105 | width, 106 | height 107 | } 108 | } 109 | 110 | fn paint_item(&mut self, item: &DisplayCommand) { 111 | 112 | fn clamp(target: f32, min: f32, max: f32) -> f32 { 113 | if target < min { 114 | min 115 | } else if target > max { 116 | max 117 | } else { 118 | target 119 | } 120 | } 121 | 122 | match item { 123 | DisplayCommand::SolidColor(color, rect) => { 124 | let x0 = clamp(rect.x, 0.0, self.width as f32) as usize; 125 | let y0 = clamp(rect.y, 0.0, self.height as f32) as usize; 126 | let x1 = clamp(rect.x + rect.width, 0.0, self.width as f32) as usize; 127 | let y1 = clamp(rect.y + rect.height, 0.0, self.height as f32) as usize; 128 | 129 | for y in y0..y1 { 130 | for x in x0 .. x1 { 131 | self.pixels[x + y * self.width] = color.clone(); 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | 139 | 140 | // NOTE: 自分なりに処理の手順メモ 141 | // 目標: 最終的にCanvasオブジェクト内にwindowのwidthとheightの大きさ 142 | // に対応した1pixelごとの色のリストを作成したい 143 | // 手順 144 | // 1. DisplayCommandを作成することでx,y,width,height,colorを一つのオブジェクトにまとめDisplayListに追加する 145 | // 2. 1.をLayoutBoxのchildrenに対して繰り返す(childrenでもparentの入ってるDisplayListにpushする) 146 | // 3. DisplayListの各要素に対してループを回してwidth,height,x,yから各ピクセルのcolorを計算しCanvasのpixelsに追加 -------------------------------------------------------------------------------- /src/html.rs: -------------------------------------------------------------------------------- 1 | use crate::dom; 2 | use std::collections::HashMap; 3 | 4 | struct Parser { 5 | pos: usize, 6 | input: String, 7 | } 8 | 9 | pub fn parse(source: String) -> dom::Node { 10 | let mut nodes = Parser {pos: 0, input: source}.parse_nodes(); 11 | 12 | if nodes.len() == 1 { 13 | nodes.swap_remove(0) 14 | } else { 15 | dom::elem("html".to_string(), HashMap::new(), nodes) 16 | } 17 | } 18 | 19 | impl Parser { 20 | 21 | fn next_char(&self) -> char { 22 | self.input[self.pos..].chars().next().unwrap() 23 | } 24 | 25 | fn starts_with(&self, s: &str) -> bool { 26 | self.input[self.pos..].starts_with(s) 27 | } 28 | 29 | fn eof(&self)->bool { 30 | self.pos >= self.input.len() 31 | } 32 | 33 | fn consume_char(&mut self) -> char { 34 | let mut iter = self.input[self.pos..].char_indices(); 35 | let (_, cur_char) = iter.next().unwrap(); 36 | let (next_pos, _) = iter.next().unwrap_or((1, ' ')); 37 | // NOTE: += next_posがなぞ 38 | self.pos += next_pos; 39 | return cur_char; 40 | } 41 | 42 | fn consume_while(&mut self, test: F) -> String where F: Fn(char) -> bool { 43 | let mut result = String::new(); 44 | while !self.eof() && test(self.next_char()) { 45 | result.push(self.consume_char()); 46 | } 47 | return result; 48 | } 49 | 50 | fn parse_attr_value(&mut self) -> String { 51 | let open_quote = self.consume_char(); 52 | assert!(open_quote == '"' || open_quote == '\''); 53 | let value = self.consume_while(|c| c != open_quote); 54 | assert!(self.consume_char() == open_quote); 55 | return value; 56 | } 57 | 58 | fn parse_attr(&mut self) -> (String, String) { 59 | let name = self.parse_tag_name(); 60 | assert!(self.consume_char() == '='); 61 | let value = self.parse_attr_value(); 62 | return (name, value); 63 | } 64 | 65 | // Consume and discard zero or more whitespace characters. 66 | fn consume_whitespace(&mut self) { 67 | self.consume_while(char::is_whitespace); 68 | } 69 | 70 | fn parse_attributes(&mut self) -> dom::AttrMap { 71 | let mut attributes = HashMap::new(); 72 | loop { 73 | self.consume_whitespace(); 74 | if self.next_char() == '>' { 75 | break; 76 | }; 77 | let (name, value) = self.parse_attr(); 78 | attributes.insert(name, value); 79 | }; 80 | return attributes; 81 | } 82 | 83 | // Parse a tag or attribute name. 84 | fn parse_tag_name(&mut self) -> String { 85 | self.consume_while(|c| match c { 86 | 'a'..='z' | 'A'..='Z' | '0'..='9' => true, 87 | _ => false 88 | }) 89 | } 90 | 91 | fn parse_text(&mut self) -> dom::Node { 92 | dom::text(self.consume_while(|c| c != '<')) 93 | } 94 | 95 | // 一つのエレメントノードをパースする 96 | fn parse_element(&mut self) -> dom::Node { 97 | assert!(self.consume_char() == '<', "the element does not start with <"); 98 | let tag_name = self.parse_tag_name(); 99 | let attrs = self.parse_attributes(); 100 | assert!(self.consume_char() == '>'); 101 | 102 | let children = self.parse_nodes(); 103 | 104 | assert!(self.consume_char() == '<'); 105 | assert!(self.consume_char() == '/'); 106 | assert!(self.parse_tag_name() == tag_name, "start tag name and end tag name is not equal"); 107 | assert!(self.consume_char() == '>'); 108 | 109 | return dom::elem(tag_name, attrs, children); 110 | } 111 | 112 | fn consume_comment(&mut self) { 113 | assert!(self.consume_char() == '<'); 114 | assert!(self.consume_char() == '!'); 115 | assert!(self.consume_char() == '-'); 116 | assert!(self.consume_char() == '-'); 117 | 118 | while !self.eof() && !self.starts_with("-->") { 119 | self.consume_char(); 120 | }; 121 | 122 | assert!(self.consume_char() == '-'); 123 | assert!(self.consume_char() == '-'); 124 | self.consume_whitespace(); 125 | assert!(self.consume_char() == '>'); 126 | } 127 | 128 | // NOTE: 一つのノードをパースする 129 | fn parse_node(&mut self) -> dom::Node { 130 | if self.starts_with("
".to_string(); 214 | let parsed_dom = parse(target_str); 215 | let expected_dom = elem("html".to_string(), HashMap::new(),vec![elem("body".to_string(), HashMap::new(), vec![elem("div".to_string(), HashMap::new(), vec![])])]); 216 | assert_eq!(parsed_dom, expected_dom); 217 | } 218 | 219 | } 220 | 221 | // let html_string = "

Title

Hello world!

"; 222 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler" 5 | version = "1.0.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 8 | 9 | [[package]] 10 | name = "adler32" 11 | version = "1.2.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 14 | 15 | [[package]] 16 | name = "autocfg" 17 | version = "1.0.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 20 | 21 | [[package]] 22 | name = "bitflags" 23 | version = "1.2.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 26 | 27 | [[package]] 28 | name = "bytemuck" 29 | version = "1.5.1" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58" 32 | 33 | [[package]] 34 | name = "byteorder" 35 | version = "1.4.2" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" 38 | 39 | [[package]] 40 | name = "cfg-if" 41 | version = "1.0.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 44 | 45 | [[package]] 46 | name = "color_quant" 47 | version = "1.1.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 50 | 51 | [[package]] 52 | name = "crc32fast" 53 | version = "1.2.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 56 | dependencies = [ 57 | "cfg-if", 58 | ] 59 | 60 | [[package]] 61 | name = "crossbeam-channel" 62 | version = "0.5.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" 65 | dependencies = [ 66 | "cfg-if", 67 | "crossbeam-utils", 68 | ] 69 | 70 | [[package]] 71 | name = "crossbeam-deque" 72 | version = "0.8.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" 75 | dependencies = [ 76 | "cfg-if", 77 | "crossbeam-epoch", 78 | "crossbeam-utils", 79 | ] 80 | 81 | [[package]] 82 | name = "crossbeam-epoch" 83 | version = "0.9.3" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" 86 | dependencies = [ 87 | "cfg-if", 88 | "crossbeam-utils", 89 | "lazy_static", 90 | "memoffset", 91 | "scopeguard", 92 | ] 93 | 94 | [[package]] 95 | name = "crossbeam-utils" 96 | version = "0.8.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" 99 | dependencies = [ 100 | "autocfg", 101 | "cfg-if", 102 | "lazy_static", 103 | ] 104 | 105 | [[package]] 106 | name = "deflate" 107 | version = "0.8.6" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" 110 | dependencies = [ 111 | "adler32", 112 | "byteorder", 113 | ] 114 | 115 | [[package]] 116 | name = "either" 117 | version = "1.6.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 120 | 121 | [[package]] 122 | name = "getopts" 123 | version = "0.2.21" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 126 | dependencies = [ 127 | "unicode-width", 128 | ] 129 | 130 | [[package]] 131 | name = "gif" 132 | version = "0.11.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "02efba560f227847cb41463a7395c514d127d4f74fff12ef0137fff1b84b96c4" 135 | dependencies = [ 136 | "color_quant", 137 | "weezl", 138 | ] 139 | 140 | [[package]] 141 | name = "hermit-abi" 142 | version = "0.1.18" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 145 | dependencies = [ 146 | "libc", 147 | ] 148 | 149 | [[package]] 150 | name = "image" 151 | version = "0.23.14" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" 154 | dependencies = [ 155 | "bytemuck", 156 | "byteorder", 157 | "color_quant", 158 | "gif", 159 | "jpeg-decoder", 160 | "num-iter", 161 | "num-rational", 162 | "num-traits", 163 | "png", 164 | "scoped_threadpool", 165 | "tiff", 166 | ] 167 | 168 | [[package]] 169 | name = "jpeg-decoder" 170 | version = "0.1.22" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" 173 | dependencies = [ 174 | "rayon", 175 | ] 176 | 177 | [[package]] 178 | name = "lazy_static" 179 | version = "1.4.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 182 | 183 | [[package]] 184 | name = "libc" 185 | version = "0.2.88" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" 188 | 189 | [[package]] 190 | name = "memoffset" 191 | version = "0.6.1" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" 194 | dependencies = [ 195 | "autocfg", 196 | ] 197 | 198 | [[package]] 199 | name = "miniz_oxide" 200 | version = "0.3.7" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 203 | dependencies = [ 204 | "adler32", 205 | ] 206 | 207 | [[package]] 208 | name = "miniz_oxide" 209 | version = "0.4.4" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 212 | dependencies = [ 213 | "adler", 214 | "autocfg", 215 | ] 216 | 217 | [[package]] 218 | name = "num-integer" 219 | version = "0.1.44" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 222 | dependencies = [ 223 | "autocfg", 224 | "num-traits", 225 | ] 226 | 227 | [[package]] 228 | name = "num-iter" 229 | version = "0.1.42" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" 232 | dependencies = [ 233 | "autocfg", 234 | "num-integer", 235 | "num-traits", 236 | ] 237 | 238 | [[package]] 239 | name = "num-rational" 240 | version = "0.3.2" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" 243 | dependencies = [ 244 | "autocfg", 245 | "num-integer", 246 | "num-traits", 247 | ] 248 | 249 | [[package]] 250 | name = "num-traits" 251 | version = "0.2.14" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 254 | dependencies = [ 255 | "autocfg", 256 | ] 257 | 258 | [[package]] 259 | name = "num_cpus" 260 | version = "1.13.0" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 263 | dependencies = [ 264 | "hermit-abi", 265 | "libc", 266 | ] 267 | 268 | [[package]] 269 | name = "png" 270 | version = "0.16.8" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" 273 | dependencies = [ 274 | "bitflags", 275 | "crc32fast", 276 | "deflate", 277 | "miniz_oxide 0.3.7", 278 | ] 279 | 280 | [[package]] 281 | name = "rayon" 282 | version = "1.5.0" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" 285 | dependencies = [ 286 | "autocfg", 287 | "crossbeam-deque", 288 | "either", 289 | "rayon-core", 290 | ] 291 | 292 | [[package]] 293 | name = "rayon-core" 294 | version = "1.9.0" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" 297 | dependencies = [ 298 | "crossbeam-channel", 299 | "crossbeam-deque", 300 | "crossbeam-utils", 301 | "lazy_static", 302 | "num_cpus", 303 | ] 304 | 305 | [[package]] 306 | name = "scoped_threadpool" 307 | version = "0.1.9" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 310 | 311 | [[package]] 312 | name = "scopeguard" 313 | version = "1.1.0" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 316 | 317 | [[package]] 318 | name = "tiff" 319 | version = "0.6.1" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" 322 | dependencies = [ 323 | "jpeg-decoder", 324 | "miniz_oxide 0.4.4", 325 | "weezl", 326 | ] 327 | 328 | [[package]] 329 | name = "unicode-width" 330 | version = "0.1.8" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 333 | 334 | [[package]] 335 | name = "violet" 336 | version = "0.1.0" 337 | dependencies = [ 338 | "getopts", 339 | "image", 340 | ] 341 | 342 | [[package]] 343 | name = "weezl" 344 | version = "0.1.4" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "4a32b378380f4e9869b22f0b5177c68a5519f03b3454fde0b291455ddbae266c" 347 | -------------------------------------------------------------------------------- /src/css.rs: -------------------------------------------------------------------------------- 1 | // NOTE: 参考: https://limpet.net/mbrubeck/2014/08/13/toy-layout-engine-3-css.html 2 | // https://github.com/mbrubeck/robinson/blob/master/src/css.rs 3 | 4 | #[derive(Debug, PartialEq)] 5 | pub struct Stylesheet { 6 | pub rules: Vec 7 | } 8 | 9 | // 一個のセレクタとdeclaration達の塊 10 | #[derive(Clone, Debug, PartialEq)] 11 | pub struct Rule { 12 | pub selectors: Vec, 13 | pub declarations: Vec 14 | } 15 | 16 | // NOTE: 今はSimpleSelectorだけだけど今後[href="example.com"]とか追加できるようになる 17 | #[derive(Clone, Debug, PartialEq)] 18 | pub enum Selector { 19 | Simple(SimpleSelector) 20 | } 21 | 22 | // NOTE: #id, .class, bodyみたいな部分 23 | #[derive(Clone, Debug, PartialEq)] 24 | pub struct SimpleSelector { 25 | pub tag_name: Option, 26 | pub id: Option, 27 | pub class: Vec 28 | } 29 | 30 | // NOTE: margin: auto; 31 | #[derive(Clone,Debug, PartialEq)] 32 | pub struct Declaration { 33 | pub name: String, 34 | pub value: Value 35 | } 36 | 37 | // NOTE: margin: auto; のautoの部分 38 | #[derive(Clone,Debug, PartialEq)] 39 | pub enum Value { 40 | Keyword(String), 41 | Length(f32, Unit), 42 | Percentage(f32), 43 | ColorValue(Color) 44 | } 45 | 46 | impl Value { 47 | pub fn to_px(&self) -> f32 { 48 | match *self { 49 | Value::Length(f, Unit::Px) => f, 50 | _ => 0.0 51 | } 52 | } 53 | } 54 | 55 | // NOTE: 現在pxのみだけど本来はvwとかemとか入る 56 | #[derive(Clone, Debug, PartialEq)] 57 | pub enum Unit { 58 | Px 59 | } 60 | 61 | // NOTE: 色の構造体 62 | #[derive(Clone,Debug, PartialEq)] 63 | pub struct Color { 64 | pub r: u8, 65 | pub g: u8, 66 | pub b: u8, 67 | pub a: u8 68 | } 69 | 70 | pub type Specificity = (usize, usize, usize); 71 | 72 | pub fn parse(source: String) -> Stylesheet { 73 | let mut parser = Parser {pos: 0, input: source}; 74 | Stylesheet {rules: parser.parse_rules()} 75 | } 76 | 77 | 78 | impl Selector { 79 | pub fn specificity(&self) -> Specificity { 80 | let Selector::Simple(ref simple) = *self; 81 | let a = simple.id.iter().count(); 82 | let b = simple.class.len(); 83 | let c = simple.tag_name.iter().count(); 84 | (a,b,c) 85 | } 86 | } 87 | 88 | 89 | 90 | struct Parser { 91 | pos: usize, 92 | input: String 93 | } 94 | 95 | 96 | 97 | impl Parser { 98 | 99 | fn parse_rules(&mut self) -> Vec { 100 | let mut rules = Vec::new(); 101 | loop { 102 | self.consume_whitespace(); 103 | if self.eof() {break}; 104 | rules.push(self.parse_rule()); 105 | } 106 | rules 107 | } 108 | 109 | fn parse_rule(&mut self) -> Rule { 110 | Rule { 111 | selectors: self.parse_selectors(), 112 | declarations: self.parse_declarations() 113 | } 114 | } 115 | 116 | fn parse_selectors(&mut self) -> Vec { 117 | let mut selectors = Vec::new(); 118 | loop { 119 | selectors.push(Selector::Simple(self.parse_simple_selector())); 120 | self.consume_whitespace(); 121 | match self.next_char() { 122 | ',' => {self.consume_char(); self.consume_whitespace();} 123 | '{' => break, 124 | c => panic!("Unexpected character {} in selector list", c) 125 | } 126 | } 127 | selectors.sort_by(|a,b| b.specificity().cmp(&a.specificity())); 128 | selectors 129 | } 130 | 131 | fn parse_declarations(&mut self) -> Vec { 132 | assert_eq!(self.consume_char(), '{'); 133 | let mut declarations = Vec::new(); 134 | loop { 135 | self.consume_whitespace(); 136 | if self.next_char() == '}' { 137 | self.consume_char(); 138 | break; 139 | } 140 | declarations.push(self.parse_declaration()); 141 | } 142 | declarations 143 | } 144 | 145 | fn parse_simple_selector(&mut self) -> SimpleSelector { 146 | let mut selector = SimpleSelector {tag_name: None, id: None, class: Vec::new()}; 147 | while !self.eof() { 148 | match self.next_char() { 149 | '#' => { 150 | self.consume_char(); 151 | selector.id = Some(self.parse_identifier()); 152 | } 153 | '.' => { 154 | self.consume_char(); 155 | selector.class.push(self.parse_identifier()); 156 | } 157 | '*' => { 158 | self.consume_char(); 159 | } 160 | c if valid_identifier_char(c) => { 161 | selector.tag_name = Some(self.parse_identifier()); 162 | } 163 | _ => break 164 | } 165 | } 166 | selector 167 | } 168 | 169 | 170 | 171 | fn parse_declaration(&mut self) -> Declaration { 172 | let property_name = self.parse_identifier(); 173 | self.consume_whitespace(); 174 | assert_eq!(self.consume_char(), ':'); 175 | self.consume_whitespace(); 176 | let value = self.parse_value(); 177 | self.consume_whitespace(); 178 | assert_eq!(self.consume_char(), ';'); 179 | 180 | Declaration { 181 | name: property_name, 182 | value, 183 | } 184 | } 185 | 186 | fn parse_value(&mut self) -> Value { 187 | match self.next_char() { 188 | '0'..='9' => self.parse_start_with_num_value(), 189 | '#' => self.parse_color(), 190 | _ => Value::Keyword(self.parse_identifier()) 191 | } 192 | } 193 | 194 | fn parse_start_with_num_value(&mut self) -> Value { 195 | let num_value = self.parse_float(); 196 | match self.next_char() { 197 | '%' => { 198 | self.consume_char(); 199 | Value::Percentage(num_value) 200 | }, 201 | _ => Value::Length(num_value, self.parse_unit()) 202 | } 203 | } 204 | 205 | fn parse_float(&mut self) -> f32 { 206 | let s = self.consume_while(|c| match c { 207 | '0'..='9' | '.' => true, 208 | _ => false 209 | }); 210 | s.parse().unwrap() 211 | } 212 | 213 | fn parse_unit(&mut self) -> Unit { 214 | match &*self.parse_identifier().to_ascii_lowercase() { 215 | "px" => Unit::Px, 216 | _ => panic!("unrecognized unit") 217 | } 218 | } 219 | 220 | fn parse_color(&mut self) -> Value { 221 | assert_eq!(self.consume_char(), '#'); 222 | Value::ColorValue(Color { 223 | r: self.parse_hex_pair(), 224 | g: self.parse_hex_pair(), 225 | b: self.parse_hex_pair(), 226 | a: 255 227 | }) 228 | } 229 | 230 | fn parse_hex_pair(&mut self) -> u8 { 231 | let s = &self.input[self.pos..self.pos + 2]; 232 | self.pos += 2; 233 | u8::from_str_radix(s, 16).unwrap() 234 | } 235 | 236 | fn parse_identifier(&mut self) -> String { 237 | self.consume_while(valid_identifier_char) 238 | } 239 | 240 | fn next_char(&self) -> char { 241 | self.input[self.pos..].chars().next().unwrap() 242 | } 243 | 244 | fn eof(&self)->bool { 245 | self.pos >= self.input.len() 246 | } 247 | 248 | fn consume_char(&mut self) -> char { 249 | let mut iter = self.input[self.pos..].char_indices(); 250 | let (_, cur_char) = iter.next().unwrap(); 251 | let (next_pos, _) = iter.next().unwrap_or((1, ' ')); 252 | // NOTE: += next_posがなぞ 253 | self.pos += next_pos; 254 | return cur_char; 255 | } 256 | 257 | fn consume_while(&mut self, test: F) -> String where F: Fn(char) -> bool { 258 | let mut result = String::new(); 259 | while !self.eof() && test(self.next_char()) { 260 | result.push(self.consume_char()); 261 | } 262 | result 263 | } 264 | 265 | fn consume_whitespace(&mut self) { 266 | self.consume_while(char::is_whitespace); 267 | } 268 | } 269 | 270 | fn valid_identifier_char(c: char) -> bool { 271 | match c { 272 | 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => true, 273 | _ => false, 274 | } 275 | } 276 | 277 | #[cfg(test)] 278 | mod tests { 279 | use super::{parse, Stylesheet, Rule, SimpleSelector, Declaration, Value, Selector, Unit}; 280 | use crate::css::Color; 281 | 282 | #[test] 283 | fn parse_id_selector() { 284 | let target_str = "#id {margin: auto;}".to_string(); 285 | let parsed_css = parse(target_str); 286 | let selector = Selector::Simple(SimpleSelector{tag_name: None, id: Some("id".to_string()), class: vec![]}); 287 | let declaration = Declaration {name: "margin".to_string(), value: Value::Keyword("auto".to_string())}; 288 | let expected_css = Stylesheet {rules: vec![Rule {selectors: vec![selector], declarations: vec![declaration]}]}; 289 | assert_eq!(parsed_css, expected_css); 290 | } 291 | 292 | #[test] 293 | fn parse_class_selector() { 294 | let target_str = ".class {margin: auto;}".to_string(); 295 | let parsed_css = parse(target_str); 296 | let selector = Selector::Simple(SimpleSelector{tag_name: None, id: None, class: vec!["class".to_string()]}); 297 | let declaration = Declaration {name: "margin".to_string(), value: Value::Keyword("auto".to_string())}; 298 | let expected_css = Stylesheet {rules: vec![Rule {selectors: vec![selector], declarations: vec![declaration]}]}; 299 | assert_eq!(parsed_css, expected_css); 300 | } 301 | 302 | #[test] 303 | fn parse_asterisk_selector() { 304 | let target_str = "* {margin: auto;}".to_string(); 305 | let parsed_css = parse(target_str); 306 | let selector = Selector::Simple(SimpleSelector{tag_name: None, id: None, class: vec![]}); 307 | let declaration = Declaration {name: "margin".to_string(), value: Value::Keyword("auto".to_string())}; 308 | let expected_css = Stylesheet {rules: vec![Rule {selectors: vec![selector], declarations: vec![declaration]}]}; 309 | assert_eq!(parsed_css, expected_css); 310 | } 311 | 312 | #[test] 313 | fn parse_tag_name_selector() { 314 | let target_str = "input {margin: auto;}".to_string(); 315 | let parsed_css = parse(target_str); 316 | let selector = Selector::Simple(SimpleSelector{tag_name: Some("input".to_string()), id: None, class: vec![]}); 317 | let declaration = Declaration {name: "margin".to_string(), value: Value::Keyword("auto".to_string())}; 318 | let expected_css = Stylesheet {rules: vec![Rule {selectors: vec![selector], declarations: vec![declaration]}]}; 319 | assert_eq!(parsed_css, expected_css); 320 | } 321 | 322 | #[test] 323 | fn parse_keyword_declaration() { 324 | let target_str = "#id {display: flex;}".to_string(); 325 | let parsed_css = parse(target_str); 326 | let selector = Selector::Simple(SimpleSelector{tag_name: None, id: Some("id".to_string()), class: vec![]}); 327 | let declaration = Declaration {name: "display".to_string(), value: Value::Keyword("flex".to_string())}; 328 | let expected_css = Stylesheet {rules: vec![Rule {selectors: vec![selector], declarations: vec![declaration]}]}; 329 | assert_eq!(parsed_css, expected_css); 330 | } 331 | 332 | #[test] 333 | fn parse_length_declaration() { 334 | let target_str = "#id {font-size: 16px;}".to_string(); 335 | let parsed_css = parse(target_str); 336 | let selector = Selector::Simple(SimpleSelector{tag_name: None, id: Some("id".to_string()), class: vec![]}); 337 | let declaration = Declaration {name: "font-size".to_string(), value: Value::Length(16.0, Unit::Px)}; 338 | let expected_css = Stylesheet {rules: vec![Rule {selectors: vec![selector], declarations: vec![declaration]}]}; 339 | assert_eq!(parsed_css, expected_css); 340 | } 341 | 342 | #[test] 343 | fn parse_color_declaration() { 344 | let target_str = "#id {color: #FFFF00;}".to_string(); 345 | let parsed_css = parse(target_str); 346 | let selector = Selector::Simple(SimpleSelector{tag_name: None, id: Some("id".to_string()), class: vec![]}); 347 | let declaration = Declaration {name: "color".to_string(), value: Value::ColorValue(Color {r: 255, g: 255, b: 0, a: 255})}; 348 | let expected_css = Stylesheet {rules: vec![Rule {selectors: vec![selector], declarations: vec![declaration]}]}; 349 | assert_eq!(parsed_css, expected_css); 350 | } 351 | 352 | #[test] 353 | fn parse_percentage_declaration() { 354 | let target_str = "#id {width: 100%;}".to_string(); 355 | let parsed_css = parse(target_str); 356 | let selector = Selector::Simple(SimpleSelector{tag_name: None, id: Some("id".to_string()), class: vec![]}); 357 | let declaration = Declaration {name: "width".to_string(), value: Value::Percentage(100.0)}; 358 | let expected_css = Stylesheet {rules: vec![Rule {selectors: vec![selector], declarations: vec![declaration]}]}; 359 | assert_eq!(parsed_css, expected_css); 360 | } 361 | 362 | #[test] 363 | fn parse_multi_rules() { 364 | let target_str = "#id {margin: auto;} .class {margin: auto;}".to_string(); 365 | let parsed_css = parse(target_str); 366 | let id_selector = Selector::Simple(SimpleSelector{tag_name: None, id: Some("id".to_string()), class: vec![]}); 367 | let class_selector = Selector::Simple(SimpleSelector{tag_name: None, id: None, class: vec!["class".to_string()]}); 368 | let declaration = Declaration {name: "margin".to_string(), value: Value::Keyword("auto".to_string())}; 369 | let id_rule = Rule {selectors: vec![id_selector], declarations: vec![declaration.clone()]}; 370 | let class_rule = Rule {selectors: vec![class_selector], declarations: vec![declaration]}; 371 | let expected_css = Stylesheet {rules: vec![id_rule, class_rule]}; 372 | assert_eq!(parsed_css, expected_css); 373 | } 374 | 375 | } 376 | -------------------------------------------------------------------------------- /src/layout.rs: -------------------------------------------------------------------------------- 1 | // 参考: https://limpet.net/mbrubeck/2014/09/08/toy-layout-engine-5-boxes.html 2 | 3 | use crate::style::{StyledNode, Display}; 4 | use crate::layout::BoxType::{BlockNode, InlineNode, AnonymousBlock}; 5 | use crate::css::Value::{Keyword, Length}; 6 | use crate::css::Unit::Px; 7 | use std::cell::RefCell; 8 | use std::rc::Rc; 9 | 10 | #[derive(Default,Clone, Debug, PartialEq)] 11 | pub struct Dimensions { 12 | // document originに対するコンテンツエリアのポジション 13 | pub content: Rect, 14 | pub padding: EdgeSize, 15 | pub border: EdgeSize, 16 | pub margin: EdgeSize 17 | } 18 | #[derive(Default,Clone, Debug, PartialEq)] 19 | pub struct Rect { 20 | pub x: f32, 21 | pub y: f32, 22 | pub width: f32, 23 | pub height: f32 24 | } 25 | 26 | #[derive(Default,Clone, Debug, PartialEq)] 27 | pub struct EdgeSize { 28 | pub left: f32, 29 | pub right: f32, 30 | pub top: f32, 31 | pub bottom: f32 32 | } 33 | 34 | #[derive(Clone, Debug, PartialEq)] 35 | pub enum BoxType<'a> { 36 | BlockNode(&'a StyledNode<'a>), 37 | InlineNode(&'a StyledNode<'a>), 38 | AnonymousBlock 39 | } 40 | 41 | #[derive(Clone, Debug, PartialEq, )] 42 | pub struct LayoutBox<'a> { 43 | pub dimensions: Rc>, 44 | pub box_type: BoxType<'a>, 45 | pub children: Vec>, 46 | } 47 | 48 | 49 | impl<'a> LayoutBox<'a> { 50 | 51 | fn new(box_type: BoxType) -> LayoutBox { 52 | LayoutBox { 53 | box_type, 54 | dimensions: Default::default(), 55 | children: Vec::new(), 56 | } 57 | } 58 | 59 | fn get_style_node(&self) -> &StyledNode { 60 | match self.box_type { 61 | BlockNode(node) | InlineNode(node) => node, 62 | AnonymousBlock => panic!("Anonymous block box has no style node") 63 | } 64 | } 65 | 66 | } 67 | 68 | pub fn layout_tree<'a>(node: &'a StyledNode<'a>, containing_block: Rc>) -> LayoutBox<'a>{ 69 | Rc::clone(&containing_block).borrow_mut().content.height = 0.0; 70 | let mut root_box = build_layout_tree(node); 71 | root_box.layout(containing_block); 72 | root_box 73 | } 74 | 75 | // NOTE: StyledNodeをとりあえず全部LayoutBoxに変換する処理 76 | fn build_layout_tree<'a>(style_node: &'a StyledNode<'a>) -> LayoutBox<'a> { 77 | let mut root = LayoutBox::new(match style_node.display() { 78 | Display::Block => BlockNode(style_node), 79 | Display::Inline => InlineNode(style_node), 80 | Display::None => panic!("Root node has display: none.") 81 | }); 82 | 83 | for child in &style_node.children { 84 | match child.display() { 85 | Display::Block => root.children.push(build_layout_tree(child)), 86 | Display::Inline => root.get_inline_container().children.push(build_layout_tree(child)), 87 | Display::None => {} 88 | } 89 | } 90 | root 91 | } 92 | 93 | impl<'a> LayoutBox<'a> { 94 | 95 | fn layout(&mut self, containing_block: Rc>) { 96 | match self.box_type { 97 | BlockNode(_) => self.layout_block(containing_block), 98 | InlineNode(_) => {}, // FIXME:処理追加 99 | AnonymousBlock => {} 100 | } 101 | } 102 | 103 | fn layout_block(&mut self, containing_block: Rc>) { 104 | // widthを計算 105 | self.calculate_block_width(containing_block.clone()); 106 | 107 | // x,yを計算 108 | self.calculate_block_position(containing_block); 109 | 110 | // 子要素を再帰的に計算、加えてそこから現在の要素のheightを計算 111 | self.layout_block_children(); 112 | 113 | // ユーザーがheightプロパティを指定していた場合のheightの値を計算 114 | self.calculate_block_height(); 115 | } 116 | 117 | // NOTE: 対象の要素の横幅(width, border-right, padding-left, margin-left等を含んだもの)を決める 118 | fn calculate_block_width(&mut self, containing_block: Rc>) { 119 | let style = self.get_style_node(); 120 | 121 | let auto = Keyword("auto".to_string()); 122 | let mut width = style.value("width").unwrap_or(auto.clone()); 123 | 124 | let zero = Length(0.0, Px); 125 | 126 | let mut margin_left = style.lookup("margin-left", "margin", &zero); 127 | let mut margin_right = style.lookup("margin-right", "margin", &zero); 128 | 129 | let border_left = style.lookup("border-left-width", "border-width", &zero); 130 | let border_right = style.lookup("border-right-width", "border-width", &zero); 131 | 132 | let padding_left = style.lookup("padding-left", "padding", &zero); 133 | let padding_right = style.lookup("padding-right", "padding", &zero); 134 | 135 | let total: f32 = [&margin_left, &margin_right, &border_left, &border_right, 136 | &padding_left, &padding_right, &width].iter().map(|v| v.to_px()).sum(); 137 | 138 | // NOTE: もし横幅が親要素よりデカかったらmargin-leftとmargin-rightでautoになってるものの値を0にする 139 | if width != auto && total > containing_block.borrow().content.width { 140 | if margin_left == auto { 141 | margin_left = Length(0.0, Px); 142 | } 143 | if margin_right == auto { 144 | margin_right = Length(0.0, Px); 145 | } 146 | } 147 | 148 | // 親要素とこの要素の横幅の違い(この値がマイナスだったらこの要素がoverflowしてる) 149 | let underflow = containing_block.borrow().content.width - total; 150 | 151 | match (width == auto, margin_left == auto, margin_right == auto) { 152 | // NOTE: width,margin_left,margin_rightが全て10pxみたいに固定値の場合margin_rightを調整する 153 | (false, false, false) => { 154 | margin_right = Length(margin_right.to_px() + underflow, Px); 155 | }, 156 | // NOTE: margin-right, margin-leftのどちらかの値がautoだった場合そちらの方のプロパティでunderflowを調整する 157 | (false, false, true) => {margin_right = Length(underflow, Px);}, 158 | (false, true, false) => {margin_left = Length(underflow, Px);}, 159 | 160 | (true, _, _) => { 161 | // NOTE: widthがautoでmargin系プロパティがautoの場合marginはゼロになる 162 | if margin_left == auto {margin_left = Length(0.0, Px);} 163 | if margin_right == auto {margin_right = Length(0.0, Px);} 164 | 165 | // NOTE: widthが残っている幅を全てとるようにする 166 | if underflow >= 0.0 { 167 | width = Length(underflow, Px); 168 | } else { 169 | // NOTE: もし要素がoverflowしていた場合はwidthをマイナス値にすることができないのでmargin_rightをマイナス値にする 170 | width = Length(0.0, Px); 171 | margin_right = Length(margin_right.to_px() + underflow, Px); 172 | } 173 | 174 | }, 175 | // NOTE: margin_left,margin_rightがどちらともautoの場合仲良く半分ずつoverflowを担当する、こうすると要素が真ん中にくる 176 | (false, true, true) => { 177 | margin_left = Length(underflow / 2.0, Px); 178 | margin_right = Length(underflow / 2.0, Px); 179 | } 180 | } 181 | 182 | let this_dimension = &mut self.dimensions.borrow_mut(); 183 | this_dimension.content.width = width.to_px(); 184 | 185 | this_dimension.padding.left = padding_left.to_px(); 186 | this_dimension.padding.right = padding_right.to_px(); 187 | 188 | this_dimension.border.left = border_left.to_px(); 189 | this_dimension.border.right = border_right.to_px(); 190 | 191 | this_dimension.margin.left = margin_left.to_px(); 192 | this_dimension.margin.right = margin_right.to_px(); 193 | } 194 | 195 | // NOTE: 対象のページ上の位置を計算、つまりxとyを計算 196 | // xとyは親要素のx,yとheight(yの場合)とmargin, padding, borderの値足した値 197 | fn calculate_block_position(&mut self, containing_block_ref: Rc>) { 198 | let style = self.get_style_node(); 199 | let this_dimensions = &mut self.dimensions.borrow_mut(); 200 | 201 | let zero = Length(0.0, Px); 202 | 203 | let containing_block = containing_block_ref.borrow(); 204 | 205 | this_dimensions.margin.top = style.lookup("margin-top", "margin", &zero).to_px(); 206 | this_dimensions.margin.bottom = style.lookup("margin-bottom", "margin", &zero).to_px(); 207 | 208 | this_dimensions.border.top = style.lookup("border-top-width", "border-width", &zero).to_px(); 209 | this_dimensions.border.bottom = style.lookup("border-bottom-width", "border-width", &zero).to_px(); 210 | 211 | this_dimensions.padding.top = style.lookup("padding-top", "padding", &zero).to_px(); 212 | this_dimensions.padding.bottom = style.lookup("padding-bottom", "padding", &zero).to_px(); 213 | 214 | this_dimensions.content.x = containing_block.content.x + this_dimensions.margin.left + this_dimensions.border.left + this_dimensions.padding.left; 215 | this_dimensions.content.y = containing_block.content.height + containing_block.content.y + this_dimensions.margin.top + this_dimensions.border.top + this_dimensions.padding.top; 216 | } 217 | 218 | fn layout_block_children(&mut self) { 219 | for child in &mut self.children { 220 | child.layout(self.dimensions.clone()); 221 | let this_dimensions = &mut self.dimensions.borrow_mut(); 222 | // loopでこの要素のheightに子要素のmargin含めたheightを足していって最終的に正しいheightを算出する 223 | this_dimensions.content.height = this_dimensions.content.height + child.dimensions.borrow().margin_box().height; 224 | } 225 | } 226 | 227 | // NOTE: デフォルトでは子要素のheightの合計から対象要素のheightを算出するけど明示的にheightプロパティで指定されていた場合はその値を使う 228 | fn calculate_block_height(&mut self) { 229 | if let Some(Length(h, Px)) = self.get_style_node().value("height") { 230 | self.dimensions.borrow_mut().content.height = h; 231 | } 232 | } 233 | 234 | fn get_inline_container(&mut self) -> &mut LayoutBox<'a> { 235 | match self.box_type { 236 | InlineNode(_) | AnonymousBlock => self, 237 | BlockNode(_) => { 238 | match self.children.last() { 239 | Some(&LayoutBox {box_type: AnonymousBlock,..}) => {}, 240 | _ => self.children.push(LayoutBox::new(AnonymousBlock)) 241 | } 242 | self.children.last_mut().unwrap() 243 | } 244 | } 245 | } 246 | } 247 | 248 | impl Dimensions { 249 | pub fn padding_box(&self) -> Rect { 250 | self.content.expanded_by(&self.padding) 251 | } 252 | 253 | pub fn border_box(&self) -> Rect { 254 | self.padding_box().expanded_by(&self.border) 255 | } 256 | 257 | // marginまで含めたx,y,width,heightの値を返す 258 | pub fn margin_box(&self) -> Rect { 259 | self.border_box().expanded_by(&self.margin) 260 | } 261 | } 262 | 263 | impl Rect { 264 | // 現在のRectのそれぞれのプロパティに対してedgeの値を足す 265 | pub fn expanded_by(&self, edge: &EdgeSize) -> Rect { 266 | Rect { 267 | x: self.x - edge.left, 268 | y: self.y - edge.top, 269 | width: self.width + edge.left + edge.right, 270 | height: self.height + edge.top + edge.bottom, 271 | } 272 | } 273 | } 274 | 275 | #[cfg(test)] 276 | mod tests { 277 | use crate::style::{StyledNode, PropertyMap}; 278 | use crate::dom::{Node, AttrMap, NodeType, ElementData}; 279 | use crate::css::{Value, Unit}; 280 | use super::{Dimensions}; 281 | use crate::layout::{layout_tree, LayoutBox, Rect, BoxType, EdgeSize}; 282 | use crate::layout::BoxType::AnonymousBlock; 283 | use std::cell::RefCell; 284 | use std::rc::Rc; 285 | 286 | // NOTE: テストしたいもの 287 | // margin, border, padding, width, height, x, y 288 | 289 | fn create_element_node(tag_name: String, attributes: AttrMap, children: Vec) -> Node { 290 | let this_element = NodeType::Element(ElementData {tag_name, attributes}); 291 | Node {node_type: this_element, children} 292 | } 293 | 294 | fn create_styled_node<'a>(node: &'a Node, specified_values: PropertyMap, children: Vec>) -> StyledNode<'a> { 295 | StyledNode {node, specified_values, children} 296 | } 297 | 298 | fn create_viewport() -> Rc> { 299 | let mut viewport: Dimensions = Default::default(); 300 | viewport.content.width = 800.0; 301 | viewport.content.height = 600.0; 302 | Rc::new(RefCell::new(viewport)) 303 | } 304 | 305 | fn create_edge_size(left: Option, right: Option, top: Option, bottom: Option) -> EdgeSize { 306 | EdgeSize { 307 | left: left.unwrap_or(0.0), 308 | right: right.unwrap_or(0.0), 309 | top: top.unwrap_or(0.0), 310 | bottom: bottom.unwrap_or(0.0) 311 | } 312 | } 313 | 314 | fn create_anonymous_layout_block(children: Vec) -> LayoutBox { 315 | let dimension = Dimensions { 316 | content: Rect { 317 | x: 0.0, 318 | y: 0.0, 319 | width: 0.0, 320 | height: 0.0 321 | }, 322 | margin: create_edge_size(None,None,None,None), 323 | border: create_edge_size(None,None,None,None), 324 | padding: create_edge_size(None,None,None,None), 325 | }; 326 | LayoutBox { 327 | dimensions: Rc::new(RefCell::new(dimension)), 328 | box_type: AnonymousBlock, 329 | children 330 | } 331 | } 332 | 333 | #[test] 334 | fn test_layout_only_block_node_tree() { 335 | //
block {margin: 8.0, padding: 4.0, width: auto} 336 | //
block {margin-left: 2.0, width: 100, height: 200} 337 | //
338 | let child_element = create_element_node("div".to_string(), AttrMap::new(), vec![]); 339 | let parent_element = create_element_node("div".to_string(), AttrMap::new(), vec![child_element.clone()]); 340 | 341 | let mut child_property_map = PropertyMap::new(); 342 | child_property_map.insert("display".to_string(), Value::Keyword("block".to_string())); 343 | child_property_map.insert("margin-left".to_string(), Value::Length(2.0, Unit::Px)); 344 | child_property_map.insert("width".to_string(), Value::Length(100.0, Unit::Px)); 345 | child_property_map.insert("height".to_string(), Value::Length(200.0, Unit::Px)); 346 | 347 | let mut parent_property_map = PropertyMap::new(); 348 | parent_property_map.insert("display".to_string(), Value::Keyword("block".to_string())); 349 | parent_property_map.insert("margin".to_string(), Value::Length(8.0, Unit::Px)); 350 | parent_property_map.insert("padding".to_string(), Value::Length(4.0, Unit::Px)); 351 | parent_property_map.insert("width".to_string(), Value::Keyword("auto".to_string())); 352 | 353 | let styled_child_node = create_styled_node(&child_element, child_property_map, vec![]); 354 | let styled_parent_node = create_styled_node(&parent_element, parent_property_map, vec![styled_child_node.clone()]); 355 | 356 | let viewport = create_viewport(); 357 | let layout = layout_tree(&styled_parent_node, viewport); 358 | 359 | let expected_child_dimension = Rc::new(RefCell::new(Dimensions { 360 | content: Rect { 361 | x: 14.0, 362 | y: 12.0, 363 | width: 100.0, 364 | height: 200.0 365 | }, 366 | margin: create_edge_size(Some(2.0), Some(674.0),None,None), // rightは親要素のwidth776pxからmargin_left2px,width100pxを引いて計算 367 | border: create_edge_size(None,None,None,None), 368 | padding: create_edge_size(None,None,None,None), 369 | })); 370 | 371 | let expected_parent_dimension = Rc::new(RefCell::new(Dimensions { 372 | content: Rect { 373 | x: 12.0, 374 | y: 12.0, 375 | width: 776.0, // 800 - 16 - 8 376 | height: 200.0 377 | }, 378 | margin: create_edge_size(Some(8.0),Some(8.0),Some(8.0),Some(8.0)), 379 | border: create_edge_size(None,None,None,None), 380 | padding: create_edge_size(Some(4.0),Some(4.0),Some(4.0),Some(4.0)), 381 | })); 382 | 383 | let expected_child_layout_box = LayoutBox { 384 | dimensions: expected_child_dimension, 385 | box_type: BoxType::BlockNode(&styled_child_node), 386 | children: vec![] 387 | }; 388 | // let anonymous_container = create_anonymous_layout_block(vec![expected_child_layout_box]); 389 | let expected_parent_layout_box = LayoutBox { 390 | dimensions: expected_parent_dimension, 391 | box_type: BoxType::BlockNode(&styled_parent_node), 392 | children: vec![expected_child_layout_box] 393 | }; 394 | 395 | assert_eq!(layout, expected_parent_layout_box); 396 | 397 | } 398 | 399 | } 400 | -------------------------------------------------------------------------------- /src/style.rs: -------------------------------------------------------------------------------- 1 | // NOTE: https://limpet.net/mbrubeck/2014/08/23/toy-layout-engine-4-style.html 2 | 3 | use std::collections::{HashMap}; 4 | use crate::css::{Value, Selector, SimpleSelector, Specificity, Rule, Stylesheet, Unit}; 5 | use crate::dom::{Node, ElementData, NodeType}; 6 | 7 | pub type PropertyMap = HashMap; 8 | 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub struct StyledNode<'a> { 11 | pub node: &'a Node, 12 | pub specified_values: PropertyMap, 13 | pub children: Vec>, 14 | } 15 | 16 | pub enum Display { 17 | Inline, 18 | Block, 19 | None 20 | } 21 | 22 | impl<'a> StyledNode<'a> { 23 | pub fn value(&self, name: &str) -> Option { 24 | self.specified_values.get(name).map(|v| v.clone()) 25 | } 26 | 27 | pub fn lookup(&self, name: &str, fallback_name: &str, default: &Value) -> Value { 28 | self.value(name).unwrap_or_else(||self.value(fallback_name).unwrap_or_else(|| default.clone())) 29 | } 30 | 31 | pub fn display(&self) -> Display { 32 | match self.value("display") { 33 | Some(Value::Keyword(s)) => match &*s { 34 | "block" => Display::Block, 35 | "none" => Display::None, 36 | _ => Display::Inline 37 | }, 38 | _ => Display::Inline 39 | } 40 | } 41 | 42 | } 43 | 44 | const INHERIT_PROPS: [&str; 4] = ["color", "font-size", "font-weight", "line-height"]; 45 | 46 | pub fn style_tree<'a>(root: &'a Node, stylesheet: &'a Stylesheet) -> StyledNode<'a> { 47 | let default_prop_map = create_default_props(); 48 | 49 | style_tree_rec(root, stylesheet, &default_prop_map) 50 | } 51 | 52 | fn create_default_props() -> PropertyMap { 53 | let mut default_prop_map = PropertyMap::new(); 54 | default_prop_map.insert("color".to_string(), Value::Keyword("#000000".to_string())); 55 | default_prop_map.insert("font-size".to_string(), Value::Length(16.0, Unit::Px)); 56 | default_prop_map.insert("font-weight".to_string(), Value::Keyword("normal".to_string())); 57 | default_prop_map.insert("line-height".to_string(), Value::Keyword("normal".to_string())); 58 | default_prop_map 59 | } 60 | 61 | fn style_tree_rec<'a>(root: &'a Node, stylesheet: &'a Stylesheet, parent_prop_map: &PropertyMap) -> StyledNode<'a> { 62 | let specified_values = match root.node_type { 63 | NodeType::Element(ref elem) => specified_values(elem, stylesheet, parent_prop_map), 64 | NodeType::Text(_) => HashMap::new() 65 | }; 66 | StyledNode { 67 | node: root, 68 | specified_values: specified_values.clone(), 69 | children: root.children.iter().map(|child| style_tree_rec(child, stylesheet, &specified_values)).collect(), 70 | } 71 | } 72 | 73 | // その要素に渡すDeclarationのプロパティ名と値のマップを返す 74 | fn specified_values(elem: &ElementData, stylesheet: &Stylesheet, parent_prop_map: &PropertyMap) -> PropertyMap { 75 | let mut values: PropertyMap = HashMap::new(); 76 | 77 | // 継承するのがデフォルトの値に対して全部親から値をとる 78 | for prop_name in INHERIT_PROPS.iter() { 79 | match parent_prop_map.get(&prop_name.to_string()) { 80 | Some(x) => {values.insert(prop_name.to_string(), x.clone());}, 81 | None => () 82 | }; 83 | } 84 | 85 | let mut rules = matching_rules(elem, stylesheet); 86 | 87 | rules.sort_by(|&(a, _), &(b,_)| a.cmp(&b)); 88 | for (_, rule) in rules { 89 | for declaration in &rule.declarations { 90 | if declaration.value == Value::Keyword("inherit".to_string()) { 91 | let parent_value_opt = parent_prop_map.get(declaration.name.as_str()); 92 | match parent_value_opt { 93 | Some(x) => {values.insert(declaration.name.clone(), x.clone());}, 94 | None => () 95 | }; 96 | }else { 97 | values.insert(declaration.name.clone(), declaration.value.clone()); 98 | } 99 | } 100 | } 101 | return values; 102 | } 103 | 104 | 105 | type MatchedRule<'a> = (Specificity, &'a Rule); 106 | 107 | //NOTE: ルールの配列に対してその要素に対応するかをそれぞれ判定 108 | fn matching_rules<'a>(elem: &ElementData, stylesheet: &'a Stylesheet) -> Vec> { 109 | stylesheet.rules.iter().filter_map(|rule| match_rule(elem, rule)).collect() 110 | } 111 | 112 | 113 | 114 | // そのルールの持つセレクタに要素が合致するか判定 115 | fn match_rule<'a>(elem: &ElementData, rule: &'a Rule) -> Option> { 116 | rule.selectors.iter() 117 | .find(|selector| matches(elem, *selector)) 118 | .map(|selector| (selector.specificity(), rule)) 119 | } 120 | 121 | 122 | // NOTE: そのセレクタがそのElementに合致するか判定 123 | fn matches(elem: &ElementData, selector: &Selector) -> bool { 124 | match *selector { 125 | Selector::Simple(ref simple_selector) => matches_simple_selector(elem, simple_selector) 126 | } 127 | } 128 | 129 | fn matches_simple_selector(elem: &ElementData, selector: &SimpleSelector) -> bool { 130 | // tag_name.iter(): Optionのiterでtag_nameの存在確認 -> anyにより存在していたうえでtag_nameと合致するかを確認、合致しなければreturn false 131 | if selector.tag_name.iter().any(|name| elem.tag_name != *name) { 132 | return false; 133 | } 134 | 135 | if selector.id.iter().any(|id| elem.id() != Some(id)) { 136 | return false; 137 | } 138 | 139 | let elem_classes = elem.classes(); 140 | if selector.class.iter().any(|class| !elem_classes.contains(&**class)) { 141 | return false; 142 | } 143 | 144 | return true; 145 | } 146 | 147 | // NOTE: 処理の手順を自分なりにまとめます 148 | // 目標: そのNodeに対応したCSSのDeclarationを付与した要素のツリー(StyledNode)を作成する 149 | 150 | // 手順: 151 | // 以下を子ノードに対して再帰的に繰り返す 152 | // 1. Rulesのセレクタの中からそのノードに一致するセレクタを探し、一致するRuleを配列にする 153 | // 2. そのRuleの配列をセレクタの優先順位の合計に沿ってソートする 154 | // 3. Ruleの配列からDeclarationのプロパティ名とプロパティの値をHashMapに代入しそれを配列化する 155 | // 4. 配列にしたDeclarationをspecified_valueとしてNodeのプロパティに入れる. 156 | 157 | #[cfg(test)] 158 | mod tests { 159 | use super::style_tree; 160 | use crate::dom::{Node, NodeType, AttrMap, ElementData}; 161 | use crate::css::{Stylesheet, Rule, Selector, SimpleSelector, Value, Declaration, Unit}; 162 | use crate::style::{StyledNode, PropertyMap}; 163 | 164 | 165 | fn create_element_node(tag_name: String, attributes: AttrMap, children: Vec) -> Node { 166 | let this_element = NodeType::Element(ElementData {tag_name, attributes}); 167 | Node {node_type: this_element, children} 168 | } 169 | 170 | fn create_text_node(text: &str) -> Node { 171 | let this_element = NodeType::Text(text.to_string()); 172 | Node {node_type: this_element, children: vec![]} 173 | } 174 | 175 | fn create_styled_node<'a>(node: &'a Node, specified_values: PropertyMap, children: Vec>) -> StyledNode<'a> { 176 | StyledNode {node, specified_values, children} 177 | } 178 | 179 | fn create_simple_selector_rule(selector_data: Vec<(Option<&str>, Option<&str>, Vec<&str>)>, declaration_data: Vec<(&str, Value)>) -> Rule { 180 | let mut selectors: Vec = vec![]; 181 | let mut declarations: Vec = vec![]; 182 | 183 | for data in selector_data { 184 | let selector = Selector::Simple(SimpleSelector { 185 | tag_name: data.0.and_then(|x| Some(x.to_string())), id: data.1.and_then(|x| Some(x.to_string())), class: data.2.iter().map(|x|x.to_string()).collect() 186 | }); 187 | selectors.push(selector); 188 | } 189 | 190 | for data in declaration_data { 191 | let declaration = Declaration { 192 | name: data.0.to_string(), 193 | value: data.1 194 | }; 195 | declarations.push(declaration); 196 | } 197 | 198 | Rule { 199 | selectors, 200 | declarations 201 | } 202 | } 203 | 204 | fn create_inherit_props_map() -> PropertyMap { 205 | let mut inherit_prop_map = PropertyMap::new(); 206 | inherit_prop_map.insert("color".to_string(), Value::Keyword("#000000".to_string())); 207 | inherit_prop_map.insert("font-size".to_string(), Value::Length(16.0, Unit::Px)); 208 | inherit_prop_map.insert("font-weight".to_string(), Value::Keyword("normal".to_string())); 209 | inherit_prop_map.insert("line-height".to_string(), Value::Keyword("normal".to_string())); 210 | inherit_prop_map 211 | } 212 | 213 | #[test] 214 | fn test_merge_one_div_and_one_rule() { 215 | 216 | let target_element = create_element_node("div".to_string(), AttrMap::new(), vec![]); 217 | let body = create_element_node("body".to_string(), AttrMap::new(), vec![target_element.clone()]); 218 | let html = create_element_node("html".to_string(), AttrMap::new(), vec![body.clone()]); 219 | 220 | let target_stylesheet = Stylesheet {rules: vec![ 221 | create_simple_selector_rule(vec![(Some("div"), None, vec![])], vec![ 222 | ("margin", Value::Keyword("auto".to_string())), ("padding", Value::Length(4.0, Unit::Px)) 223 | ]) 224 | ]}; 225 | let styled_html = style_tree(&html, &target_stylesheet); 226 | 227 | let mut expected_property_map = create_inherit_props_map(); 228 | expected_property_map.insert("margin".to_string(), Value::Keyword("auto".to_string())); 229 | expected_property_map.insert("padding".to_string(), Value::Length(4.0, Unit::Px)); 230 | 231 | let expected_styled_target_node = create_styled_node(&target_element, expected_property_map, vec![]); 232 | let expected_styled_body = create_styled_node(&body, create_inherit_props_map(), vec![expected_styled_target_node]); 233 | let expected_styled_html = create_styled_node(&html, create_inherit_props_map(), vec![expected_styled_body]); 234 | 235 | assert_eq!(styled_html, expected_styled_html); 236 | } 237 | 238 | #[test] 239 | fn test_merge_style_rule_by_id() { 240 | let id = "id1".to_string(); 241 | let mut target_attr = AttrMap::new(); 242 | target_attr.insert("id".to_string(), id.clone()); 243 | let target_element = create_element_node("div".to_string(), target_attr, vec![]); 244 | let body = create_element_node("body".to_string(), AttrMap::new(), vec![target_element.clone()]); 245 | let html = create_element_node("html".to_string(), AttrMap::new(), vec![body.clone()]); 246 | 247 | let target_stylesheet = Stylesheet {rules: vec![ 248 | create_simple_selector_rule(vec![(None, Some(id.clone().as_str()), vec![])], vec![ 249 | ("margin", Value::Keyword("auto".to_string())), ("padding", Value::Length(4.0, Unit::Px)) 250 | ]) 251 | ]}; 252 | 253 | let styled_html = style_tree(&html, &target_stylesheet); 254 | 255 | let mut expected_property_map = create_inherit_props_map(); 256 | expected_property_map.insert("margin".to_string(), Value::Keyword("auto".to_string())); 257 | expected_property_map.insert("padding".to_string(), Value::Length(4.0, Unit::Px)); 258 | 259 | let expected_styled_target_node = create_styled_node(&target_element, expected_property_map, vec![]); 260 | let expected_styled_body = create_styled_node(&body, create_inherit_props_map(), vec![expected_styled_target_node]); 261 | let expected_styled_html = create_styled_node(&html, create_inherit_props_map(), vec![expected_styled_body]); 262 | 263 | assert_eq!(styled_html, expected_styled_html); 264 | 265 | } 266 | 267 | #[test] 268 | fn test_merge_style_rule_by_class() { 269 | let class = "class1".to_string(); 270 | let mut target_attr = AttrMap::new(); 271 | target_attr.insert("class".to_string(), class.clone()); 272 | let target_element = create_element_node("div".to_string(), target_attr, vec![]); 273 | let body = create_element_node("body".to_string(), AttrMap::new(), vec![target_element.clone()]); 274 | let html = create_element_node("html".to_string(), AttrMap::new(), vec![body.clone()]); 275 | 276 | let target_stylesheet = Stylesheet {rules: vec![ 277 | create_simple_selector_rule(vec![(None, None, vec![class.as_str()])], vec![ 278 | ("margin", Value::Keyword("auto".to_string())), ("padding", Value::Length(4.0, Unit::Px)) 279 | ]) 280 | ]}; 281 | 282 | let styled_html = style_tree(&html, &target_stylesheet); 283 | 284 | let mut expected_property_map = create_inherit_props_map(); 285 | expected_property_map.insert("margin".to_string(), Value::Keyword("auto".to_string())); 286 | expected_property_map.insert("padding".to_string(), Value::Length(4.0, Unit::Px)); 287 | 288 | let expected_styled_target_node = create_styled_node(&target_element, expected_property_map, vec![]); 289 | let expected_styled_body = create_styled_node(&body, create_inherit_props_map(), vec![expected_styled_target_node]); 290 | let expected_styled_html = create_styled_node(&html, create_inherit_props_map(), vec![expected_styled_body]); 291 | 292 | assert_eq!(styled_html, expected_styled_html); 293 | 294 | } 295 | 296 | #[test] 297 | fn test_merge_nodes_including_text_node_and_style() { 298 | let text_node = create_text_node("sample"); 299 | let target_element = create_element_node("div".to_string(), AttrMap::new(), vec![text_node.clone()]); 300 | let body = create_element_node("body".to_string(), AttrMap::new(), vec![target_element.clone()]); 301 | let html = create_element_node("html".to_string(), AttrMap::new(), vec![body.clone()]); 302 | 303 | let target_stylesheet = Stylesheet {rules: vec![ 304 | create_simple_selector_rule(vec![(Some("div"), None, vec![])], vec![ 305 | ("margin", Value::Keyword("auto".to_string())), ("padding", Value::Length(4.0, Unit::Px)) 306 | ]) 307 | ]}; 308 | let styled_html = style_tree(&html, &target_stylesheet); 309 | 310 | let mut expected_property_map = create_inherit_props_map(); 311 | expected_property_map.insert("margin".to_string(), Value::Keyword("auto".to_string())); 312 | expected_property_map.insert("padding".to_string(), Value::Length(4.0, Unit::Px)); 313 | 314 | let expected_styled_text_node = create_styled_node(&text_node, PropertyMap::new(), vec![]); 315 | let expected_styled_target_node = create_styled_node(&target_element, expected_property_map, vec![expected_styled_text_node]); 316 | let expected_styled_body = create_styled_node(&body, create_inherit_props_map(), vec![expected_styled_target_node]); 317 | let expected_styled_html = create_styled_node(&html, create_inherit_props_map(), vec![expected_styled_body]); 318 | 319 | assert_eq!(styled_html, expected_styled_html); 320 | } 321 | 322 | #[test] 323 | fn test_merge_a_element_and_multi_rules() { 324 | let id = "id1".to_string(); 325 | let mut attr = AttrMap::new(); 326 | attr.insert("id".to_string(), id.clone()); 327 | let target_element = create_element_node("div".to_string(), attr, vec![]); 328 | let body = create_element_node("body".to_string(), AttrMap::new(), vec![target_element.clone()]); 329 | let html = create_element_node("html".to_string(), AttrMap::new(), vec![body.clone()]); 330 | 331 | let target_stylesheet = Stylesheet {rules: vec![ 332 | create_simple_selector_rule(vec![(Some("div"), None, vec![])], vec![ 333 | ("margin", Value::Keyword("auto".to_string())) 334 | ]), 335 | create_simple_selector_rule(vec![(None, Some(id.as_str()),vec![])], vec![("padding", Value::Length(4.0, Unit::Px))]) 336 | ]}; 337 | let styled_html = style_tree(&html, &target_stylesheet); 338 | 339 | let mut expected_property_map = create_inherit_props_map(); 340 | expected_property_map.insert("margin".to_string(), Value::Keyword("auto".to_string())); 341 | expected_property_map.insert("padding".to_string(), Value::Length(4.0, Unit::Px)); 342 | 343 | let expected_styled_target_node = create_styled_node(&target_element, expected_property_map, vec![]); 344 | let expected_styled_body = create_styled_node(&body, create_inherit_props_map(), vec![expected_styled_target_node]); 345 | let expected_styled_html = create_styled_node(&html, create_inherit_props_map(), vec![expected_styled_body]); 346 | 347 | assert_eq!(styled_html, expected_styled_html); 348 | } 349 | 350 | #[test] 351 | fn test_merge_multi_elements_and_a_rule() { 352 | 353 | let target_element1 = create_element_node("div".to_string(), AttrMap::new(), vec![]); 354 | let target_element2 = create_element_node("div".to_string(), AttrMap::new(), vec![]); 355 | let body = create_element_node("body".to_string(), AttrMap::new(), vec![target_element1.clone(), target_element2.clone()]); 356 | let html = create_element_node("html".to_string(), AttrMap::new(), vec![body.clone()]); 357 | 358 | let target_stylesheet = Stylesheet {rules: vec![ 359 | create_simple_selector_rule(vec![(Some("div"), None, vec![])], vec![ 360 | ("margin", Value::Keyword("auto".to_string())) 361 | ]) 362 | ]}; 363 | let styled_html = style_tree(&html, &target_stylesheet); 364 | 365 | let mut expected_property_map = create_inherit_props_map(); 366 | expected_property_map.insert("margin".to_string(), Value::Keyword("auto".to_string())); 367 | 368 | let expected_styled_target_node1 = create_styled_node(&target_element1, expected_property_map.clone(), vec![]); 369 | let expected_styled_target_node2 = create_styled_node(&target_element1, expected_property_map, vec![]); 370 | let expected_styled_body = create_styled_node(&body, create_inherit_props_map(), vec![expected_styled_target_node1, expected_styled_target_node2]); 371 | let expected_styled_html = create_styled_node(&html, create_inherit_props_map(), vec![expected_styled_body]); 372 | 373 | assert_eq!(styled_html, expected_styled_html); 374 | } 375 | 376 | #[test] 377 | fn test_inherit_prop_when_specified_inherit_as_value() { 378 | let target_element = create_element_node("div".to_string(), AttrMap::new(), vec![]); 379 | let body = create_element_node("body".to_string(), AttrMap::new(), vec![target_element.clone()]); 380 | let html = create_element_node("html".to_string(), AttrMap::new(), vec![body.clone()]); 381 | 382 | let target_stylesheet = Stylesheet {rules: vec![ 383 | create_simple_selector_rule(vec![(Some("div"), None, vec![])], vec![ 384 | ("margin", Value::Keyword("inherit".to_string())) 385 | ]), 386 | create_simple_selector_rule(vec![(Some("body"), None, vec![])], vec![ 387 | ("margin", Value::Length(4.0, Unit::Px)) 388 | ]), 389 | ]}; 390 | 391 | let styled_html = style_tree(&html, &target_stylesheet); 392 | 393 | let mut expected_property_map = create_inherit_props_map(); 394 | expected_property_map.insert("margin".to_string(), Value::Length(4.0, Unit::Px)); 395 | 396 | let expected_styled_target_node = create_styled_node(&target_element, expected_property_map.clone(), vec![]); 397 | let expected_styled_body = create_styled_node(&body, expected_property_map, vec![expected_styled_target_node]); 398 | let expected_styled_html = create_styled_node(&html, create_inherit_props_map(), vec![expected_styled_body]); 399 | 400 | assert_eq!(styled_html, expected_styled_html); 401 | 402 | } 403 | 404 | 405 | } --------------------------------------------------------------------------------