Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding functions to compute the visual width of east asian characters #8

Merged
merged 6 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

# Ignore the example binaries
examples/simple
examples/visualWidth

# Ignore the generated files
generated-docs
package/InternalGBPGen
package/GraphemeTestGen
package/InternalEmojiGen
package/InternalEmojiGen
package/InternalEAWGen
24 changes: 24 additions & 0 deletions examples/visualWidth.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.11.0/SY4WWMhWQ9NvQgvIthcv15AUeA7rAIJHAHgiaSHGhdY.tar.br",
unicode: "../package/main.roc", # use release URL (ends in tar.br) for local example, see github.com/roc/unicode/releases
}

import pf.Stdout
import unicode.CodePoint

word = "世界"

visualWidth : Result U32 CodePoint.Utf8ParseErr
visualWidth =
word
|> Str.toUtf8
|> CodePoint.parseUtf8
|> Result.map (\lst -> List.map lst CodePoint.visualWidth)
|> Result.map List.sum

main =
when visualWidth is
Ok width -> Stdout.line "\n\nThe word $(word) will be displayed with the width of $(Num.toStr width) characters on most UIs.\n\n"
Err _ -> crash "ERROR: Unable to parse $(word)!"

expect visualWidth == Ok 4
62 changes: 44 additions & 18 deletions package/CodePoint.roc
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
interface CodePoint
exposes [
Utf8ParseErr,
CodePoint,
utf8Len,
toU32,
fromU32,
isHighSurrogate,
isLowSurrogate,
isValidScalar,
appendUtf8,
parseUtf8,
countUtf8Bytes,
toStr,
]
imports [
InternalCP.{ CP, fromU32Unchecked },
]
module [
Utf8ParseErr,
CodePoint,
utf8Len,
toU32,
fromU32,
isHighSurrogate,
isLowSurrogate,
isValidScalar,
appendUtf8,
parseUtf8,
countUtf8Bytes,
toStr,
eastAsianWidthProperty,
visualWidth,
]

import InternalCP exposing [CP, fromU32Unchecked]
import InternalEAW

## A [Unicode code point](http://www.unicode.org/glossary/#code_point).
CodePoint : CP
Expand Down Expand Up @@ -243,6 +244,7 @@ parse4 = \first, second, third, fourth ->

Utf8ParseErr : [OverlongEncoding, ExpectedContinuation, EncodesSurrogateHalf, InvalidUtf8, ListWasEmpty, CodepointTooLarge]

## Parses a list of bytes into a list of code points
parseUtf8 : List U8 -> Result (List CodePoint) Utf8ParseErr
parseUtf8 = \bytes ->
# we will have at most List.len bytes code points
Expand Down Expand Up @@ -351,6 +353,30 @@ cpsToStrHelp = \cps, bytes ->
(List.dropFirst cps 1)
(CodePoint.appendUtf8 bytes cp)

## The East Asian Width property in Unicode categorizes characters based on
## their typical width and display behavior in East Asian typography, helping
## to ensure proper alignment and spacing in text layout for languages like
## Chinese, Japanese, and Korean.
EastAsianProperty : [Fullwidth, Wide, Ambiguous, Halfwidth, Neutral, Narrow]

## Computes the "east asian width" property for a given code point.
## See https://www.unicode.org/Public/15.1.0/ucd/EastAsianWidth.txt
eastAsianWidthProperty : CodePoint -> EastAsianProperty
eastAsianWidthProperty = \cp ->
eaw = cp |> toU32 |> InternalEAW.eastAsianWidthProperty
when eaw is
F -> Fullwidth
W -> Wide
A -> Ambiguous
H -> Halfwidth
N -> Neutral
Na -> Narrow

## Computes the visual width of a code point as assigned by the Unicode Character Database
visualWidth : CodePoint -> U32
visualWidth = \cp ->
cp |> toU32 |> InternalEAW.eastAsianWidth

