feat: <sup> and <sub> support (#2131)

## Summary

* **What is the goal of this PR?** Support for `<sup>` and `<sub>` tags.

## Additional Context

This isn't my work, but @jpirnay 's (missing you here, man!). I migrated
his work from
https://github.com/jpirnay/crosspoint-reader/commit/bcd8c32cf26447ccc792cfedd2fdbfce4fee5210
with some micro-optimizations.

Screenshots: 

[Subscript-and-Superscript-Tests_ch2_p1_10pct_55632.bmp](https://github.com/user-attachments/files/28196936/Subscript-and-Superscript-Tests_ch2_p1_10pct_55632.bmp)

[Subscript-and-Superscript-Tests_ch3_p1_21pct_77473.bmp](https://github.com/user-attachments/files/28196937/Subscript-and-Superscript-Tests_ch3_p1_21pct_77473.bmp)


---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< YES >**_

---------

Co-authored-by: jpirnay <jens@pirnay.com>
Co-authored-by: Julia <julia@uxj.io>
This commit is contained in:
Uri Tauber
2026-05-26 12:38:40 -04:00
committed by GitHub
co-authored by jpirnay Julia
parent 34e923d722
commit c5e861d71c
11 changed files with 443 additions and 18 deletions
+23 -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 == "vertical-align") {
const std::string v = normalized(propValueBuf);
if (v == "super") {
style.verticalAlign = CssVerticalAlign::Super;
style.defined.verticalAlign = 1;
} else if (v == "sub") {
style.verticalAlign = CssVerticalAlign::Sub;
style.defined.verticalAlign = 1;
}
}
}
@@ -720,9 +729,10 @@ bool CssParser::saveToCache() const {
writeLength(style.imageHeight);
writeLength(style.imageWidth);
file.write(static_cast<uint8_t>(style.display));
file.write(static_cast<uint8_t>(style.verticalAlign));
// Write defined flags as uint16_t
uint16_t definedBits = 0;
uint32_t definedBits = 0;
if (style.defined.textAlign) definedBits |= 1 << 0;
if (style.defined.fontStyle) definedBits |= 1 << 1;
if (style.defined.fontWeight) definedBits |= 1 << 2;
@@ -739,6 +749,7 @@ 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;
file.write(reinterpret_cast<const uint8_t*>(&definedBits), sizeof(definedBits));
}
@@ -789,7 +800,7 @@ bool CssParser::loadFromCache() {
constexpr size_t CSS_LENGTH_FIELD_COUNT = 11;
constexpr size_t CSS_LENGTH_BYTES = sizeof(float) + sizeof(uint8_t);
constexpr size_t CSS_FIXED_STYLE_BYTES =
4 * sizeof(uint8_t) + (CSS_LENGTH_FIELD_COUNT * CSS_LENGTH_BYTES) + sizeof(uint8_t) + sizeof(uint16_t);
5 * sizeof(uint8_t) + (CSS_LENGTH_FIELD_COUNT * CSS_LENGTH_BYTES) + sizeof(uint8_t) + sizeof(uint32_t);
// Read each rule
for (uint16_t i = 0; i < ruleCount; ++i) {
@@ -880,8 +891,16 @@ bool CssParser::loadFromCache() {
}
style.display = static_cast<CssDisplay>(displayVal);
// Read verticalAlign value
uint8_t verticalAlignVal;
if (file.read(&verticalAlignVal, 1) != 1) {
rulesBySelector_.clear();
return false;
}
style.verticalAlign = static_cast<CssVerticalAlign>(verticalAlignVal);
// Read defined flags
uint16_t definedBits = 0;
uint32_t definedBits = 0;
if (file.read(&definedBits, sizeof(definedBits)) != sizeof(definedBits)) {
rulesBySelector_.clear();
return false;
@@ -902,6 +921,7 @@ 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;
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 = 4;
static constexpr uint8_t CSS_CACHE_VERSION = 5;
explicit CssParser(std::string cachePath) : cachePath(std::move(cachePath)) {}
~CssParser() = default;
+16 -4
View File
@@ -57,6 +57,9 @@ enum class CssTextDecoration : uint8_t { None = 0, Underline = 1 };
// Display options - only None and Block are relevant for e-ink rendering
enum class CssDisplay : uint8_t { Block = 0, None = 1 };
// Vertical alignment options for inline elements (e.g. superscript/subscript)
enum class CssVerticalAlign : uint8_t { Baseline = 0, Super = 1, Sub = 2 };
// Bitmask for tracking which properties have been explicitly set
struct CssPropertyFlags {
uint16_t textAlign : 1;
@@ -75,6 +78,7 @@ struct CssPropertyFlags {
uint16_t imageHeight : 1;
uint16_t imageWidth : 1;
uint16_t display : 1;
uint16_t verticalAlign : 1;
CssPropertyFlags()
: textAlign(0),
@@ -92,19 +96,20 @@ struct CssPropertyFlags {
paddingRight(0),
imageHeight(0),
imageWidth(0),
display(0) {}
display(0),
verticalAlign(0) {}
[[nodiscard]] bool anySet() const {
return textAlign || fontStyle || fontWeight || textDecoration || textIndent || marginTop || marginBottom ||
marginLeft || marginRight || paddingTop || paddingBottom || paddingLeft || paddingRight || imageHeight ||
imageWidth || display;
imageWidth || display || verticalAlign;
}
void clearAll() {
textAlign = fontStyle = fontWeight = textDecoration = textIndent = 0;
marginTop = marginBottom = marginLeft = marginRight = 0;
paddingTop = paddingBottom = paddingLeft = paddingRight = 0;
imageHeight = imageWidth = display = 0;
imageHeight = imageWidth = display = verticalAlign = 0;
}
};
@@ -128,7 +133,8 @@ struct CssStyle {
CssLength paddingRight; // Padding right
CssLength imageHeight; // Height for img (e.g. 2em) width derived from aspect ratio when only height set
CssLength imageWidth; // Width for img when both or only width set
CssDisplay display = CssDisplay::Block; // display property (Block or None)
CssDisplay display = CssDisplay::Block; // display property (Block or None)
CssVerticalAlign verticalAlign = CssVerticalAlign::Baseline; // vertical-align (super/sub positioning)
CssPropertyFlags defined; // Tracks which properties were explicitly set
@@ -199,6 +205,10 @@ struct CssStyle {
display = base.display;
defined.display = 1;
}
if (base.hasVerticalAlign()) {
verticalAlign = base.verticalAlign;
defined.verticalAlign = 1;
}
}
[[nodiscard]] bool hasTextAlign() const { return defined.textAlign; }
@@ -217,6 +227,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 hasVerticalAlign() const { return defined.verticalAlign; }
void reset() {
textAlign = CssTextAlign::Left;
@@ -228,6 +239,7 @@ struct CssStyle {
paddingTop = paddingBottom = paddingLeft = paddingRight = CssLength{};
imageHeight = imageWidth = CssLength{};
display = CssDisplay::Block;
verticalAlign = CssVerticalAlign::Baseline;
defined.clearAll();
}
};