diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 5f5263a3..e404a73b 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -382,29 +382,105 @@ static inline uint8_t get2BitPixel(const uint8_t* const bitmap, const int stride return get2BitPixel(bitmap, row * stride + col); } -template -static constexpr uint8_t drawMaskFor2BitMode() { - if constexpr (mode == GfxRenderer::BW) - return 0x0E; // draw raw {1,2,3} - else if constexpr (mode == GfxRenderer::GRAYSCALE_MSB) - return 0x06; // draw raw {1,2} - else - return 0x04; // GRAYSCALE_LSB: draw raw {2} +// Compute the runtime drawMask for a given render mode and text darkness. +// Bit N set ⇒ draw when raw 2-bit font value == N +// (raw: 0=white, 1=light gray, 2=dark gray, 3=black). +// +// BW always draws every non-white pixel (darkness has no effect). +// For grayscale modes, increasing darkness folds more AA shades into the +// "draw" set so text becomes progressively bolder. The default darkness=1 +// keeps the historical behavior (MSB pass draws both AA shades, LSB pass +// draws only the dark AA shade). +// +// At "Maximum" (darkness>=3) the grayscale passes are suppressed entirely +// (drawMask 0x00). The BW pass already writes raw {1,2,3} as solid black, +// so AA pixels render as hard black with no gray-LUT softening — visibly +// darker than darkness=2 because the gray waveform is skipped. +// +// darkness | GRAYSCALE_MSB | GRAYSCALE_LSB +// --------- ------------------------- ------------------------- +// 0 | 0x02 (raw {1}) | 0x04 (raw {2}) +// 1 | 0x06 (raw {1,2}) ←dflt | 0x04 (raw {2}) ←dflt +// 2 | 0x06 (raw {1,2}) | 0x06 (raw {1,2}) +// 3+ | 0x00 (none) | 0x00 (none) +// +// ─── Worked example ──────────────────────────────────────────────────────── +// Imagine a 2-bit antialiased glyph for the diagonal stroke of a letter 'A'. +// Each cell holds the raw font value at that pixel: +// +// raw values . . . 2 3 legend: +// . . 2 3 1 . = 0 (white, never drawn) +// . 2 3 1 . 1 = light gray AA +// 2 3 1 . . 2 = dark gray AA +// 3 1 . . . 3 = solid black (stroke core) +// +// Three render passes write to three independent planes; the panel's +// grayscale waveform combines the BW plane with (MSB,LSB) into 4 shades: +// +// (MSB, LSB) → panel shade +// (0,0) → white +// (1,0) → light gray +// (0,1) → dark gray +// (1,1) → black +// +// Per-pixel result for each darkness level (●=black, ▓=dark gray, +// ░=light gray, ·=white): +// +// darkness=0 Normal — true 4-level AA +// . . . ▓ ● raw=1 → (1,0) light gray +// . . ▓ ● ░ raw=2 → (0,1) dark gray +// . ▓ ● ░ . raw=3 → BW black +// ▓ ● ░ . . Crisp edges, lightest stroke. Best for thin/serif fonts. +// ● ░ . . . +// +// darkness=1 Dark — historical default +// . . . ● ● raw=1 → (1,0) light gray (unchanged) +// . . ● ● ░ raw=2 → (1,1) black (was dark gray) +// . ● ● ░ . Dark-gray fringe collapses to black; light fringe +// ● ● ░ . . survives. Stroke core thickens by ~1px on the +// ● ░ . . . steep side of the slope. +// +// darkness=2 Extra Dark — both AA shades go black +// . . . ● ● raw=1 → (1,1) black +// . . ● ● ● raw=2 → (1,1) black +// . ● ● ● . All AA pixels are pushed to "black" in the gray +// ● ● ● . . plane. The gray waveform still runs, so pixels +// ● ● . . . share the gray-pass voltage profile (slightly +// softer than Maximum). +// +// darkness=3 Maximum — grayscale pass skipped entirely +// . . . ● ● Both grayscale drawMasks are 0x00; nothing is +// . . ● ● ● written to the (MSB,LSB) planes. The BW pass — +// . ● ● ● . which already writes raw {1,2,3} as solid black — +// ● ● ● . . is the only pass the panel sees, refreshed with +// ● ● . . . the hard FAST waveform. Visually identical pixel +// footprint to darkness=2 but driven harder, so +// strokes look noticeably bolder/blacker on the +// physical e-ink panel. +// ─────────────────────────────────────────────────────────────────────────── +static inline uint8_t drawMaskFor2BitMode(const GfxRenderer::RenderMode mode, const uint8_t darkness) { + if (mode == GfxRenderer::BW) return 0x0E; // draw raw {1,2,3} + if (darkness >= 3) return 0x00; // skip grayscale entirely (Maximum) + if (mode == GfxRenderer::GRAYSCALE_MSB) { + return (darkness == 0) ? 0x02 : 0x06; + } + // GRAYSCALE_LSB + return (darkness >= 2) ? 0x06 : 0x04; } // 2-bit pipeline — fused gather+threshold (X axis): the 2-bit analog of extractGlyphBlock, but // gather and threshold are collapsed into one pass. The threshold (2-bit raw value → 1-bit on/off) // is information-lossy, so no contiguous 2-bit intermediate block can be formed mid-pipeline. // The resulting 1-bit mask feeds writeRowBits directly (scatter). build2BitColMask is the Y-axis counterpart. -template +// +// Templated on the drawMask byte (a non-type template parameter) so each render-mode/darkness +// combination compiles to its own specialization with the mask folded into a constant. +template static inline uint8_t build2BitRowMask(const uint8_t* const bitmap, const int rowStartPixel, const int glyphXStartOrEnd, const int count, const bool reverseXInChunk) { // drawMask uses raw 2-bit glyph values directly from font bitmaps: // raw 0=white, 1=light gray, 2=dark gray, 3=black. // Bit N set means: draw/update when raw==N. - // Compile-time constant lets the compiler reduce (drawMask >> raw) & 1 to a single comparison. - constexpr uint8_t drawMask = drawMaskFor2BitMode(); - uint8_t mask = 0; for (int i = 0; i < count; i++) { const int logicalX = reverseXInChunk ? (glyphXStartOrEnd - i) : (glyphXStartOrEnd + i); @@ -419,10 +495,11 @@ static inline uint8_t build2BitRowMask(const uint8_t* const bitmap, const int ro // The 2-bit glyph bitmap stores 4 pixels per byte, MSB-first: // byte b = [p0.msb p0.lsb p1.msb p1.lsb p2.msb p2.lsb p3.msb p3.lsb] // -// For each render mode the draw decision collapses to a two-bit boolean: -// BW (draw if raw ≠ 0): msb | lsb -// GRAYSCALE_MSB (draw if raw ∈ {1,2}): msb ^ lsb -// GRAYSCALE_LSB (draw if raw == 2): msb & ~lsb +// For each drawMask the draw decision collapses to a two-bit boolean: +// 0x0E (raw ∈ {1,2,3}): msb | lsb +// 0x06 (raw ∈ {1,2}): msb ^ lsb +// 0x04 (raw == 2): msb & ~lsb +// 0x02 (raw == 1): ~msb & lsb // // Derivation for one byte: // msb_bits = b & 0xAA → bits 7,5,3,1 hold p0.msb … p3.msb; bits 6,4,2,0 = 0 @@ -439,7 +516,7 @@ static inline uint8_t build2BitRowMask(const uint8_t* const bitmap, const int ro // processes the full 8-pixel chunk in ~16 ALU ops instead of ~56. // The caller is responsible for only calling this when pixelStart is // 4-pixel (1-byte) aligned (pixelStart & 3 == 0) and count == 8. -template +template static inline uint8_t build2BitRowMaskFromTwoBytes(const uint8_t b0, const uint8_t b1) { const uint8_t msb0 = b0 & 0xAA; const uint8_t lsb0 = (b0 & 0x55) << 1; @@ -447,15 +524,19 @@ static inline uint8_t build2BitRowMaskFromTwoBytes(const uint8_t b0, const uint8 const uint8_t lsb1 = (b1 & 0x55) << 1; uint8_t draw0, draw1; - if constexpr (mode == GfxRenderer::BW) { + if constexpr (drawMask == 0x0E) { // BW: raw ∈ {1,2,3} draw0 = msb0 | lsb0; draw1 = msb1 | lsb1; - } else if constexpr (mode == GfxRenderer::GRAYSCALE_MSB) { + } else if constexpr (drawMask == 0x06) { // raw ∈ {1,2} draw0 = msb0 ^ lsb0; draw1 = msb1 ^ lsb1; - } else { // GRAYSCALE_LSB + } else if constexpr (drawMask == 0x04) { // raw == 2 (dark gray) draw0 = msb0 & ~lsb0; draw1 = msb1 & ~lsb1; + } else { // drawMask == 0x02, raw == 1 (light gray) + static_assert(drawMask == 0x02, "unsupported drawMask in build2BitRowMaskFromTwoBytes"); + draw0 = ~msb0 & lsb0; + draw1 = ~msb1 & lsb1; } // Compact each nibble's draw flags from bit positions 7,5,3,1 → 7,6,5,4. @@ -468,10 +549,9 @@ static inline uint8_t build2BitRowMaskFromTwoBytes(const uint8_t b0, const uint8 // 2-bit pipeline — fused gather+threshold (Y axis): column-direction counterpart to build2BitRowMask. // Samples count pixels down glyph column glyphX starting at row glyphYStart; reverseRows implements // a negative-stride view along Y (reads bottom-to-top), needed for PortraitInverted. -template +template static inline uint8_t build2BitColMask(const uint8_t* const bitmap, const int glyphWidth, const int glyphX, const int glyphYStart, const int count, const bool reverseRows) { - constexpr uint8_t drawMask = drawMaskFor2BitMode(); uint8_t mask = 0; for (int i = 0; i < count; i++) { const int row = reverseRows ? (glyphYStart + count - 1 - i) : (glyphYStart + i); @@ -485,7 +565,7 @@ static inline uint8_t build2BitColMask(const uint8_t* const bitmap, const int gl // inverted=false → Portrait (phyY counts down, phyBitPos counts up). // inverted=true → PortraitInverted (phyY counts up, phyBitPos counts down). // Both template params are compile-time constants; all ternaries fold away. -template +template static void renderGlyphFast2BitPortrait(uint8_t* const frameBuffer, const uint8_t* const bitmap, const int glyphWidth, const int glyphHeight, const int screenXBase, const int screenYBase, const bool writeState) { @@ -495,7 +575,7 @@ static void renderGlyphFast2BitPortrait(uint8_t* const frameBuffer, const uint8_ uint8_t* const row = frameBuffer + phyY * HalDisplay::DISPLAY_WIDTH_BYTES; for (int glyphY = 0; glyphY < glyphHeight; glyphY += 8) { const int count = std::min(8, glyphHeight - glyphY); - const uint8_t mask = build2BitColMask(bitmap, glyphWidth, glyphX, glyphY, count, inverted); + const uint8_t mask = build2BitColMask(bitmap, glyphWidth, glyphX, glyphY, count, inverted); if (mask == 0) continue; const int phyBitPos = inverted ? (HalDisplay::DISPLAY_WIDTH - 1 - screenYBase - (glyphY + count - 1)) : (screenYBase + glyphY); @@ -505,13 +585,14 @@ static void renderGlyphFast2BitPortrait(uint8_t* const frameBuffer, const uint8_ } } -template +template static void renderGlyphFast2Bit(uint8_t* const frameBuffer, const uint8_t* const bitmap, const int glyphWidth, const int glyphHeight, const int screenXBase, const int screenYBase, const bool pixelState, const GfxRenderer::Orientation orientation) { // Non-rotated text fast path for 2-bit glyphs. Writes compact masks directly to framebuffer rows. // TextRotation::Rotated90CW keeps the legacy per-pixel fallback path for safety and readability. - const bool writeState = (mode == GfxRenderer::BW) ? pixelState : false; + // BW (drawMask 0x0E) honors the caller's pixelState; grayscale passes always clear the bit. + const bool writeState = (drawMask == 0x0E) ? pixelState : false; switch (orientation) { case GfxRenderer::LandscapeCounterClockwise: { @@ -526,9 +607,9 @@ static void renderGlyphFast2Bit(uint8_t* const frameBuffer, const uint8_t* const uint8_t mask; if (count == 8 && (pixelStart & 3) == 0) { const int srcByteIdx = pixelStart >> 2; - mask = build2BitRowMaskFromTwoBytes(bitmap[srcByteIdx], bitmap[srcByteIdx + 1]); + mask = build2BitRowMaskFromTwoBytes(bitmap[srcByteIdx], bitmap[srcByteIdx + 1]); } else { - mask = build2BitRowMask(bitmap, rowStartPixel, glyphX, count, false); + mask = build2BitRowMask(bitmap, rowStartPixel, glyphX, count, false); } if (mask == 0) continue; const int phyBitPos = screenXBase + glyphX; @@ -555,9 +636,9 @@ static void renderGlyphFast2Bit(uint8_t* const frameBuffer, const uint8_t* const uint8_t mask; if (count == 8 && (pixelStart & 3) == 0) { const int srcByteIdx = pixelStart >> 2; - mask = reverseBits8(build2BitRowMaskFromTwoBytes(bitmap[srcByteIdx], bitmap[srcByteIdx + 1])); + mask = reverseBits8(build2BitRowMaskFromTwoBytes(bitmap[srcByteIdx], bitmap[srcByteIdx + 1])); } else { - mask = build2BitRowMask(bitmap, rowStartPixel, chunkEnd, count, true); + mask = build2BitRowMask(bitmap, rowStartPixel, chunkEnd, count, true); } if (mask == 0) continue; const int phyBitPos = HalDisplay::DISPLAY_WIDTH - 1 - screenXBase - chunkEnd; @@ -569,13 +650,13 @@ static void renderGlyphFast2Bit(uint8_t* const frameBuffer, const uint8_t* const } case GfxRenderer::Portrait: - renderGlyphFast2BitPortrait(frameBuffer, bitmap, glyphWidth, glyphHeight, screenXBase, screenYBase, - writeState); + renderGlyphFast2BitPortrait(frameBuffer, bitmap, glyphWidth, glyphHeight, screenXBase, + screenYBase, writeState); break; case GfxRenderer::PortraitInverted: - renderGlyphFast2BitPortrait(frameBuffer, bitmap, glyphWidth, glyphHeight, screenXBase, screenYBase, - writeState); + renderGlyphFast2BitPortrait(frameBuffer, bitmap, glyphWidth, glyphHeight, screenXBase, + screenYBase, writeState); break; } } @@ -614,27 +695,42 @@ static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode } if (is2Bit) { + // Compute the drawMask once per glyph from the current render mode + text-darkness setting. + // The fast path dispatches on this at runtime to a template specialization so the mask is + // a compile-time constant inside the inner loops. + const uint8_t drawMask = drawMaskFor2BitMode(renderMode, renderer.getTextDarkness()); + + // drawMask == 0 means "draw nothing" — used by Maximum darkness to skip grayscale passes. + if (drawMask == 0) return; + if constexpr (rotation == TextRotation::None) { // Fast path for normal text orientation. Handles all device orientations via renderGlyphFast2Bit. - // Dispatch on renderMode at compile time so each specialization gets a constant drawMask. - switch (renderMode) { - case GfxRenderer::BW: - renderGlyphFast2Bit(renderer.getFrameBuffer(), bitmap, width, height, innerBase, outerBase, - pixelState, renderer.getOrientation()); + switch (drawMask) { + case 0x0E: // BW + renderGlyphFast2Bit<0x0E>(renderer.getFrameBuffer(), bitmap, width, height, innerBase, outerBase, + pixelState, renderer.getOrientation()); break; - case GfxRenderer::GRAYSCALE_MSB: - renderGlyphFast2Bit(renderer.getFrameBuffer(), bitmap, width, height, innerBase, - outerBase, pixelState, renderer.getOrientation()); + case 0x06: // raw {1,2} + renderGlyphFast2Bit<0x06>(renderer.getFrameBuffer(), bitmap, width, height, innerBase, outerBase, + pixelState, renderer.getOrientation()); break; - case GfxRenderer::GRAYSCALE_LSB: - renderGlyphFast2Bit(renderer.getFrameBuffer(), bitmap, width, height, innerBase, - outerBase, pixelState, renderer.getOrientation()); + case 0x04: // raw {2} + renderGlyphFast2Bit<0x04>(renderer.getFrameBuffer(), bitmap, width, height, innerBase, outerBase, + pixelState, renderer.getOrientation()); + break; + case 0x02: // raw {1} + renderGlyphFast2Bit<0x02>(renderer.getFrameBuffer(), bitmap, width, height, innerBase, outerBase, + pixelState, renderer.getOrientation()); break; } return; } - // Rotated text fallback: keep explicit per-pixel behavior. + // Rotated text fallback: per-pixel path. Uses the same drawMask as the fast path so darkness + // takes effect uniformly. (Previously this branch had a separate X4-only "draw light gray too" + // quirk; that quirk is now subsumed by the default darkness=1 mask, which already includes + // both AA shades for the MSB pass.) + const bool isBW = (drawMask == 0x0E); int pixelPosition = 0; for (int glyphY = 0; glyphY < height; glyphY++) { const int outerCoord = outerBase + glyphY; @@ -650,22 +746,12 @@ static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode const uint8_t byte = bitmap[pixelPosition >> 2]; const uint8_t bit_index = (3 - (pixelPosition & 3)) * 2; - // the direct bit from the font is 0 -> white, 1 -> light gray, 2 -> dark gray, 3 -> black - // we swap this to better match the way images and screen think about colors: - // 0 -> black, 1 -> dark grey, 2 -> light grey, 3 -> white - const uint8_t bmpVal = 3 - ((byte >> bit_index) & 0x3); + // raw value straight from the font: 0=white, 1=light gray, 2=dark gray, 3=black + const uint8_t raw = (byte >> bit_index) & 0x3; - if (renderMode == GfxRenderer::BW && bmpVal < 3) { - // Black (also paints over the grays in BW mode) - renderer.drawPixel(screenX, screenY, pixelState); - } else if (renderMode == GfxRenderer::GRAYSCALE_MSB && (bmpVal == 1 || (gpio.deviceIsX4() && bmpVal == 2))) { - // Light gray (also mark the MSB if it's going to be a dark gray too) - // X3 AA tuning: keep only the darker antialias level to avoid washed text - // We have to flag pixels in reverse for the gray buffers, as 0 leave alone, 1 update - renderer.drawPixel(screenX, screenY, false); - } else if (renderMode == GfxRenderer::GRAYSCALE_LSB && bmpVal == 1) { - // Dark gray - renderer.drawPixel(screenX, screenY, false); + if ((drawMask >> raw) & 0x01) { + // BW honors caller's pixelState; grayscale passes always clear the bit (false) + renderer.drawPixel(screenX, screenY, isBW ? pixelState : false); } } } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 55b4dbf2..4a0c5580 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -35,6 +35,18 @@ class GfxRenderer { RenderMode renderMode; Orientation orientation; bool fadingFix; + // Text darkness for 2-bit grayscale glyph rendering. + // 0 = Normal — true 4-level AA (raw=1 → light gray, raw=2 → dark gray) + // 1 = Dark — historical default; raw=2 collapses to black + // 2 = Extra Dark — both AA shades go black in the grayscale plane + // 3 = Maximum — grayscale pass skipped entirely; only the hard BW + // pass remains, so AA pixels render as solid black + // with the FAST waveform (no gray-LUT softening) + // Only affects AA pixels in GRAYSCALE_MSB / GRAYSCALE_LSB rendering of 2-bit fonts. + // 1-bit fonts and the BW pass are unchanged. Default is 1 to preserve historical + // rendering. See drawMaskFor2BitMode() in GfxRenderer.cpp for the per-level + // pixel breakdown and a worked example glyph. + uint8_t textDarkness = 1; uint8_t* frameBuffer = nullptr; uint16_t panelWidth = HalDisplay::DISPLAY_WIDTH; uint16_t panelHeight = HalDisplay::DISPLAY_HEIGHT; @@ -155,6 +167,14 @@ class GfxRenderer { // Grayscale functions void setRenderMode(const RenderMode mode) { this->renderMode = mode; } RenderMode getRenderMode() const { return renderMode; } + + // Text darkness control: + // 0 = Normal, 1 = Dark, 2 = Extra Dark, 3 = Maximum. + // Only affects anti-aliased pixels in 2-bit (grayscale) glyph rendering; + // 1-bit fonts and the BW pass are unchanged. See drawMaskFor2BitMode() in + // GfxRenderer.cpp for the per-level pixel breakdown and a worked example. + void setTextDarkness(const uint8_t d) { textDarkness = d; } + uint8_t getTextDarkness() const { return textDarkness; } void copyGrayscaleLsbBuffers() const; void copyGrayscaleMsbBuffers() const; void displayGrayBuffer() const; diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 80ad7e79..e3f4a23c 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -66,6 +66,9 @@ STR_SLEEP_COVER_MODE: "Sleep Screen Cover Mode" STR_HIDE_BATTERY: "Hide Battery %" STR_EXTRA_SPACING: "Extra Paragraph Spacing" STR_TEXT_AA: "Text Anti-Aliasing" +STR_TEXT_DARKNESS: "Text Darkness" +STR_EXTRA_DARK: "Extra Dark" +STR_MAX_DARK: "Maximum" STR_IMAGES: "Images" STR_IMAGES_DISPLAY: "Display" STR_IMAGES_PLACEHOLDER: "Placeholder" diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml index 17fa3950..74dee8e8 100644 --- a/lib/I18n/translations/french.yaml +++ b/lib/I18n/translations/french.yaml @@ -66,6 +66,9 @@ STR_SLEEP_COVER_MODE: "Mode écran de veille" STR_HIDE_BATTERY: "Masquer % batterie" STR_EXTRA_SPACING: "Espacement paragraphes" STR_TEXT_AA: "Lissage du texte" +STR_TEXT_DARKNESS: "Densité du texte" +STR_EXTRA_DARK: "Très foncé" +STR_MAX_DARK: "Maximum" STR_IMAGES: "Images" STR_IMAGES_DISPLAY: "Affichage" STR_IMAGES_PLACEHOLDER: "Espace réservé" diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index 97ffb71a..b3bb55dc 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -66,6 +66,9 @@ STR_SLEEP_COVER_MODE: "Standby-Bildmodus" STR_HIDE_BATTERY: "Batterie % ausblenden" STR_EXTRA_SPACING: "Absatzabstand" STR_TEXT_AA: "Schriftglättung" +STR_TEXT_DARKNESS: "Schriftstärke" +STR_EXTRA_DARK: "Extra dunkel" +STR_MAX_DARK: "Maximum" STR_IMAGE_DITHERING: "Bild-Dithering" STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index dba769c8..8c805347 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -66,6 +66,9 @@ STR_SLEEP_COVER_MODE: "Copertina in sospensione" STR_HIDE_BATTERY: "Nascondi % batteria" STR_EXTRA_SPACING: "Spaziatura extra paragrafi" STR_TEXT_AA: "Anti-aliasing testo" +STR_TEXT_DARKNESS: "Intensità testo" +STR_EXTRA_DARK: "Molto scuro" +STR_MAX_DARK: "Massimo" STR_IMAGES: "Immagini" STR_IMAGES_DISPLAY: "Visualizza" STR_IMAGES_PLACEHOLDER: "Segnaposto" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index 3367f767..e3aa458f 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -66,6 +66,9 @@ STR_SLEEP_COVER_MODE: "Modo de pantalla de suspensión" STR_HIDE_BATTERY: "Ocultar % de batería" STR_EXTRA_SPACING: "Espaciado entre párrafos" STR_TEXT_AA: "Suavizado de texto" +STR_TEXT_DARKNESS: "Intensidad del texto" +STR_EXTRA_DARK: "Muy oscuro" +STR_MAX_DARK: "Máximo" STR_IMAGES: "Imágenes" STR_IMAGES_DISPLAY: "Mostrar" STR_IMAGES_PLACEHOLDER: "Reemplazar"