about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2021-08-05 17:05:20 +0200
committerbptato <nincsnevem662@gmail.com>2021-08-05 17:05:20 +0200
commit69a0f081e6eefdd6a52b0da6586100349b1a6ea8 (patch)
tree23a61bc20918809fb99927071feca01a55e37c92
parentcaad7b577162a73524277a943050493c489bfb59 (diff)
downloadchawan-69a0f081e6eefdd6a52b0da6586100349b1a6ea8.tar.gz
more stuff
-rw-r--r--makefile8
-rw-r--r--res/widthconv.json100
-rw-r--r--src/config.nim9
-rw-r--r--src/html/dom.nim15
-rw-r--r--src/html/htmlparser.nim92
-rw-r--r--src/io/buffer.nim57
-rw-r--r--src/io/display.nim12
-rw-r--r--src/main.nim2
-rw-r--r--src/types/color.nim9
-rw-r--r--src/types/enums.nim8
-rw-r--r--src/utils/twtstr.nim105
11 files changed, 342 insertions, 75 deletions
diff --git a/makefile b/makefile
index a3baa746..3b038c16 100644
--- a/makefile
+++ b/makefile
@@ -4,11 +4,11 @@ FILES = src/main.nim
 
 debug:
 	$(NIMC) $(FLAGS) -d:small $(FILES)
-release:
-	$(NIMC) $(FLAGS) -d:release $(FILES)
 small:
-	$(NIMC) $(FLAGS) -d:danger -d:small $(FILES)
-danger:
 	$(NIMC) $(FLAGS) -d:danger $(FILES)
+release:
+	$(NIMC) $(FLAGS) -d:release -d:full $(FILES)
+danger:
+	$(NIMC) $(FLAGS) -d:danger -d:full $(FILES)
 clean:
 	rm ./twt
