aboutsummaryrefslogtreecommitdiffstats
path: root/text/test-layout.c
diff options
context:
space:
mode:
Diffstat (limited to 'text/test-layout.c')
-rw-r--r--text/test-layout.c188
1 files changed, 188 insertions, 0 deletions
diff --git a/text/test-layout.c b/text/test-layout.c
new file mode 100644
index 0000000..5282572
--- /dev/null
+++ b/text/test-layout.c
@@ -0,0 +1,188 @@
+/* Paragraph layout against what its parts promise (text/layout.h):
+ * test-layout SHAPED.vsym [PARAGRAPHS]
+ * Random paragraphs of Latin, Arabic and Hebrew words (with marks, digits,
+ * brackets, directional marks and line feeds between), set at random
+ * widths. Every layout must: cover the paragraph with lines in order; end
+ * a line at every mandatory break; keep each line within the width unless
+ * it has no break opportunity inside; keep each glyph's cluster in its
+ * line; and put glyphs in the order UAX #9 puts the line's characters
+ * (vega_bidi_order, with L1 at the line's end). Prints one JSON line;
+ * exits 1 on a failure (the first few shown). */
+#define _POSIX_C_SOURCE 200809L
+#include "layout.h"
+#include "symbols.h"
+#include "text.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+static unsigned failures;
+static void fail(size_t paragraph, const char *what) {
+ if (failures++ < 10) fprintf(stderr, "paragraph %zu: %s\n", paragraph, what);
+}
+
+static uint64_t state = 0x9E3779B97F4A7C15ULL;
+static uint32_t below(uint32_t n) {
+ state ^= state >> 12;
+ state ^= state << 25;
+ state ^= state >> 27;
+ return n ? (uint32_t)((state * 0x2545F4914F6CDD1DULL) >> 33) % n : 0;
+}
+
+typedef struct { uint32_t items[4096]; size_t count; } Pool;
+
+static bool whitespace(VegaBidiClass c) { return c == VEGA_BIDI_WS; }
+static bool trailing_kind(VegaBidiClass c) {
+ return c == VEGA_BIDI_WS || c == VEGA_BIDI_LRI || c == VEGA_BIDI_RLI || c == VEGA_BIDI_FSI || c == VEGA_BIDI_PDI ||
+ c == VEGA_BIDI_BN || c == VEGA_BIDI_LRE || c == VEGA_BIDI_RLE || c == VEGA_BIDI_LRO || c == VEGA_BIDI_RLO ||
+ c == VEGA_BIDI_PDF;
+}
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ fprintf(stderr, "usage: test-layout SHAPED.vsym [PARAGRAPHS]\n");
+ return 2;
+ }
+ size_t paragraphs = argc > 2 ? strtoul(argv[2], NULL, 10) : 3000;
+ FILE *f = fopen(argv[1], "rb");
+ if (!f) { perror(argv[1]); return 2; }
+ fseek(f, 0, SEEK_END);
+ size_t size = (size_t)ftell(f);
+ fseek(f, 0, SEEK_SET);
+ unsigned char *bytes = malloc(size);
+ if (!bytes || fread(bytes, 1, size, f) != size) return 2;
+ fclose(f);
+ char error[128];
+ VegaSymbols symbols;
+ VegaShaping shaping;
+ if (!vega_symbols_open(&symbols, bytes, size, error, sizeof(error)) || !symbols.shaping ||
+ !vega_shaping_open(&shaping, symbols.shaping, symbols.shaping_size, error, sizeof(error))) {
+ fprintf(stderr, "%s: %s\n", argv[1], symbols.shaping ? error : "no shaping");
+ return 2;
+ }
+ /* Words from each script's letters (marks after some), and what goes
+ * between them. */
+ static Pool latin, arabic, hebrew, marks_latin, marks_arabic, marks_hebrew;
+ for (uint32_t i = 0; i < symbols.symbols; ++i) {
+ uint32_t code = vega_symbol_at(&symbols, i).code;
+ if (code >= VEGA_GLYPH_SYMBOLS) continue;
+ VegaScript script = vega_script(code);
+ VegaCategory c = vega_category(code);
+ bool mark = c == VEGA_MN || c == VEGA_MC;
+ bool letter = c >= VEGA_LU && c <= VEGA_LO;
+ Pool *p = NULL;
+ if (script == VEGA_SCRIPT_LATIN && letter) p = &latin;
+ else if (script == VEGA_SCRIPT_ARABIC && letter && code != 0x0640) p = &arabic;
+ else if (script == VEGA_SCRIPT_HEBREW && letter) p = &hebrew;
+ else if (mark && code >= 0x0300 && code <= 0x036F) p = &marks_latin;
+ else if (mark && code >= 0x064B && code <= 0x0652) p = &marks_arabic;
+ else if (mark && code >= 0x05B0 && code <= 0x05C2) p = &marks_hebrew;
+ if (p && p->count < 4096) p->items[p->count++] = code;
+ }
+ if (!latin.count || !arabic.count || !hebrew.count) {
+ fprintf(stderr, "%s: needs Latin, Arabic and Hebrew letters\n", argv[1]);
+ return 2;
+ }
+ static const uint32_t between[] = {' ', ' ', ' ', ' ', ',', '.', '(', ')', '[', ']', '-', '!', '?', 0x060C, 0x061F,
+ '1', '2', '3', 0x0661, 0x0662, 0x200E, 0x200F, 0x2028, 0x2014, 0x00A0};
+ VegaShaper *shaper = vega_shaper_new();
+ VegaLayout layout = {0};
+ size_t lines = 0, glyphs = 0, characters = 0;
+ double seconds = 0;
+ static uint32_t codes[2048];
+ static uint8_t breaks[2049], levels[2048], line_levels[2048];
+ static VegaBidiClass classes[2048];
+ static uint32_t order[2048], rank[2048];
+ for (size_t k = 0; k < paragraphs; ++k) {
+ size_t n = 0;
+ unsigned words = 1 + below(40);
+ for (unsigned w = 0; w < words && n < 1900; ++w) {
+ unsigned which = below(10);
+ const Pool *letters = which < 5 ? &latin : which < 8 ? &arabic : &hebrew;
+ const Pool *marks = which < 5 ? &marks_latin : which < 8 ? &marks_arabic : &marks_hebrew;
+ if (w) {
+ codes[n++] = between[below(sizeof(between) / sizeof(*between))];
+ if (below(3)) codes[n++] = ' ';
+ }
+ unsigned length = 1 + below(12);
+ for (unsigned c = 0; c < length; ++c) {
+ codes[n++] = letters->items[below((uint32_t)letters->count)];
+ if (marks->count && below(100) < (which < 5 ? 5u : 30u)) codes[n++] = marks->items[below((uint32_t)marks->count)];
+ }
+ }
+ int direction = below(3) == 0 ? VEGA_BIDI_AUTO : below(2) ? VEGA_BIDI_LTR : VEGA_BIDI_RTL;
+ int32_t width = below(5) == 0 ? 0 : (int32_t)(shaping.upem * (1 + below(30)));
+ struct timespec a, b;
+ clock_gettime(CLOCK_MONOTONIC, &a);
+ if (!vega_layout(&layout, shaper, &shaping, codes, n, direction, width)) {
+ fail(k, "no layout");
+ continue;
+ }
+ clock_gettime(CLOCK_MONOTONIC, &b);
+ seconds += (double)(b.tv_sec - a.tv_sec) + (double)(b.tv_nsec - a.tv_nsec) / 1e9;
+ lines += layout.line_count;
+ glyphs += layout.glyph_count;
+ characters += n;
+ vega_line_breaks(codes, n, breaks);
+ for (size_t i = 0; i < n; ++i) classes[i] = vega_bidi_class(codes[i]);
+ int paragraph = vega_bidi_levels(classes, codes, n, direction, levels);
+ size_t expect = 0;
+ for (size_t i = 0; i < layout.line_count; ++i) {
+ const VegaLine *line = &layout.lines[i];
+ if (line->start != expect || line->end <= line->start || line->end > n) {
+ fail(k, "lines do not cover the paragraph in order");
+ break;
+ }
+ expect = line->end;
+ for (size_t p = line->start + 1; p < line->end; ++p)
+ if (breaks[p] == VEGA_BREAK_MANDATORY) fail(k, "a line runs past a mandatory break");
+ if (width && line->width > width) {
+ bool inside = false;
+ for (size_t p = line->start + 1; p < line->end && !inside; ++p) {
+ /* an opportunity after only spaces is no way to shorten the line */
+ bool spaces_only = true;
+ for (size_t q = p; q < line->end && spaces_only; ++q) spaces_only = whitespace(classes[q]);
+ inside = breaks[p] != VEGA_BREAK_NONE && !spaces_only;
+ }
+ if (inside) fail(k, "a line wider than asked though it could break inside");
+ }
+ for (size_t g = line->first; g < line->first + line->count; ++g)
+ if (layout.glyphs[g].cluster < line->start || layout.glyphs[g].cluster >= line->end)
+ fail(k, "a glyph's cluster is outside its line");
+ /* L1 at the line's end, then the characters' visual order. */
+ memcpy(line_levels, levels + line->start, line->end - line->start);
+ for (size_t i2 = line->end, trailing = 1; i2-- > line->start;) {
+ VegaBidiClass c = classes[i2];
+ if (c == VEGA_BIDI_S || c == VEGA_BIDI_B) line_levels[i2 - line->start] = (uint8_t)paragraph, trailing = 1;
+ else if (trailing_kind(c)) {
+ if (trailing && line_levels[i2 - line->start] != VEGA_BIDI_REMOVED)
+ line_levels[i2 - line->start] = (uint8_t)paragraph;
+ } else
+ trailing = 0;
+ }
+ size_t shown = vega_bidi_order(line_levels, line->end - line->start, order);
+ for (size_t r = 0; r < line->end - line->start; ++r) rank[r] = UINT32_MAX;
+ for (size_t r = 0; r < shown; ++r) rank[order[r]] = (uint32_t)r;
+ uint32_t last = 0;
+ for (size_t g = line->first; g < line->first + line->count; ++g) {
+ uint32_t r = rank[layout.glyphs[g].cluster - line->start];
+ if (r == UINT32_MAX) continue;
+ if (r < last) {
+ fail(k, "glyphs out of the characters' visual order");
+ break;
+ }
+ last = r;
+ }
+ }
+ if (expect != n) fail(k, "lines end before the paragraph does");
+ }
+ printf("{\"paragraphs\":%zu,\"characters\":%zu,\"lines\":%zu,\"glyphs\":%zu,\"ns_per_character\":%.1f,"
+ "\"failures\":%u}\n",
+ paragraphs, characters, lines, glyphs, characters ? 1e9 * seconds / (double)characters : 0, failures);
+ vega_layout_free(&layout);
+ vega_shaper_free(shaper);
+ vega_shaping_close(&shaping);
+ free(bytes);
+ return failures ? 1 : 0;
+}