expect
# test toStr
cr = fromU32Unchecked 13
Expand Down
68 changes: 25 additions & 43 deletions package/GraphemeTestGen.roc
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,28 @@
##
## This file will read the test data from `data/GraphemeBreakTest-15.1.0.txt`
## parse it and then generate the individual tests.
app "gen"
packages {
pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br",
parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.5.2/9VrPjwfQQ1QeSL3CfmWr2Pr9DESdDIXy97pwpuq84Ck.tar.br",
}
imports [
pf.Stdout,
pf.Stderr,
pf.Task.{ Task },
pf.Path.{ Path },
pf.Arg,
pf.File,
parser.Core.{ Parser, const, keep, skip, oneOf, oneOrMore, many, chompWhile },
parser.String.{ parseStr, string, codeunit, codeunitSatisfies },
"data/GraphemeBreakTest-15.1.0.txt" as inputFile : Str,
Helpers.{ hexBytesToU32 },
CodePoint.{ CodePoint },
InternalCP.{ fromU32Unchecked },
]
provides [main] to pf
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.11.0/SY4WWMhWQ9NvQgvIthcv15AUeA7rAIJHAHgiaSHGhdY.tar.br",
parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.5.2/9VrPjwfQQ1QeSL3CfmWr2Pr9DESdDIXy97pwpuq84Ck.tar.br",
}

import pf.Task exposing [Task]
import pf.Arg
import pf.File
import parser.Core exposing [Parser, const, keep, skip, oneOf, oneOrMore, many, chompWhile]
import parser.String exposing [parseStr, string, codeunit, codeunitSatisfies]
import "data/GraphemeBreakTest-15.1.0.txt" as inputFile : Str
import Helpers exposing [hexBytesToU32]
import CodePoint exposing [CodePoint]
import InternalCP exposing [fromU32Unchecked]

Rule : [GB1, GB2, GB3, GB4, GB5, GB6, GB7, GB8, GB9, GB9a, GB9b, GB9c, GB11, GB12, GB13, GB999]
TestTokens : List [BR Rule, NB Rule, CP CodePoint]

main : Task {} I32
main = getFilePath |> Task.await writeToFile |> Task.onErr \err -> Stderr.line "$(err)"
main =
when Arg.list! |> List.get 1 is
Err _ -> Task.err (InvalidArguments "USAGE: roc run GraphemeTest.roc -- path/to/package/")
Ok arg -> File.writeUtf8 "$(Helpers.removeTrailingSlash arg)/GraphemeTest.roc" template

template : Str
template =
Expand Down Expand Up @@ -59,14 +55,14 @@ template =

# GraphemeBreakTest-15.1.0.txt:line $(Num.toStr test.lineNo)
# $(sanitisedLine)
expect
expect
exp = Ok $(codePointsList |> Inspect.toStr)
got =
got =
$(codePointsList
|> List.join
|> Inspect.toStr)
|> List.map InternalCP.fromU32Unchecked
|> CodePoint.toStr
|> Inspect.toStr)
|> List.map InternalCP.fromU32Unchecked
|> CodePoint.toStr
|> Result.try Grapheme.split
|> Result.map toCodePointList

Expand All @@ -82,28 +78,14 @@ template =

toCodePointList : List Str -> List (List U32)
toCodePointList = \\strings ->
strings |> List.map \\str ->
when str |> Str.toUtf8 |> CodePoint.parseUtf8 is
strings |> List.map \\str ->
when str |> Str.toUtf8 |> CodePoint.parseUtf8 is
Ok cps -> List.map cps CodePoint.toU32
Err _ -> crash \"expected valid utf8\"

