Goal: Understand how to transform html and css to image.
If I write simple html and css:
<html>
<body>
<div class="outer">
<div class="inner" />
<div>
</body>
</html>
* { display: block; padding: 12px; }
.outer { background: #800000; }
.inner { background: #0000ff; }
I execute this:
node_modules/.bin/ts-node example/toy-engine-cli.ts --css=index.css --html=index.html | display
I will get this image!
Current Status
node_modules/.bin/ts-node example/demo.ts | display
It works!
Look back to pipeline.
https://github.com/sanemat/ts-toy-engine
https://dev.to/sanemat/let-s-build-browser-engine-in-typescript-vol0-toy-browser-engine-egm
HTML -(HTML parser)-> DOM
Not Yet!
shortcut
// <html>
// <body>
// <div class="outer">
// <div class="inner" />
// <div>
// </body>
// </html>
const domNode = elem("html", new Map([]), []);
const body = elem("body", new Map([]), []);
const outer = elem("div", new Map([["class", "outer"]]), []);
const inner = elem("div", new Map([["class", "inner"]]), []);
domNode.children.push(body);
body.children.push(outer);
outer.children.push(inner);
// console.dir(domNode, { depth: null });
//
// DomNode {
// children: [
// DomNode {
// children: [
// DomNode {
// children: [
// DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'outer' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData { tagName: 'body', attributes: Map {} }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData { tagName: 'html', attributes: Map {} }
// }
// }
CSS -(CSS Parser)-> Rules
Not Yet!
shortcut
// * { display: block; padding: 12px; }
// .outer { background: maroon; /* 800000 */ }
// .inner { background: blue; /* 0000ff */ }
const stylesheet = new Stylesheet([]);
stylesheet.rules.push(
new Rule(
[new Selector.Simple(new SimpleSelector(null, null, []))],
[
new Declaration("display", new CssValue.Keyword("block")),
new Declaration("padding", new CssValue.Length(12, Unit.Px))
]
)
);
stylesheet.rules.push(
new Rule(
[new Selector.Simple(new SimpleSelector(null, null, ["outer"]))],
[new Declaration("background", new CssValue.ColorValue(new Color(128, 0, 0, 255)))]
)
);
stylesheet.rules.push(
new Rule(
[new Selector.Simple(new SimpleSelector(null, null, ["inner"]))],
[new Declaration("background", new CssValue.ColorValue(new Color(0, 0, 255, 255)))]
)
);
// console.dir(stylesheet, { depth: null });
DOM+Rules -> Style Tree
const styleRoot = styleTree(domNode, stylesheet);
// console.dir(styleRoot, { depth: null });
// Stylesheet {
// rules: [
// Rule {
// selectors: [
// Simple {
// format: 0,
// selector: SimpleSelector { tagName: null, id: null, classValue: [] }
// }
// ],
// declarations: [
// Declaration {
// name: 'display',
// value: Keyword { format: 0, keyword: 'block' }
// },
// Declaration {
// name: 'padding',
// value: Length { format: 1, length: 12, unit: 0 }
// }
// ]
// },
// Rule {
// selectors: [
// Simple {
// format: 0,
// selector: SimpleSelector {
// tagName: null,
// id: null,
// classValue: [ 'outer' ]
// }
// }
// ],
// declarations: [
// Declaration {
// name: 'background',
// value: ColorValue {
// format: 2,
// colorValue: Color { r: 128, g: 0, b: 0, a: 255 }
// }
// }
// ]
// },
// Rule {
// selectors: [
// Simple {
// format: 0,
// selector: SimpleSelector {
// tagName: null,
// id: null,
// classValue: [ 'inner' ]
// }
// }
// ],
// declarations: [
// Declaration {
// name: 'background',
// value: ColorValue {
// format: 2,
// colorValue: Color { r: 0, g: 0, b: 255, a: 255 }
// }
// }
// ]
// }
// ]
// }
Style Tree -> Layout Tree
const viewport = new Dimensions(
new Rect(0, 0, 200, 100),
new EdgeSizes(0, 0, 0, 0),
new EdgeSizes(0, 0, 0, 0),
new EdgeSizes(0, 0, 0, 0)
);
const layoutRoot = layoutTree(styleRoot, viewport);
// console.dir(layoutRoot, { depth: null });
// LayoutBox {
// dimensions: Dimensions {
// content: Rect { x: 12, y: 12, width: 176, height: 72 },
// padding: EdgeSizes { left: 12, right: 12, top: 12, bottom: 12 },
// border: EdgeSizes { left: 0, right: 0, top: 0, bottom: 0 },
// margin: EdgeSizes { left: 0, right: 0, top: 0, bottom: 0 }
// },
// boxType: BlockNode {
// format: 0,
// styledNode: StyledNode {
// node: DomNode {
// children: [
// DomNode {
// children: [
// DomNode {
// children: [
// DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'outer' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData { tagName: 'body', attributes: Map {} }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData { tagName: 'html', attributes: Map {} }
// }
// },
// specifiedValues: Map {
// 'display' => Keyword { format: 0, keyword: 'block' },
// 'padding' => Length { format: 1, length: 12, unit: 0 }
// },
// children: [
// StyledNode {
// node: DomNode {
// children: [
// DomNode {
// children: [
// DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'outer' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData { tagName: 'body', attributes: Map {} }
// }
// },
// specifiedValues: Map {
// 'display' => Keyword { format: 0, keyword: 'block' },
// 'padding' => Length { format: 1, length: 12, unit: 0 }
// },
// children: [
// StyledNode {
// node: DomNode {
// children: [
// DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'outer' }
// }
// }
// },
// specifiedValues: Map {
// 'display' => Keyword { format: 0, keyword: 'block' },
// 'padding' => Length { format: 1, length: 12, unit: 0 },
// 'background' => ColorValue {
// format: 2,
// colorValue: Color { r: 128, g: 0, b: 0, a: 255 }
// }
// },
// children: [
// StyledNode {
// node: DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// },
// specifiedValues: Map {
// 'display' => Keyword { format: 0, keyword: 'block' },
// 'padding' => Length { format: 1, length: 12, unit: 0 },
// 'background' => ColorValue {
// format: 2,
// colorValue: Color { r: 0, g: 0, b: 255, a: 255 }
// }
// },
// children: []
// }
// ]
// }
// ]
// }
// ]
// }
// },
// children: [
// LayoutBox {
// dimensions: Dimensions {
// content: Rect { x: 24, y: 24, width: 152, height: 48 },
// padding: EdgeSizes { left: 12, right: 12, top: 12, bottom: 12 },
// border: EdgeSizes { left: 0, right: 0, top: 0, bottom: 0 },
// margin: EdgeSizes { left: 0, right: 0, top: 0, bottom: 0 }
// },
// boxType: BlockNode {
// format: 0,
// styledNode: StyledNode {
// node: DomNode {
// children: [
// DomNode {
// children: [
// DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'outer' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData { tagName: 'body', attributes: Map {} }
// }
// },
// specifiedValues: Map {
// 'display' => Keyword { format: 0, keyword: 'block' },
// 'padding' => Length { format: 1, length: 12, unit: 0 }
// },
// children: [
// StyledNode {
// node: DomNode {
// children: [
// DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'outer' }
// }
// }
// },
// specifiedValues: Map {
// 'display' => Keyword { format: 0, keyword: 'block' },
// 'padding' => Length { format: 1, length: 12, unit: 0 },
// 'background' => ColorValue {
// format: 2,
// colorValue: Color { r: 128, g: 0, b: 0, a: 255 }
// }
// },
// children: [
// StyledNode {
// node: DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// },
// specifiedValues: Map {
// 'display' => Keyword { format: 0, keyword: 'block' },
// 'padding' => Length { format: 1, length: 12, unit: 0 },
// 'background' => ColorValue {
// format: 2,
// colorValue: Color { r: 0, g: 0, b: 255, a: 255 }
// }
// },
// children: []
// }
// ]
// }
// ]
// }
// },
// children: [
// LayoutBox {
// dimensions: Dimensions {
// content: Rect { x: 36, y: 36, width: 128, height: 24 },
// padding: EdgeSizes { left: 12, right: 12, top: 12, bottom: 12 },
// border: EdgeSizes { left: 0, right: 0, top: 0, bottom: 0 },
// margin: EdgeSizes { left: 0, right: 0, top: 0, bottom: 0 }
// },
// boxType: BlockNode {
// format: 0,
// styledNode: StyledNode {
// node: DomNode {
// children: [
// DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// }
// ],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'outer' }
// }
// }
// },
// specifiedValues: Map {
// 'display' => Keyword { format: 0, keyword: 'block' },
// 'padding' => Length { format: 1, length: 12, unit: 0 },
// 'background' => ColorValue {
// format: 2,
// colorValue: Color { r: 128, g: 0, b: 0, a: 255 }
// }
// },
// children: [
// StyledNode {
// node: DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// },
// specifiedValues: Map {
// 'display' => Keyword { format: 0, keyword: 'block' },
// 'padding' => Length { format: 1, length: 12, unit: 0 },
// 'background' => ColorValue {
// format: 2,
// colorValue: Color { r: 0, g: 0, b: 255, a: 255 }
// }
// },
// children: []
// }
// ]
// }
// },
// children: [
// LayoutBox {
// dimensions: Dimensions {
// content: Rect { x: 48, y: 48, width: 104, height: 0 },
// padding: EdgeSizes { left: 12, right: 12, top: 12, bottom: 12 },
// border: EdgeSizes { left: 0, right: 0, top: 0, bottom: 0 },
// margin: EdgeSizes { left: 0, right: 0, top: 0, bottom: 0 }
// },
// boxType: BlockNode {
// format: 0,
// styledNode: StyledNode {
// node: DomNode {
// children: [],
// nodeType: Element {
// format: 0,
// element: ElementData {
// tagName: 'div',
// attributes: Map { 'class' => 'inner' }
// }
// }
// },
// specifiedValues: Map {
// 'display' => Keyword { format: 0, keyword: 'block' },
// 'padding' => Length { format: 1, length: 12, unit: 0 },
// 'background' => ColorValue {
// format: 2,
// colorValue: Color { r: 0, g: 0, b: 255, a: 255 }
// }
// },
// children: []
// }
// },
// children: []
// }
// ]
// }
// ]
// }
// ]
// }
Layout Tree -> Image
DONE
code
// node_modules/.bin/ts-node example/demo.ts | display
import { elem } from "../src/dom";
import {
Color,
CssValue,
Declaration,
Rule,
Selector,
SimpleSelector,
Stylesheet,
Unit
} from "../src/css";
import { styleTree } from "../src/style";
import { Dimensions, EdgeSizes, layoutTree, Rect } from "../src/layout";
import { paint } from "../src/painting";
import * as Jimp from "jimp";
// <html>
// <body>
// <div class="outer">
// <div class="inner" />
// <div>
// </body>
// </html>
// * { display: block; padding: 12px; }
// .outer { background: maroon; /* 800000 */ }
// .inner { background: blue; /* 0000ff */ }
const domNode = elem("html", new Map([]), []);
const body = elem("body", new Map([]), []);
const outer = elem("div", new Map([["class", "outer"]]), []);
const inner = elem("div", new Map([["class", "inner"]]), []);
domNode.children.push(body);
body.children.push(outer);
outer.children.push(inner);
const stylesheet = new Stylesheet([]);
stylesheet.rules.push(
new Rule(
[new Selector.Simple(new SimpleSelector(null, null, []))],
[
new Declaration("display", new CssValue.Keyword("block")),
new Declaration("padding", new CssValue.Length(12, Unit.Px))
]
)
);
stylesheet.rules.push(
new Rule(
[new Selector.Simple(new SimpleSelector(null, null, ["outer"]))],
[new Declaration("background", new CssValue.ColorValue(new Color(128, 0, 0, 255)))]
)
);
stylesheet.rules.push(
new Rule(
[new Selector.Simple(new SimpleSelector(null, null, ["inner"]))],
[new Declaration("background", new CssValue.ColorValue(new Color(0, 0, 255, 255)))]
)
);
const styleRoot = styleTree(domNode, stylesheet);
const viewport = new Dimensions(
new Rect(0, 0, 200, 100),
new EdgeSizes(0, 0, 0, 0),
new EdgeSizes(0, 0, 0, 0),
new EdgeSizes(0, 0, 0, 0)
);
const layoutRoot = layoutTree(styleRoot, viewport);
const canvas = paint(layoutRoot, viewport.content);
Jimp.create(canvas.width, canvas.height)
.then(value => {
let buffer = value.bitmap.data;
for (let i = 0; i < canvas.pixels.length; i++) {
buffer[i * 4] = canvas.pixels[i].r;
buffer[i * 4 + 1] = canvas.pixels[i].g;
buffer[i * 4 + 2] = canvas.pixels[i].b;
buffer[i * 4 + 3] = canvas.pixels[i].a;
}
return value.getBufferAsync(Jimp.MIME_PNG);
})
.then(value => {
process.stdout.write(value);
})
.catch(error => {
console.error(error);
});
Next Action
- HTML -(HTML parser)-> DOM
- CSS -(CSS Parser)-> Rules
Top comments (0)