about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--res/ua.css20
-rw-r--r--src/css/selector.nim33
-rw-r--r--src/css/style.nim36
-rw-r--r--src/css/values.nim42
-rw-r--r--src/html/dom.nim96
-rw-r--r--src/html/parser.nim42
-rw-r--r--src/layout/engine.nim32
-rw-r--r--src/types/enums.nim9
-rw-r--r--src/utils/twtstr.nim103
9 files changed, 341 insertions, 72 deletions
diff --git a/res/ua.css b/res/ua.css
index 3e9d2a08..c44d783d 100644
--- a/res/ua.css
+++ b/res/ua.css
@@ -16,6 +16,10 @@ textarea, tt, var, font, iframe, u, s, strike, frame, input, img {
 	display: inline-block;
 }
 
+ol, ul {
+	margin-left: 3ch;
+}
+
 table {
 	display: table;
 }
@@ -100,3 +104,19 @@ sup::before {
 sub::before {
 	content: '~';
 }
+
+ol {
+	list-style-type: decimal;
+}
+
+ul {
+	list-style-type: disc;
+}
+
+:is(ol, ul, menu, dir) ul {
+	list-style-type: circle;
+}
+
+:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) ul {
+	list-style-type: square;
+}
diff --git a/src/css/selector.nim b/src/css/selector.nim
index 7cfbf328..13131f80 100644
--- a/src/css/selector.nim
+++ b/src/css/selector.nim
@@ -48,7 +48,7 @@ type
       elem*: string
     of FUNC_SELECTOR:
       name*: string
-      fsels*: SelectorList
+      fsels*: seq[SelectorList]
     of COMBINATOR_SELECTOR:
       ct*: CombinatorType
       csels*: seq[SelectorList]
@@ -77,13 +77,14 @@ func getSpecificity(sel: Selector): int =
     case sel.name
     of "is":
       var best = 0
-      for child in sel.fsels.sels:
+      for child in sel.fsels:
         let s = getSpecificity(child)
         if s > best:
           best = s
       result += best
     of "not":
-      result += getSpecificity(sel.fsels)
+      for child in sel.fsels:
+        result += getSpecificity(child)
     else: discard
   of UNIVERSAL_SELECTOR:
     discard
@@ -129,6 +130,12 @@ proc addSelector(state: var SelectorParser, sel: Selector) =
   else:
     state.selectors[^1].add(sel)
 
+proc getLastSel(state: SelectorParser): Selector =
+  if state.combinator != nil:
+    return state.combinator.csels[^1].sels[^1]
+  else:
+    return state.selectors[^1].sels[^1]
+
 proc addSelectorList(state: var SelectorParser) =
   if state.combinator != nil:
     state.selectors[^1].add(state.combinator)
