about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-12-11 01:12:26 +0100
committerbptato <nincsnevem662@gmail.com>2023-12-11 01:18:22 +0100
commit6b9db7e8d77c3ce68558f45f9162121a13a96a2b (patch)
tree9620c5c2266cf0eac24b233024fdecbc49d69e06
parentb787ab592f582fc4e6c9feb7dbec2820aa2f2c7b (diff)
downloadchawan-6b9db7e8d77c3ce68558f45f9162121a13a96a2b.tar.gz
css: add text-transform
Probably not fully correct, but it's a good start.

Includes proprietary extension -cha-half-width, which converts
full-width characters to half-width ones.
-rw-r--r--res/kanamap.tab58
-rw-r--r--res/widthconv.json100
-rw-r--r--src/css/values.nim34
-rw-r--r--src/layout/engine.nim24
-rw-r--r--src/utils/twtstr.nim116
5 files changed, 321 insertions, 11 deletions
diff --git a/res/kanamap.tab b/res/kanamap.tab
new file mode 100644
index 00000000..94069a5c
--- /dev/null
+++ b/res/kanamap.tab
@@ -0,0 +1,58 @@
+ぁ	あ
+ぃ	い
+ぅ	う
+ぇ	え
+ぉ	お
+ゕ	か
+ゖ	け
+𛄲	こ
+っ	つ
+ゃ	や
+ゅ	ゆ
+ょ	よ
+ゎ	わ
+𛅐	ゐ
+𛅑	ゑ
+𛅒	を
+ァ	ア
+ィ	イ
+ゥ	ウ
+ェ	エ
+ォ	オ
+ヵ	カ
+ㇰ	ク
+ヶ	ケ
+𛅕	コ
+ㇱ	シ
+ㇲ	ス
+ッ	ツ
+ㇳ	ト
+ㇴ	ヌ
+ㇵ	ハ
+ㇶ	ヒ
+ㇷ	フ
+ㇸ	ヘ
+ㇹ	ホ
+ㇺ	ム
+ャ	ヤ
+ュ	ユ
+ョ	ヨ
+ㇻ	ラ
+ㇼ	リ
+ㇽ	ル
+ㇾ	レ
+ㇿ	ロ
+ヮ	ワ
+𛅤	ヰ
+𛅥	ヱ
+𛅦	ヲ
+𛅧	ン
+ァ	ア
+ィ	イ
+ゥ	ウ
+ェ	エ
+ォ	オ
+ッ	ツ
+ャ	ヤ
+ュ	ユ
+ョ	ヨ
diff --git a/res/widthconv.json b/res/widthconv.json
new file mode 100644
index 00000000..297c55db
--- /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/css/values.nim b/src/css/values.nim
index b3e3e24b..bd6bc549 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -40,7 +40,8 @@ type
     PROPERTY_COUNTER_RESET, PROPERTY_MAX_WIDTH, PROPERTY_MAX_HEIGHT,
     PROPERTY_MIN_WIDTH, PROPERTY_MIN_HEIGHT, PROPERTY_BACKGROUND_IMAGE,
     PROPERTY_CHA_COLSPAN, PROPERTY_CHA_ROWSPAN, PROPERTY_FLOAT,
-    PROPERTY_VISIBILITY, PROPERTY_BOX_SIZING, PROPERTY_CLEAR
+    PROPERTY_VISIBILITY, PROPERTY_BOX_SIZING, PROPERTY_CLEAR,
+    PROPERTY_TEXT_TRANSFORM
 
   CSSValueType* = enum
     VALUE_NONE, VALUE_LENGTH, VALUE_COLOR, VALUE_CONTENT, VALUE_DISPLAY,
@@ -49,7 +50,7 @@ type
     VALUE_TEXT_ALIGN, VALUE_LIST_STYLE_POSITION, VALUE_POSITION,
     VALUE_CAPTION_SIDE, VALUE_LENGTH2, VALUE_BORDER_COLLAPSE, VALUE_QUOTES,
     VALUE_COUNTER_RESET, VALUE_IMAGE, VALUE_FLOAT, VALUE_VISIBILITY,
-    VALUE_BOX_SIZING, VALUE_CLEAR
+    VALUE_BOX_SIZING, VALUE_CLEAR, VALUE_TEXT_TRANSFORM
 
   CSSGlobalValueType* = enum
     VALUE_NOGLOBAL, VALUE_INITIAL, VALUE_INHERIT, VALUE_REVERT, VALUE_UNSET
@@ -131,6 +132,11 @@ type
     CLEAR_NONE, CLEAR_LEFT, CLEAR_RIGHT, CLEAR_BOTH, CLEAR_INLINE_START,
     CLEAR_INLINE_END
 
