diff options
author | bptato <nincsnevem662@gmail.com> | 2023-12-11 01:12:26 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-12-11 01:18:22 +0100 |
commit | 6b9db7e8d77c3ce68558f45f9162121a13a96a2b (patch) | |
tree | 9620c5c2266cf0eac24b233024fdecbc49d69e06 | |
parent | b787ab592f582fc4e6c9feb7dbec2820aa2f2c7b (diff) | |
download | chawan-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.tab | 58 | ||||
-rw-r--r-- | res/widthconv.json | 100 | ||||
-rw-r--r-- | src/css/values.nim | 34 | ||||
-rw-r--r-- | src/layout/engine.nim | 24 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 116 |
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]) |