@@ -227,22 +234,24 @@ proc parseSelectorSimpleBlock(state: var SelectorParser, cssblock: CSSSimpleBloc
             state.query = QUERY_DELIM
             state.addSelector(Selector(t: ATTR_SELECTOR, attr: $csstoken.value, rel: ' '))
           of QUERY_VALUE:
-            state.selectors[^1].sels[^1].value = $csstoken.value
+            state.getLastSel().value = $csstoken.value
             break
           else: discard
         of CSS_STRING_TOKEN:
           case state.query
           of QUERY_VALUE:
-            state.selectors[^1].sels[^1].value = $csstoken.value
+            state.getLastSel().value = $csstoken.value
             break
           else: discard
         of CSS_DELIM_TOKEN:
           case csstoken.rvalue
           of Rune('~'), Rune('|'), Rune('^'), Rune('$'), Rune('*'):
             if state.query == QUERY_DELIM:
-              state.selectors[^1].sels[^1].rel = char(csstoken.rvalue)
+              state.getLastSel().rel = char(csstoken.rvalue)
           of Rune('='):
             if state.query == QUERY_DELIM:
+              if state.getLastSel().rel == ' ':
+                state.getLastSel().rel = '='
               state.query = QUERY_VALUE
           else: discard
         else: discard
@@ -257,9 +266,13 @@ proc parseSelectorFunction(state: var SelectorParser, cssfunction: CSSFunction)
     state.query = QUERY_TYPE
   else: return
   var fun = Selector(t: FUNC_SELECTOR, name: $cssfunction.name)
-  fun.fsels = SelectorList(parent: state.selectors[^1])
   state.addSelector(fun)
-  state.selectors[^1] = fun.fsels
+
+  let osels = state.selectors
+  let ocomb = state.combinator
+  state.combinator = nil
+  state.selectors = newSeq[SelectorList]()
+  state.addSelectorList()
   for cval in cssfunction.value:
     if cval of CSSToken:
       state.parseSelectorToken(CSSToken(cval))
@@ -267,7 +280,9 @@ proc parseSelectorFunction(state: var SelectorParser, cssfunction: CSSFunction)
       state.parseSelectorSimpleBlock(CSSSimpleBlock(cval))
     elif cval of CSSFunction:
       state.parseSelectorFunction(CSSFunction(cval))
-  state.selectors[^1] = fun.fsels.parent
+  fun.fsels = state.selectors
+  state.selectors = osels
+  state.combinator = ocomb
 
 func parseSelectors*(cvals: seq[CSSComponentValue]): seq[SelectorList] =
   var state = SelectorParser()
diff --git a/src/css/style.nim b/src/css/style.nim
index e2bf2347..efffb8c6 100644
--- a/src/css/style.nim
+++ b/src/css/style.nim
@@ -53,6 +53,22 @@ func pseudoElemSelectorMatches(elem: Element, sel: Selector): SelectResult =
 
 func selectorsMatch(elem: Element, selectors: SelectorList): SelectResult
 
+func funcSelectorMatches(elem: Element, sel: Selector): SelectResult =
+  case sel.name
+  of "not":
+    for slist in sel.fsels:
+      let res = elem.selectorsMatch(slist)
+      if res.success:
+        return selectres(false)
+    return selectres(true)
+  of "is", "where":
+    for slist in sel.fsels:
+      let res = elem.selectorsMatch(slist)
+      if not res.success:
+        return selectres(false)
+    return selectres(true)
+  else: discard
+
 func selectorMatches(elem: Element, sel: Selector): SelectResult =
   case sel.t
   of TYPE_SELECTOR:
@@ -70,7 +86,7 @@ func selectorMatches(elem: Element, sel: Selector): SelectResult =
   of UNIVERSAL_SELECTOR:
     return selectres(true)
   of FUNC_SELECTOR:
-    return selectres(false)
+    return funcSelectorMatches(elem, sel)
   of COMBINATOR_SELECTOR:
     #combinator without at least two members makes no sense
     assert sel.csels.len > 1
@@ -110,7 +126,6 @@ func selectorMatches(elem: Element, sel: Selector): SelectResult =
             return selectres(false)
 
           if not res.success:
-            eprint "fail", e.tagType
             return selectres(false)
           dec i
           e = e.previousElementSibling
@@ -157,12 +172,7 @@ func selectElems(document: Document, sel: Selector): seq[Element] =
   of PSELEM_SELECTOR:
     return document.all_elements.filter((elem) => pseudoElemSelectorMatches(elem, sel))
   of FUNC_SELECTOR:
-    case sel.name
-    of "not":
-      return document.all_elements.filter((elem) => not selectorsMatch(elem, sel.fsels).psuccess)
-    of "is", "where":
-      return document.all_elements.filter((elem) => selectorsMatch(elem, sel.fsels).psuccess)
-    return newSeq[Element]()
+    return document.all_elements.filter((elem) => selectorMatches(elem, sel))
   of COMBINATOR_SELECTOR:
     return document.all_elements.filter((elem) => selectorMatches(elem, sel))
 
@@ -173,15 +183,7 @@ func selectElems(document: Document, selectors: SelectorList): seq[Element] =
   var i = 1
 
   while i < sellist.len:
-    if sellist[i].t == FUNC_SELECTOR:
-      case sellist[i].name
-      of "not":
-        result = result.filter((elem) => not selectorsMatch(elem, sellist[i].fsels).psuccess)
-      of "is", "where":
-        result = result.filter((elem) => selectorsMatch(elem, sellist[i].fsels).psuccess)
-      else: discard
-    else:
-      result = result.filter((elem) => selectorMatches(elem, sellist[i]).psuccess)
+    result = result.filter((elem) => selectorMatches(elem, sellist[i]).psuccess)
     inc i
 
 proc querySelector*(document: Document, q: string): seq[Element] =
diff --git a/src/css/values.nim b/src/css/values.nim
index 7d5d5bc5..b55869f7 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -39,6 +39,8 @@ type
       textdecoration*: CSSTextDecoration
     of VALUE_WORD_BREAK:
       wordbreak*: CSSWordBreak
+    of VALUE_LIST_STYLE_TYPE:
+      liststyletype*: CSSListStyleType
     of VALUE_NONE: discard
 
   CSSComputedValues* = ref array[low(CSSPropertyType)..high(CSSPropertyType), CSSComputedValue]
@@ -66,6 +68,7 @@ const PropertyNames = {
   "word-break": PROPERTY_WORD_BREAK,
   "width": PROPERTY_WIDTH,
   "height": PROPERTY_HEIGHT,
+  "list-style-type": PROPERTY_LIST_STYLE_TYPE,
 }.toTable()
 
 const ValueTypes = [
@@ -86,11 +89,13 @@ const ValueTypes = [
   PROPERTY_WORD_BREAK: VALUE_WORD_BREAK,
   PROPERTY_WIDTH: VALUE_LENGTH,
   PROPERTY_HEIGHT: VALUE_LENGTH,
+  PROPERTY_LIST_STYLE_TYPE: VALUE_LIST_STYLE_TYPE,
 ]
 
 const InheritedProperties = {
   PROPERTY_COLOR, PROPERTY_FONT_STYLE, PROPERTY_WHITE_SPACE,
-  PROPERTY_FONT_WEIGHT, PROPERTY_TEXT_DECORATION, PROPERTY_WORD_BREAK
+  PROPERTY_FONT_WEIGHT, PROPERTY_TEXT_DECORATION, PROPERTY_WORD_BREAK,
+  PROPERTY_LIST_STYLE_TYPE
 }
 
 func getPropInheritedArray(): array[low(CSSPropertyType)..high(CSSPropertyType), bool] =
@@ -123,6 +128,15 @@ func cells*(l: CSSLength): int =
     #TODO
     return int(l.num / 8)
 
+func listMarker*(t: CSSListStyleType, i: int): string =
+  case t
+  of LIST_STYLE_TYPE_NONE: return ""
+  of LIST_STYLE_TYPE_DISC: return "* "
+  of LIST_STYLE_TYPE_CIRCLE: return "o "
+  of LIST_STYLE_TYPE_SQUARE: return "O "
+  of LIST_STYLE_TYPE_DECIMAL: return $i & ". "
+  of LIST_STYLE_TYPE_JAPANESE_INFORMAL: return japaneseNumber(i) & "、"
+
 func r(c: CSSColor): int =
   return c.rgba.r
 
@@ -436,8 +450,8 @@ func cssDisplay(d: CSSDeclaration): CSSDisplay =
       case $tok.value
       of "block": return DISPLAY_BLOCK
       of "inline": return DISPLAY_INLINE
+      of "list-item": return DISPLAY_LIST_ITEM
       # of "inline-block": return DISPLAY_INLINE_BLOCK
-      # of "list-item": return DISPLAY_LIST_ITEM
       # of "table": return DISPLAY_TABLE
       # of "table-row-group": return DISPLAY_TABLE_ROW_GROUP
       # of "table-header-group": return DISPLAY_TABLE_HEADER_GROUP
@@ -511,6 +525,19 @@ func cssWordBreak(d: CSSDeclaration): CSSWordBreak =
       of "keep-all": return WORD_BREAK_KEEP_ALL
   raise newException(CSSValueError, "Invalid text decoration")
 
+func cssListStyleType(d: CSSDeclaration): CSSListStyleType =
+  if isToken(d):
+    let tok = getToken(d)
+    if tok.tokenType == CSS_IDENT_TOKEN:
+      case $tok.value
+      of "none": return LIST_STYLE_TYPE_NONE
+      of "disc": return LIST_STYLE_TYPE_DISC
+      of "circle": return LIST_STYLE_TYPE_CIRCLE
+      of "square": return LIST_STYLE_TYPE_SQUARE
+      of "decimal": return LIST_STYLE_TYPE_DECIMAL
+      of "japanese-informal": return LIST_STYLE_TYPE_JAPANESE_INFORMAL
+  raise newException(CSSValueError, "Invalid list style")
+
 func getSpecifiedValue*(d: CSSDeclaration): CSSSpecifiedValue =
   let name = $d.name
   let ptype = propertyType(name)
@@ -529,6 +556,7 @@ func getSpecifiedValue*(d: CSSDeclaration): CSSSpecifiedValue =
         result.integer = cssFontWeight(d)
     of VALUE_TEXT_DECORATION: result.textdecoration = cssTextDecoration(d)
     of VALUE_WORD_BREAK: result.wordbreak = cssWordBreak(d)
+    of VALUE_LIST_STYLE_TYPE: result.liststyletype = cssListStyleType(d)
     of VALUE_NONE: discard
   except CSSValueError:
     result.globalValue = VALUE_UNSET
@@ -611,6 +639,8 @@ func getComputedValue*(prop: CSSSpecifiedValue, current: CSSComputedValues): CSS
     return CSSComputedValue(t: prop.t, v: VALUE_TEXT_DECORATION, textdecoration: prop.textdecoration)
   of VALUE_WORD_BREAK:
     return CSSComputedValue(t: prop.t, v: VALUE_WORD_BREAK, wordbreak: prop.wordbreak)
+  of VALUE_LIST_STYLE_TYPE:
+    return CSSComputedValue(t: prop.t, v: VALUE_LIST_STYLE_TYPE, liststyletype: prop.liststyletype)
   of VALUE_NONE: return CSSComputedValue(t: prop.t, v: VALUE_NONE)
 
 func getComputedValue*(d: CSSDeclaration, current: CSSComputedValues): CSSComputedValue =
@@ -629,3 +659,11 @@ proc inheritProperties*(vals: var CSSComputedValues, parent: CSSComputedValues)
       vals[prop] = getDefault(prop)
     if inherited(prop) and parent[prop] != nil and vals[prop] == getDefault(prop):
       vals[prop] = parent[prop]
+
+func inheritProperties*(parent: CSSComputedValues): CSSComputedValues =
+  new(result)
+  for prop in low(CSSPropertyType)..high(CSSPropertyType):
+    if inherited(prop) and parent[prop] != nil:
+      result[prop] = parent[prop]
+    else:
+      result[prop] = getDefault(prop)
diff --git a/src/html/dom.nim b/src/html/dom.nim
index f2c98bd0..0c7d385a 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -1,5 +1,7 @@
 import uri
 import tables
+import options
+import strutils
 
 import css/values
 import types/enums
@@ -70,40 +72,46 @@ type
     cssapplied*: bool
     rendered*: bool
 
-  HTMLElement* = ref HTMLElementObj
-  HTMLElementObj = object of ElementObj
+  HTMLElement* = ref object of ElementObj
 
-  HTMLInputElement* = ref HTMLInputElementObj
-  HTMLInputElementObj = object of HTMLElementObj
+  HTMLInputElement* = ref object of HTMLElement
     itype*: InputType
     autofocus*: bool
     required*: bool
     value*: string
     size*: int
 
-  HTMLAnchorElement* = ref HTMLAnchorElementObj
-  HTMLAnchorElementObj = object of HTMLElementObj
+  HTMLAnchorElement* = ref object of HTMLElement
     href*: string
 
-  HTMLSelectElement* = ref HTMLSelectElementObj
-  HTMLSelectElementObj = object of HTMLElementObj
+  HTMLSelectElement* = ref object of HTMLElement
     name*: string
     value*: string
     valueSet*: bool
 
-  HTMLSpanElement* = ref HTMLSpanElementObj
-  HTMLSpanElementObj = object of HTMLElementObj
+  HTMLSpanElement* = ref object of HTMLElement
 
-  HTMLOptionElement* = ref HTMLOptionElementObj
-  HTMLOptionElementObj = object of HTMLElementObj
+  HTMLOptionElement* = ref object of HTMLElement
     value*: string
   
-  HTMLHeadingElement* = ref HTMLHeadingElementObj
-  HTMLHeadingElementObj = object of HTMLElementObj
+  HTMLHeadingElement* = ref object of HTMLElement
     rank*: uint16
 
-  HTMLBRElement* = ref HTMLBRElementObj
-  HTMLBRElementObj = object of HTMLElementObj
+  HTMLBRElement* = ref object of HTMLElement
+
+  HTMLMenuElement* = ref object of HTMLElement
+    ordinalcounter*: int
+
+  HTMLUListElement* = ref object of HTMLElement
+    ordinalcounter*: int
+
+  HTMLOListElement* = ref object of HTMLElement
+    start*: Option[int]
+    ordinalcounter*: int
+
+  HTMLLIElement* = ref object of HTMLElement
+    value*: Option[int]
+    ordinalvalue*: int
 
 func firstChild(node: Node): Node =
   if node.childNodes.len == 0:
@@ -191,6 +199,49 @@ func toInputType*(str: string): InputType =
   of "week": INPUT_WEEK
   else: INPUT_UNKNOWN
 
+func ancestor(node: Node, tagTypes: set[TagType]): Element =
+  var elem = node.parentElement
+  while elem != nil:
+    if elem.tagType in tagTypes:
+      return elem
+
+    elem = elem.parentElement
+  return nil
+
+func attr*(element: Element, s: string): string =
+  return element.attributes.getOrDefault(s, "")
+
+func attri*(element: Element, s: string): Option[int] =
+  let a = element.attr(s)
+  try:
+    return some(parseInt(a))
+  except ValueError:
+    return none(int)
+
+proc applyOrdinal*(elem: HTMLLIElement) =
+  let val = elem.attri("value")
+  if val.issome:
+    elem.ordinalvalue = val.get
+  else:
+    let owner = elem.ancestor({TAG_OL, TAG_UL, TAG_MENU})
+    if owner == nil:
+      elem.ordinalvalue = 1
+    else:
+      case owner.tagType
+      of TAG_OL:
+        let ol = HTMLOListElement(owner)
+        elem.ordinalvalue = ol.ordinalcounter
+        inc ol.ordinalcounter
+      of TAG_UL:
+        let ul = HTMLUListElement(owner)
+        elem.ordinalvalue = ul.ordinalcounter
+        inc ul.ordinalcounter
+      of TAG_MENU:
+        let menu = HTMLMenuElement(owner)
+        elem.ordinalvalue = menu.ordinalcounter
+        inc menu.ordinalcounter
+      else: discard
+
 func newText*(): Text =
   new(result)
   result.nodeType = TEXT_NODE
@@ -215,6 +266,16 @@ func newHtmlElement*(tagType: TagType): HTMLElement =
     result = new(HTMLBRElement)
   of TAG_SPAN:
     result = new(HTMLSpanElement)
+  of TAG_OL:
+    result = new(HTMLOListElement)
+  of TAG_UL:
+    result = new(HTMLUListElement)
+    HTMLUListElement(result).ordinalcounter = 1
+  of TAG_MENU:
+    result = new(HTMLMenuElement)
+    HTMLMenuElement(result).ordinalcounter = 1
+  of TAG_LI:
+    result = new(HTMLLIElement)
   else:
     result = new(HTMLElement)
 
@@ -235,6 +296,3 @@ func newAttr*(parent: Element, key: string, value: string): Attr =
   result.ownerElement = parent
   result.name = key
   result.value = value
-
-func attr*(element: Element, s: string): string =
-  return element.attributes.getOrDefault(s, "")
diff --git a/src/html/parser.nim b/src/html/parser.nim
index a3a6885d..70b749a6 100644
--- a/src/html/parser.nim
+++ b/src/html/parser.nim
@@ -3,6 +3,7 @@ import unicode
 import strutils
 import tables
 import json
+import options
 
 import types/enums
 import types/tagtypes
@@ -250,6 +251,11 @@ proc processDocumentStartElement(state: var HTMLParseState, element: Element, ta
     HTMLAnchorElement(element).href = element.attr("href")
   of TAG_OPTION:
     HTMLOptionElement(element).value = element.attr("href")
+  of TAG_OL:
+    HTMLOListElement(element).start = element.attri("start") 
+    HTMLOListElement(element).ordinalcounter = HTMLOListElement(element).start.get(1)
+  of TAG_LI:
+    HTMLLIElement(element).value = element.attri("value")
   of TAG_HTML:
     add = false
   of TAG_HEAD:
@@ -261,29 +267,27 @@ proc processDocumentStartElement(state: var HTMLParseState, element: Element, ta
     add = false
   of TAG_PRE:
     state.skip_lf = true
+  of TAG_H1:
+    HTMLHeadingElement(element).rank = 1
+  of TAG_H2:
+    HTMLHeadingElement(element).rank = 2
+  of TAG_H3:
+    HTMLHeadingElement(element).rank = 3
+  of TAG_H4:
+    HTMLHeadingElement(element).rank = 4
+  of TAG_H5:
+    HTMLHeadingElement(element).rank = 5
+  of TAG_H6:
+    HTMLHeadingElement(element).rank = 6
   else: discard
 
   if not state.in_body and not (element.tagType in HeadTagTypes):
     processDocumentBody(state)
 
   if state.elementNode.nodeType == ELEMENT_NODE:
-    case element.tagType
-    of SelfClosingTagTypes:
+    if element.tagType in SelfClosingTagTypes:
       if state.elementNode.tagType == element.tagType:
         processDocumentEndNode(state)
-    of TAG_H1:
-      HTMLHeadingElement(element).rank = 1
-    of TAG_H2:
-      HTMLHeadingElement(element).rank = 2
-    of TAG_H3:
-      HTMLHeadingElement(element).rank = 3
-    of TAG_H4:
-      HTMLHeadingElement(element).rank = 4
-    of TAG_H5:
-      HTMLHeadingElement(element).rank = 5
-    of TAG_H6:
-      HTMLHeadingElement(element).rank = 6
-    else: discard
 
     if state.elementNode.tagType == TAG_P and element.tagType in PClosingTagTypes:
       processDocumentEndNode(state)
@@ -292,8 +296,12 @@ proc processDocumentStartElement(state: var HTMLParseState, element: Element, ta
     processDocumentAddNode(state, element)
     state.elementNode = element
 
-  if element.tagType in VoidTagTypes:
-    processDocumentEndNode(state)
+    case element.tagType
+    of VoidTagTypes:
+      processDocumentEndNode(state)
+    of TAG_LI:
+      HTMLLIElement(element).applyOrdinal() #needs to know parent
+    else: discard
 
 proc processDocumentEndElement(state: var HTMLParseState, tag: DOMParsedTag) =
   if tag.tagid in VoidTagTypes:
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 614fd332..d9987de2 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -292,7 +292,8 @@ func isBlock(node: Node): bool =
   if node.nodeType != ELEMENT_NODE:
     return false
   let elem = Element(node)
-  return elem.cssvalues[PROPERTY_DISPLAY].display == DISPLAY_BLOCK
+  return elem.cssvalues[PROPERTY_DISPLAY].display == DISPLAY_BLOCK or
+          elem.cssvalues[PROPERTY_DISPLAY].display == DISPLAY_LIST_ITEM
 
 func isInline(node: Node): bool =
   if node.nodeType == TEXT_NODE:
@@ -311,6 +312,8 @@ proc processComputedValueBox(state: var LayoutState, parent: CSSBox, values: CSS
     #CSSBlockBox(result).tag = $elem.tagType
   of DISPLAY_INLINE:
     result = newInlineBox(parent, values)
+  of DISPLAY_LIST_ITEM:
+    result = state.newBlockBox(parent, values)
   of DISPLAY_NONE:
     return nil
   else:
@@ -351,8 +354,7 @@ proc processAnonComputedValues(state: var LayoutState, parent: CSSBox, c: CSSCom
   if parent.bcontext.has_blocks:
     if c[PROPERTY_DISPLAY].display == DISPLAY_INLINE:
       if parent.bcontext.anon_block == nil:
-        var cssvals: CSSComputedValues
-        cssvals.inheritProperties(parent.cssvalues)
+        let cssvals = parent.cssvalues.inheritProperties()
         parent.bcontext.anon_block = state.newBlockBox(parent, cssvals)
       state.add(parent.bcontext.anon_block, state.processComputedValueBox(parent.bcontext.anon_block, c))
       return true
@@ -376,13 +378,17 @@ proc processAnonBlock(state: var LayoutState, parent: CSSBox, c: Node): bool =
   return false
 
 func needsAnonymousBlockBoxes(node: Node): bool =
+  if not node.isBlock():
+    return false
   if node.nodeType == ELEMENT_NODE:
     let elem = Element(node)
     if elem.cssvalues_before != nil:
-      if elem.cssvalues_before[PROPERTY_DISPLAY].display == DISPLAY_BLOCK:
+      if elem.cssvalues_before[PROPERTY_DISPLAY].display == DISPLAY_BLOCK or
+          elem.cssvalues_before[PROPERTY_DISPLAY].display == DISPLAY_LIST_ITEM:
         return true
     if elem.cssvalues_after != nil:
-      if elem.cssvalues_after[PROPERTY_DISPLAY].display == DISPLAY_BLOCK:
+      if elem.cssvalues_after[PROPERTY_DISPLAY].display == DISPLAY_BLOCK or
+          elem.cssvalues_after[PROPERTY_DISPLAY].display == DISPLAY_LIST_ITEM:
         return true
 
   for c in node.childNodes:
@@ -443,6 +449,20 @@ proc processAfterPseudoElem(state: var LayoutState, parent: CSSBox, node: Node)
       if box != parent.bcontext.anon_block:
         state.add(parent, box)
 
+proc processMarker(state: var LayoutState, parent: CSSBox, node: Node) =
+  if node.nodeType == ELEMENT_NODE:
+    let elem = Element(node)
+    if elem.cssvalues[PROPERTY_DISPLAY].display == DISPLAY_LIST_ITEM:
+      var ordinalvalue = 1
+      if elem.tagType == TAG_LI:
+        ordinalvalue = HTMLLIElement(elem).ordinalvalue
+
+      let text = elem.cssvalues[PROPERTY_LIST_STYLE_TYPE].liststyletype.listMarker(ordinalvalue)
+      let tlen = text.width()
+      parent.icontext.fromx -= tlen
+      let marker = state.processInlineBox(parent, text)
+      state.add(parent, marker)
+
 proc processNodes(state: var LayoutState, parent: CSSBox, node: Node) =
   state.nodes.add(node)
 
@@ -450,6 +470,8 @@ proc processNodes(state: var LayoutState, parent: CSSBox, node: Node) =
 
   state.processBeforePseudoElem(parent, node)
 
+  state.processMarker(parent, node)
+
   for c in node.childNodes:
     let isanon = state.processAnonBlock(parent, c)
     if not isanon:
diff --git a/src/types/enums.nim b/src/types/enums.nim
index aeb53567..028b7557 100644
--- a/src/types/enums.nim
+++ b/src/types/enums.nim
@@ -61,12 +61,12 @@ type
     PROPERTY_MARGIN_BOTTOM, PROPERTY_FONT_STYLE, PROPERTY_DISPLAY,
     PROPERTY_CONTENT, PROPERTY_WHITE_SPACE, PROPERTY_FONT_WEIGHT,
     PROPERTY_TEXT_DECORATION, PROPERTY_WORD_BREAK, PROPERTY_WIDTH,
-    PROPERTY_HEIGHT
+    PROPERTY_HEIGHT, PROPERTY_LIST_STYLE_TYPE
 
   CSSValueType* = enum
     VALUE_NONE, VALUE_LENGTH, VALUE_COLOR, VALUE_CONTENT, VALUE_DISPLAY,
     VALUE_FONT_STYLE, VALUE_WHITE_SPACE, VALUE_INTEGER, VALUE_TEXT_DECORATION,
-    VALUE_WORD_BREAK
+    VALUE_WORD_BREAK, VALUE_LIST_STYLE_TYPE
 
   CSSGlobalValueType* = enum
     VALUE_NOGLOBAL, VALUE_INITIAL, VALUE_INHERIT, VALUE_REVERT, VALUE_UNSET
@@ -95,3 +95,8 @@ type
 
   CSSWordBreak* = enum
     WORD_BREAK_NORMAL, WORD_BREAK_BREAK_ALL, WORD_BREAK_KEEP_ALL
+
+  CSSListStyleType* = enum
+    LIST_STYLE_TYPE_NONE, LIST_STYLE_TYPE_DISC, LIST_STYLE_TYPE_CIRCLE,
+    LIST_STYLE_TYPE_SQUARE, LIST_STYLE_TYPE_DECIMAL,
+    LIST_STYLE_TYPE_JAPANESE_INFORMAL
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index 5bd25247..a75c936f 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -5,6 +5,7 @@ import tables
 import json
 import os
 import math
+import sugar
 
 when defined(posix):
   import posix
@@ -189,6 +190,106 @@ func skipBlanks*(buf: string, at: int): int =
   while result < buf.len and buf[result].isWhitespace():
     inc result
 
+#TODO
+func japaneseNumber*(i: int): string =
+  if i == 0:
+    return "〇"
+  var n = i
+  if i < 0:
+    result &= "ス"
+    n *= -1
+
+  let o = n
+
+  var ss: seq[string]
+  var d = 0
+  var mark = false
+  var res = false
+  while n > 0:
+    let m = n mod 10
+
+    if m != 0:
+      case d
+      of 1: ss.add("十")
+      of 2: ss.add("百")
+      of 3: ss.add("千")
+      of 4:
+        ss.add("万")
+        ss.add("一")
+      of 5:
+        ss.add("万")
+        ss.add("十")
+      of 6:
+        ss.add("万")
+        ss.add("百")
+      of 7:
+        ss.add("万")
+        ss.add("千")
+        ss.add("一")
+      of 8:
+        ss.add("億")
+        ss.add("一")
+      of 9:
+        ss.add("億")
+        ss.add("十")
+      else: discard
+    case m
+    of 0:
+      inc d
+      n = n div 10
+      mark = true
+    of 1:
+      if o == n:
+        ss.add("一")
+    of 2: ss.add("二")
+    of 3: ss.add("三")
+    of 4: ss.add("四")
+    of 5: ss.add("五")
+    of 6: ss.add("六")
+    of 7: ss.add("七")
+    of 8: ss.add("八")
+    of 9: ss.add("九")
+    else: discard
+    n -= m
+
+  n = ss.len - 1
+  while n >= 0:
+    result &= ss[n]
+    dec n
+
+func parseInt32*(s: string): int =
+  var sign = 1
+  var t = 1
+  var integer: int = 0
+  var e: int = 0
+
+  var i = 0
+  if i < s.len and s[i] == '-':
+    sign = -1
+    inc i
+  elif i < s.len and s[i] == '+':
+    inc i
+
+  while i < s.len and isDigit(s[i]):
+    integer *= 10
+    integer += decValue(s[i])
+    inc i
+
+  if i < s.len and (s[i] == 'e' or s[i] == 'E'):
+    inc i
+    if i < s.len and s[i] == '-':
+      t = -1
+      inc i
+    elif i < s.len and s[i] == '+':
+      inc i
+
+    while i < s.len and isDigit(s[i]):
+      e *= 10
+      e += decValue(s[i])
+      inc i
+
+  return sign * integer * 10 ^ (t * e)
+
 func parseInt64*(s: string): int64 =
   var sign = 1
   var t = 1
@@ -220,7 +321,7 @@ func parseInt64*(s: string): int64 =
       e += decValue(s[i])
       inc i
 
-  return sign * integer * 10 ^ t * e
+  return sign * integer * 10 ^ (t * e)
 
 func parseFloat64*(s: string): float64 =
   var sign = 1