aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/check.py89
1 files changed, 89 insertions, 0 deletions
diff --git a/tests/check.py b/tests/check.py
index b63a728..af8ce62 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -1078,6 +1078,94 @@ def pixels(vega, tool, module, work):
return {'frames': len(frames), 'cells': checked}
+def typeset(vega, tool, module, work):
+ """The pixel surface setting type, its first font shaping: fonts in order
+ of preference (DejaVu Sans for Latin and Arabic, Noto Sans CJK KR for
+ Hangul, FreeSans for Devanagari). The frames' text is the TUI's. A label
+ in one script, alone in its list, is pixel for pixel what the font that
+ has it sets by itself (vega-symbols paragraph): each cluster went to that
+ font, shaped and placed as the layout says (ink may pass its row, as
+ marks below a line do). With several labels, one too long for its row is
+ cut within it, rows without text stay blank, and the text cursor stands
+ after the prompt."""
+ fonts = {'latin': Path('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'),
+ 'hangul': Path('/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc'),
+ 'deva': Path('/usr/share/fonts/truetype/freefont/FreeSans.ttf')}
+ if not all(path.exists() for path in fonts.values()):
+ return None
+ faces = {name: work / f'type-{name}.vsym' for name in fonts}
+ for name, args in (('latin', ['--codes', '0x20-0x7e,0xa0-0xff,0x600-0x6ff,0x2010-0x2027,0x2190-0x21ff',
+ '--shape', 'Latn,Arab,Zyyy']),
+ ('hangul', ['--face', 1, '--codes', '0x3131-0x318e,0xac00-0xd7a3', '--shape', 'Hang']),
+ ('deva', ['--codes', '0x900-0x97f', '--shape', 'Deva'])):
+ built = run(tool, 'build', '--font', fonts[name], *args, '--runs', 300, '-o', faces[name])
+ assert built.returncode == 0, built.stderr
+ stack = [a for face in faces.values() for a in ('--symbols', face)]
+ columns, rows, size = 48, 12, 20
+ background, focused, normal = (0x1e, 0x1e, 0x2e), (0x31, 0x32, 0x44), (0xcd, 0xd6, 0xf4)
+ metrics = {name: json.loads(run(tool, 'info', face).stdout.split(',"symbols"')[0] + '}') for name, face in faces.items()}
+ baseline = lambda name: math.ceil(metrics[name]['ascent'] * size / metrics[name]['units'])
+
+ def frame(items):
+ script = ''.join(f'text {item.encode().hex()}\n' for item in items) + 'show\n'
+ text = run(vega, 'tui', module, '--dump', f'{columns}x{rows}', stdin=script)
+ drawn = run(vega, 'pixels', module, *stack, '--size', size, '--grid', f'{columns}x{rows}', '--out', work / 'type',
+ stdin=script)
+ assert text.returncode == 0 and drawn.returncode == 0, drawn.stderr
+ lines = text.stdout.splitlines()
+ assert [line for line in drawn.stdout.splitlines() if not line.startswith('frame ')] == lines
+ (_, path, _), = [line.split() for line in drawn.stdout.splitlines() if line.startswith('frame ')]
+ header, image = Path(path).read_bytes().split(b'255\n', 1)
+ width, height = map(int, header.split()[1:3])
+ return lines, image, width, width // columns, height // rows
+
+ compared = 0
+ for label, name in {'Kerning AV Wa To': 'latin', '우유 사기': 'hangul', 'مرحبا بالعالم': 'latin',
+ 'नमस्ते दुनिया': 'deva'}.items():
+ lines, image, width, cell_width, cell_height = frame([label])
+ row = next(r for r, line in enumerate(lines[:rows]) if line[1:].split('\t')[0][2:].rstrip() == label)
+ set_alone = run(tool, 'paragraph', faces[name], size, 0, work / 'alone.pgm', label)
+ assert set_alone.returncode == 0, set_alone.stderr
+ head, gray = (work / 'alone.pgm').read_bytes().split(b'255\n', 1)
+ alone_width, alone_height = map(int, head.split()[1:3])
+ # The paragraph's pen starts at (size, size + its ascent); the label's at column 2, on the row's baseline.
+ dx, dy = 2 * cell_width - size, row * cell_height + baseline('latin') - size - baseline(name)
+ # coverage of the label's tone over the background of the row the pixel is in
+ under = lambda y: (focused if lines[y // cell_height][0] == '>' else background)[2]
+ at = lambda x, y: round((image[3 * (y * width + x) + 2] - under(y)) * 255 / (normal[2] - under(y)))
+ ink = 0
+ for y in range(alone_height):
+ for x in range(alone_width):
+ if 255 - gray[y * alone_width + x]:
+ ink += 1
+ assert abs(at(x + dx, y + dy) - (255 - gray[y * alone_width + x])) <= 4, (label, x, y)
+ for y in range(row * cell_height, (row + 1) * cell_height):
+ for x in range(2 * cell_width, width):
+ px, py = x - dx, y - dy
+ expected = 255 - gray[py * alone_width + px] if 0 <= px < alone_width and 0 <= py < alone_height else 0
+ assert abs(at(x, y) - expected) <= 4, (label, x, y, at(x, y), expected)
+ compared += 1
+ assert ink > 50, label
+ # Several: the long label cut, its ink reaching into its row's last cells.
+ long = 'Pure 런타임 ' + 'and a tail far too long to fit ' * 3
+ lines, image, width, cell_width, cell_height = frame(['우유 사기', 'Pure 런타임 문서 읽기', long])
+ pixel = lambda x, y: tuple(image[3 * (y * width + x):3 * (y * width + x) + 3])
+ row = next(r for r, line in enumerate(lines[:rows]) if line[3:].startswith('Pure 런타임 and'))
+ color = focused if lines[row][0] == '>' else background
+ inked = [x for x in range(width) if any(pixel(x, y) != color for y in range(row * cell_height, (row + 1) * cell_height))]
+ assert inked and width - 3 * cell_width <= inked[-1] < width, (inked[-1:], width)
+ # Rows without text (and none after a row of text: nothing there passes its row) are blank.
+ for row, line in enumerate(lines[:rows]):
+ if not line[1:].split('\t')[0].strip() and (not row or not lines[row - 1][1:].split('\t')[0].strip()):
+ color = focused if line[0] == '>' else background
+ assert all(pixel(x, y) == color for y in range(row * cell_height, (row + 1) * cell_height)
+ for x in range(width)), row
+ prompt = rows - 1
+ assert any(sum(pixel(x, y) == normal for y in range(prompt * cell_height, (prompt + 1) * cell_height)) >= cell_height // 2
+ for x in range(width)), 'no text cursor'
+ return {'labels': 4, 'pixels': compared}
+
+
def display(vega, module, work):
"""The same frames on a display through KMS, on a writeback connector: each
comes back from the display controller exactly as drawn, and it is the
@@ -1600,6 +1688,7 @@ def main():
if args.symbols:
report['symbols'] = symbols(args.symbols.resolve(), work)
report['pixels'] = pixels(vega, args.symbols.resolve(), modules['todo'], work)
+ report['typeset'] = typeset(vega, args.symbols.resolve(), modules['todo'], work)
report['display'] = display(vega, modules['todo'], work)
report['accessibility'] = accessibility(vega, modules['todo'], work)
report['live_display'] = live_display(vega, modules['todo'], work)