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
+78 -1
View File
@@ -842,6 +842,68 @@ static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode
}
}
// Render a glyph at 50% scale via nearest-neighbor sampling. Used for SUP/SUB style bits.
//
// Nearest-neighbor is chosen deliberately: at 50% every source pixel maps cleanly to one
// destination pixel (srcX = dstX*2, srcY = dstY*2), so there is no blending and no new
// gray levels are introduced — important for 1-bit BW rendering.
//
// For 2-bit (anti-aliased) fonts only raw values >= 2 (dark-gray and black) are drawn.
// Dropping the light-gray level keeps small glyphs crisp rather than muddy.
//
// The advance width is also halved in drawText() so layout reserves exactly the right
// horizontal space for the scaled glyph.
static void renderCharScaled(const GfxRenderer& renderer, GfxRenderer::RenderMode renderMode,
const EpdFontFamily& fontFamily, const uint32_t cp, int cursorX, int cursorY,
const bool pixelState, const EpdFontFamily::Style style) {
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
if (!glyph) return;
const EpdFontData* fontData = fontFamily.getData(style);
const uint8_t* bitmap = renderer.getGlyphBitmap(fontData, glyph);
if (!bitmap) return;
const int srcW = glyph->width;
const int srcH = glyph->height;
const int dstW = (srcW + 1) / 2; // ceil so odd-width glyphs aren't clipped
const int dstH = (srcH + 1) / 2;
// Scale the glyph bearing by the same factor so the scaled glyph sits at the correct
// pixel offset from the (already-shifted) cursor position.
const int baseX = cursorX + glyph->left / 2;
const int baseY = cursorY - glyph->top / 2;
if (fontData->is2Bit) {
// 2-bit packed format: 4 pixels per byte, MSB first, 2 bits per pixel.
// raw value: 0=white, 1=light-gray, 2=dark-gray, 3=black.
for (int dstY = 0; dstY < dstH; dstY++) {
const int srcY = dstY * 2;
for (int dstX = 0; dstX < dstW; dstX++) {
const int srcX = dstX * 2;
const int pos = srcY * srcW + srcX;
const uint8_t byte = bitmap[pos >> 2];
const uint8_t raw = (byte >> ((3 - (pos & 3)) * 2)) & 0x3;
if (raw >= 2) { // threshold: skip light-gray, draw dark-gray and black
renderer.drawPixel(baseX + dstX, baseY + dstY, pixelState);
}
}
}
} else {
// 1-bit packed format: 8 pixels per byte, MSB first.
for (int dstY = 0; dstY < dstH; dstY++) {
const int srcY = dstY * 2;
for (int dstX = 0; dstX < dstW; dstX++) {
const int srcX = dstX * 2;
const int pos = srcY * srcW + srcX;
const uint8_t byte = bitmap[pos >> 3];
const uint8_t bit = 7 - (pos & 7);
if ((byte >> bit) & 1) {
renderer.drawPixel(baseX + dstX, baseY + dstY, pixelState);
}
}
}
}
}
// IMPORTANT: This function is in critical rendering path and is called for every pixel. Please keep it as simple and
// efficient as possible.
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
@@ -960,9 +1022,21 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
lastBaseWidth = glyph->width;
lastBaseTop = glyph->top;
lastBaseAdvanceFP = glyph->advanceX;
const bool isSupSub = (style & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0;
if (isSupSub) {
// Halve the advance so the cursor advances by the same amount the scaled glyph
// actually occupies, keeping spacing correct without needing a separate smaller font.
lastBaseAdvanceFP = (lastBaseAdvanceFP + 1) / 2;
}
prevAdvanceFP = lastBaseAdvanceFP;
renderCharImpl<TextRotation::None>(*this, renderModeSnapshot, font, cp, lastBaseX, yPos, black, style);
if (isSupSub) {
// yPos already carries the vertical offset applied by TextBlock::render().
renderCharScaled(*this, renderModeSnapshot, font, cp, lastBaseX, yPos, black, style);
} else {
renderCharImpl<TextRotation::None>(*this, renderModeSnapshot, font, cp, lastBaseX, yPos, black, style);
}
prevCp = cp;
}
}
@@ -2017,6 +2091,9 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFami
continue;
}
prevAdvanceFP = glyph->advanceX;
if ((style & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0) {
prevAdvanceFP = (prevAdvanceFP + 1) / 2;
}
prevCp = cp;
}
widthPx += fp4::toPixel(prevAdvanceFP); // final glyph's advance