$(tests)
"""

getFilePath : Task Path Str
getFilePath =
args <- Arg.list |> Task.await

when args |> List.get 1 is
Ok arg -> Task.ok (Path.fromStr "$(Helpers.removeTrailingSlash arg)/GraphemeTest.roc")
Err _ -> Task.err "USAGE: roc run GraphemeTest.roc -- path/to/package/"

writeToFile : Path -> Task {} Str
writeToFile = \path ->
File.writeUtf8 path template
|> Task.mapErr \_ -> "ERROR: unable to write to $(Path.display path)"
|> Task.await \_ -> Stdout.line "\nSuccessfully wrote to $(Path.display path)\n"

toU32List : TestTokens -> List (List U32)
toU32List = \tokens ->

Expand Down
48 changes: 48 additions & 0 deletions package/InternalEAW.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## WARNING This file is automatically generated. Do not edit it manually. ##
module [eastAsianWidthProperty, eastAsianWidth]

eastAsianWidth : U32 -> U32
lukewilliamboswell marked this conversation as resolved.
Show resolved Hide resolved
eastAsianWidth = \codePoint ->
when eastAsianWidthProperty codePoint is
F | W | A -> 2
H | N | Na -> 1

eastAsianWidthProperty = \cp -> if (0x0020 <= cp && cp <= 0x007E) || (0x00A2 <= cp && cp <= 0x00A3) || (0x00A5 <= cp && cp <= 0x00A6) || cp == 0x00AC || cp == 0x00AF || (0x27E6 <= cp && cp <= 0x27ED) || (0x2985 <= cp && cp <= 0x2986) then (Na) else if cp == 0x00A1 || cp == 0x00A4 || (0x00A7 <= cp && cp <= 0x00A8) || cp == 0x00AA || (0x00AD <= cp && cp <= 0x00AE) || (0x00B0 <= cp && cp <= 0x00B4) || (0x00B6 <= cp && cp <= 0x00BA) || (0x00BC <= cp && cp <= 0x00BF) || cp == 0x00C6 || cp == 0x00D0 || (0x00D7 <= cp && cp <= 0x00D8) || (0x00DE <= cp && cp <= 0x00E1) || cp == 0x00E6 || (0x00E8 <= cp && cp <= 0x00EA) || (0x00EC <= cp && cp <= 0x00ED) || cp == 0x00F0 || (0x00F2 <= cp && cp <= 0x00F3) || (0x00F7 <= cp && cp <= 0x00FA) || cp == 0x00FC || cp == 0x00FE || cp == 0x0101 || cp == 0x0111 || cp == 0x0113 || cp == 0x011B || (0x0126 <= cp && cp <= 0x0127) || cp == 0x012B || (0x0131 <= cp && cp <= 0x0133) || cp == 0x0138 || (0x013F <= cp && cp <= 0x0142) || cp == 0x0144 || (0x0148 <= cp && cp <= 0x014B) || cp == 0x014D || (0x0152 <= cp && cp <= 0x0153) || (0x0166 <= cp && cp <= 0x0167) || cp == 0x016B || cp == 0x01CE || cp == 0x01D0 || cp == 0x01D2 || cp == 0x01D4 || cp == 0x01D6 || cp == 0x01D8 || cp == 0x01DA || cp == 0x01DC || cp == 0x0251 || cp == 0x0261 || cp == 0x02C4 || cp == 0x02C7 || (0x02C9 <= cp && cp <= 0x02CB) || cp == 0x02CD || cp == 0x02D0 || (0x02D8 <= cp && cp <= 0x02DB) || cp == 0x02DD || cp == 0x02DF || (0x0300 <= cp && cp <= 0x036F) || (0x0391 <= cp && cp <= 0x03A1) || (0x03A3 <= cp && cp <= 0x03A9) || (0x03B1 <= cp && cp <= 0x03C1) || (0x03C3 <= cp && cp <= 0x03C9) || cp == 0x0401 || (0x0410 <= cp && cp <= 0x044F) || cp == 0x0451 || cp == 0x2010 || (0x2013 <= cp && cp <= 0x2016) || (0x2018 <= cp && cp <= 0x2019) || (0x201C <= cp && cp <= 0x201D) || (0x2020 <= cp && cp <= 0x2022) || (0x2024 <= cp && cp <= 0x2027) || cp == 0x2030 || (0x2032 <= cp && cp <= 0x2033) || cp == 0x2035 || cp == 0x203B || cp == 0x203E || cp == 0x2074 || cp == 0x207F || (0x2081 <= cp && cp <= 0x2084) || cp == 0x20AC || cp == 0x2103 || cp == 0x2105 || cp == 0x2109 || cp == 0x2113 || cp == 0x2116 || (0x2121 <= cp && cp <= 0x2122) || cp == 0x2126 || cp == 0x212B || (0x2153 <= cp && cp <= 0x2154) || (0x215B <= cp && cp <= 0x215E) || (0x2160 <= cp && cp <= 0x216B) || (0x2170 <= cp && cp <= 0x2179) || cp == 0x2189 || (0x2190 <= cp && cp <= 0x2199) || (0x21B8 <= cp && cp <= 0x21B9) || cp == 0x21D2 || cp == 0x21D4 || cp == 0x21E7 || cp == 0x2200 || (0x2202 <= cp && cp <= 0x2203) || (0x2207 <= cp && cp <= 0x2208) || cp == 0x220B || cp == 0x220F || cp == 0x2211 || cp == 0x2215 || cp == 0x221A || (0x221D <= cp && cp <= 0x2220) || cp == 0x2223 || cp == 0x2225 || (0x2227 <= cp && cp <= 0x222C) || cp == 0x222E || (0x2234 <= cp && cp <= 0x2237) || (0x223C <= cp && cp <= 0x223D) || cp == 0x2248 || cp == 0x224C || cp == 0x2252 || (0x2260 <= cp && cp <= 0x2261) || (0x2264 <= cp && cp <= 0x2267) || (0x226A <= cp && cp <= 0x226B) || (0x226E <= cp && cp <= 0x226F) || (0x2282 <= cp && cp <= 0x2283) || (0x2286 <= cp && cp <= 0x2287) || cp == 0x2295 || cp == 0x2299 || cp == 0x22A5 || cp == 0x22BF || cp == 0x2312 || (0x2460 <= cp && cp <= 0x24E9) || (0x24EB <= cp && cp <= 0x254B) || (0x2550 <= cp && cp <= 0x2573) || (0x2580 <= cp && cp <= 0x258F) || (0x2592 <= cp && cp <= 0x2595) || (0x25A0 <= cp && cp <= 0x25A1) || (0x25A3 <= cp && cp <= 0x25A9) || (0x25B2 <= cp && cp <= 0x25B3) || (0x25B6 <= cp && cp <= 0x25B7) || (0x25BC <= cp && cp <= 0x25BD) || (0x25C0 <= cp && cp <= 0x25C1) || (0x25C6 <= cp && cp <= 0x25C8) || cp == 0x25CB || (0x25CE <= cp && cp <= 0x25D1) || (0x25E2 <= cp && cp <= 0x25E5) || cp == 0x25EF || (0x2605 <= cp && cp <= 0x2606) || cp == 0x2609 || (0x260E <= cp && cp <= 0x260F) || cp == 0x261C || cp == 0x261E || cp == 0x2640 || cp == 0x2642 || (0x2660 <= cp && cp <= 0x2661) || (0x2663 <= cp && cp <= 0x2665) || (0x2667 <= cp && cp <= 0x266A) || (0x266C <= cp && cp <= 0x266D) || cp == 0x266F || (0x269E <= cp && cp <= 0x269F) || cp == 0x26BF || (0x26C6 <= cp && cp <= 0x26CD) || (0x26CF <= cp && cp <= 0x26D3) || (0x26D5 <= cp && cp <= 0x26E1) || cp == 0x26E3 || (0x26E8 <= cp && cp <= 0x26E9) || (0x26EB <= cp && cp <= 0x26F1) || cp == 0x26F4 || (0x26F6 <= cp && cp <= 0x26F9) || (0x26FB <= cp && cp <= 0x26FC) || (0x26FE <= cp && cp <= 0x26FF) || cp == 0x273D || (0x2776 <= cp && cp <= 0x277F) || (0x2B56 <= cp && cp <= 0x2B59) || (0x3248 <= cp && cp <= 0x324F) || (0xE000 <= cp && cp <= 0xF8FF) || (0xFE00 <= cp && cp <= 0xFE0F) || cp == 0xFFFD || (0x1F100 <= cp && cp <= 0x1F10A) || (0x1F110 <= cp && cp <= 0x1F12D) || (0x1F130 <= cp && cp <= 0x1F169) || (0x1F170 <= cp && cp <= 0x1F18D) || (0x1F18F <= cp && cp <= 0x1F190) || (0x1F19B <= cp && cp <= 0x1F1AC) || (0xE0100 <= cp && cp <= 0xE01EF) || (0xF0000 <= cp && cp <= 0xFFFFD) || (0x100000 <= cp && cp <= 0x10FFFD) then (A) else if (0x1100 <= cp && cp <= 0x115F) || (0x231A <= cp && cp <= 0x231B) || (0x2329 <= cp && cp <= 0x232A) || (0x23E9 <= cp && cp <= 0x23EC) || cp == 0x23F0 || cp == 0x23F3 || (0x25FD <= cp && cp <= 0x25FE) || (0x2614 <= cp && cp <= 0x2615) || (0x2648 <= cp && cp <= 0x2653) || cp == 0x267F || cp == 0x2693 || cp == 0x26A1 || (0x26AA <= cp && cp <= 0x26AB) || (0x26BD <= cp && cp <= 0x26BE) || (0x26C4 <= cp && cp <= 0x26C5) || cp == 0x26CE || cp == 0x26D4 || cp == 0x26EA || (0x26F2 <= cp && cp <= 0x26F3) || cp == 0x26F5 || cp == 0x26FA || cp == 0x26FD || cp == 0x2705 || (0x270A <= cp && cp <= 0x270B) || cp == 0x2728 || cp == 0x274C || cp == 0x274E || (0x2753 <= cp && cp <= 0x2755) || cp == 0x2757 || (0x2795 <= cp && cp <= 0x2797) || cp == 0x27B0 || cp == 0x27BF || (0x2B1B <= cp && cp <= 0x2B1C) || cp == 0x2B50 || cp == 0x2B55 || (0x2E80 <= cp && cp <= 0x2E99) || (0x2E9B <= cp && cp <= 0x2EF3) || (0x2F00 <= cp && cp <= 0x2FD5) || (0x2FF0 <= cp && cp <= 0x2FFF) || (0x3001 <= cp && cp <= 0x303E) || (0x3041 <= cp && cp <= 0x3096) || (0x3099 <= cp && cp <= 0x30FF) || (0x3105 <= cp && cp <= 0x312F) || (0x3131 <= cp && cp <= 0x318E) || (0x3190 <= cp && cp <= 0x31E3) || (0x31EF <= cp && cp <= 0x321E) || (0x3220 <= cp && cp <= 0x3247) || (0x3250 <= cp && cp <= 0x4DBF) || (0x4E00 <= cp && cp <= 0xA48C) || (0xA490 <= cp && cp <= 0xA4C6) || (0xA960 <= cp && cp <= 0xA97C) || (0xAC00 <= cp && cp <= 0xD7A3) || (0xF900 <= cp && cp <= 0xFAFF) || (0xFE10 <= cp && cp <= 0xFE19) || (0xFE30 <= cp && cp <= 0xFE52) || (0xFE54 <= cp && cp <= 0xFE66) || (0xFE68 <= cp && cp <= 0xFE6B) || (0x16FE0 <= cp && cp <= 0x16FE4) || (0x16FF0 <= cp && cp <= 0x16FF1) || (0x17000 <= cp && cp <= 0x187F7) || (0x18800 <= cp && cp <= 0x18CD5) || (0x18D00 <= cp && cp <= 0x18D08) || (0x1AFF0 <= cp && cp <= 0x1AFF3) || (0x1AFF5 <= cp && cp <= 0x1AFFB) || (0x1AFFD <= cp && cp <= 0x1AFFE) || (0x1B000 <= cp && cp <= 0x1B122) || cp == 0x1B132 || (0x1B150 <= cp && cp <= 0x1B152) || cp == 0x1B155 || (0x1B164 <= cp && cp <= 0x1B167) || (0x1B170 <= cp && cp <= 0x1B2FB) || cp == 0x1F004 || cp == 0x1F0CF || cp == 0x1F18E || (0x1F191 <= cp && cp <= 0x1F19A) || (0x1F200 <= cp && cp <= 0x1F202) || (0x1F210 <= cp && cp <= 0x1F23B) || (0x1F240 <= cp && cp <= 0x1F248) || (0x1F250 <= cp && cp <= 0x1F251) || (0x1F260 <= cp && cp <= 0x1F265) || (0x1F300 <= cp && cp <= 0x1F320) || (0x1F32D <= cp && cp <= 0x1F335) || (0x1F337 <= cp && cp <= 0x1F37C) || (0x1F37E <= cp && cp <= 0x1F393) || (0x1F3A0 <= cp && cp <= 0x1F3CA) || (0x1F3CF <= cp && cp <= 0x1F3D3) || (0x1F3E0 <= cp && cp <= 0x1F3F0) || cp == 0x1F3F4 || (0x1F3F8 <= cp && cp <= 0x1F43E) || cp == 0x1F440 || (0x1F442 <= cp && cp <= 0x1F4FC) || (0x1F4FF <= cp && cp <= 0x1F53D) || (0x1F54B <= cp && cp <= 0x1F54E) || (0x1F550 <= cp && cp <= 0x1F567) || cp == 0x1F57A || (0x1F595 <= cp && cp <= 0x1F596) || cp == 0x1F5A4 || (0x1F5FB <= cp && cp <= 0x1F64F) || (0x1F680 <= cp && cp <= 0x1F6C5) || cp == 0x1F6CC || (0x1F6D0 <= cp && cp <= 0x1F6D2) || (0x1F6D5 <= cp && cp <= 0x1F6D7) || (0x1F6DC <= cp && cp <= 0x1F6DF) || (0x1F6EB <= cp && cp <= 0x1F6EC) || (0x1F6F4 <= cp && cp <= 0x1F6FC) || (0x1F7E0 <= cp && cp <= 0x1F7EB) || cp == 0x1F7F0 || (0x1F90C <= cp && cp <= 0x1F93A) || (0x1F93C <= cp && cp <= 0x1F945) || (0x1F947 <= cp && cp <= 0x1F9FF) || (0x1FA70 <= cp && cp <= 0x1FA7C) || (0x1FA80 <= cp && cp <= 0x1FA88) || (0x1FA90 <= cp && cp <= 0x1FABD) || (0x1FABF <= cp && cp <= 0x1FAC5) || (0x1FACE <= cp && cp <= 0x1FADB) || (0x1FAE0 <= cp && cp <= 0x1FAE8) || (0x1FAF0 <= cp && cp <= 0x1FAF8) || (0x20000 <= cp && cp <= 0x2FFFD) || (0x30000 <= cp && cp <= 0x3FFFD) then (W) else if cp == 0x20A9 || (0xFF61 <= cp && cp <= 0xFFBE) || (0xFFC2 <= cp && cp <= 0xFFC7) || (0xFFCA <= cp && cp <= 0xFFCF) || (0xFFD2 <= cp && cp <= 0xFFD7) || (0xFFDA <= cp && cp <= 0xFFDC) || (0xFFE8 <= cp && cp <= 0xFFEE) then (H) else if cp == 0x3000 || (0xFF01 <= cp && cp <= 0xFF60) || (0xFFE0 <= cp && cp <= 0xFFE6) then (F) else N

expect eastAsianWidthProperty 198 == A

expect eastAsianWidthProperty 9449 == A

expect eastAsianWidthProperty 9742 == A

expect eastAsianWidthProperty 8361 == H

expect eastAsianWidthProperty 65379 == H

expect eastAsianWidthProperty 162 == Na

expect eastAsianWidthProperty 165 == Na

expect eastAsianWidthProperty 172 == Na

expect eastAsianWidthProperty 12295 == W

expect eastAsianWidthProperty 12321 == W

expect eastAsianWidthProperty 12329 == W

expect eastAsianWidthProperty 65092 == W

expect eastAsianWidthProperty 65097 == W

expect eastAsianWidthProperty 194564 == W

expect eastAsianWidthProperty 65313 == F

expect eastAsianWidthProperty 65510 == F

expect eastAsianWidthProperty 70200 == N

expect eastAsianWidthProperty 70419 == N

expect eastAsianWidthProperty 72330 == N
Loading
Loading