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
+21 -3
View File
@@ -344,6 +344,15 @@ void CssParser::parseDeclarationIntoStyle(const std::string& decl, CssStyle& sty
const std::string_view displayValue = stripTrailingImportant(propValueBuf);
style.display = (displayValue == "none") ? CssDisplay::None : CssDisplay::Block;
style.defined.display = 1;
} else if (propNameBuf == "direction") {
const std::string_view directionValue = stripTrailingImportant(propValueBuf);
if (directionValue == "rtl") {
style.direction = CssTextDirection::Rtl;
style.defined.direction = 1;
} else if (directionValue == "ltr") {
style.direction = CssTextDirection::Ltr;
style.defined.direction = 1;
}
} else if (propNameBuf == "vertical-align") {
const std::string v = normalized(propValueBuf);
if (v == "super") {
@@ -710,6 +719,7 @@ bool CssParser::saveToCache() const {
file.write(static_cast<uint8_t>(style.fontStyle));
file.write(static_cast<uint8_t>(style.fontWeight));
file.write(static_cast<uint8_t>(style.textDecoration));
file.write(static_cast<uint8_t>(style.direction));
// Write CssLength fields (value + unit)
auto writeLength = [&file](const CssLength& len) {
@@ -731,7 +741,7 @@ bool CssParser::saveToCache() const {
file.write(static_cast<uint8_t>(style.display));
file.write(static_cast<uint8_t>(style.verticalAlign));
// Write defined flags as uint16_t
// Write defined flags as uint32_t
uint32_t definedBits = 0;
if (style.defined.textAlign) definedBits |= 1 << 0;
if (style.defined.fontStyle) definedBits |= 1 << 1;
@@ -749,7 +759,8 @@ bool CssParser::saveToCache() const {
if (style.defined.imageHeight) definedBits |= 1 << 13;
if (style.defined.imageWidth) definedBits |= 1 << 14;
if (style.defined.display) definedBits |= 1 << 15;
if (style.defined.verticalAlign) definedBits |= 1 << 16;
if (style.defined.direction) definedBits |= 1 << 16;
if (style.defined.verticalAlign) definedBits |= 1 << 17;
file.write(reinterpret_cast<const uint8_t*>(&definedBits), sizeof(definedBits));
}
@@ -862,6 +873,12 @@ bool CssParser::loadFromCache() {
}
style.textDecoration = static_cast<CssTextDecoration>(enumVal);
if (file.read(&enumVal, 1) != 1) {
rulesBySelector_.clear();
return false;
}
style.direction = static_cast<CssTextDirection>(enumVal);
// Read CssLength fields
auto readLength = [&file](CssLength& len) -> bool {
if (file.read(&len.value, sizeof(len.value)) != sizeof(len.value)) {
@@ -921,7 +938,8 @@ bool CssParser::loadFromCache() {
style.defined.imageHeight = (definedBits & 1 << 13) != 0;
style.defined.imageWidth = (definedBits & 1 << 14) != 0;
style.defined.display = (definedBits & 1 << 15) != 0;
style.defined.verticalAlign = (definedBits & 1 << 16) != 0;
style.defined.direction = (definedBits & 1 << 16) != 0;
style.defined.verticalAlign = (definedBits & 1 << 17) != 0;
rulesBySelector_[selector] = style;
}
+1 -1
View File
@@ -31,7 +31,7 @@
class CssParser {
public:
// Bump when CSS cache format or rules change; section caches are invalidated when this changes
static constexpr uint8_t CSS_CACHE_VERSION = 5;
static constexpr uint8_t CSS_CACHE_VERSION = 6;
explicit CssParser(std::string cachePath) : cachePath(std::move(cachePath)) {}
~CssParser() = default;
+16 -2
View File
@@ -5,6 +5,7 @@
// Matches order of PARAGRAPH_ALIGNMENT in CrossPointSettings
enum class CssTextAlign : uint8_t { Justify = 0, Left = 1, Center = 2, Right = 3, None = 4 };
enum class CssUnit : uint8_t { Pixels = 0, Em = 1, Rem = 2, Points = 3, Percent = 4 };
enum class CssTextDirection : uint8_t { Ltr = 0, Rtl = 1 };
// Represents a CSS length value with its unit, allowing deferred resolution to pixels
struct CssLength {
@@ -78,6 +79,7 @@ struct CssPropertyFlags {
uint16_t imageHeight : 1;
uint16_t imageWidth : 1;
uint16_t display : 1;
uint16_t direction : 1;
uint16_t verticalAlign : 1;
CssPropertyFlags()
@@ -97,22 +99,27 @@ struct CssPropertyFlags {
imageHeight(0),
imageWidth(0),
display(0),
direction(0),
verticalAlign(0) {}
[[nodiscard]] bool anySet() const {
return textAlign || fontStyle || fontWeight || textDecoration || textIndent || marginTop || marginBottom ||
marginLeft || marginRight || paddingTop || paddingBottom || paddingLeft || paddingRight || imageHeight ||
imageWidth || display || verticalAlign;
imageWidth || display || direction || verticalAlign;
}
void clearAll() {
textAlign = fontStyle = fontWeight = textDecoration = textIndent = 0;
marginTop = marginBottom = marginLeft = marginRight = 0;
paddingTop = paddingBottom = paddingLeft = paddingRight = 0;
imageHeight = imageWidth = display = verticalAlign = 0;
imageHeight = imageWidth = display = direction = verticalAlign = 0;
}
};
// Cache serializes defined flags as uint32_t with bit indices 0..17.
static_assert(sizeof(CssPropertyFlags) <= sizeof(uint32_t),
"CssPropertyFlags exceeds 32 bits; update cache read/write in CssParser.cpp");
// Represents a collection of CSS style properties
// Only stores properties relevant to e-ink text rendering
// Length values are stored as CssLength (value + unit) for deferred resolution
@@ -121,6 +128,7 @@ struct CssStyle {
CssFontStyle fontStyle = CssFontStyle::Normal;
CssFontWeight fontWeight = CssFontWeight::Normal;
CssTextDecoration textDecoration = CssTextDecoration::None;
CssTextDirection direction = CssTextDirection::Ltr;
CssLength textIndent; // First-line indent (deferred resolution)
CssLength marginTop; // Vertical spacing before block
@@ -205,6 +213,10 @@ struct CssStyle {
display = base.display;
defined.display = 1;
}
if (base.hasDirection()) {
direction = base.direction;
defined.direction = 1;
}
if (base.hasVerticalAlign()) {
verticalAlign = base.verticalAlign;
defined.verticalAlign = 1;
@@ -227,6 +239,7 @@ struct CssStyle {
[[nodiscard]] bool hasImageHeight() const { return defined.imageHeight; }
[[nodiscard]] bool hasImageWidth() const { return defined.imageWidth; }
[[nodiscard]] bool hasDisplay() const { return defined.display; }
[[nodiscard]] bool hasDirection() const { return defined.direction; }
[[nodiscard]] bool hasVerticalAlign() const { return defined.verticalAlign; }
void reset() {
@@ -234,6 +247,7 @@ struct CssStyle {
fontStyle = CssFontStyle::Normal;
fontWeight = CssFontWeight::Normal;
textDecoration = CssTextDecoration::None;
direction = CssTextDirection::Ltr;
textIndent = CssLength{};
marginTop = marginBottom = marginLeft = marginRight = CssLength{};
paddingTop = paddingBottom = paddingLeft = paddingRight = CssLength{};