forked from entailz/toes
tabs and tab overview
This commit is contained in:
commit
d07c2a5cc9
244 changed files with 72046 additions and 0 deletions
51
scripts/benchmark.py
Executable file
51
scripts/benchmark.py
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import argparse
|
||||
import fcntl
|
||||
import os
|
||||
import statistics
|
||||
import struct
|
||||
import sys
|
||||
import termios
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('files', type=argparse.FileType('rb'), nargs='+')
|
||||
parser.add_argument('--iterations', type=int, default=20)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
lines, cols, height, width = struct.unpack(
|
||||
'HHHH',
|
||||
fcntl.ioctl(sys.stdout.fileno(),
|
||||
termios.TIOCGWINSZ,
|
||||
struct.pack('HHHH', 0, 0, 0, 0)))
|
||||
|
||||
times: dict[str, list[float]] = {name: [] for name in [f.name for f in args.files]}
|
||||
|
||||
for f in args.files:
|
||||
bench_bytes = f.read()
|
||||
|
||||
for _ in range(args.iterations):
|
||||
start = datetime.now()
|
||||
sys.stdout.buffer.write(bench_bytes)
|
||||
stop = datetime.now()
|
||||
|
||||
times[f.name].append((stop - start).total_seconds())
|
||||
|
||||
del bench_bytes
|
||||
|
||||
print('\033[J')
|
||||
print(times)
|
||||
print(f'cols={cols}, lines={lines}, width={width}px, height={height}px')
|
||||
for f in args.files:
|
||||
print(f'{os.path.basename(f.name)}: '
|
||||
f'{statistics.mean(times[f.name]):.3f}s '
|
||||
f'±{statistics.stdev(times[f.name]):.3f}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
270
scripts/generate-alt-random-writes.py
Executable file
270
scripts/generate-alt-random-writes.py
Executable file
|
|
@ -0,0 +1,270 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import enum
|
||||
import fcntl
|
||||
import random
|
||||
import signal
|
||||
import struct
|
||||
import sys
|
||||
import termios
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
class ColorVariant(enum.IntEnum):
|
||||
NONE = enum.auto()
|
||||
REGULAR = enum.auto()
|
||||
BRIGHT = enum.auto()
|
||||
CUBE = enum.auto()
|
||||
RGB = enum.auto()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'out', type=argparse.FileType(mode='w'), nargs='?', help='name of output file')
|
||||
parser.add_argument('--cols', type=int)
|
||||
parser.add_argument('--rows', type=int)
|
||||
parser.add_argument('--colors-regular', action='store_true')
|
||||
parser.add_argument('--colors-bright', action='store_true')
|
||||
parser.add_argument('--colors-256', action='store_true')
|
||||
parser.add_argument('--colors-rgb', action='store_true')
|
||||
parser.add_argument('--scroll', action='store_true')
|
||||
parser.add_argument('--scroll-region', action='store_true')
|
||||
parser.add_argument('--attr-bold', action='store_true')
|
||||
parser.add_argument('--attr-italic', action='store_true')
|
||||
parser.add_argument('--attr-underline', action='store_true')
|
||||
parser.add_argument('--sixel', action='store_true')
|
||||
parser.add_argument('--seed', type=int)
|
||||
|
||||
opts = parser.parse_args()
|
||||
out = opts.out if opts.out is not None else sys.stdout
|
||||
|
||||
lines: int | None = None
|
||||
cols: int | None = None
|
||||
width: int | None = None
|
||||
height: int | None = None
|
||||
|
||||
if opts.rows is None or opts.cols is None:
|
||||
try:
|
||||
def dummy(*args: Any) -> None:
|
||||
"""Need a handler installed for sigwait() to trigger."""
|
||||
_ = args
|
||||
pass
|
||||
signal.signal(signal.SIGWINCH, dummy)
|
||||
|
||||
while True:
|
||||
with open('/dev/tty', 'rb') as pty:
|
||||
lines, cols, height, width = struct.unpack(
|
||||
'HHHH',
|
||||
fcntl.ioctl(pty,
|
||||
termios.TIOCGWINSZ,
|
||||
struct.pack('HHHH', 0, 0, 0, 0)))
|
||||
|
||||
assert width is not None
|
||||
assert height is not None
|
||||
|
||||
if width > 0 and height > 0:
|
||||
break
|
||||
|
||||
# We’re early; the foot window hasn’t been mapped yet. Or,
|
||||
# to be more precise, fonts haven’t yet been loaded,
|
||||
# meaning it doesn’t have any cell geometry yet.
|
||||
signal.sigwait([signal.SIGWINCH])
|
||||
|
||||
signal.signal(signal.SIGWINCH, signal.SIG_DFL)
|
||||
|
||||
except OSError:
|
||||
lines = None
|
||||
cols = None
|
||||
height = None
|
||||
width = None
|
||||
|
||||
if opts.rows is not None:
|
||||
lines = opts.rows
|
||||
assert lines is not None
|
||||
height = 15 * lines # PGO helper binary hardcodes cell height to 15px
|
||||
if opts.cols is not None:
|
||||
cols = opts.cols
|
||||
assert cols is not None
|
||||
width = 8 * cols # PGO help binary hardcodes cell width to 8px
|
||||
|
||||
if lines is None or cols is None or height is None or width is None:
|
||||
raise Exception('could not get terminal width/height; use --rows and --cols')
|
||||
|
||||
assert lines > 0, f'{lines}'
|
||||
assert cols > 0, f'{cols}'
|
||||
assert width > 0, f'{width}'
|
||||
assert height > 0, f'{height}'
|
||||
|
||||
# Number of characters to write to screen
|
||||
count = 256 * 1024**1
|
||||
|
||||
# Characters to choose from
|
||||
alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSTUVWXYZ0123456789 öäå 👨👩🧒👩🏽🔬🇸🇪'
|
||||
|
||||
color_variants = ([ColorVariant.NONE] +
|
||||
([ColorVariant.REGULAR] if opts.colors_regular else []) +
|
||||
([ColorVariant.BRIGHT] if opts.colors_bright else []) +
|
||||
([ColorVariant.CUBE] if opts.colors_256 else []) +
|
||||
([ColorVariant.RGB] if opts.colors_rgb else []))
|
||||
|
||||
# Enter alt screen
|
||||
out.write('\033[?1049h')
|
||||
|
||||
# uses system time or /dev/urandom if available if opt.seed == None
|
||||
# pin seeding method to make seeding stable across future versions
|
||||
random.seed(a=opts.seed, version=2)
|
||||
|
||||
for _ in range(count):
|
||||
if opts.scroll and random.randrange(256) == 0:
|
||||
out.write('\033[m')
|
||||
|
||||
if opts.scroll_region and random.randrange(256) == 0:
|
||||
top = random.randrange(3)
|
||||
bottom = random.randrange(3)
|
||||
out.write(f'\033[{top};{lines - bottom}r')
|
||||
|
||||
lines_to_scroll = random.randrange(lines - 1)
|
||||
rev = random.randrange(2)
|
||||
if not rev and random.randrange(2):
|
||||
out.write(f'\033[{lines};{cols}H')
|
||||
out.write('\n' * lines_to_scroll)
|
||||
else:
|
||||
out.write(f'\033[{lines_to_scroll + 1}{"T" if rev == 1 else "S"}')
|
||||
continue
|
||||
|
||||
# Generate a random location and a random character
|
||||
row = random.randrange(lines)
|
||||
col = random.randrange(cols)
|
||||
c = random.choice(alphabet)
|
||||
|
||||
repeat = random.randrange((cols - col) + 1)
|
||||
assert col + repeat <= cols
|
||||
|
||||
color_variant = random.choice(color_variants)
|
||||
|
||||
# Position cursor
|
||||
out.write(f'\033[{row + 1};{col + 1}H')
|
||||
|
||||
if color_variant in [ColorVariant.REGULAR, ColorVariant.BRIGHT]:
|
||||
do_bg = random.randrange(2)
|
||||
base = 40 if do_bg else 30
|
||||
base += 60 if color_variant == ColorVariant.BRIGHT else 0
|
||||
|
||||
idx = random.randrange(8)
|
||||
out.write(f'\033[{base + idx}m')
|
||||
|
||||
elif color_variant == ColorVariant.CUBE:
|
||||
do_bg = random.randrange(2)
|
||||
base = 48 if do_bg else 38
|
||||
|
||||
idx = random.randrange(256)
|
||||
if random.randrange(2):
|
||||
# Old-style
|
||||
out.write(f'\033[{base};5;{idx}m')
|
||||
else:
|
||||
# New-style (sub-parameter based)
|
||||
out.write(f'\033[{base}:5:{idx}m')
|
||||
|
||||
elif color_variant == ColorVariant.RGB:
|
||||
do_bg = random.randrange(2)
|
||||
base = 48 if do_bg else 38
|
||||
|
||||
# use list comprehension in favor of randbytes(n)
|
||||
# which is only available for Python >= 3.9
|
||||
rgb = [random.randrange(256) for _ in range(3)]
|
||||
|
||||
if random.randrange(2):
|
||||
# Old-style
|
||||
out.write(f'\033[{base};2;{rgb[0]};{rgb[1]};{rgb[2]}m')
|
||||
else:
|
||||
# New-style (sub-parameter based)
|
||||
out.write(f'\033[{base}:2::{rgb[0]}:{rgb[1]}:{rgb[2]}m')
|
||||
|
||||
if opts.attr_bold and random.randrange(5) == 0:
|
||||
out.write('\033[1m')
|
||||
if opts.attr_italic and random.randrange(5) == 0:
|
||||
out.write('\033[3m')
|
||||
if opts.attr_underline and random.randrange(5) == 0:
|
||||
out.write('\033[4m')
|
||||
|
||||
out.write(c * repeat)
|
||||
|
||||
do_sgr_reset = random.randrange(2)
|
||||
if do_sgr_reset:
|
||||
reset_actions = ['\033[m', '\033[39m', '\033[49m']
|
||||
out.write(random.choice(reset_actions))
|
||||
|
||||
# Reset colors
|
||||
out.write('\033[m\033[r')
|
||||
|
||||
if opts.sixel:
|
||||
# The sixel 'alphabet'
|
||||
sixels = '?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
|
||||
|
||||
last_pos: tuple[int, int] | None = None
|
||||
last_size: tuple[int, int] = 0, 0
|
||||
|
||||
for _ in range(20):
|
||||
if last_pos is not None and random.randrange(2):
|
||||
# Overwrite last sixel. I.e. use same position and
|
||||
# size as last sixel
|
||||
pass
|
||||
else:
|
||||
# Random origin in upper left quadrant
|
||||
last_pos = random.randrange(lines // 2) + 1, random.randrange(cols // 2) + 1
|
||||
last_size = random.randrange((height + 1) // 2), random.randrange((width + 1) // 2)
|
||||
|
||||
out.write(f'\033[{last_pos[0]};{last_pos[1]}H')
|
||||
six_height, six_width = last_size
|
||||
six_rows = (six_height + 5) // 6 # Round up; each sixel is 6 pixels
|
||||
|
||||
# Begin sixel (with P2 set to either 0 or 1 - opaque or transparent)
|
||||
sixel_p2 = random.randrange(2)
|
||||
out.write(f'\033P;{sixel_p2}q')
|
||||
|
||||
# Sixel size. Without this, sixels will be
|
||||
# auto-resized on cell-boundaries.
|
||||
out.write(f'"1;1;{six_width};{six_height}')
|
||||
|
||||
# Set up 256 random colors
|
||||
for idx in range(256):
|
||||
# param 2: 1=HLS, 2=RGB.
|
||||
# param 3/4/5: HLS/RGB values in range 0-100
|
||||
# (except 'hue' which is 0..360)
|
||||
out.write(f'#{idx};2;{random.randrange(101)};{random.randrange(101)};{random.randrange(101)}')
|
||||
|
||||
for row in range(six_rows):
|
||||
band_count = random.randrange(4, 33)
|
||||
for band in range(band_count):
|
||||
# Choose a random color
|
||||
out.write(f'#{random.randrange(256)}')
|
||||
|
||||
if random.randrange(2):
|
||||
for col in range(six_width):
|
||||
out.write(f'{random.choice(sixels)}')
|
||||
else:
|
||||
pix_left = six_width
|
||||
while pix_left > 0:
|
||||
repeat_count = random.randrange(1, pix_left + 1)
|
||||
out.write(f'!{repeat_count}{random.choice(sixels)}')
|
||||
pix_left -= repeat_count
|
||||
|
||||
# Next line
|
||||
if band + 1 < band_count:
|
||||
# Move cursor to beginning of current row
|
||||
out.write('$')
|
||||
elif row + 1 < six_rows:
|
||||
# Newline
|
||||
out.write('-')
|
||||
|
||||
# End sixel
|
||||
out.write('\033\\')
|
||||
|
||||
# Leave alt screen
|
||||
out.write('\033[?1049l')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
230
scripts/generate-builtin-terminfo.py
Executable file
230
scripts/generate-builtin-terminfo.py
Executable file
|
|
@ -0,0 +1,230 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
class Capability:
|
||||
def __init__(self, name: str, value: bool | int | str):
|
||||
self._name = name
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def value(self) -> bool | int | str:
|
||||
return self._value
|
||||
|
||||
def __lt__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return self._name < other._name
|
||||
|
||||
def __le__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return self._name <= other._name
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return self._name == other._name
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return bool(self._name != other._name)
|
||||
|
||||
def __gt__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return bool(self._name > other._name)
|
||||
|
||||
def __ge__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return self._name >= other._name
|
||||
|
||||
|
||||
class BoolCapability(Capability):
|
||||
def __init__(self, name: str):
|
||||
super().__init__(name, True)
|
||||
|
||||
|
||||
class IntCapability(Capability):
|
||||
pass
|
||||
|
||||
|
||||
class StringCapability(Capability):
|
||||
def __init__(self, name: str, value: str):
|
||||
# see terminfo(5) for valid escape sequences
|
||||
|
||||
# Control characters
|
||||
def translate_ctrl_chr(m: re.Match[str]) -> str:
|
||||
ctrl = m.group(1)
|
||||
if ctrl == '?':
|
||||
return '\\x7f'
|
||||
return f'\\x{ord(ctrl) - ord("@"):02x}'
|
||||
value = re.sub(r'\^([@A-Z[\\\\\]^_?])', translate_ctrl_chr, value)
|
||||
|
||||
# Ensure e.g. \E7 (or \e7) doesn’t get translated to “\0337”,
|
||||
# which would be interpreted as octal 337 by the C compiler
|
||||
value = re.sub(r'(\\E|\\e)([0-7])', r'\\033" "\2', value)
|
||||
|
||||
# Replace \E and \e with ESC
|
||||
value = re.sub(r'\\E|\\e', r'\\033', value)
|
||||
|
||||
# Unescape ,:^
|
||||
value = re.sub(r'\\(,|:|\^)', r'\1', value)
|
||||
|
||||
# Replace \s with space
|
||||
value = value.replace('\\s', ' ')
|
||||
|
||||
# Let \\, \n, \r, \t, \b and \f "fall through", to the C string literal
|
||||
|
||||
if re.search(r'\\l', value):
|
||||
raise NotImplementedError('\\l escape sequence')
|
||||
|
||||
super().__init__(name, value)
|
||||
|
||||
|
||||
class Fragment:
|
||||
def __init__(self, name: str, description: str):
|
||||
self._name = name
|
||||
self._description = description
|
||||
self._caps = dict[str, Capability]()
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return self._description
|
||||
|
||||
@property
|
||||
def caps(self) -> dict[str, Capability]:
|
||||
return self._caps
|
||||
|
||||
def add_capability(self, cap: Capability) -> None:
|
||||
assert cap.name not in self._caps
|
||||
self._caps[cap.name] = cap
|
||||
|
||||
def del_capability(self, name: str) -> None:
|
||||
del self._caps[name]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('source_entry_name')
|
||||
parser.add_argument('source', type=argparse.FileType('r'))
|
||||
parser.add_argument('target_entry_name')
|
||||
parser.add_argument('target', type=argparse.FileType('w'))
|
||||
|
||||
opts = parser.parse_args()
|
||||
source_entry_name = opts.source_entry_name
|
||||
target_entry_name = opts.target_entry_name
|
||||
source = opts.source
|
||||
target = opts.target
|
||||
|
||||
lines = list[str]()
|
||||
for line in source.readlines():
|
||||
line = line.strip()
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
lines.append(line)
|
||||
|
||||
fragments = dict[str, Fragment]()
|
||||
cur_fragment: Fragment | None = None
|
||||
|
||||
for m in re.finditer(
|
||||
r'(?P<name>(?P<entry_name>[-+\w@]+)\|(?P<entry_desc>.+?),)|'
|
||||
r'(?P<bool_cap>(?P<bool_name>\w+),)|'
|
||||
r'(?P<int_cap>(?P<int_name>\w+)#(?P<int_val>(0x)?[0-9a-fA-F]+),)|'
|
||||
r'(?P<str_cap>(?P<str_name>\w+)=(?P<str_val>(.+?)),)',
|
||||
''.join(lines)):
|
||||
|
||||
if m.group('name') is not None:
|
||||
name = m.group('entry_name')
|
||||
description = m.group('entry_desc')
|
||||
|
||||
assert name not in fragments
|
||||
fragments[name] = Fragment(name, description)
|
||||
cur_fragment = fragments[name]
|
||||
|
||||
elif m.group('bool_cap') is not None:
|
||||
name = m.group('bool_name')
|
||||
assert cur_fragment is not None
|
||||
cur_fragment.add_capability(BoolCapability(name))
|
||||
|
||||
elif m.group('int_cap') is not None:
|
||||
name = m.group('int_name')
|
||||
int_value = int(m.group('int_val'), 0)
|
||||
assert cur_fragment is not None
|
||||
cur_fragment.add_capability(IntCapability(name, int_value))
|
||||
|
||||
elif m.group('str_cap') is not None:
|
||||
name = m.group('str_name')
|
||||
str_value = m.group('str_val')
|
||||
assert cur_fragment is not None
|
||||
cur_fragment.add_capability(StringCapability(name, str_value))
|
||||
|
||||
else:
|
||||
assert False
|
||||
|
||||
# Expand ‘use’ capabilities
|
||||
for frag in fragments.values():
|
||||
for cap in frag.caps.values():
|
||||
if cap.name == 'use':
|
||||
assert isinstance(cap, StringCapability)
|
||||
assert isinstance(cap.value, str)
|
||||
|
||||
use_frag = fragments[cap.value]
|
||||
for use_cap in use_frag.caps.values():
|
||||
frag.add_capability(use_cap)
|
||||
|
||||
|
||||
frag.del_capability(cap.name)
|
||||
break
|
||||
|
||||
entry = fragments[source_entry_name]
|
||||
|
||||
try:
|
||||
entry.del_capability('RGB')
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
entry.add_capability(IntCapability('Co', 256))
|
||||
entry.add_capability(StringCapability('TN', target_entry_name))
|
||||
entry.add_capability(StringCapability('name', target_entry_name))
|
||||
entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel
|
||||
entry.add_capability(StringCapability('query-os-name', os.uname().sysname))
|
||||
|
||||
terminfo_parts = list[str]()
|
||||
for cap in sorted(entry.caps.values()):
|
||||
name = cap.name
|
||||
value = str(cap.value)
|
||||
|
||||
# Escape ‘“‘
|
||||
name = name.replace('"', '\"')
|
||||
value = value.replace('"', '\"')
|
||||
|
||||
terminfo_parts.append(name)
|
||||
if isinstance(cap, BoolCapability):
|
||||
terminfo_parts.append('')
|
||||
else:
|
||||
terminfo_parts.append(value)
|
||||
|
||||
terminfo = '\\0" "'.join(terminfo_parts)
|
||||
|
||||
target.write('#pragma once\n')
|
||||
target.write('\n')
|
||||
target.write(f'static const char terminfo_capabilities[] = "{terminfo}";')
|
||||
target.write('\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
102
scripts/generate-emoji-variation-sequences.py
Normal file
102
scripts/generate-emoji-variation-sequences.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
class Codepoint:
|
||||
def __init__(self, start: int, end: None | int = None):
|
||||
self.start = start
|
||||
self.end = start if end is None else end
|
||||
self.vs15 = False
|
||||
self.vs16 = False
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'{self.start:x}-{self.end:x}, vs15={self.vs15}, vs16={self.vs16}'
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('input', type=argparse.FileType('r'))
|
||||
parser.add_argument('output', type=argparse.FileType('w'))
|
||||
opts = parser.parse_args()
|
||||
|
||||
codepoints: dict[int, Codepoint] = {}
|
||||
|
||||
for line in opts.input:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
if line[0] == '#':
|
||||
continue
|
||||
|
||||
# Example: "0023 FE0E ; text style; # (1.1) NUMBER SIGN"
|
||||
cps, _ = line.split(';', maxsplit=1) # cps = "0023 FE0F "
|
||||
cps = cps.strip().split(' ') # cps = ["0023", "FE0F"]
|
||||
|
||||
if len(cps) != 2:
|
||||
raise NotImplementedError(f'emoji variation sequences with more than one base codepoint: {cps}')
|
||||
|
||||
cp, vs = cps # cp = "0023", vs = "FE0F"
|
||||
cp = int(cp, 16) # cp = 0x23
|
||||
vs = int(vs, 16) # vs = 0xfe0f
|
||||
|
||||
assert vs in [0xfe0e, 0xfe0f]
|
||||
|
||||
if cp not in codepoints:
|
||||
codepoints[cp] = Codepoint(cp)
|
||||
|
||||
assert codepoints[cp].start == cp
|
||||
|
||||
if vs == 0xfe0e:
|
||||
codepoints[cp].vs15 = True
|
||||
else:
|
||||
codepoints[cp].vs16 = True
|
||||
|
||||
sorted_list = sorted(codepoints.values(), key=lambda cp: cp.start)
|
||||
|
||||
compacted: list[Codepoint] = []
|
||||
for i, cp in enumerate(sorted_list):
|
||||
assert cp.end == cp.start
|
||||
|
||||
if i == 0:
|
||||
compacted.append(cp)
|
||||
continue
|
||||
|
||||
last_cp = compacted[-1]
|
||||
if last_cp.end == cp.start - 1 and last_cp.vs15 == cp.vs15 and last_cp.vs16 == cp.vs16:
|
||||
compacted[-1].end = cp.start
|
||||
else:
|
||||
compacted.append(cp)
|
||||
|
||||
opts.output.write('#pragma once\n')
|
||||
opts.output.write('#include <stdint.h>\n')
|
||||
opts.output.write('#include <stdbool.h>\n')
|
||||
opts.output.write('\n')
|
||||
opts.output.write('struct emoji_vs {\n')
|
||||
opts.output.write(' uint32_t start:21;\n')
|
||||
opts.output.write(' uint32_t end:21;\n')
|
||||
opts.output.write(' bool vs15:1;\n')
|
||||
opts.output.write(' bool vs16:1;\n')
|
||||
opts.output.write('} __attribute__((packed));\n')
|
||||
opts.output.write('_Static_assert(sizeof(struct emoji_vs) == 6, "unexpected struct size");\n')
|
||||
opts.output.write('\n')
|
||||
opts.output.write('#if defined(FOOT_GRAPHEME_CLUSTERING)\n')
|
||||
opts.output.write('\n')
|
||||
|
||||
opts.output.write(f'static const struct emoji_vs emoji_vs[{len(compacted)}] = {{\n')
|
||||
|
||||
for cp in compacted:
|
||||
opts.output.write(' {\n')
|
||||
opts.output.write(f' .start = 0x{cp.start:X},\n')
|
||||
opts.output.write(f' .end = 0x{cp.end:x},\n')
|
||||
opts.output.write(f' .vs15 = {"true" if cp.vs15 else "false"},\n')
|
||||
opts.output.write(f' .vs16 = {"true" if cp.vs16 else "false"},\n')
|
||||
opts.output.write(' },\n')
|
||||
|
||||
opts.output.write('};\n')
|
||||
opts.output.write('\n')
|
||||
opts.output.write('#endif /* FOOT_GRAPHEME_CLUSTERING */\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
70
scripts/srgb.py
Executable file
70
scripts/srgb.py
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import math
|
||||
|
||||
|
||||
# Note: we use a pure gamma 2.2 function, rather than the piece-wise
|
||||
# sRGB transfer function, since that is what all compositors do.
|
||||
|
||||
def srgb_to_linear(f: float) -> float:
|
||||
assert(f >= 0 and f <= 1.0)
|
||||
return math.pow(f, 2.2)
|
||||
|
||||
|
||||
def linear_to_srgb(f: float) -> float:
|
||||
return math.pow(f, 1 / 2.2)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('c_output', type=argparse.FileType('w'))
|
||||
parser.add_argument('h_output', type=argparse.FileType('w'))
|
||||
opts = parser.parse_args()
|
||||
|
||||
linear_table: list[int] = []
|
||||
|
||||
for i in range(256):
|
||||
linear_table.append(int(srgb_to_linear(float(i) / 255) * 65535 + 0.5))
|
||||
|
||||
|
||||
opts.h_output.write("#pragma once\n")
|
||||
opts.h_output.write("#include <stdint.h>\n")
|
||||
opts.h_output.write("\n")
|
||||
opts.h_output.write('/* 8-bit input, 16-bit output */\n')
|
||||
opts.h_output.write("extern const uint16_t srgb_decode_8_to_16_table[256];")
|
||||
|
||||
opts.h_output.write('\n')
|
||||
opts.h_output.write('static inline uint16_t\n')
|
||||
opts.h_output.write('srgb_decode_8_to_16(uint8_t v)\n')
|
||||
opts.h_output.write('{\n')
|
||||
opts.h_output.write(' return srgb_decode_8_to_16_table[v];\n')
|
||||
opts.h_output.write('}\n')
|
||||
|
||||
opts.h_output.write('\n')
|
||||
opts.h_output.write('/* 8-bit input, 8-bit output */\n')
|
||||
opts.h_output.write("extern const uint8_t srgb_decode_8_to_8_table[256];\n")
|
||||
|
||||
opts.h_output.write('\n')
|
||||
opts.h_output.write('static inline uint8_t\n')
|
||||
opts.h_output.write('srgb_decode_8_to_8(uint8_t v)\n')
|
||||
opts.h_output.write('{\n')
|
||||
opts.h_output.write(' return srgb_decode_8_to_8_table[v];\n')
|
||||
opts.h_output.write('}\n')
|
||||
|
||||
opts.c_output.write('#include "srgb.h"\n')
|
||||
opts.c_output.write('\n')
|
||||
|
||||
opts.c_output.write("const uint16_t srgb_decode_8_to_16_table[256] = {\n")
|
||||
for i in range(256):
|
||||
opts.c_output.write(f' {linear_table[i]},\n')
|
||||
opts.c_output.write('};\n')
|
||||
|
||||
opts.c_output.write("const uint8_t srgb_decode_8_to_8_table[256] = {\n")
|
||||
for i in range(256):
|
||||
opts.c_output.write(f' {linear_table[i] >> 8},\n')
|
||||
opts.c_output.write('};\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue