├── .gitignore ├── README.md ├── src └── com │ └── adhamenaya │ ├── run │ ├── Parser.java │ ├── LayoutTreeBuilder.java │ ├── Main.java │ ├── HTMLParser.java │ ├── DisplayListBuilder.java │ ├── StyleBuilder.java │ └── CSSParser.java │ ├── html │ ├── DOM.java │ ├── Node.java │ ├── NodeType.java │ ├── ElementData.java │ └── Pair.java │ ├── css │ ├── Unit.java │ ├── StyleSheet.java │ ├── Rule.java │ ├── Declaration.java │ ├── SimpleSelector.java │ ├── Value.java │ ├── Specificity.java │ ├── Color.java │ ├── Keyword.java │ ├── Length.java │ └── Selector.java │ ├── layout │ ├── Display.java │ ├── BoxType.java │ ├── AnonymousType.java │ ├── EdgeSizes.java │ ├── BoxTypeVisitor.java │ ├── BlockType.java │ ├── InlineType.java │ ├── Dimensions.java │ ├── Rect.java │ └── LayoutBox.java │ ├── paint │ ├── DisplayCommand.java │ ├── DrawText.java │ ├── SolidColor.java │ └── LayoutCanvas.java │ └── style │ ├── MatchedRule.java │ └── StyledNode.java ├── .classpath ├── .project └── .settings └── org.eclipse.jdt.core.prefs /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ToyBrowser 2 | A toy web engine built using java, that parses and displays simple HMTL and CSS files. 3 | -------------------------------------------------------------------------------- /src/com/adhamenaya/run/Parser.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adhamenaya/ToyBrowser/HEAD/src/com/adhamenaya/run/Parser.java -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/com/adhamenaya/html/DOM.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.html; 9 | 10 | public class DOM { 11 | public Node rootNode; 12 | } 13 | -------------------------------------------------------------------------------- /src/com/adhamenaya/css/Unit.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | public enum Unit { 11 | px, pt /* ... other unites */ 12 | } -------------------------------------------------------------------------------- /src/com/adhamenaya/layout/Display.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.layout; 9 | 10 | public enum Display { 11 | 12 | INLINE, BLOCK, NONE 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/com/adhamenaya/paint/DisplayCommand.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.paint; 9 | 10 | public abstract class DisplayCommand { 11 | 12 | abstract void paint(); 13 | } 14 | -------------------------------------------------------------------------------- /src/com/adhamenaya/layout/BoxType.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.layout; 9 | 10 | public interface BoxType { 11 | public T accept(BoxTypeVisitor visitor); 12 | } 13 | -------------------------------------------------------------------------------- /src/com/adhamenaya/css/StyleSheet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | import java.util.Vector; 11 | 12 | public class StyleSheet { 13 | 14 | public Vector rules = new Vector(); 15 | } 16 | -------------------------------------------------------------------------------- /src/com/adhamenaya/css/Rule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | import java.util.Vector; 11 | 12 | public class Rule { 13 | 14 | public Vector selectors; 15 | public Vector declarations; 16 | 17 | } -------------------------------------------------------------------------------- /src/com/adhamenaya/layout/AnonymousType.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.layout; 9 | 10 | public class AnonymousType implements BoxType { 11 | 12 | @Override 13 | public T accept(BoxTypeVisitor visitor) { 14 | return null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | PinkBerry 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/com/adhamenaya/layout/EdgeSizes.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.layout; 9 | 10 | public class EdgeSizes { 11 | 12 | public float left = 0.0f; 13 | public float right = 0.0f; 14 | public float top = 0.0f; 15 | public float bottom = 0.0f; 16 | } 17 | -------------------------------------------------------------------------------- /src/com/adhamenaya/layout/BoxTypeVisitor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.layout; 9 | 10 | public interface BoxTypeVisitor { 11 | public R visit(BlockType box); 12 | 13 | public R visit(InlineType box); 14 | 15 | public R visit(AnonymousType box); 16 | } -------------------------------------------------------------------------------- /src/com/adhamenaya/css/Declaration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | public class Declaration { 11 | 12 | public String name; 13 | public Value value; 14 | 15 | public Declaration(String name, Value value) { 16 | this.name = name; 17 | this.value = value; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/com/adhamenaya/paint/DrawText.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.paint; 9 | 10 | import com.adhamenaya.layout.Rect; 11 | 12 | public class DrawText extends DisplayCommand { 13 | 14 | public Rect rect; 15 | public String text; 16 | 17 | @Override 18 | void paint() { 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/com/adhamenaya/paint/SolidColor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.paint; 9 | 10 | import com.adhamenaya.css.Color; 11 | import com.adhamenaya.layout.Rect; 12 | 13 | public class SolidColor extends DisplayCommand { 14 | 15 | public Color color; 16 | public Rect rect; 17 | 18 | @Override 19 | void paint() { 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/com/adhamenaya/style/MatchedRule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.style; 9 | 10 | import com.adhamenaya.css.Rule; 11 | import com.adhamenaya.css.Specificity; 12 | 13 | public class MatchedRule { 14 | 15 | public Specificity specificity; 16 | public Rule rule; 17 | 18 | public MatchedRule(Specificity specificity, Rule rule) { 19 | this.rule = rule; 20 | this.specificity = specificity; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/com/adhamenaya/layout/BlockType.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.layout; 9 | 10 | import com.adhamenaya.style.StyledNode; 11 | 12 | public class BlockType implements BoxType { 13 | public final StyledNode styledNode; 14 | 15 | public BlockType(StyledNode styledNode) { 16 | this.styledNode = styledNode; 17 | } 18 | 19 | @Override 20 | public T accept(BoxTypeVisitor visitor) { 21 | return visitor.visit(this); 22 | } 23 | } -------------------------------------------------------------------------------- /src/com/adhamenaya/css/SimpleSelector.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | import java.util.Vector; 11 | 12 | public class SimpleSelector { 13 | 14 | public String id; 15 | public String tagName; 16 | public Vector classNames; 17 | 18 | public SimpleSelector(String tagName, String id, Vector classNames) { 19 | this.id = id; 20 | this.tagName = tagName; 21 | this.classNames = classNames; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/com/adhamenaya/layout/InlineType.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.layout; 9 | 10 | import com.adhamenaya.style.StyledNode; 11 | 12 | public class InlineType implements BoxType { 13 | public final StyledNode styledNode; 14 | 15 | public InlineType(StyledNode styledNode) { 16 | this.styledNode = styledNode; 17 | } 18 | 19 | @Override 20 | public T accept(BoxTypeVisitor visitor) { 21 | return visitor.visit(this); 22 | } 23 | } -------------------------------------------------------------------------------- /src/com/adhamenaya/css/Value.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | abstract public class Value { 11 | 12 | protected String valueString = ""; 13 | 14 | abstract public void setKeyword(Keyword keyword); 15 | 16 | abstract public void setColor(Color color); 17 | 18 | abstract public void setLength(Length length); 19 | 20 | abstract public float toPx(); 21 | 22 | public String getValueString() { 23 | return this.valueString; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.7 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.7 12 | -------------------------------------------------------------------------------- /src/com/adhamenaya/html/Node.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.html; 9 | 10 | import java.util.Map; 11 | import java.util.Vector; 12 | 13 | public class Node { 14 | 15 | public Vector children; 16 | public NodeType type; 17 | 18 | public Node(String data) { 19 | children = new Vector(); 20 | type = new NodeType(data); 21 | } 22 | 23 | public Node(String tagName, Map attributes, Vector children) { 24 | 25 | this.children = children; 26 | type = new NodeType(tagName, attributes); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/com/adhamenaya/css/Specificity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | public class Specificity { 11 | 12 | public int a; // ID level 13 | public int b; // Class level 14 | public int c; // Tag level 15 | 16 | public int getInt() { 17 | /* 18 | * DIV#myDiv.class1.class2.class3 Example : ID = 1, class = 3, tag = 1 19 | * Specificity integer = 131 20 | */ 21 | return Integer.parseInt(String.valueOf(a) + String.valueOf(b) + String.valueOf(c)); 22 | } 23 | 24 | public String get() { 25 | return String.valueOf(a) + String.valueOf(b) + String.valueOf(c); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/com/adhamenaya/html/NodeType.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.html; 9 | 10 | import java.util.Map; 11 | 12 | public class NodeType { 13 | 14 | // Text node 15 | public String text; 16 | 17 | // Element node 18 | public ElementData element; 19 | 20 | public NodeType(String text) { 21 | this.text = text; 22 | } 23 | 24 | public NodeType(String tagName, Map attributes) { 25 | this.element = new ElementData(tagName, attributes); 26 | } 27 | 28 | public boolean isText() { 29 | if (element == null && text != null) 30 | return true; 31 | else 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/adhamenaya/css/Color.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | public class Color extends Value { 11 | 12 | public int r; 13 | public int g; 14 | public int b; 15 | public int a; 16 | 17 | public void setColor(Color color) { 18 | this.r = color.r; 19 | this.b = color.b; 20 | this.g = color.g; 21 | this.a = color.a; 22 | } 23 | 24 | @Override 25 | public void setKeyword(Keyword keyword) { 26 | // TODO Auto-generated method stub 27 | 28 | } 29 | 30 | @Override 31 | public void setLength(Length length) { 32 | // TODO Auto-generated method stub 33 | 34 | } 35 | 36 | @Override 37 | public float toPx() { 38 | // TODO Auto-generated method stub 39 | return 0; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/adhamenaya/html/ElementData.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.html; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.Map; 13 | 14 | public class ElementData { 15 | 16 | public String tagName; 17 | public Map attributes; 18 | 19 | public ElementData(String tagName, Map attributes) { 20 | 21 | this.tagName = tagName; 22 | this.attributes = attributes; 23 | } 24 | 25 | public String getId() { 26 | return attributes.get("id"); 27 | } 28 | 29 | public ArrayList getClasses() { 30 | if (attributes.get("class") != null) 31 | return new ArrayList(Arrays.asList(attributes.get("class").split(" "))); 32 | else 33 | return new ArrayList(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/com/adhamenaya/html/Pair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.html; 9 | 10 | public class Pair { 11 | private final String left; 12 | private final String right; 13 | 14 | public Pair(String left, String right) { 15 | this.left = left; 16 | this.right = right; 17 | } 18 | 19 | public String getKey() { 20 | return left; 21 | } 22 | 23 | public String getValue() { 24 | return right; 25 | } 26 | 27 | @Override 28 | public int hashCode() { 29 | return left.hashCode() ^ right.hashCode(); 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (o == null) 35 | return false; 36 | if (!(o instanceof Pair)) 37 | return false; 38 | Pair pairo = (Pair) o; 39 | return this.left.equals(pairo.getKey()) && this.right.equals(pairo.getValue()); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/com/adhamenaya/css/Keyword.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | public class Keyword extends Value { 11 | public String kString; 12 | 13 | public Keyword(String kString) { 14 | this.kString = kString; 15 | super.valueString = kString; 16 | } 17 | 18 | public String getKeyword() { 19 | return this.kString; 20 | } 21 | 22 | @Override 23 | public void setKeyword(Keyword keyword) { 24 | // TODO Auto-generated method stub 25 | 26 | } 27 | 28 | @Override 29 | public void setColor(Color color) { 30 | // TODO Auto-generated method stub 31 | 32 | } 33 | 34 | @Override 35 | public void setLength(Length length) { 36 | // TODO Auto-generated method stub 37 | 38 | } 39 | 40 | @Override 41 | public float toPx() { 42 | // TODO Auto-generated method stub 43 | return 0; 44 | } 45 | } -------------------------------------------------------------------------------- /src/com/adhamenaya/css/Length.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | public class Length extends Keyword { 11 | 12 | float value = 0.0f; 13 | Unit unit; 14 | 15 | public Length() { 16 | super(""); 17 | } 18 | 19 | public Length(float value, Unit unit) { 20 | super(value + "" + unit.toString()); 21 | this.value = value; 22 | this.unit = unit; 23 | } 24 | 25 | public void setLength(Length length) { 26 | this.value = length.value; 27 | this.unit = length.unit; 28 | } 29 | 30 | // Return the size of a length in px, or zero for non-lengths. 31 | public float toPx() { 32 | return this.value; 33 | 34 | } 35 | 36 | @Override 37 | public void setKeyword(Keyword keyword) { 38 | // TODO Auto-generated method stub 39 | 40 | } 41 | 42 | @Override 43 | public void setColor(Color color) { 44 | // TODO Auto-generated method stub 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/com/adhamenaya/layout/Dimensions.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.layout; 9 | 10 | public class Dimensions { 11 | 12 | // Position of the content area relative to the document origin: 13 | public Rect content; 14 | 15 | // Surrounding edges: 16 | public EdgeSizes padding; 17 | public EdgeSizes border; 18 | public EdgeSizes margin; 19 | 20 | public Dimensions() { 21 | content = new Rect(); 22 | 23 | padding = new EdgeSizes(); 24 | border = new EdgeSizes(); 25 | margin = new EdgeSizes(); 26 | } 27 | 28 | // The area covered by the content area plus its padding. 29 | public Rect paddingBox() { 30 | return content.expandedBy(padding); 31 | } 32 | 33 | // The area covered by the content area plus padding and borders. 34 | public Rect borderBox() { 35 | return content.expandedBy(border); 36 | } 37 | 38 | // The area covered by the content area plus padding, borders, and margin. 39 | public Rect marginBox() { 40 | return content.expandedBy(margin); 41 | } 42 | 43 | public Rect extraBox() { 44 | return content.expandByAll(border, margin, padding); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/com/adhamenaya/css/Selector.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.css; 9 | 10 | public class Selector { 11 | 12 | public SimpleSelector simple; 13 | 14 | // Get the specify for simple selector as String 15 | public String getSpecificityString() { 16 | 17 | Specificity specificity = new Specificity(); 18 | 19 | specificity.a = (simple.id.isEmpty() ? 0 : 1); 20 | specificity.b = simple.classNames.size(); 21 | specificity.c = (simple.tagName.isEmpty() ? 0 : 1); 22 | 23 | return specificity.get(); 24 | } 25 | 26 | public Specificity getSpecificity() { 27 | 28 | Specificity specificity = new Specificity(); 29 | 30 | // Element ID 31 | if (simple != null & simple.id != null) { 32 | specificity.a = (simple.id.isEmpty() ? 0 : 1); 33 | } else 34 | specificity.a = 0; 35 | 36 | // Classes 37 | specificity.b = simple.classNames.size(); 38 | 39 | // Tag name 40 | if (simple != null & simple.tagName != null) { 41 | specificity.c = (simple.tagName.isEmpty() ? 0 : 1); 42 | } else 43 | specificity.c = 0; 44 | 45 | return specificity; 46 | } 47 | 48 | public void setSimpleSelector(SimpleSelector simple) { 49 | this.simple = simple; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/com/adhamenaya/layout/Rect.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.layout; 9 | 10 | public class Rect { 11 | 12 | public float x = 0.0f; 13 | public float y = 0.0f; 14 | public float width = 0.0f; 15 | public float height = 0.0f; 16 | 17 | public Rect() { 18 | } 19 | 20 | public Rect(float x, float y, float width, float height) { 21 | this.x = x; 22 | this.y = y; 23 | this.width = width; 24 | this.height = height; 25 | } 26 | 27 | public Rect expandedBy(EdgeSizes edge) { 28 | Rect rect = new Rect(); 29 | 30 | rect.x = this.x - edge.left; 31 | rect.y = this.y - edge.top; 32 | 33 | rect.width = this.width + edge.left + edge.right; 34 | rect.height = this.height + edge.top + edge.bottom; 35 | return rect; 36 | } 37 | 38 | public Rect expandByAll(EdgeSizes border, EdgeSizes margin, EdgeSizes padding) { 39 | Rect rect = new Rect(); 40 | 41 | rect.x = this.x - border.left - margin.left - padding.left; 42 | rect.y = this.y - border.top - margin.top - padding.top; 43 | 44 | rect.width = this.width + border.left + border.right + margin.left + margin.right + padding.left + padding.right; 45 | rect.height = this.height + border.top + border.bottom + margin.top + margin.bottom + padding.top + padding.bottom; 46 | return rect; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/com/adhamenaya/style/StyledNode.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.style; 9 | 10 | import java.util.HashMap; 11 | import java.util.Vector; 12 | 13 | import com.adhamenaya.css.Keyword; 14 | import com.adhamenaya.css.Value; 15 | import com.adhamenaya.html.Node; 16 | import com.adhamenaya.layout.Display; 17 | 18 | public class StyledNode { 19 | 20 | public Node node; 21 | 22 | // Property map 23 | public HashMap specifiedValues; 24 | 25 | public Vector children; 26 | 27 | // Return the specified value of a property if it exists, otherwise `None`. 28 | public Value getValueOf(String name) { 29 | return specifiedValues.get(name); 30 | } 31 | 32 | // The value of the `display` property (defaults to in line). 33 | public Display getValueOfDisplay() { 34 | 35 | if (specifiedValues.containsKey("display")) { 36 | if (((Keyword) specifiedValues.get("display")).getKeyword().equals("block")) 37 | return Display.BLOCK; 38 | 39 | else if (((Keyword) specifiedValues.get("display")).getKeyword().equals("none")) 40 | return Display.NONE; 41 | 42 | else 43 | return Display.INLINE; 44 | 45 | } else 46 | return Display.INLINE; 47 | } 48 | 49 | public void insertValue1(String key, Value value) { 50 | specifiedValues.put(key, value); 51 | } 52 | 53 | public Value lookUp(Value defualtValue, String args[]) { 54 | 55 | for (int i = 0; i < args.length; i++) { 56 | if (specifiedValues.get(args[i]) != null) 57 | return specifiedValues.get(args[i]); 58 | } 59 | 60 | return defualtValue; 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/com/adhamenaya/run/LayoutTreeBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.run; 9 | 10 | import com.adhamenaya.layout.BlockType; 11 | import com.adhamenaya.layout.Dimensions; 12 | import com.adhamenaya.layout.Display; 13 | import com.adhamenaya.layout.InlineType; 14 | import com.adhamenaya.layout.LayoutBox; 15 | import com.adhamenaya.style.StyledNode; 16 | 17 | public class LayoutTreeBuilder { 18 | 19 | public LayoutBox layoutTree(StyledNode node, Dimensions containingBlock) { 20 | // The layout algorithm expects the container height to start at 0. 21 | // TODO: Save the initial containing block height, for calculating 22 | // percent heights. 23 | 24 | containingBlock.content.height = 0.0f; 25 | 26 | LayoutBox rootBox; 27 | try { 28 | rootBox = buildLayoutTree(node); 29 | rootBox.layout(containingBlock); 30 | return rootBox; 31 | } catch (Exception e) { 32 | e.printStackTrace(); 33 | return null; 34 | } 35 | } 36 | 37 | private LayoutBox buildLayoutTree(StyledNode root) throws Exception { 38 | 39 | LayoutBox rootBox = null; 40 | 41 | Display display = root.getValueOfDisplay(); 42 | 43 | switch (display) { 44 | case BLOCK: 45 | rootBox = new LayoutBox(new BlockType(root)); 46 | break; 47 | 48 | case INLINE: 49 | rootBox = new LayoutBox(new InlineType(root)); 50 | break; 51 | 52 | default: 53 | throw new Exception("Root node has display: none."); 54 | } 55 | 56 | // Traverse the children nodes 57 | if (root.children != null && root.children.size() > 0) { 58 | 59 | for (StyledNode childNode : root.children) { 60 | 61 | switch (childNode.getValueOfDisplay()) { 62 | case BLOCK: 63 | rootBox.children.add(buildLayoutTree(childNode)); 64 | break; 65 | 66 | case INLINE: 67 | rootBox.getInlineContainer().children.add(buildLayoutTree(childNode)); 68 | break; 69 | 70 | default: 71 | break; 72 | } 73 | } 74 | 75 | } 76 | 77 | return rootBox; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/com/adhamenaya/paint/LayoutCanvas.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.paint; 9 | 10 | import java.awt.Color; 11 | import java.awt.Dimension; 12 | import java.awt.Font; 13 | import java.awt.Graphics; 14 | import java.util.Vector; 15 | 16 | import javax.swing.JComponent; 17 | 18 | public class LayoutCanvas extends JComponent { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | private Vector commandList = null; 23 | 24 | private static Font serifFont = new Font("Serif", Font.BOLD, 24); 25 | 26 | public void setDisplayCommandsList(Vector commandList) { 27 | this.commandList = commandList; 28 | } 29 | 30 | public void paintComponent(Graphics g) { 31 | super.paintComponent(g); 32 | 33 | // draw entire component white 34 | g.setColor(Color.white); 35 | g.fillRect(0, 0, getWidth(), getHeight()); 36 | 37 | for (DisplayCommand cmd : commandList) { 38 | if (cmd instanceof SolidColor) { 39 | com.adhamenaya.css.Color myColor = ((SolidColor) cmd).color; 40 | if (myColor != null) { 41 | Color color = new Color(myColor.r, myColor.g, myColor.b, myColor.a); 42 | g.setColor(color); 43 | } 44 | 45 | com.adhamenaya.layout.Rect myRect = ((SolidColor) cmd).rect; 46 | if (myRect != null) { 47 | g.fillRect((int) myRect.x, (int) myRect.y, (int) myRect.width, (int) (myRect.height)); 48 | 49 | } 50 | } else if (cmd instanceof DrawText) { 51 | com.adhamenaya.layout.Rect myRect1 = ((DrawText) cmd).rect; 52 | 53 | if (myRect1 != null) { 54 | 55 | Color color = new Color(0, 0, 0); 56 | g.setColor(color); 57 | 58 | String perparedString = prepareString(g, ((DrawText) cmd).text, (int) myRect1.width); 59 | 60 | drawString(g, perparedString, (int) myRect1.x, (int) myRect1.y); 61 | 62 | } 63 | } 64 | } 65 | } 66 | 67 | private String prepareString(Graphics g, String text, int boxWidth) { 68 | StringBuilder string = new StringBuilder(); 69 | int lineWidth = 0; 70 | int fontWidth = g.getFontMetrics().getWidths()[0]; 71 | 72 | for (int i = 0; i < text.length(); i++) { 73 | string.append(String.valueOf(text.toCharArray()[i])); 74 | lineWidth += fontWidth; 75 | 76 | if (lineWidth > boxWidth) { 77 | lineWidth = 0; 78 | string.append("\n"); 79 | } 80 | } 81 | return string.toString(); 82 | } 83 | 84 | private void drawString(Graphics g, String text, int x, int y) { 85 | for (String line : text.split("\n")) 86 | g.drawString(line, x, y += g.getFontMetrics().getHeight()); 87 | } 88 | 89 | public Dimension getPreferredSize() { 90 | return new Dimension(400, 400); 91 | } 92 | 93 | public Dimension getMinimumSize() { 94 | return getPreferredSize(); 95 | } 96 | } -------------------------------------------------------------------------------- /src/com/adhamenaya/run/Main.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.run; 9 | 10 | import java.util.Vector; 11 | 12 | import javax.swing.JFrame; 13 | import javax.swing.JScrollPane; 14 | 15 | import com.adhamenaya.css.StyleSheet; 16 | import com.adhamenaya.html.Node; 17 | import com.adhamenaya.layout.Dimensions; 18 | import com.adhamenaya.layout.EdgeSizes; 19 | import com.adhamenaya.layout.LayoutBox; 20 | import com.adhamenaya.layout.Rect; 21 | import com.adhamenaya.paint.DisplayCommand; 22 | import com.adhamenaya.paint.LayoutCanvas; 23 | import com.adhamenaya.style.StyledNode; 24 | 25 | public class Main { 26 | 27 | public static void main(String[] args) { 28 | 29 | String html = "
Div A" + "
Div B" + "
Div C" + "
Div D" 30 | + "
Div E" + "
Div F" + "
Div G
" + "
" + "
" 31 | + "
" + "
" + "
" + "
Adham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWAAdham Enaya from palestine I am working at UNRWA as ABAP developer can youUNRWA as ABAP developer can youUNRWA as ABAP developer can youUNRWA as ABAP developer can youUNRWA as ABAP developer can you check my LinkedIn profile
"; 32 | 33 | String css = " * { display: block; padding: 12px;}" + ".a { background: #ff0000; }" + ".b { background: #ffa500; }" 34 | + ".c { background: #ffff00; }" + ".d { background: #008000; }" + ".e { background: #0000ff; }" 35 | + ".f { background: #4b0082; }" + ".g { background: #800080; }"; 36 | 37 | Dimensions initialBlock = new Dimensions(); 38 | 39 | initialBlock.content = new Rect(0.0f, 0.0f, 800.0f, 600.0f); 40 | initialBlock.border = new EdgeSizes(); 41 | initialBlock.margin = new EdgeSizes(); 42 | initialBlock.padding = new EdgeSizes(); 43 | 44 | CSSParser cssParser = new CSSParser(); 45 | HTMLParser htmlParser = new HTMLParser(); 46 | StyleBuilder styleBuilder = new StyleBuilder(); 47 | LayoutTreeBuilder layoutBuilder = new LayoutTreeBuilder(); 48 | DisplayListBuilder displayListBuilder = new DisplayListBuilder(); 49 | LayoutCanvas layoutCanvas = new LayoutCanvas(); 50 | 51 | // 1. Parse CSS file 52 | StyleSheet style = cssParser.parse(css); 53 | 54 | // 2. Parse HTML file 55 | Node htmlTree = htmlParser.parse(html); 56 | 57 | // 3. Generate the style tree 58 | StyledNode styleTree = styleBuilder.build(htmlTree, style); 59 | 60 | // 4. Generate the layout boxes tree 61 | LayoutBox box = layoutBuilder.layoutTree(styleTree, initialBlock); 62 | 63 | // 5. Generate the paint command list 64 | Vector displayList = displayListBuilder.buildDisplayList(box); 65 | 66 | // 6. paint on the canvas 67 | layoutCanvas.setDisplayCommandsList(displayList); 68 | 69 | // 7. Display the paints 70 | final JFrame mainFrame = new JFrame("PinkBerry web browser!"); 71 | mainFrame.getContentPane().add(layoutCanvas); 72 | mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH); 73 | JScrollPane pane = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); 74 | pane.setViewportView(layoutCanvas); 75 | mainFrame.setContentPane(pane); 76 | mainFrame.setVisible(true); 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/com/adhamenaya/run/HTMLParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.run; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.Vector; 13 | 14 | import com.adhamenaya.html.Node; 15 | import com.adhamenaya.html.Pair; 16 | 17 | public class HTMLParser extends Parser { 18 | 19 | // Consume characters until `test` returns false. 20 | String consumeWhile() { 21 | StringBuilder sb = new StringBuilder(); 22 | while (!eof()) { 23 | sb.append(consumeChar()); 24 | } 25 | return sb.toString(); 26 | } 27 | 28 | // Parse a tag or attribute name. 29 | String parseTagName() { 30 | StringBuilder sb = new StringBuilder(); 31 | while (!eof() && nextChar().toString().matches("[0-9A-Za-z]")) { 32 | Character consumedChar = consumeChar(); 33 | sb.append(consumedChar.toString()); 34 | } 35 | return sb.toString(); 36 | } 37 | 38 | // Parse a single node, we assume that the node start with '<', and the text 39 | // can't contains this special char. 40 | Node parseNode() { 41 | Node node; 42 | if (nextChar().equals('<')) 43 | node = parseElement(); 44 | else 45 | node = parseText(); 46 | return node; 47 | } 48 | 49 | // Parse a single element, including its open tag, contents, and closing 50 | // tag. 51 | Node parseElement() { 52 | // Opening tag. 53 | if (!consumeChar().equals('<')) 54 | return null; 55 | 56 | // Opening tag 57 | String tagName = parseTagName(); 58 | 59 | // The attributes of the tag 60 | Map attributes = parseAttributes(); 61 | 62 | Character cc = consumeChar(); 63 | 64 | if (!cc.equals('>')) 65 | return null; 66 | 67 | // Contents. 68 | Vector children = parseNodes(); 69 | 70 | // Closing tag. 71 | if (!consumeChar().equals('<')) 72 | return null; 73 | if (!consumeChar().equals('/')) 74 | return null; 75 | if (!parseTagName().equals(tagName)) 76 | return null; 77 | if (!consumeChar().equals('>')) 78 | return null; 79 | 80 | return new Node(tagName, attributes, children); 81 | 82 | } 83 | 84 | // Parse a text node. 85 | Node parseText() { 86 | StringBuilder sb = new StringBuilder(); 87 | while (!eof() && !nextChar().equals('<')) { 88 | sb.append(consumeChar()); 89 | } 90 | return new Node(sb.toString()); 91 | } 92 | 93 | // Parse a single name="value" pair. 94 | Pair parseAttribute() { 95 | 96 | final String KeyName = parseTagName(); 97 | 98 | String charAfterKey = consumeChar().toString(); 99 | 100 | if (!charAfterKey.equals("=")) 101 | return null; 102 | 103 | final String ValueName = parseAttributeValue(); 104 | 105 | return new Pair(KeyName, ValueName); 106 | } 107 | 108 | // Parse a quoted value for the attribute. 109 | String parseAttributeValue() { 110 | 111 | Character openQoute = consumeChar(); 112 | if (!openQoute.equals('"') && !openQoute.equals('\'')) 113 | return null; 114 | 115 | // Consume while the close quote not reached yet. 116 | StringBuilder sb = new StringBuilder(); 117 | while (!eof() && !nextChar().equals(openQoute)) { 118 | sb.append(consumeChar()); 119 | } 120 | String value = sb.toString(); 121 | 122 | if (!consumeChar().equals(openQoute)) 123 | return null; 124 | 125 | return value; 126 | } 127 | 128 | // Parse a list of name="value" pairs, separated by whitespace. 129 | HashMap parseAttributes() { 130 | HashMap attributes = new HashMap(); 131 | 132 | while (true) { 133 | consumeWhiteSpace(); 134 | 135 | Character nextChar = nextChar(); 136 | 137 | if (eof() || nextChar.equals('>')) 138 | break; 139 | 140 | Pair attribute = parseAttribute(); 141 | 142 | if (attribute != null) 143 | attributes.put(attribute.getKey(), attribute.getValue()); 144 | } 145 | return attributes; 146 | } 147 | 148 | // Parse a sequence of sibling nodes. 149 | Vector parseNodes() { 150 | Vector nodes = new Vector(); 151 | 152 | while (true) { 153 | consumeWhiteSpace(); 154 | if (eof() || startsWith(" nodes = parseNodes(); 169 | if (nodes.size() == 1) 170 | return nodes.get(0); 171 | else 172 | return new Node("html", new HashMap(), nodes); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/com/adhamenaya/run/DisplayListBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.run; 9 | 10 | import java.util.Vector; 11 | 12 | import com.adhamenaya.css.Color; 13 | import com.adhamenaya.css.Value; 14 | import com.adhamenaya.layout.BlockType; 15 | import com.adhamenaya.layout.Dimensions; 16 | import com.adhamenaya.layout.InlineType; 17 | import com.adhamenaya.layout.LayoutBox; 18 | import com.adhamenaya.layout.Rect; 19 | import com.adhamenaya.paint.DisplayCommand; 20 | import com.adhamenaya.paint.DrawText; 21 | import com.adhamenaya.paint.SolidColor; 22 | 23 | public class DisplayListBuilder { 24 | 25 | Vector list = null; 26 | 27 | public Vector buildDisplayList(LayoutBox rootBox) { 28 | list = new Vector(); 29 | renderLayoutBox(rootBox); 30 | return list; 31 | } 32 | 33 | private void renderLayoutBox(LayoutBox rootBox) { 34 | 35 | renderBackground(rootBox); 36 | list.addAll(renderBorders(rootBox)); 37 | 38 | // Draw text element 39 | if (rootBox.getStyleNode() != null && rootBox.getStyleNode().node.type.isText()) { 40 | renderText(rootBox); 41 | 42 | } 43 | 44 | for (LayoutBox child : rootBox.children) { 45 | renderLayoutBox(child); 46 | } 47 | } 48 | 49 | private void renderText(LayoutBox rootBox) { 50 | 51 | DrawText command = new DrawText(); 52 | command.rect = rootBox.dimensions.borderBox(); 53 | command.text = rootBox.getStyleNode().node.type.text; 54 | 55 | list.add(command); 56 | } 57 | 58 | private Vector renderBorders(LayoutBox rootBox) { 59 | 60 | Vector list = new Vector(); 61 | 62 | Color borderColor = (Color) getColor(rootBox, "border-color"); 63 | Dimensions dims = rootBox.dimensions; 64 | Rect borderBox = rootBox.dimensions.borderBox(); 65 | 66 | // Left border 67 | SolidColor leftBorderCommand = new SolidColor(); 68 | leftBorderCommand.color = borderColor; 69 | 70 | Rect leftBorderRect = new Rect(); 71 | leftBorderRect.x = borderBox.x; 72 | leftBorderRect.y = borderBox.y; 73 | leftBorderRect.width = dims.border.left; 74 | leftBorderRect.height = borderBox.height; 75 | 76 | leftBorderCommand.rect = rootBox.dimensions.borderBox(); 77 | list.add(leftBorderCommand); 78 | 79 | // Right border 80 | SolidColor rightBorderCommand = new SolidColor(); 81 | rightBorderCommand.color = borderColor; 82 | 83 | Rect rightBorderRect = new Rect(); 84 | rightBorderRect.x = borderBox.x + borderBox.width - dims.border.right; 85 | rightBorderRect.y = borderBox.y; 86 | rightBorderRect.width = dims.border.right; 87 | rightBorderRect.height = borderBox.height; 88 | 89 | rightBorderCommand.rect = rootBox.dimensions.borderBox(); 90 | list.add(rightBorderCommand); 91 | 92 | // Top border 93 | SolidColor topBorderCommand = new SolidColor(); 94 | topBorderCommand.color = borderColor; 95 | 96 | Rect topBorderRect = new Rect(); 97 | topBorderRect.x = borderBox.x; 98 | topBorderRect.y = borderBox.y; 99 | topBorderRect.width = borderBox.width; 100 | topBorderRect.height = dims.border.top; 101 | 102 | topBorderCommand.rect = rootBox.dimensions.borderBox(); 103 | list.add(topBorderCommand); 104 | 105 | // Bottom border 106 | SolidColor bottomBorderCommand = new SolidColor(); 107 | bottomBorderCommand.color = borderColor; 108 | 109 | Rect bottomBorderRect = new Rect(); 110 | bottomBorderRect.x = borderBox.x; 111 | bottomBorderRect.y = borderBox.y + borderBox.height - dims.border.bottom; 112 | bottomBorderRect.width = borderBox.width; 113 | bottomBorderRect.height = dims.border.bottom; 114 | 115 | bottomBorderCommand.rect = rootBox.dimensions.borderBox(); 116 | list.add(bottomBorderCommand); 117 | 118 | return list; 119 | } 120 | 121 | private void renderBackground(LayoutBox rootBox) { 122 | 123 | Color backgroundColor = (Color) getColor(rootBox, "background"); 124 | 125 | SolidColor command = new SolidColor(); 126 | command.color = backgroundColor; 127 | command.rect = rootBox.dimensions.borderBox(); 128 | 129 | list.add(command); 130 | } 131 | 132 | private Value getColor(LayoutBox box, String name) { 133 | 134 | // White color 135 | Color defaultColor = new Color(); 136 | defaultColor.a = 0; 137 | defaultColor.r = 0; 138 | defaultColor.g = 0; 139 | defaultColor.b = 0; 140 | 141 | if (box.boxType instanceof BlockType || box.boxType instanceof InlineType) { 142 | return box.getStyleNode().lookUp(defaultColor, new String[] { name }); 143 | } 144 | return null; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/com/adhamenaya/run/StyleBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.run; 9 | 10 | import java.util.HashMap; 11 | import java.util.Vector; 12 | 13 | import com.adhamenaya.css.Declaration; 14 | import com.adhamenaya.css.Rule; 15 | import com.adhamenaya.css.Selector; 16 | import com.adhamenaya.css.SimpleSelector; 17 | import com.adhamenaya.css.StyleSheet; 18 | import com.adhamenaya.css.Value; 19 | import com.adhamenaya.html.ElementData; 20 | import com.adhamenaya.html.Node; 21 | import com.adhamenaya.style.MatchedRule; 22 | import com.adhamenaya.style.StyledNode; 23 | 24 | public class StyleBuilder { 25 | 26 | // Apply a style sheet to an entire DOM tree, returning a StyledNode tree. 27 | public StyledNode build(Node root, StyleSheet styleSheet) { 28 | 29 | // Create Style tree root node. 30 | StyledNode styledNode = new StyledNode(); 31 | 32 | // Add the DOM node for the styled node. 33 | styledNode.node = root; 34 | 35 | if (root.type.isText()) { 36 | // If it is a text, then create an empty style values 37 | styledNode.specifiedValues = generalSpecifiedValues(styleSheet); 38 | 39 | } else { 40 | // Get the CSS style values. 41 | styledNode.specifiedValues = specifiedValues(root.type.element, styleSheet); 42 | } 43 | 44 | // Traverse the children nodes 45 | if (root.children != null && root.children.size() > 0) { 46 | Vector childrenStyle = new Vector(); 47 | 48 | for (Node childNode : root.children) { 49 | childrenStyle.add(build(childNode, styleSheet)); 50 | } 51 | 52 | styledNode.children = childrenStyle; 53 | } 54 | 55 | return styledNode; 56 | 57 | } 58 | 59 | // Apply styles to a single element, returning the specified values. 60 | private HashMap specifiedValues(ElementData elementData, StyleSheet styleSheet) { 61 | 62 | // Create a hash map for the declarations values. 63 | HashMap values = new HashMap(); 64 | 65 | // Get the matching rules for the element 66 | Vector rules = matchRules(elementData, styleSheet); 67 | 68 | // Iterate all the matching rules 69 | for (MatchedRule matchedRule : rules) { 70 | // Iterate all the declarations in the matching rule 71 | for (Declaration declaration : matchedRule.rule.declarations) { 72 | values.put(declaration.name, declaration.value); 73 | } 74 | } 75 | return values; 76 | } 77 | 78 | private HashMap generalSpecifiedValues(StyleSheet stylesheet) { 79 | 80 | // Create a hash map for the declarations values. 81 | HashMap values = new HashMap(); 82 | 83 | for (Rule rule : stylesheet.rules) { 84 | for (Selector selector : rule.selectors) { 85 | if (selector.simple.classNames.contains("*")) { 86 | for (Declaration declaration : rule.declarations) { 87 | values.put(declaration.name, declaration.value); 88 | } 89 | } 90 | } 91 | } 92 | 93 | return values; 94 | } 95 | 96 | // Find all CSS rules that match the given element. 97 | private Vector matchRules(ElementData elementData, StyleSheet styleSheet) { 98 | 99 | Vector matchedRules = new Vector(); 100 | Vector rules = styleSheet.rules; 101 | 102 | for (Rule rule : rules) { 103 | MatchedRule matchedRule = matchRule(elementData, rule); 104 | if (null != matchedRule) 105 | matchedRules.add(matchedRule); 106 | } 107 | return matchedRules; 108 | } 109 | 110 | // Matching single CSS rule with a given element data 111 | private MatchedRule matchRule(ElementData elementData, Rule rule) { 112 | 113 | for (Selector s : rule.selectors) { 114 | boolean matchedSimpleSelector = matchSimpleSelector(elementData, s.simple); 115 | 116 | if (matchedSimpleSelector) { 117 | return new MatchedRule(s.getSpecificity(), rule); 118 | } 119 | } 120 | return null; 121 | } 122 | 123 | private boolean matchSimpleSelector(ElementData elementData, SimpleSelector selector) { 124 | boolean found = false; 125 | 126 | // Check the tag name 127 | if (selector.tagName != null) 128 | if (selector.tagName.equals(elementData.tagName)) 129 | found = true; 130 | 131 | // Check the class name 132 | for (int i = 0; i < elementData.getClasses().size(); i++) { 133 | if (selector.classNames.contains("*") || selector.classNames.contains(elementData.getClasses().get(i))) { 134 | found = true; 135 | break; 136 | } 137 | } 138 | 139 | // Check the element ID 140 | if (elementData.getId() != null) 141 | if (selector.id.equals(elementData.getId())) 142 | found = true; 143 | 144 | // Return true if one or more of the previous is exists. 145 | return found; 146 | } 147 | 148 | // Check the type of the selector, here we will work with simple selector. 149 | private boolean matches(ElementData elementData, Selector selector) { 150 | if (isSimpleSelector(selector)) 151 | return matchSimpleSelector(elementData, selector.simple); 152 | else 153 | return false; 154 | } 155 | 156 | private boolean isSimpleSelector(Selector selector) { 157 | return selector.simple != null; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/com/adhamenaya/run/CSSParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.run; 9 | 10 | import java.util.Collections; 11 | import java.util.Comparator; 12 | import java.util.Vector; 13 | 14 | import com.adhamenaya.css.Color; 15 | import com.adhamenaya.css.Declaration; 16 | import com.adhamenaya.css.Keyword; 17 | import com.adhamenaya.css.Length; 18 | import com.adhamenaya.css.Rule; 19 | import com.adhamenaya.css.Selector; 20 | import com.adhamenaya.css.SimpleSelector; 21 | import com.adhamenaya.css.StyleSheet; 22 | import com.adhamenaya.css.Unit; 23 | import com.adhamenaya.css.Value; 24 | 25 | public class CSSParser extends Parser { 26 | 27 | // Parse one simple selector, e.g.: `type#id.class1.class2.class3` 28 | SimpleSelector parseSimpleSelector() { 29 | SimpleSelector SimpleSelector = new SimpleSelector(null, null, new Vector()); 30 | 31 | while (true) { 32 | Character ch = nextChar(); 33 | switch (ch) { 34 | case '#': 35 | // Consume char to move after the # 36 | consumeChar(); 37 | SimpleSelector.id = parseIdentifier(); 38 | break; 39 | case '.': 40 | consumeChar(); 41 | SimpleSelector.classNames.add(parseIdentifier()); 42 | break; 43 | case '*': 44 | consumeChar(); 45 | SimpleSelector.classNames.add("*"); 46 | default: 47 | if (!isValidIdentifierChar(nextChar())) 48 | return SimpleSelector; 49 | 50 | // Get the tag name i.e. div, body ..etc 51 | String tagName = parseIdentifier(); 52 | SimpleSelector.tagName = tagName; 53 | break; 54 | } 55 | } 56 | } 57 | 58 | Vector parseSelectors() { 59 | 60 | consumeWhiteSpace(); 61 | 62 | Vector selectors = new Vector(); 63 | 64 | // Loop until the end of selector section and the start of the 65 | // declaration 66 | 67 | while (true) { 68 | 69 | SimpleSelector simple = parseSimpleSelector(); 70 | 71 | Selector selector = new Selector(); 72 | 73 | selector.setSimpleSelector(simple); 74 | 75 | selectors.add(selector); 76 | 77 | Character nextChar = nextChar(); 78 | 79 | switch (nextChar) { 80 | 81 | case ',': 82 | consumeChar(); 83 | break; 84 | case '{': 85 | consumeWhiteSpace(); 86 | return sort(selectors); 87 | default: 88 | return sort(selectors); 89 | } 90 | } 91 | } 92 | 93 | // Sort the selectors list according the specificity value 94 | Vector sort(Vector selectors) { 95 | 96 | Collections.sort(selectors, new Comparator() { 97 | 98 | @Override 99 | public int compare(Selector selector1, Selector selector2) { 100 | return (selector1.getSpecificityString().compareTo(selector2.getSpecificityString())); 101 | } 102 | }); 103 | 104 | Collections.reverse(selectors); 105 | 106 | return selectors; 107 | } 108 | 109 | Vector parseDeclarations() { 110 | 111 | consumeWhiteSpace(); 112 | 113 | Vector declarations = new Vector(); 114 | Character consumedChar = consumeChar(); 115 | 116 | if (!consumedChar.toString().equals("{")) 117 | return null; 118 | 119 | while (true) { 120 | consumeWhiteSpace(); 121 | 122 | boolean endOfDeclaration = nextChar().toString().equals("}"); 123 | 124 | if (eof() || endOfDeclaration) { 125 | consumeChar(); 126 | return declarations; 127 | } 128 | 129 | Declaration declaration = parseDeclaration(); 130 | if (declaration != null) 131 | declarations.add(declaration); 132 | 133 | } 134 | } 135 | 136 | Declaration parseDeclaration() { 137 | 138 | consumeWhiteSpace(); 139 | String propertyName = parseIdentifier(); 140 | 141 | consumeWhiteSpace(); 142 | if (!consumeChar().toString().equals(":")) 143 | return null; 144 | 145 | consumeWhiteSpace(); 146 | Value propertyValue = parseValue(); 147 | 148 | if (!consumeChar().toString().equals(";")) 149 | return null; 150 | 151 | return new Declaration(propertyName, propertyValue); 152 | } 153 | 154 | Value parseValue() { 155 | Value value = null; 156 | 157 | if (nextChar().toString().matches("[0-9]")) { 158 | value = parseLength(); 159 | } else if (nextChar().toString().equals("#")) { 160 | value = parseColor(); 161 | } else { 162 | value = new Keyword(parseIdentifier()); 163 | } 164 | return value; 165 | } 166 | 167 | Length parseLength() { 168 | return new Length(parseFloat(), parseUnit()); 169 | } 170 | 171 | float parseFloat() { 172 | StringBuilder sb = new StringBuilder(); 173 | while (!eof() && nextChar().toString().matches("[0-9.]")) { 174 | sb.append(consumeChar()); 175 | } 176 | return Float.parseFloat(sb.toString()); 177 | } 178 | 179 | Unit parseUnit() { 180 | 181 | String unitStr = parseIdentifier().toLowerCase(); 182 | if (unitStr.equals("px")) 183 | return Unit.px; 184 | else 185 | return null; 186 | } 187 | 188 | Color parseColor() { 189 | 190 | if (!consumeChar().toString().equals("#")) 191 | return null; 192 | 193 | Color color = new Color(); 194 | color.a = 255; 195 | color.r = parseHexPair(); 196 | color.g = parseHexPair(); 197 | color.b = parseHexPair(); 198 | 199 | return color; 200 | 201 | } 202 | 203 | // Convert from Hexadecimal into decimal 204 | int parseHexPair() { 205 | String str = input.substring(poistion, poistion + 2); 206 | poistion += 2; 207 | return Integer.parseInt(str, 16); 208 | } 209 | 210 | String parseIdentifier() { 211 | // Continue reading characters while you don't have white space or { 212 | StringBuilder sb = new StringBuilder(); 213 | while (!eof() && nextChar().toString().matches("[a-zA-Z0-9_-]")) { 214 | Character consumedChar = consumeChar(); 215 | sb.append(consumedChar); 216 | } 217 | return sb.toString(); 218 | } 219 | 220 | // Parse a rule set: ` { }`. 221 | Rule parseRule() { 222 | 223 | Rule rule = new Rule(); 224 | 225 | Vector selectors = parseSelectors(); 226 | Vector declarations = parseDeclarations(); 227 | 228 | rule.selectors = selectors; 229 | rule.declarations = declarations; 230 | 231 | return rule; 232 | } 233 | 234 | // Parse a list of rule sets, separated by optional whitespace. 235 | Vector parseRules() { 236 | Vector rules = new Vector(); 237 | while (!eof()) { 238 | Rule rule = parseRule(); 239 | rules.add(rule); 240 | } 241 | return rules; 242 | } 243 | 244 | public StyleSheet parse(String source) { 245 | input = source; 246 | poistion = 0; 247 | 248 | StyleSheet sheet = new StyleSheet(); 249 | sheet.rules = parseRules(); 250 | return sheet; 251 | } 252 | 253 | boolean isValidIdentifierChar(Character ch) { 254 | return ch.toString().matches("[a-zA-Z0-9_-]"); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/com/adhamenaya/layout/LayoutBox.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A toy web browser engine built using java, that parses and displays simple HMTL and CSS files 3 | * 4 | * @author Adham Enaya 5 | * @version 1.0 6 | * @since 2015-01-15 7 | */ 8 | package com.adhamenaya.layout; 9 | 10 | import java.util.Vector; 11 | 12 | import com.adhamenaya.css.Keyword; 13 | import com.adhamenaya.css.Length; 14 | import com.adhamenaya.css.Unit; 15 | import com.adhamenaya.css.Value; 16 | import com.adhamenaya.style.StyledNode; 17 | 18 | public class LayoutBox { 19 | 20 | public Dimensions dimensions; 21 | public BoxType boxType; 22 | public Vector children; 23 | 24 | public LayoutBox(BoxType boxType) { 25 | 26 | dimensions = new Dimensions(); 27 | this.boxType = boxType; 28 | children = new Vector(); 29 | 30 | } 31 | 32 | public StyledNode getStyleNode() { 33 | 34 | return boxType.accept(new BoxTypeVisitor() { 35 | 36 | @Override 37 | public StyledNode visit(BlockType box) { 38 | return box.styledNode; 39 | } 40 | 41 | @Override 42 | public StyledNode visit(InlineType box) { 43 | return box.styledNode; 44 | } 45 | 46 | @Override 47 | public StyledNode visit(AnonymousType box) { 48 | return null; 49 | } 50 | }); 51 | 52 | } 53 | 54 | // Where a new in line child should go. 55 | public LayoutBox getInlineContainer() { 56 | 57 | if (boxType instanceof AnonymousType || boxType instanceof InlineType) { 58 | return this; 59 | } else if (boxType instanceof BlockType) { 60 | // If we've just generated an anonymous block box, keep using it. 61 | // Otherwise, create a new one. 62 | Vector children1 = children; 63 | 64 | // if (children.get(children.size() - 1).boxType instance of 65 | // AnonymousType) { 66 | children.add(new LayoutBox(new AnonymousType())); 67 | // } 68 | return this; 69 | } else { 70 | return null; 71 | } 72 | } 73 | 74 | // Lay out a box and its descendants. 75 | public void layout(Dimensions containingBlock) { 76 | if (boxType instanceof BlockType) { 77 | layoutBlock(containingBlock); 78 | } else if (boxType instanceof InlineType) { 79 | // TODO : implement the inline type 80 | layoutBlock(containingBlock); 81 | 82 | } else { 83 | 84 | } 85 | 86 | } 87 | 88 | private void layoutBlock(Dimensions containingBlock) { 89 | // Child width can depend on parent width, so we need to calculate 90 | // this box's width before laying out its children. 91 | calculateBlockWidth(containingBlock); 92 | 93 | // Determine where the box is located within its container. 94 | calculateBlockPosition(containingBlock); 95 | 96 | // Recursively lay out the children of this box. 97 | layoutBlockChildren(); 98 | 99 | // Parent height can depend on child height, so `calculate_height` 100 | // must be called *after* the children are laid out. 101 | calculateBlockHeight(); 102 | 103 | } 104 | 105 | // Calculate the width of a block-level non-replaced element in normal flow. 106 | // http://www.w3.org/TR/CSS2/visudet.html#blockwidth 107 | // Sets the horizontal margin/padding/border dimensions, and the `width`. 108 | private void calculateBlockWidth(Dimensions containingBlock) { 109 | 110 | StyledNode style = getStyleNode(); 111 | 112 | // `width` has initial value `auto`. 113 | Value initWidth = new Keyword("auto"); 114 | 115 | Value width = style.lookUp(initWidth, new String[] { "width" }); 116 | 117 | // margin, border, and padding have initial value 0. 118 | Length zeroLength = new Length(0.0f, Unit.px); 119 | 120 | // Try to lookup the margin values in the attributes list. 121 | Value leftMargin = style.lookUp(zeroLength, new String[] { "margin-left", "margin" }); 122 | Value rightMargin = style.lookUp(zeroLength, new String[] { "margin-right", "margin" }); 123 | 124 | // Border 125 | Value leftBorder = style.lookUp(zeroLength, new String[] { "border-left-width", "border-width" }); 126 | Value rightBorder = style.lookUp(zeroLength, new String[] { "border-right-width", "border-width" }); 127 | 128 | // Padding 129 | Value leftPadding = style.lookUp(zeroLength, new String[] { "padding-left", "padding" }); 130 | Value rightPadding = style.lookUp(zeroLength, new String[] { "padding-right", "padding" }); 131 | 132 | float total = leftMargin.toPx() + rightMargin.toPx() + leftBorder.toPx() + rightBorder.toPx() + leftPadding.toPx() 133 | + rightPadding.toPx() + width.toPx(); 134 | 135 | // If width is not auto and the total is wider than the container, treat 136 | // auto margins as 0. 137 | 138 | if (!width.getValueString().equals("auto") && total > containingBlock.content.width) { 139 | if (leftMargin.getValueString().equals("auto")) { 140 | leftMargin = new Length(0.0f, Unit.px); 141 | } 142 | 143 | if (rightMargin.getValueString().equals("auto")) { 144 | rightMargin = new Length(0.0f, Unit.px); 145 | } 146 | } 147 | 148 | // Adjust used values so that the above sum equals 149 | // `containing_block.width`. 150 | // Each arm of the `match` should increase the total width by exactly 151 | // `underflow`, 152 | // and afterward all values should be absolute lengths in px. 153 | float underflow = containingBlock.content.width - total; 154 | 155 | boolean widthAuto, marginLeftAuto, marginRightAuto; 156 | 157 | widthAuto = width.getValueString().equals("auto"); 158 | marginLeftAuto = leftMargin.getValueString().equals("auto"); 159 | marginRightAuto = rightMargin.getValueString().equals("auto"); 160 | 161 | if (!widthAuto & !marginLeftAuto & !marginRightAuto) { 162 | // If the values are over constrained, calculate margin_right. 163 | rightMargin = new Length(rightMargin.toPx() + underflow, Unit.px); 164 | 165 | } else if ((!widthAuto & !marginLeftAuto & marginRightAuto)) { 166 | rightMargin = new Length(underflow, Unit.px); 167 | } else if ((!widthAuto & marginLeftAuto & !marginRightAuto)) { 168 | leftMargin = new Length(underflow, Unit.px); 169 | } else if ((!widthAuto & marginLeftAuto & marginRightAuto)) { 170 | leftMargin = new Length(underflow / 2, Unit.px); 171 | rightMargin = new Length(underflow / 2, Unit.px); 172 | 173 | } else if (widthAuto) { 174 | if (marginLeftAuto) 175 | leftMargin = new Length(0.0f, Unit.px); 176 | if (marginRightAuto) 177 | rightMargin = new Length(0.0f, Unit.px); 178 | 179 | if (underflow >= 0.0f) { 180 | // Expand width to fill the underflow. 181 | width = new Length(underflow, Unit.px); 182 | } else { 183 | // Width can't be negative. Adjust the right margin instead. 184 | width = new Length(0.0f, Unit.px); 185 | rightMargin = new Length(rightMargin.toPx() + underflow, Unit.px); 186 | } 187 | } 188 | 189 | this.dimensions.content.width = width.toPx(); 190 | this.dimensions.padding.left = leftPadding.toPx(); 191 | this.dimensions.padding.right = rightPadding.toPx(); 192 | this.dimensions.margin.right = rightMargin.toPx(); 193 | this.dimensions.margin.left = leftMargin.toPx(); 194 | 195 | } 196 | 197 | // / Finish calculating the block's edge sizes, and position it within its 198 | // containing block. 199 | // / http://www.w3.org/TR/CSS2/visudet.html#normal-block 200 | // / Sets the vertical margin/padding/border dimensions, and the `x`, `y` 201 | // values. 202 | private void calculateBlockPosition(Dimensions containingBlock) { 203 | 204 | StyledNode style = getStyleNode(); 205 | 206 | // margin, border, and padding have initial value 0. 207 | Length zeroLength = new Length(0.0f, Unit.px); 208 | 209 | // Margin 210 | this.dimensions.margin.top = (style.lookUp(zeroLength, new String[] { "margin-top", "margin" })).toPx(); 211 | this.dimensions.margin.bottom = (style.lookUp(zeroLength, new String[] { "margin-bottom", "margin" })).toPx(); 212 | 213 | // Border 214 | this.dimensions.border.top = (style.lookUp(zeroLength, new String[] { "border-top-width", "border-width" })).toPx(); 215 | this.dimensions.border.bottom = ((Length) style 216 | .lookUp(zeroLength, new String[] { "border-bottom-width", "border-width" })).toPx(); 217 | 218 | // Padding 219 | this.dimensions.padding.top = (style.lookUp(zeroLength, new String[] { "padding-top", "padding" })).toPx(); 220 | this.dimensions.padding.bottom = (style.lookUp(zeroLength, new String[] { "padding-bottom", "padding" })).toPx(); 221 | 222 | // Position the box below all the previous boxes in the container. 223 | this.dimensions.content.x = containingBlock.content.x + this.dimensions.margin.left + this.dimensions.border.left 224 | + this.dimensions.padding.left; 225 | 226 | this.dimensions.content.y = containingBlock.content.y + containingBlock.content.height + this.dimensions.margin.top 227 | + this.dimensions.border.top + this.dimensions.padding.top; 228 | } 229 | 230 | // Lay out the block's children within its content area. 231 | // Sets `self.dimensions.height` to the total content height. 232 | private void layoutBlockChildren() { 233 | 234 | int minimumHeight = 20; 235 | 236 | if (children.size() > 0) 237 | for (LayoutBox child : children) { 238 | child.layout(dimensions); 239 | // Increment the height so each child is laid out below the 240 | // previous 241 | // one. 242 | this.dimensions.content.height += child.dimensions.extraBox().height; 243 | } 244 | else 245 | this.dimensions.content.height = minimumHeight + this.dimensions.content.height + this.dimensions.extraBox().height; 246 | 247 | } 248 | 249 | private void calculateBlockHeight() { 250 | StyledNode style = getStyleNode(); 251 | 252 | Value height = style.specifiedValues.get("height"); 253 | if (height != null && height.toPx() != 0.0f) { 254 | this.dimensions.content.height = height.toPx(); 255 | } 256 | } 257 | } 258 | --------------------------------------------------------------------------------