Proper support of superscript/subscript

This commit is contained in:
jpirnay
2026-05-15 20:47:01 +02:00
parent ae704153d9
commit 95d33a67f0
6 changed files with 132 additions and 7 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
#include "FsHelpers.h"
namespace {
constexpr uint8_t BOOK_CACHE_VERSION = 8;
constexpr uint8_t BOOK_CACHE_VERSION = 9;
constexpr char bookBinFile[] = "/book.bin";
constexpr char tmpSpineBinFile[] = "/spine.bin.tmp";
constexpr char tmpTocBinFile[] = "/toc.bin.tmp";
+12 -1
View File
@@ -12,10 +12,21 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
return;
}
const int ascender = renderer.getFontAscenderSize(fontId);
for (size_t i = 0; i < words.size(); i++) {
const int wordX = wordXpos[i] + x;
const EpdFontFamily::Style currentStyle = wordStyles[i];
renderer.drawText(fontId, wordX, y, words[i].c_str(), true, currentStyle);
// SUP/SUB shift the baseline passed to drawText; the glyph is also scaled 50% inside
// drawText, so these offsets are chosen relative to the full-size ascender:
// SUP: raise by 40% of ascender — sits clearly above the cap-height
// SUB: lower by 25% of ascender — descends below baseline without clashing with ascenders below
int wordY = y;
if ((currentStyle & EpdFontFamily::SUP) != 0) {
wordY -= ascender * 2 / 5;
} else if ((currentStyle & EpdFontFamily::SUB) != 0) {
wordY += ascender / 4;
}
renderer.drawText(fontId, wordX, wordY, words[i].c_str(), true, currentStyle);
const std::string& w = words[i];
const bool hasEmSpacePrefix = w.size() >= 3 && static_cast<uint8_t>(w[0]) == 0xE2 &&
@@ -205,6 +205,8 @@ void ChapterHtmlSlimParser::updateEffectiveInlineStyle() {
effectiveStrikethrough =
currentCssStyle.hasTextDecoration() && (static_cast<uint8_t>(currentCssStyle.textDecoration) &
static_cast<uint8_t>(CssTextDecoration::LineThrough)) != 0;
effectiveSup = false;
effectiveSub = false;
// Apply inline style stack in order
for (const auto& entry : inlineStyleStack) {
@@ -220,6 +222,14 @@ void ChapterHtmlSlimParser::updateEffectiveInlineStyle() {
if (entry.hasStrikethrough) {
effectiveStrikethrough = entry.strikethrough;
}
if (entry.hasSup) {
effectiveSup = entry.sup;
if (entry.sup) effectiveSub = false;
}
if (entry.hasSub) {
effectiveSub = entry.sub;
if (entry.sub) effectiveSup = false;
}
}
}
@@ -280,6 +290,11 @@ bool ChapterHtmlSlimParser::flushPartWordBuffer() {
if (isStrikethrough) {
fontStyle = static_cast<EpdFontFamily::Style>(fontStyle | EpdFontFamily::STRIKETHROUGH);
}
if (effectiveSup) {
fontStyle = static_cast<EpdFontFamily::Style>(fontStyle | EpdFontFamily::SUP);
} else if (effectiveSub) {
fontStyle = static_cast<EpdFontFamily::Style>(fontStyle | EpdFontFamily::SUB);
}
// flush the buffer — route to table cell text when inside a <td>/<th>
partWordBuffer[partWordBufferIndex] = '\0';
@@ -1168,12 +1183,17 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
if (!self->flushPartWordBuffer()) return;
self->nextWordContinues = true;
}
const char* prefix = (strcmp(name, "sup") == 0) ? "^" : "_";
self->currentTextBlock->addWord(prefix, EpdFontFamily::REGULAR, false, true);
self->nextWordContinues = true;
StyleStackEntry entry;
entry.depth = self->depth;
if (strcmp(name, "sup") == 0) {
entry.hasSup = true;
entry.sup = true;
} else {
entry.hasSub = true;
entry.sub = true;
}
self->inlineStyleStack.push_back(entry);
self->updateEffectiveInlineStyle();
} else if (strcmp(name, "span") == 0 || !isHeaderOrBlock(name)) {
// Handle span and other inline elements for CSS styling
if (cssStyle.hasFontWeight() || cssStyle.hasFontStyle() || cssStyle.hasTextDecoration()) {
@@ -66,6 +66,8 @@ class ChapterHtmlSlimParser final : public Print {
bool hasItalic = false, italic = false;
bool hasUnderline = false, underline = false;
bool hasStrikethrough = false, strikethrough = false;
bool hasSup = false, sup = false;
bool hasSub = false, sub = false;
};
std::vector<StyleStackEntry> inlineStyleStack;
CssStyle currentCssStyle;
@@ -73,6 +75,8 @@ class ChapterHtmlSlimParser final : public Print {
bool effectiveItalic = false;
bool effectiveUnderline = false;
bool effectiveStrikethrough = false;
bool effectiveSup = false;
bool effectiveSub = false;
// Buffered table model — populated while inside <table>, emitted on </table>
struct BufferedTableCell {
std::unique_ptr<ParsedText> text;