feat: add RTL support in epub and txt readers (#1700)

Co-authored-by: Zach Nelson <zach@zdnelson.com>
This commit is contained in:
Uri Tauber
2026-05-29 03:25:17 -04:00
committed by GitHub
co-authored by Zach Nelson
parent cc2079a578
commit f5bc554ae7
27 changed files with 1621 additions and 119 deletions
+115
View File
@@ -0,0 +1,115 @@
#ifndef MINIBIDI_H
#define MINIBIDI_H
/*
* minibidi.h — standalone header for ESP32C3 BiDi calculations
*
* Derived from [mintty](https://github.com/mintty/mintty/) (Thomas Wolff, MIT licence).
* Stripped of: Arabic shaping, box-drawing mirror, terminal dependencies,
* GCC nested functions, VLAs, and non-Hebrew/English Unicode data.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/* ── Basic types ─────────────────────────────────────────────────────── */
typedef uint8_t uchar;
typedef uint32_t ucschar; /* Unicode codepoint; BMP-only content fits uint16_t
but uint32_t is safer and ESP32C3 is 32-bit anyway */
/* ── Convenience macros ──────────────────────────────────────────────── */
#define lengthof(a) ((int)(sizeof(a) / sizeof(*(a))))
/* PuTTY/mintty switch-case style — kept for readability of algorithm */
#define when \
break; \
case
#define otherwise \
break; \
default
/* Maximum line length the algorithm will process.
Adjust to your actual screen width. Stack cost = ~5×MAX bytes. */
#define BIDI_MAX_LINE 128
/* ── bidi_char ───────────────────────────────────────────────────────── */
/* origwc: the codepoint as it came from the epub text stream
wc: working codepoint (may be replaced by mirrored form after do_bidi)
index: original logical position, so the caller can reorder glyphs */
typedef struct {
ucschar origwc;
ucschar wc;
uint16_t index;
} bidi_char;
/* ── Bidi character classes (UAX #9) ────────────────────────────────── */
enum {
L, /* Left-to-Right */
LRE, /* Left-to-Right Embedding */
LRO, /* Left-to-Right Override */
R, /* Right-to-Left */
AL, /* Right-to-Left Arabic */
RLE, /* Right-to-Left Embedding */
RLO, /* Right-to-Left Override */
PDF, /* Pop Directional Format */
EN, /* European Number */
ES, /* European Number Separator */
ET, /* European Number Terminator */
AN, /* Arabic Number */
CS, /* Common Number Separator */
NSM, /* Non-Spacing Mark */
BN, /* Boundary Neutral */
B, /* Paragraph Separator */
S, /* Segment Separator */
WS, /* Whitespace */
ON, /* Other Neutrals */
/* Unicode 6.3 isolate types */
LRI, /* Left-to-Right Isolate */
RLI, /* Right-to-Left Isolate */
FSI, /* First Strong Isolate */
PDI, /* Pop Directional Isolate */
};
/* ── Public API ──────────────────────────────────────────────────────── */
/*
* bidi_class(ch)
* Returns the UAX#9 bidi class of Unicode codepoint ch.
* Unknown characters return ON (correct per spec).
*/
uchar bidi_class(ucschar ch);
/*
* is_rtl_class(bc)
* Returns true if bidi class bc can cause RTL reordering.
* Use to fast-skip lines with no RTL content.
*/
bool is_rtl_class(uchar bc);
/*
* mirror(ch)
* Returns the mirrored form of Unicode codepoint ch for UAX#9 rule L4.
* If no mirror exists, returns ch unchanged.
*/
ucschar mirror(ucschar ch);
/*
* do_bidi(autodir, paragraphLevel, line, count)
*
* Applies UAX#9 Bidirectional Algorithm (rules PL) to `line[0..count-1]`.
* Reorders the array in-place; sets line[i].wc to the mirrored form where
* required (rule L4). Returns the resolved paragraph level (0=LTR, 1=RTL),
* or 0 if the line was left-to-right and no reordering was done.
*
* autodir: true → detect paragraph direction from content (P2/P3)
* false → use paragraphLevel as-is
* paragraphLevel: 0 = LTR, 1 = RTL. Ignored when autodir=true unless
* the content has no strong type (used as fallback).
*
* count must be ≤ BIDI_MAX_LINE; lines longer than that are silently
* truncated to BIDI_MAX_LINE before processing.
*/
int do_bidi(bool autodir, int paragraphLevel, bidi_char* line, int count);
#endif /* MINIBIDI_H */