209 lines
5.1 KiB
Diff
209 lines
5.1 KiB
Diff
|
|
From e57d55a11d3df86c8da12a27ba6146aa052ef359 Mon Sep 17 00:00:00 2001
|
||
|
|
From: Tony Cebzanov <tonycpsu@gmail.com>
|
||
|
|
Date: Sat, 13 Jun 2020 20:45:05 -0400
|
||
|
|
Subject: [PATCH] Add script for detecting character widths and dumping width
|
||
|
|
tables
|
||
|
|
|
||
|
|
New char_width_tools.py script. Usage:
|
||
|
|
|
||
|
|
To detect character widths for the current terminal:
|
||
|
|
|
||
|
|
./char_width_tools.py analyze terminal.csv
|
||
|
|
|
||
|
|
To print the widths of each character in a string:
|
||
|
|
|
||
|
|
./char_width_tools.py test "string"
|
||
|
|
|
||
|
|
To load a file saved with "analyze" as C/Python source to be pasted
|
||
|
|
into str_util.c and old_str_util.python:
|
||
|
|
|
||
|
|
./char_width_tools.py dump terminal.csv c
|
||
|
|
|
||
|
|
./char_width_tools.py dump terminal.csv python
|
||
|
|
---
|
||
|
|
bin/char_width_tools.py | 172 ++++++++++++++++++++++++++++++++++++++++
|
||
|
|
1 file changed, 172 insertions(+)
|
||
|
|
create mode 100644 bin/char_width_tools.py
|
||
|
|
|
||
|
|
diff --git a/bin/char_width_tools.py b/bin/char_width_tools.py
|
||
|
|
new file mode 100644
|
||
|
|
index 0000000..8ab40ed
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/bin/char_width_tools.py
|
||
|
|
@@ -0,0 +1,172 @@
|
||
|
|
+#!/usr/bin/env python
|
||
|
|
+
|
||
|
|
+# Build a table of unicode character widths as rendered by the current terminal.
|
||
|
|
+
|
||
|
|
+import sys
|
||
|
|
+import tty
|
||
|
|
+import termios
|
||
|
|
+from select import select
|
||
|
|
+import re
|
||
|
|
+import argparse
|
||
|
|
+import csv
|
||
|
|
+
|
||
|
|
+MIN_CODE_POINT = 0
|
||
|
|
+MAX_CODE_POINT = 0x110000
|
||
|
|
+
|
||
|
|
+ESC = "\x1b"
|
||
|
|
+CLEAR = ESC + "[2J"
|
||
|
|
+
|
||
|
|
+BEGINNING_OF_LINE = ESC +"[1;2H"
|
||
|
|
+
|
||
|
|
+def getpos():
|
||
|
|
+
|
||
|
|
+ buf = ""
|
||
|
|
+ stdin = sys.stdin.fileno()
|
||
|
|
+ tattr = termios.tcgetattr(stdin)
|
||
|
|
+
|
||
|
|
+ try:
|
||
|
|
+ tty.setcbreak(stdin, termios.TCSANOW)
|
||
|
|
+ sys.stdout.write("\x1b[6n")
|
||
|
|
+ sys.stdout.flush()
|
||
|
|
+
|
||
|
|
+ while True:
|
||
|
|
+ buf += sys.stdin.read(1)
|
||
|
|
+ if buf[-1] == "R":
|
||
|
|
+ break
|
||
|
|
+
|
||
|
|
+ finally:
|
||
|
|
+ termios.tcsetattr(stdin, termios.TCSANOW, tattr)
|
||
|
|
+
|
||
|
|
+ return [
|
||
|
|
+ int(x)
|
||
|
|
+ for x in re.match(r"^\x1b\[(\d*);(\d*)R", buf).groups()
|
||
|
|
+ ]
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def get_width(i):
|
||
|
|
+ c = chr(i)
|
||
|
|
+ print(BEGINNING_OF_LINE)
|
||
|
|
+ try:
|
||
|
|
+ sys.stdout.write(c)
|
||
|
|
+ except UnicodeEncodeError:
|
||
|
|
+ return None
|
||
|
|
+ return getpos()[1]-1
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def test_string(s):
|
||
|
|
+
|
||
|
|
+ widths = []
|
||
|
|
+ for c in s:
|
||
|
|
+ widths.append(get_width(ord(c)))
|
||
|
|
+
|
||
|
|
+ print(CLEAR)
|
||
|
|
+
|
||
|
|
+ for c, w in zip(s, widths):
|
||
|
|
+ print("%s\t%d" %(c, w))
|
||
|
|
+ print()
|
||
|
|
+ print("total\t%d" %(sum(widths)))
|
||
|
|
+
|
||
|
|
+def write_table(filename):
|
||
|
|
+ widths = list()
|
||
|
|
+ last_width = None
|
||
|
|
+
|
||
|
|
+ try:
|
||
|
|
+ for i in range(MIN_CODE_POINT, MAX_CODE_POINT):
|
||
|
|
+ w = get_width(i)
|
||
|
|
+ if w is None:
|
||
|
|
+ continue
|
||
|
|
+ if i != MIN_CODE_POINT and w != last_width:
|
||
|
|
+ widths.append((i-1, last_width))
|
||
|
|
+ last_width = w
|
||
|
|
+ if not i % 1000:
|
||
|
|
+ pct = i / MAX_CODE_POINT
|
||
|
|
+ print()
|
||
|
|
+ print("%d/%d (%.02f%%)" %(i, MAX_CODE_POINT, pct*100))
|
||
|
|
+ widths.append((i, last_width))
|
||
|
|
+
|
||
|
|
+ finally:
|
||
|
|
+ with open(filename, "w") as f:
|
||
|
|
+ writer = csv.writer(f)
|
||
|
|
+ for w in widths:
|
||
|
|
+ writer.writerow("%d\t%d\n" %(w[0], w[1]))
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def dump(infile, lang):
|
||
|
|
+
|
||
|
|
+ widths = list()
|
||
|
|
+ last_width = None
|
||
|
|
+
|
||
|
|
+ with open(infile) as f:
|
||
|
|
+ reader = csv.reader(f)
|
||
|
|
+ for row in reader:
|
||
|
|
+ widths.append((row[0], row[1]))
|
||
|
|
+
|
||
|
|
+ if args.lang == "c":
|
||
|
|
+ print("static const int widths[] = {")
|
||
|
|
+ elif args.lang == "python":
|
||
|
|
+ print("widths = [")
|
||
|
|
+
|
||
|
|
+ for i, w in widths:
|
||
|
|
+ if lang == "c":
|
||
|
|
+ print(" %s, %s," %(i, w))
|
||
|
|
+
|
||
|
|
+ elif lang == "python":
|
||
|
|
+ print(" (%s, %s)," %(i, w))
|
||
|
|
+ else:
|
||
|
|
+ raise RuntimeError("unknown language")
|
||
|
|
+
|
||
|
|
+ if lang == "c":
|
||
|
|
+ print(" NULL")
|
||
|
|
+ print("};")
|
||
|
|
+ elif lang == "python":
|
||
|
|
+ print("]")
|
||
|
|
+
|
||
|
|
+def main():
|
||
|
|
+
|
||
|
|
+ global args
|
||
|
|
+ parser = argparse.ArgumentParser()
|
||
|
|
+
|
||
|
|
+ subparsers = parser.add_subparsers(dest="command", required=True)
|
||
|
|
+
|
||
|
|
+ analyze_parser = subparsers.add_parser(
|
||
|
|
+ "analyze",
|
||
|
|
+ help="analyze character widths and save them to csv file"
|
||
|
|
+ )
|
||
|
|
+ analyze_parser.add_argument(
|
||
|
|
+ "outfile", metavar="FILE",
|
||
|
|
+ help="write character widths to this file"
|
||
|
|
+ )
|
||
|
|
+ analyze_parser.set_defaults(func=lambda args: write_table(args.outfile))
|
||
|
|
+
|
||
|
|
+ test_parser = subparsers.add_parser(
|
||
|
|
+ "test",
|
||
|
|
+ help="analyze a set of characters specified on the command line"
|
||
|
|
+ )
|
||
|
|
+ test_parser.add_argument(
|
||
|
|
+ "string", metavar="CHAR",
|
||
|
|
+ help="character to test"
|
||
|
|
+ )
|
||
|
|
+ test_parser.set_defaults(func=lambda args: test_string(args.string))
|
||
|
|
+
|
||
|
|
+ dump_parser = subparsers.add_parser(
|
||
|
|
+ "dump",
|
||
|
|
+ help="generate source code for width tables from a saved file"
|
||
|
|
+ )
|
||
|
|
+ dump_parser.add_argument(
|
||
|
|
+ "infile", metavar="FILE",
|
||
|
|
+ help="read character widths from this file"
|
||
|
|
+ )
|
||
|
|
+ dump_parser.add_argument(
|
||
|
|
+ "lang", metavar="LANGUAGE", choices=["c", "python"],
|
||
|
|
+ help="Language to dump data for."
|
||
|
|
+ )
|
||
|
|
+ dump_parser.set_defaults(func=lambda args: dump(args.infile, args.lang))
|
||
|
|
+
|
||
|
|
+ args = parser.parse_args()
|
||
|
|
+
|
||
|
|
+ print(CLEAR)
|
||
|
|
+
|
||
|
|
+ args.func(args)
|
||
|
|
+
|
||
|
|
+if __name__ == "__main__":
|
||
|
|
+ main()
|
||
|
|
--
|
||
|
|
2.39.0.windows.2
|
||
|
|
|