diff --git a/res/widthconv.json b/res/widthconv.json
new file mode 100644
index 00000000..09d4c23b
--- /dev/null
+++ b/res/widthconv.json
@@ -0,0 +1,100 @@
+{
+  "!": "!",
+  "\"": """,
+  "#": "#",
+  "$": "$",
+  "%": "%",
+  "&": "&",
+  "'": "'",
+  "(": "(",
+  ")": ")",
+  "*": "*",
+  "+": "+",
+  ",": ",",
+  "-": "-",
+  ".": ".",
+  "/": "/",
+ 
+  "0": "0",
+  "1": "1",
+  "2": "2",
+  "3": "3",
+  "4": "4",
+  "5": "5",
+  "6": "6",
+  "7": "7",
+  "8": "8",
+  "9": "9",
+  ":": ":",
+  ";": ";",
+  "<": "<",
+  "=": "=",
+  ">": ">",
+  "?": "?",
+
+  "⦆": "⦆",
+  "。": "。",
+  "「": "「",
+  "」": "」",
+  "、": "、",
+  "・": "・",
+  "ヲ": ["ヲ", "を"],
+  "ァ": ["ァ", "ぁ"],
+  "ィ": ["ィ", "ぃ"],
+  "ゥ": ["ゥ", "ぅ"],
+  "ェ": ["ェ", "ぇ"],
+  "ォ": ["ォ", "ぉ"],
+  "ャ": ["ャ", "ゃ"],
+  "ュ": ["ュ", "ゅ"],
+  "ョ": ["ョ", "ょ"],
+  "ッ": ["ッ", "っ"],
+
+  "ー": "ー",
+  "ア": ["ア", "あ"],
+  "イ": ["イ", "い"],
+  "ウ": ["ウ", "う"],
+  "エ": ["エ", "え"],
+  "オ": ["オ", "お"],
+  "カ": ["カ", "か"],
+  "キ": ["キ", "き"],
+  "ク": ["ク", "く"],
+  "ケ": ["ケ", "け"],
+  "コ": ["コ", "こ"],
+  "サ": ["サ", "さ"],
+  "シ": ["シ", "し"],
+  "ス": ["ス", "す"],
+  "セ": ["セ", "せ"],
+  "ソ": ["ソ", "そ"],
+
+  "タ": ["タ", "た"],
+  "チ": ["チ", "ち"],
+  "ツ": ["ツ", "つ"],
+  "テ": ["テ", "て"],
+  "ト": ["ト", "と"],
+  "ナ": ["ナ", "な"],
+  "ニ": ["ニ", "に"],
+  "ヌ": ["ヌ", "ぬ"],
+  "ネ": ["ネ", "ね"],
+  "ノ": ["ノ", "の"],
+  "ハ": ["ハ", "は"],
+  "ヒ": ["ヒ", "ひ"],
+  "フ": ["フ", "ふ"],
+  "ヘ": ["ヘ", "へ"],
+  "ホ": ["ホ", "ほ"],
+  "マ": ["マ", "ま"],
+
+  "ミ": ["ミ", "み"],
+  "ム": ["ム", "む"],
+  "メ": ["メ", "め"],
+  "モ": ["モ", "も"],
+  "ヤ": ["ヤ", "や"],
+  "ユ": ["ユ", "ゆ"],
+  "ヨ": ["ヨ", "よ"],
+  "ラ": ["ラ", "ら"],
+  "リ": ["リ", "り"],
+  "ル": ["ル", "る"],
+  "レ": ["レ", "れ"],
+  "ロ": ["ロ", "ろ"],
+  "ワ": ["ワ", "わ"],
+  "ン": ["ン", "ん"]
+}
diff --git a/src/config.nim b/src/config.nim
index 73dff47d..002532cc 100644
--- a/src/config.nim
+++ b/src/config.nim
@@ -124,11 +124,10 @@ proc staticReadKeymap(): (ActionMap, ActionMap, Table[string, string]) =
   lemap = constructActionTable(lemap)
   return (nmap, lemap, compose)
 
-when not defined(small):
-  const (normalActionMap, linedActionMap, composeMap) = staticReadKeymap()
-  normalActionRemap = normalActionMap
-  linedActionRemap = linedActionMap
-  composeRemap = composeMap.toRadixTree()
+const (normalActionMap, linedActionMap, composeMap) = staticReadKeymap()
+normalActionRemap = normalActionMap
+linedActionRemap = linedActionMap
+composeRemap = composeMap.toRadixTree()
 
 proc readConfig*(filename: string): bool =
   var f: File
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 1a393134..2227aece 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -66,6 +66,7 @@ type
     all_elements*: seq[Element]
     head*: HTMLElement
     body*: HTMLElement
+    root*: Element
 
   CharacterData* = ref CharacterDataObj
   CharacterDataObj = object of NodeObj
@@ -259,14 +260,8 @@ proc getRawText*(htmlNode: Node): string =
     else: return ""
   elif htmlNode.isTextNode():
     let chardata = CharacterData(htmlNode)
-    #eprint "char data", chardata.data
     if htmlNode.parentElement != nil and htmlNode.parentElement.tagType != TAG_PRE:
       result = chardata.data.remove("\n")
-      #if unicode.strip(result).runeLen() > 0:
-      #  if htmlNode.getStyle().display != DISPLAY_INLINE:
-      #    result = unicode.strip(result)
-      #else:
-      #  result = ""
     else:
       result = unicode.strip(chardata.data)
     if htmlNode.parentElement != nil and htmlNode.parentElement.tagType == TAG_OPTION:
@@ -334,6 +329,7 @@ func newHtmlElement*(tagType: TagType): HTMLElement =
 
 func newDocument*(): Document =
   new(result)
+  result.root = newHtmlElement(TAG_HTML)
   result.head = newHtmlElement(TAG_HEAD)
   result.body = newHtmlElement(TAG_BODY)
   result.nodeType = DOCUMENT_NODE
@@ -467,7 +463,8 @@ func calcRules(elem: Element, rules: CSSStylesheet): seq[CSSSimpleBlock] =
 proc applyRules*(document: Document, rules: CSSStylesheet): seq[tuple[e:Element,d:CSSDeclaration]] =
   var stack: seq[Element]
 
-  stack.add(document.firstElementChild)
+  stack.add(document.root)
+
   while stack.len > 0:
     let elem = stack.pop()
     for oblock in calcRules(elem, rules):
@@ -475,9 +472,9 @@ proc applyRules*(document: Document, rules: CSSStylesheet): seq[tuple[e:Element,
       for item in decls:
         if item of CSSDeclaration:
           if ((CSSDeclaration)item).important:
-            result.add((elem, (CSSDeclaration)item))
+            result.add((elem, CSSDeclaration(item)))
           else:
-            elem.style.applyProperty((CSSDeclaration)item)
+            elem.style.applyProperty(CSSDeclaration(item))
 
     for child in elem.children:
       stack.add(child)
diff --git a/src/html/htmlparser.nim b/src/html/htmlparser.nim
index 3cfc1d8d..67aec2e4 100644
--- a/src/html/htmlparser.nim
+++ b/src/html/htmlparser.nim
@@ -28,8 +28,9 @@ type
     in_style: bool
     in_noscript: bool
     in_body: bool
-    parentNode: Node
+    elementNode: Element
     textNode: Text
+    commentNode: Comment
 
 func inputSize*(str: string): int =
   if str.len == 0:
@@ -88,38 +89,38 @@ proc getescapecmd(buf: string, at: var int): string =
   elif not isAlphaAscii(buf[i]):
     return ""
 
-  when defined(small):
-    var n = entityMap
+  when defined(full):
+    var n = 0
     var s = ""
     while true:
       s &= buf[i]
       if not entityMap.hasPrefix(s, n):
         break
       let pn = n
-      n = n{s}
+      n = entityMap{s, n}
       if n != pn:
         s = ""
       inc i
 
-    if n.leaf:
+    if entityMap.nodes[n].leaf:
       at = i
-      return n.value
+      return entityMap.nodes[n].value
   else:
-    var n = 0
+    var n = entityMap
     var s = ""
     while true:
       s &= buf[i]
       if not entityMap.hasPrefix(s, n):
         break
       let pn = n
-      n = entityMap{s, n}
+      n = n{s}
       if n != pn:
         s = ""
       inc i
 
-    if entityMap.nodes[n].leaf:
+    if n.leaf:
       at = i
-      return entityMap.nodes[n].value
+      return n.value
 
   return ""
 
@@ -163,12 +164,13 @@ proc parse_tag(buf: string, at: var int): DOMParsedTag =
         let startc = buf[at]
         inc at
         while at < buf.len and buf[at] != startc:
-          var r: Rune
-          fastRuneAt(buf, at, r)
-          if r == Rune('&'):
+          if buf[at + 1] == '&':
+            inc at
             value &= getescapecmd(buf, at)
           else:
-            value &= $r
+            var r: Rune
+            fastRuneAt(buf, at, r)
+            value &= r
         if at < buf.len:
           inc at
       elif at < buf.len:
@@ -223,23 +225,22 @@ proc insertNode(parent: Node, node: Node) =
 proc processDocumentBody(state: var HTMLParseState) =
   if not state.in_body:
     state.in_body = true
-    if state.parentNode.ownerDocument != nil:
-      state.parentNode = state.parentNode.ownerDocument.body
+    if state.elementNode.ownerDocument != nil:
+      state.elementNode = state.elementNode.ownerDocument.body
 
-proc processDocumentStartNode(state: var HTMLParseState, newNode: Node) =
-  if state.parentNode.nodeType == ELEMENT_NODE and ((Element)state.parentNode).tagType == TAG_HTML:
+proc processDocumentAddNode(state: var HTMLParseState, newNode: Node) =
+  if state.elementNode.nodeType == ELEMENT_NODE and ((Element)state.elementNode).tagType == TAG_HTML:
     if state.in_body:
-      state.parentNode = state.parentNode.ownerDocument.body
+      state.elementNode = state.elementNode.ownerDocument.body
     else:
-      state.parentNode = state.parentNode.ownerDocument.head
+      state.elementNode = state.elementNode.ownerDocument.head
 
-  insertNode(state.parentNode, newNode)
-  state.parentNode = newNode
+  insertNode(state.elementNode, newNode)
 
 proc processDocumentEndNode(state: var HTMLParseState) =
-  if state.parentNode == nil or state.parentNode.parentNode == nil:
+  if state.elementNode == nil or state.elementNode.nodeType == DOCUMENT_NODE:
     return
-  state.parentNode = state.parentNode.parentNode
+  state.elementNode = state.elementNode.parentElement
 
 proc processDocumentText(state: var HTMLParseState) =
   if state.textNode != nil and state.textNode.data.len > 0:
@@ -247,8 +248,7 @@ proc processDocumentText(state: var HTMLParseState) =
   if state.textNode == nil:
     state.textNode = newText()
 
-    processDocumentStartNode(state, state.textNode)
-    processDocumentEndNode(state)
+    processDocumentAddNode(state, state.textNode)
 
 proc processDocumentStartElement(state: var HTMLParseState, element: Element, tag: DOMParsedTag) =
   var add = true
@@ -288,10 +288,10 @@ proc processDocumentStartElement(state: var HTMLParseState, element: Element, ta
     processDocumentBody(state)
   else: discard
 
-  if state.parentNode.nodeType == ELEMENT_NODE:
+  if state.elementNode.nodeType == ELEMENT_NODE:
     case element.tagType
-    of TAG_LI, TAG_P:
-      if Element(state.parentNode).tagType == element.tagType:
+    of SelfClosingTagTypes:
+      if Element(state.elementNode).tagType == element.tagType:
         processDocumentEndNode(state)
     of TAG_H1:
       HTMLHeadingElement(element).rank = 1
@@ -307,8 +307,12 @@ proc processDocumentStartElement(state: var HTMLParseState, element: Element, ta
       HTMLHeadingElement(element).rank = 6
     else: discard
 
+    if Element(state.elementNode).tagType == TAG_P and element.tagType in PClosingTagTypes:
+      processDocumentEndNode(state)
+
   if add:
-    processDocumentStartNode(state, element)
+    processDocumentAddNode(state, element)
+    state.elementNode = element
 
   if element.tagType in VoidTagTypes:
     processDocumentEndNode(state)
@@ -321,8 +325,8 @@ proc processDocumentEndElement(state: var HTMLParseState, tag: DOMParsedTag) =
     return
   if tag.tagid == TAG_BODY:
     return
-  if state.parentNode.nodeType == ELEMENT_NODE:
-    if Element(state.parentNode).tagType in {TAG_LI, TAG_P}:
+  if state.elementNode.nodeType == ELEMENT_NODE and tag.tagid != Element(state.elementNode).tagType:
+    if Element(state.elementNode).tagType in SelfClosingTagTypes:
       processDocumentEndNode(state)
   
   processDocumentEndNode(state)
@@ -364,13 +368,13 @@ proc processDocumentPart(state: var HTMLParseState, buf: string) =
       inc at
       let p = getescapecmd(buf, at)
       if state.in_comment:
-        CharacterData(state.parentNode).data &= p
+        state.commentNode.data &= p
       else:
         processDocumentText(state)
         state.textNode.data &= p
     of '<':
       if state.in_comment:
-        CharacterData(state.parentNode).data &= buf[at]
+        state.commentNode.data &= buf[at]
         inc at
       else:
         var p = at
@@ -383,7 +387,9 @@ proc processDocumentPart(state: var HTMLParseState, buf: string) =
               inc p
               at = p
               state.in_comment = true
-              processDocumentStartNode(state, newComment())
+              let comment = newComment()
+              state.commentNode = comment
+              processDocumentAddNode(state, comment)
               if state.textNode != nil:
                 state.textNode.rawtext = state.textNode.getRawText()
                 state.textNode = nil
@@ -420,31 +426,29 @@ proc processDocumentPart(state: var HTMLParseState, buf: string) =
         if p < max and buf[p] == '>':
           inc p
           at = p
+          state.commentNode = nil
           state.in_comment = false
-          processDocumentEndNode(state)
 
       if state.in_comment:
-        CharacterData(state.parentNode).data &= buf[at]
+        state.commentNode.data &= buf[at]
         inc at
     else:
       var r: Rune
       fastRuneAt(buf, at, r)
       if state.in_comment:
-        CharacterData(state.parentNode).data &= $r
+        state.commentNode.data &= $r
       else:
         processDocumentText(state)
         state.textNode.data &= $r
 
 proc parseHtml*(inputStream: Stream): Document =
   let document = newDocument()
-  let html = newHtmlElement(TAG_HTML)
-  insertNode(document, html)
-  insertNode(html, document.head)
-  insertNode(html, document.body)
-  #eprint document.body.firstElementChild != nil
+  insertNode(document, document.root)
+  insertNode(document.root, document.head)
+  insertNode(document.root, document.body)
 
   var state = HTMLParseState()
-  state.parentNode = html
+  state.elementNode = document.root
 
   var till_when = false
 
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index ce28e84e..6d79700f 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -5,6 +5,7 @@ import strutils
 import unicode
 
 import ../types/enums
+import ../types/color
 
 import ../utils/termattrs
 import ../utils/twtstr
@@ -14,10 +15,19 @@ import ../html/dom
 import ./twtio
 
 type
+  BufferCell = object
+    rune*: Rune
+    fgcolor*: CellColor
+    bgcolor*: CellColor
+    italic: bool
+    bold: bool
+    underline: bool
+
   Buffer* = ref BufferObj
   BufferObj = object
-    text*: seq[Rune]
     title*: string
+    lines*: seq[seq[BufferCell]]
+    display*: seq[BufferCell]
     hovertext*: string
     width*: int
     height*: int
@@ -35,15 +45,54 @@ type
     printwrite*: bool
     attrs*: TermAttributes
     document*: Document
+    displaycontrols*: bool
 
     #TODO remove these
     fmttext*: seq[string]
     rawtext*: seq[string]
 
 proc newBuffer*(attrs: TermAttributes): Buffer =
-  return Buffer(width: attrs.termWidth,
-                height: attrs.termHeight,
-                attrs: attrs)
+  new(result)
+  result.width = attrs.termWidth
+  result.height = attrs.termHeight
+  result.attrs = attrs
+
+  let cells = result.width * result.height
+  result.display = newSeq[BufferCell](cells)
+
+proc setText*(buffer: Buffer, x: int, y: int, text: seq[Rune]) =
+  discard
+
+proc setDisplayText(buffer: Buffer, x: int, y: int, text: seq[Rune]) =
+  let pos = y * buffer.width + x
+  var i = 0
+  while i < text.len:
+    buffer.display[pos + i].rune = text[i]
+
+proc refreshDisplay*(buffer: Buffer) =
+  var y = 0
+  for line in buffer.lines[buffer.fromy..buffer.fromy+buffer.height]:
+    var w = 0
+    var i = 0
+    while w < buffer.fromx and i < line.len:
+      w += line[i].rune.width()
+      inc i
+
+    let dls = y * buffer.width
+    var j = 0
+    while w < buffer.fromx + buffer.width and i < line.len:
+      w += line[i].rune.width()
+      buffer.display[dls + j] = line[i]
+      inc i
+
+    inc y
+
+func generateFullOutput*(buffer: Buffer): string =
+  var x = 0
+  var y = 0
+  for cell in buffer.display:
+    
+    discard
 
 #TODO go through these and remove ones that don't make sense in the new model
 func lastLine*(buffer: Buffer): int =
diff --git a/src/io/display.nim b/src/io/display.nim
index 1c72d959..d4c772d6 100644
--- a/src/io/display.nim
+++ b/src/io/display.nim
@@ -39,6 +39,7 @@ type
     docenter: bool
     indent: int
     listval: int
+    lastelem: Element
 
 func newRenderState(): RenderState =
   return RenderState(blanklines: 1)
@@ -54,7 +55,8 @@ proc write(state: var RenderState, fs: string, rs: string) =
 proc flushLine(buffer: Buffer, state: var RenderState) =
   if state.rawline.len == 0:
     inc state.blanklines
-  assert(state.rawline.runeLen() < buffer.width, "line too long:\n" & state.rawline)
+  assert(state.rawline.runeLen() < buffer.width, "line too long: (for node " &
+         $state.lastelem & " " & $state.lastelem.style.display & ")\n" & state.rawline)
   buffer.writefmt(state.fmtline)
   buffer.writeraw(state.rawline)
   state.x = 0
@@ -186,7 +188,11 @@ proc renderNode(buffer: Buffer, node: Node, state: var RenderState) =
   if node.nodeType == ELEMENT_NODE:
     if Element(node).tagType in {TAG_SCRIPT, TAG_STYLE, TAG_NOSCRIPT, TAG_TITLE}:
       return
-  if style.hidden: return
+  if style.hidden or style.display == DISPLAY_NONE: return
+  if node.nodeType == ELEMENT_NODE:
+    state.lastelem = (Element)node
+  else:
+    state.lastelem = node.parentElement
 
   if not state.docenter:
     if style.centered:
@@ -231,7 +237,7 @@ proc setLastHtmlLine(buffer: Buffer, state: var RenderState) =
 
 proc renderHtml*(buffer: Buffer) =
   var stack: seq[Node]
-  let first = buffer.document
+  let first = buffer.document.root
   stack.add(first)
 
   var state = newRenderState()
diff --git a/src/main.nim b/src/main.nim
index 92d6b3b9..7912a431 100644
--- a/src/main.nim
+++ b/src/main.nim
@@ -38,8 +38,6 @@ proc getPageUri(uri: Uri): Stream =
 var buffers: seq[Buffer]
 
 
-const defaultcss = staticRead"../res/default.css"
-
 proc main*() =
   if paramCount() != 1:
     eprint "Invalid parameters. Usage:\ntwt <url>"
diff --git a/src/types/color.nim b/src/types/color.nim
new file mode 100644
index 00000000..2a956baa
--- /dev/null
+++ b/src/types/color.nim
@@ -0,0 +1,9 @@
+type
+  RGBColor* = tuple[r: uint8, g: uint8, b: uint8]
+
+  CellColor* = object
+    case rgb*: bool
+    of true:
+      rgbcolor: RGBColor
+    of false:
+      color*: uint8
diff --git a/src/types/enums.nim b/src/types/enums.nim
index f88f665b..e364aeb9 100644
--- a/src/types/enums.nim
+++ b/src/types/enums.nim
@@ -88,3 +88,11 @@ const VoidTagTypes* = {
   TAG_AREA, TAG_BASE, TAG_BR, TAG_COL, TAG_FRAME, TAG_HR, TAG_IMG, TAG_INPUT,
   TAG_SOURCE, TAG_TRACK, TAG_LINK, TAG_META, TAG_PARAM, TAG_WBR, TAG_HR
 }
+
+const PClosingTagTypes* = {
+  TAG_ADDRESS, TAG_ARTICLE, TAG_ASIDE, TAG_BLOCKQUOTE, TAG_DETAILS, TAG_DIV,
+  TAG_DL, TAG_FIELDSET, TAG_FIGCAPTION, TAG_FIGURE, TAG_FOOTER, TAG_FORM,
+  TAG_H1, TAG_H2, TAG_H3, TAG_H4, TAG_H5, TAG_H6, TAG_HEADER, TAG_HGROUP,
+  TAG_HR, TAG_MAIN, TAG_MENU, TAG_NAV, TAG_OL, TAG_P, TAG_PRE, TAG_SECTION,
+  TAG_TABLE, TAG_UL
+}
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index d5de3eef..29a38c59 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -1,6 +1,10 @@
 import terminal
 import strutils
 import unicode
+import tables
+import json
+import sugar
+import sequtils
 
 func ansiStyle*(str: string, style: Style): seq[string] =
   result &= ansiStyleCode(style)
@@ -321,12 +325,12 @@ func makewidthtable(): array[0..0x10FFFF, byte] =
     else:
       result[ucs] = 1
 
-when defined(small):
-  # compute lookup table on startup
-  let width_table = makewidthtable()
-else:
+when defined(full):
   # store lookup table in executable
   const width_table = makewidthtable()
+else:
+  # compute lookup table on startup
+  let width_table = makewidthtable()
 
 {.push boundChecks:off.}
 func width*(r: Rune): int =
@@ -453,3 +457,96 @@ func mk_wcswidth_cjk(s: string): int =
   for r in s.runes:
     result += mk_wcwidth_cjk(r)
   return result
+
+const CanHaveDakuten = "かきくけこさしすせそたちつてとはひふへほカキクケコサシスセソタチツテトハヒフヘホ".toRunes()
+
+const CanHaveHandakuten = "はひふへほハヒフヘホ".toRunes()
+
+const HasDakuten = "がぎぐげござじずぜぞだぢづでどばびぶべぼガギグゲゴザジゼゾダヂヅデドバビブベボ".toRunes()
+
+const HasHanDakuten = "ぱぴぷぺぽパピプペポ".toRunes()
+
+#in unicode, char + 1 is dakuten and char + 2 handakuten
+#゙゚
+
+const Dakuten = "゙".toRunes()[0]
+const HanDakuten = "゚".toRunes()[0]
+
+func dakuten*(r: Rune): Rune =
+  if r in CanHaveDakuten:
+    return cast[Rune](cast[int](r) + 1)
+  return r
+
+func handakuten*(r: Rune): Rune =
+  if r in CanHaveHandakuten:
+    return cast[Rune](cast[int](r) + 2)
+
+func nodakuten*(r: Rune): Rune =
+  return cast[Rune](cast[int](r) - 1)
+
+func nohandakuten*(r: Rune): Rune =
+  return cast[Rune](cast[int](r) - 2)
+
+# Halfwidth to fullwidth & vice versa
+const widthconv = staticRead"../../res/widthconv.json"
+proc genHalfWidthTable(): Table[Rune, Rune] =
+  let widthconvjson = parseJson(widthconv)
+  for k, v in widthconvjson:
+    if v.kind == JString:
+      result[v.getStr().toRunes()[0]] = k.toRunes()[0]
+    else:
+      for s in v:
+        result[s.getStr().toRunes()[0]] = k.toRunes()[0]
+
+proc genFullWidthTable(): Table[Rune, Rune] =
+  let widthconvjson = parseJson(widthconv)
+  for k, v in widthconvjson:
+    if v.kind == JString:
+      result[k.toRunes()[0]] = v.getStr().toRunes()[0]
+    else:
+      result[k.toRunes()[0]] = v[0].getStr().toRunes()[0]
+
+const halfwidthtable = genHalfWidthTable()
+const fullwidthtable = genFullWidthTable()
+
+func halfwidth*(r: Rune): Rune =
+  return halfwidthtable.getOrDefault(r, r)
+
+func halfwidth*(s: seq[Rune]): seq[Rune] =
+  for r in s:
+    #TODO implement a setting to enable this, I personally dislike it
+    #if r in HasDakuten:
+    #  result.add(halfwidth(r.nodakuten()))
+    #  result.add(Dakuten)
+    #elif r in HasHanDakuten:
+    #  result.add(halfwidth(r.nohandakuten()))
+    #  result.add(HanDakuten)
+    #else:
+    result.add(halfwidth(r))
+
+func halfwidth*(s: string): string =
+  return $halfwidth(s.toRunes())
+
+func fullwidth*(r: Rune): Rune =
+  return fullwidthtable.getOrDefault(r, r)
+
+proc fullwidth*(s: seq[Rune]): seq[Rune] =
+  for r in s:
+    if r == Rune(0xFF9E): #dakuten
+      if result.len > 0:
+        result[^1] = result[^1].dakuten()
+      else:
+        result.add(r)
+    elif r == Rune(0xFF9F): #handakuten
+      if result.len > 0:
+        result[^1] = result[^1].handakuten()
+      else:
+        result.add(r)
+    else:
+      result.add(fullwidth(r))
+
+proc fullwidth*(s: string): string =
+  return $fullwidth(s.toRunes())
+
+echo (halfwidth("とうギょう"))
+echo "東京"