diff options
author | bptato <nincsnevem662@gmail.com> | 2021-08-05 17:05:20 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-08-05 17:05:20 +0200 |
commit | 69a0f081e6eefdd6a52b0da6586100349b1a6ea8 (patch) | |
tree | 23a61bc20918809fb99927071feca01a55e37c92 | |
parent | caad7b577162a73524277a943050493c489bfb59 (diff) | |
download | chawan-69a0f081e6eefdd6a52b0da6586100349b1a6ea8.tar.gz |
more stuff
-rw-r--r-- | makefile | 8 | ||||
-rw-r--r-- | res/widthconv.json | 100 | ||||
-rw-r--r-- | src/config.nim | 9 | ||||
-rw-r--r-- | src/html/dom.nim | 15 | ||||
-rw-r--r-- | src/html/htmlparser.nim | 92 | ||||
-rw-r--r-- | src/io/buffer.nim | 57 | ||||
-rw-r--r-- | src/io/display.nim | 12 | ||||
-rw-r--r-- | src/main.nim | 2 | ||||
-rw-r--r-- | src/types/color.nim | 9 | ||||
-rw-r--r-- | src/types/enums.nim | 8 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 105 |
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 "東京" |