+  CSSTextTransform* = enum
+    TEXT_TRANSFORM_NONE, TEXT_TRANSFORM_CAPITALIZE, TEXT_TRANSFORM_UPPERCASE,
+    TEXT_TRANSFORM_LOWERCASE, TEXT_TRANSFORM_FULL_WIDTH,
+    TEXT_TRANSFORM_FULL_SIZE_KANA, TEXT_TRANSFORM_CHA_HALF_WIDTH
+
 const RowGroupBox* = {DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_HEADER_GROUP,
                       DISPLAY_TABLE_FOOTER_GROUP}
 const ProperTableChild* = {DISPLAY_TABLE_ROW, DISPLAY_TABLE_COLUMN,
@@ -216,6 +222,8 @@ type
       boxsizing*: CSSBoxSizing
     of VALUE_CLEAR:
       clear*: CSSClear
+    of VALUE_TEXT_TRANSFORM:
+      texttransform*: CSSTextTransform
     of VALUE_NONE: discard
 
   CSSComputedValues* = ref array[CSSPropertyType, CSSComputedValue]
@@ -291,7 +299,8 @@ const PropertyNames = {
   "float": PROPERTY_FLOAT,
   "visibility": PROPERTY_VISIBILITY,
   "box-sizing": PROPERTY_BOX_SIZING,
-  "clear": PROPERTY_CLEAR
+  "clear": PROPERTY_CLEAR,
+  "text-transform": PROPERTY_TEXT_TRANSFORM
 }.toTable()
 
 const ValueTypes* = [
@@ -341,7 +350,8 @@ const ValueTypes* = [
   PROPERTY_FLOAT: VALUE_FLOAT,
   PROPERTY_VISIBILITY: VALUE_VISIBILITY,
   PROPERTY_BOX_SIZING: VALUE_BOX_SIZING,
-  PROPERTY_CLEAR: VALUE_CLEAR
+  PROPERTY_CLEAR: VALUE_CLEAR,
+  PROPERTY_TEXT_TRANSFORM: VALUE_TEXT_TRANSFORM
 ]
 
 const InheritedProperties = {
@@ -350,7 +360,7 @@ const InheritedProperties = {
   PROPERTY_LIST_STYLE_TYPE, PROPERTY_WORD_SPACING, PROPERTY_LINE_HEIGHT,
   PROPERTY_TEXT_ALIGN, PROPERTY_LIST_STYLE_POSITION, PROPERTY_CAPTION_SIDE,
   PROPERTY_BORDER_SPACING, PROPERTY_BORDER_COLLAPSE, PROPERTY_QUOTES,
-  PROPERTY_VISIBILITY
+  PROPERTY_VISIBILITY, PROPERTY_TEXT_TRANSFORM
 }
 
 func getPropInheritedArray(): array[CSSPropertyType, bool] =
@@ -1092,6 +1102,18 @@ func cssClear(cval: CSSComponentValue): Opt[CSSClear] =
   }
   return cssIdent(ClearMap, cval)
 
+func cssTextTransform(cval: CSSComponentValue): Opt[CSSTextTransform] =
+  const TextTransformMap = {
+    "none": TEXT_TRANSFORM_NONE,
+    "capitalize": TEXT_TRANSFORM_CAPITALIZE,
+    "uppercase": TEXT_TRANSFORM_UPPERCASE,
+    "lowercase": TEXT_TRANSFORM_LOWERCASE,
+    "full-width": TEXT_TRANSFORM_FULL_WIDTH,
+    "full-size-kana": TEXT_TRANSFORM_FULL_SIZE_KANA,
+    "-cha-half-width": TEXT_TRANSFORM_CHA_HALF_WIDTH
+  }
+  return cssIdent(TextTransformMap, cval)
+
 proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration,
     vtype: CSSValueType, ptype: CSSPropertyType): Err[void] =
   var i = 0
@@ -1172,6 +1194,8 @@ proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration,
     val.boxsizing = ?cssBoxSizing(cval)
   of VALUE_CLEAR:
     val.clear = ?cssClear(cval)
+  of VALUE_TEXT_TRANSFORM:
+    val.texttransform = ?cssTextTransform(cval)
   of VALUE_NONE:
     discard
   return ok()
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 92a1e251..0f2aba6f 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -697,9 +697,7 @@ func newInlineContext(bctx: var BlockContext, space: AvailableSpace,
   ictx.initLine()
   return ictx
 
-proc layoutText(ictx: var InlineContext, state: var InlineState, str: string) =
-  ictx.flushWhitespace(state)
-  ictx.newWord(state)
+proc layoutTextLoop(ictx: var InlineContext, state: var InlineState, str: string) =
   var i = 0
   while i < str.len:
     let c = str[i]
@@ -733,6 +731,26 @@ proc layoutText(ictx: var InlineContext, state: var InlineState, str: string) =
   let shift = ictx.computeShift(state)
   ictx.currentLine.widthAfterWhitespace = ictx.currentLine.size.w + shift
 
+proc layoutText(ictx: var InlineContext, state: var InlineState, str: string) =
+  ictx.flushWhitespace(state)
+  ictx.newWord(state)
+  case state.computed{"text-transform"}
+  of TEXT_TRANSFORM_NONE:
+    ictx.layoutTextLoop(state, str)
+    {.linearScanEnd.}
+  of TEXT_TRANSFORM_CAPITALIZE:
+    ictx.layoutTextLoop(state, str.capitalize())
+  of TEXT_TRANSFORM_UPPERCASE:
+    ictx.layoutTextLoop(state, str.toUpper())
+  of TEXT_TRANSFORM_LOWERCASE:
+    ictx.layoutTextLoop(state, str.toLower())
+  of TEXT_TRANSFORM_FULL_WIDTH:
+    ictx.layoutTextLoop(state, str.fullwidth())
+  of TEXT_TRANSFORM_FULL_SIZE_KANA:
+    ictx.layoutTextLoop(state, str.fullsize())
+  of TEXT_TRANSFORM_CHA_HALF_WIDTH:
+    ictx.layoutTextLoop(state, str.halfwidth())
+
 func spx(l: CSSLength, lctx: LayoutState, p: SizeConstraint,
     computed: CSSComputedValues, padding: LayoutUnit): LayoutUnit =
   let u = l.px(lctx, p)
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index 9e40b920..28a91558 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -1,9 +1,11 @@
 import algorithm
-import strutils
-import unicode
-import os
+import json
 import math
 import options
+import os
+import strutils
+import tables
+import unicode
 
 import bindings/libunicode
 import data/charwidth
@@ -1096,3 +1098,111 @@ proc makeCRLF*(s: string): string =
       result &= '\n'
     else:
       result &= s[i]
+
+const CanHaveDakuten = ("かきくけこさしすせそたちつてとはひふへほカキクケコ" &
+  "サシスセソタチツテトハヒフヘホ").toRunes()
+
+const CanHaveHandakuten = "はひふへほハヒフヘホ".toRunes()
+
+const HasDakuten = ("がぎぐげござじずぜぞだぢづでどばびぶべぼガギグゲゴ" &
+  "ザジゼゾダヂヅデドバビブベボ").toRunes()
+
+const HasHanDakuten = "ぱぴぷぺぽパピプペポ".toRunes()
+
+# in unicode, char + 1 is dakuten and char + 2 handakuten
+
+const HalfDakuten = Rune(0xFF9E) # half-width dakuten
+const HalfHanDakuten = Rune(0xFF9F)
+
+func dakuten(r: Rune): Rune =
+  if r in CanHaveDakuten:
+    return Rune(int32(r) + 1)
+  return r
+
+func handakuten(r: Rune): Rune =
+  if r in CanHaveHandakuten:
+    return Rune(int32(r) + 2)
+  return r
+
+func nodakuten(r: Rune): Rune =
+  return Rune(int32(r) - 1)
+
+func nohandakuten(r: Rune): Rune =
+  return Rune(int32(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: string): string =
+  for r in s.runes:
+    case r
+    of HasDakuten:
+      result.add(halfwidth(r.nodakuten()))
+      result.add(HalfDakuten)
+    of HasHanDakuten:
+      result.add(halfwidth(r.nohandakuten()))
+      result.add(HalfHanDakuten)
+    else:
+      result.add(halfwidth(r))
+
+func fullwidth(r: Rune): Rune =
+  return fullwidthtable.getOrDefault(r, r)
+
+proc fullwidth(s: seq[Rune]): seq[Rune] =
+  for r in s:
+    if r == HalfDakuten: #dakuten
+      if result.len > 0:
+        result[^1] = result[^1].dakuten()
+      else:
+        result.add(r)
+    elif r == HalfHanDakuten: #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())
+
+const kanamap = staticRead"res/kanamap.tab"
+func genFullSizeMap(): seq[(uint32, uint32)] =
+  result = @[]
+  for line in kanamap.split('\n'):
+    if line.len == 0: break
+    let rs = line.toRunes()
+    assert rs[1] == Rune('\t')
+    result.add((uint32(rs[0]), uint32(rs[2])))
+const fullSizeMap = genFullSizeMap()
+
+proc fullsize*(s: string): string =
+  result = ""
+  for r in s.runes():
+    let i = searchInMap(fullSizeMap, uint32(r))
+    if i == -1:
+      result &= r
+    else:
+      result &= $Rune(fullSizeMap[i][1])