Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
acef815
README: 1.6.5 起点の作り直し(vertical-1.6.5)の段階表と段階1の結果
osakanataro Sep 28, 2026
540cc65
feat: 1.6.5 起点の新ツリーに時計モードと監視を載せる(作り直しの段階2)
osakanataro Sep 28, 2026
b4fb5a6
README: 作り直しの段階2(時計と監視)の結果と、日本時間の設定場所の変更
osakanataro Sep 28, 2026
cfb2286
diag: 字形の計測を入れ、異体字選択子を読み飛ばす(作り直しの段階3)
osakanataro Sep 28, 2026
6184285
README: 作り直しの段階3(字形の計測と素の実測①)の結果
osakanataro Sep 28, 2026
b6cd581
perf: 章の組み立て中は字の幅だけを読み、開いたファイルと覚えた幅を使い回す(段階9から前倒し)
osakanataro Sep 28, 2026
dbce99b
README: 幅だけを読む経路の前倒しの結果と、段階3の 39 秒の訂正
osakanataro Sep 28, 2026
49da3fe
fix: 横書きの禁則を JLREQ 3.1.7/3.1.8 の各類に広げる(本家 PR #3609 を取り込み、作り直しの段階4)
osakanataro Sep 28, 2026
72237ce
fix: 長い段落の字境を1回の走査で作り、分けて組んだ続きに字下げ・上の余白を入れない(段階4)
osakanataro Sep 28, 2026
e603904
README: 作り直しの段階4(横書きの組版)の結果
osakanataro Sep 28, 2026
2a4c61a
README: 禁則の塊を割ったときの行末の「-」は残す判断を記録
osakanataro Sep 28, 2026
929416f
feat: 1.6.5 起点の新ツリーに縦書きの中核を載せる(作り直しの段階5)
osakanataro Sep 28, 2026
48e1123
README: 作り直しの段階5(縦書きの中核)の結果
osakanataro Sep 28, 2026
a46a497
feat: 本家の字間の設定(#3528)を縦書きの字送りにも効かせる(作り直しの段階6)
osakanataro Sep 28, 2026
e45c49c
README: 作り直しの段階6(縦書きの字間)の結果
osakanataro Sep 28, 2026
c808e9d
feat: 縦書きの本に挿絵と外字を載せる(作り直しの段階7)
osakanataro Sep 28, 2026
6cb944a
README: 作り直しの段階7(挿絵)の結果
osakanataro Sep 28, 2026
a5118a4
feat: 表紙が SVG の包みの本に対応する(作り直しの段階8)
osakanataro Sep 28, 2026
8ca9fc2
README: 作り直しの段階8(表紙)の結果
osakanataro Sep 28, 2026
1d965a4
feat: 書体を空き側の firmware 領域に複製して flash から読む(18pt は 16pt の 9/8 倍、作り直しの段階8a)
osakanataro Sep 29, 2026
a9ef863
fix: 挿絵の大きさを読む展開にも画面用の記憶を貸す
osakanataro Sep 29, 2026
5d9a5a2
README: 作り直しの段階8a(flash から書体を読む)の結果
osakanataro Sep 29, 2026
2a3befb
README: 段階表の 8a 行の列ずれを直す
osakanataro Sep 29, 2026
c2d60c7
feat: order and group the Library by a book's file-as reading
osakanataro Sep 21, 2026
79db5bc
fix: resolve queued file-as refinements from a moved-out copy
osakanataro Sep 21, 2026
2f7ab80
fix: bound the OPF ids and EPUB 2 readings that file-as copies
osakanataro Sep 23, 2026
0b78030
fix: fold halfwidth katakana readings and bound UTF-8 decoding
osakanataro Sep 27, 2026
36b1fe2
README: 作り直しの段階8b(Library の読み順)の結果
osakanataro Sep 29, 2026
d9bd19f
fix: 字形の一時置き場を 4KB の塊に分けて確保する(作り直しの段階9)
osakanataro Sep 29, 2026
cfd360a
fix: 本からの取り出しの塊を空きに合わせて 512B まで縮める
osakanataro Sep 29, 2026
5e3abff
fix: 字形の置き場の塊が取れないときは字数を減らして作り直す
osakanataro Sep 29, 2026
7e74f03
fix: JPEG の挿絵も章の組み立て中に .pxc まで展開する
osakanataro Sep 29, 2026
bf24289
README: 作り直しの段階9(実測②と補い)の途中経過
osakanataro Sep 29, 2026
53f16ab
fix: 組み立てが空き不足で止まっているときはメニューを開く前に一時停止する
osakanataro Sep 29, 2026
b0fa039
fix: 章の組み立てには、その章で当たりうる CSS の規則だけを読み込む
osakanataro Sep 29, 2026
ecf6c98
fix: 設定項目の一覧を常駐させず、使うたびに作る
osakanataro Sep 29, 2026
a5bdf3c
README: 作り直しの段階9(空きの底の引き上げ)
osakanataro Sep 29, 2026
0f1e728
README: OST版 2026092923-diag(1.6.5 ベースの最初の pre-release)
osakanataro Sep 29, 2026
8448c4c
Update README.md
osakanataro Sep 29, 2026
066dead
README: 開発方針の見出しの日付を 2026/09/29 に直す
osakanataro Sep 29, 2026
f9c465f
chore: X4 Pro 向けの診断版を X3 と同じ番号で作る
osakanataro Sep 29, 2026
acde75e
README: X4 Pro 向けの版(pre-release 20260929 に追加)
osakanataro Sep 29, 2026
92e3fc0
fix: X4 Pro の時計画面をホームキーで終了できるようにする
osakanataro Sep 29, 2026
7ad2520
README: X4 Pro の時計画面をホームキーで終了
osakanataro Sep 29, 2026
d72b5ef
README: pre-release 20260929 の添付を 2026093001 に差し替え
osakanataro Sep 29, 2026
f0e6711
fix: 待ち時間の組み立てで画面用の記憶を貸したら描き直し、重ね表示を載せない
osakanataro Sep 30, 2026
9a937d5
README: 本家 PR #3802 と重ね表示の保護
osakanataro Sep 30, 2026
f56041d
perf: remove ButtonNavigator allocation churn (#3698)
serialx Sep 28, 2026
7a4ff45
fix: Footnote selection with power button (#3766)
Uri-Tauber Sep 28, 2026
77ab7ee
Fix header back button tap handling in input routing
osakanataro Sep 30, 2026
e7db4af
fix: 空きが少ないときにツールバーを開いても落ちないようにする
osakanataro Sep 30, 2026
6da0757
fix: 自動ページ送りの間は自動スリープさせない
osakanataro Sep 30, 2026
7a8d811
fix: guard EPUB lookups at the end-of-book position
osakanataro Sep 30, 2026
cc07572
fix(storage): handle file-wrapper allocation failures
osakanataro Sep 30, 2026
0d4945c
fix: bound serialized string lengths read from caches
osakanataro Sep 30, 2026
868d5a5
README: 本家・他フォークからの小さな修正
osakanataro Sep 30, 2026
6dd1daa
fix: CodeRabbit の指摘2件(診断版の番号の見分け、待ち時間の組み立て失敗の記録)
osakanataro Sep 30, 2026
2d77777
README: CodeRabbit による段階ごとのレビューの開始
osakanataro Sep 30, 2026
5d64452
review only: README・自動生成の ClockDigits.h・文書とテストの組み立て設定などを土台と同じ状態に戻す(Co…
osakanataro Sep 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/EpdFont/EpdFontFamily.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ class EpdFontFamily {
SUP = 16, // superscript: glyph scaled 50%, raised ~40% of ascender
SUB = 32, // subscript: glyph scaled 50%, lowered ~25% of ascender
RUBY_CONTINUE = 64, // Group ruby follower marker (used internally by Epub layout)
// Vertical layout only: the token is set the opposite way from what its text implies
// (an ASCII letter upright, a fullwidth ! turned, a Latin run in one tate-chu-yoko
// cell). Carries CSS text-orientation / text-combine-upright through the page cache
// without a per-word behaviour array; TextBlock::renderVertical reads it, nothing else.
VERTICAL_FLIP = 128,
};
static constexpr uint8_t TEXT_DECORATION_MASK = static_cast<uint8_t>(UNDERLINE | STRIKETHROUGH);

Expand Down
568 changes: 481 additions & 87 deletions lib/EpdFont/SdCardFont.cpp

Large diffs are not rendered by default.

141 changes: 134 additions & 7 deletions lib/EpdFont/SdCardFont.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#pragma once

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "EpdFont.h"
#include "EpdFontData.h"

class HalFile;

// On-disk binary format version for .cpfont files. Defined as a preprocessor
// macro (rather than a constexpr) so it can be stringified into the SD-fonts
// release URL — see FONT_MANIFEST_URL in FontDownloadActivity.h. No integer
Expand All @@ -21,11 +24,20 @@
class SdCardFont {
public:
static constexpr uint16_t MAX_PAGE_GLYPHS = 512;
// prewarmStyle: the bitmap arena did not fit the largest free block.
// Distinct from a missed-glyph count so the caller can retry smaller.
static constexpr int PREWARM_ARENA_TOO_LARGE = -2;
static constexpr uint8_t MAX_STYLES = 4;

// Recent frees of a style's mini arena, for INPUT_DIAG (who dropped the page's glyphs, and when).
struct MiniFreeEvent {
uint32_t caller = 0;
uint32_t ms = 0;
uint32_t freeHeap = 0;
uint16_t glyphs = 0;
uint8_t style = 0;
bool metadataOnly = false;
};
static constexpr uint8_t MINI_FREE_EVENTS = 4;
static const MiniFreeEvent* miniFreeEvents(uint32_t& total);

SdCardFont() = default;
~SdCardFont();
// Owns raw buffers freed in dtor — no shallow-copy semantics. Make any
Expand All @@ -38,7 +50,19 @@ class SdCardFont {
// Load .cpfont file: reads header + intervals into RAM, records file layout offsets.
// Supports v4 (multi-style) format.
// Returns true on success.
bool load(const char* path);
// isReaderFont: the family's body-text size (its first load), reported by INPUT_DIAG.
// scaleNum/scaleDen: every glyph record (width, height, bearings, advance) and bitmap is
// scaled by this ratio as it enters the resident caches, and the style's line metrics at
// load, so the renderer sees a font of the scaled size. 1/1 = the file as it is. Only 2-bit
// fonts are scaled; a 1-bit font ignores the ratio. Kerning values are not scaled (the CJK
// fonts this is for carry none).
// preferFlash: read through the copy in the inactive OTA slot (SdCardFontCache) when it holds
// this very file; otherwise, and for any range the copy does not hold, the SD card is read.
bool load(const char* path, bool isReaderFont = false, uint8_t scaleNum = 1, uint8_t scaleDen = 1,
bool preferFlash = false);
// True while reads are served from the flash copy (a read failure turns it off for good).
bool usingFlash() const { return useFlash_; }
size_t flashPayloadBytes() const { return flashPayloadBytes_; }

// Pre-read glyphs needed for the given UTF-8 text from SD card.
// styleMask: bitmask of styles to prewarm (bit 0=regular, 1=bold, 2=italic, 3=bolditalic).
Expand Down Expand Up @@ -81,6 +105,12 @@ class SdCardFont {
// Look up advanceX for a codepoint from the advance table.
// Returns the 12.4 fixed-point advance, or 0 if not found.
uint16_t getAdvance(uint32_t codepoint, uint8_t style) const;
// Advance of one codepoint the table does not hold, without loading its bitmap: the resident
// arena, then the overflow ring, then the 12-byte glyph record straight from the card. 0 when
// the font has no such glyph (or the read failed), so callers can fall back to a full load.
uint16_t readAdvanceOnly(uint32_t codepoint, uint8_t styleIdx) const;
// Close the file readAdvanceOnly keeps open for the duration of a measuring pass.
void closeMeasureFile() const;

// Returns true if advance table is populated for at least one style.
bool hasAdvanceTable() const;
Expand Down Expand Up @@ -126,6 +156,12 @@ class SdCardFont {
// Returns the bitmap for an on-demand-loaded (overflow) glyph.
const uint8_t* getOverflowBitmap(const EpdGlyph* glyph) const;

// Resolves a prewarmed mini glyph's chunked bitmap. `ctx` is the glyphMissCtx
// (an OverflowContext identifying the style); `dataOffset` is the glyph's
// virtual offset into the style's chunked arena. Returns nullptr if the chunk
// is absent or out of range. Called by GfxRenderer::getGlyphBitmap().
const uint8_t* miniGlyphBitmap(const void* ctx, uint32_t dataOffset) const;

// Extract SdCardFont* from an opaque glyphMissCtx pointer.
// Used by GfxRenderer::getGlyphBitmap() to recover the SdCardFont from EpdFontData::glyphMissCtx.
static SdCardFont* fromMissCtx(void* ctx);
Expand All @@ -141,6 +177,30 @@ class SdCardFont {
void resetStats();
const Stats& getStats() const { return stats_; }

// Counters for INPUT_DIAG. Glyphs read one at a time through the overflow ring because no
// prewarm covered them; mini-arena rebuilds (the reads go straight into the arena, so no other
// figure records them) and their time; prewarms that bailed before any style was touched; and
// the advance-table work a section build spends its font time on.
uint32_t overflowLoads() const { return overflowLoads_; }
uint32_t miniRebuilds() const { return miniRebuilds_; }
uint32_t miniRebuildMs() const { return miniRebuildMs_; }
uint32_t prewarmEntryFails() const { return prewarmEntryFails_; }
uint32_t advanceFetchCalls() const { return advanceFetchCalls_; }
uint32_t advanceFetchMs() const { return advanceFetchMs_; }
uint32_t advanceFullSkips() const { return advanceFullSkips_; }
// readAdvanceOnly: calls, the ones that had to read the card, and the time those reads took.
uint32_t advanceOnlyCalls() const { return advanceOnlyCalls_; }
uint32_t advanceOnlySdReads() const { return advanceOnlySdReads_; }
uint32_t advanceOnlyMs() const { return advanceOnlyMs_; }
uint32_t advanceTableMax() const {
uint32_t largest = 0;
for (uint8_t i = 0; i < MAX_STYLES; i++) {
if (advanceTableSize_[i] > largest) largest = advanceTableSize_[i];
}
return largest;
}
static constexpr uint32_t advanceTableLimit() { return ADVANCE_CACHE_LIMIT; }

// Content hash of the file header + style TOC entries (computed during load).
// Used to generate deterministic font IDs for section cache invalidation.
uint32_t contentHash() const { return contentHash_; }
Expand All @@ -162,6 +222,16 @@ class SdCardFont {
};

// All per-style data: file offsets, intervals, kern/lig, prewarm cache, EpdFont
// The per-style mini bitmap arena is stored as a list of fixed-size chunks
// instead of one contiguous block: a fragmented heap routinely has the bytes
// but not one block large enough for a page's glyphs, and a failed arena sent
// every glyph through the 8-slot overflow ring. Each glyph's bitmap is placed
// wholly within one chunk (never straddling); its EpdGlyph::dataOffset is a
// virtual offset that maps to (chunk index, offset-in-chunk) via miniGlyphBitmap().
static constexpr uint32_t MINI_BM_CHUNK_SHIFT = 12; // 4 KB chunks
static constexpr uint32_t MINI_BM_CHUNK_SIZE = 1u << MINI_BM_CHUNK_SHIFT;
static constexpr uint32_t MINI_BM_MAX_CHUNKS = 24; // 96 KB ceiling per style/page

struct PerStyle {
CpFontHeader header{};

Expand Down Expand Up @@ -225,18 +295,20 @@ class SdCardFont {
EpdFontData miniData{};
EpdUnicodeInterval* miniIntervals = nullptr;
EpdGlyph* miniGlyphs = nullptr;
uint8_t* miniBitmap = nullptr;
// Chunked mini bitmap arena (see MINI_BM_CHUNK_* above). Chunks are allocated
// on demand during prewarm and kept for reuse; miniBitmapChunkCount is how many are live.
uint8_t* miniBitmapChunks[MINI_BM_MAX_CHUNKS] = {};
uint32_t miniBitmapChunkCount = 0;
uint32_t miniIntervalCount = 0;
uint32_t miniGlyphCount = 0;
uint32_t miniIntervalCapacity = 0;
uint32_t miniGlyphCapacity = 0;
// Bytes held by the live chunks (miniBitmapChunkCount * MINI_BM_CHUNK_SIZE).
uint32_t miniBitmapCapacity = 0;
// Bitmap bytes the current page actually used (set by prewarmStyle), the
// underuse-hysteresis signal; 0 = no bitmap built this scope (metadata-only
// prewarm), which leaves the hysteresis counter untouched.
uint32_t miniBitmapUsed = 0;
// Exact bitmap bytes per glyph of the last requested set, for the arena retry.
uint32_t measuredBytesPerGlyph = 0;
uint8_t miniUnderuseRuns = 0;
// True when the resident mini was built metadata-only (no bitmaps): it can
// serve metadata requests but a full render request must rebuild.
Expand Down Expand Up @@ -274,6 +346,34 @@ class SdCardFont {
uint8_t styleCount_ = 0;

char filePath_[128] = {};
// Flash copy state (see load()). Mutable: the const read paths flip it off on a read failure.
mutable bool useFlash_ = false;
size_t flashPayloadBytes_ = 0;
bool loadSelectedSource(bool isReaderFont, uint8_t scaleNum, uint8_t scaleDen);

// Glyph scale applied on the way into the resident caches (see load()).
uint8_t scaleNum_ = 1;
uint8_t scaleDen_ = 1;
bool isScaled() const { return scaleNum_ != scaleDen_; }
// Nearest for bearings, ascender/descender and advances.
int16_t scaledBearing(int16_t v) const;
uint16_t scaleAdvance(uint16_t advanceFP) const;
// The scaled glyph box: the source box's edges multiplied by the exact ratio and rounded
// outward, so the ink keeps its scaled position inside it (never clipped, never shifted).
struct ScaledBox {
int16_t left;
int16_t top;
uint16_t width;
uint16_t height;
};
ScaledBox scaledBox(const EpdGlyph& g) const;
// width/height/left/top/advanceX only; dataLength/dataOffset are the caller's.
void scaleGlyphMetrics(EpdGlyph& g) const;
static uint32_t bitmapBytes2Bit(uint32_t w, uint32_t h) { return (w * h + 3) / 4; }
// Bilinear resample of a packed 2-bit (4 levels) glyph bitmap `src` (box `g`, unscaled) into
// `dst` (box `box` = scaledBox(g)) at exactly scaleNum_/scaleDen_; dst must hold
// bitmapBytes2Bit(box.width, box.height) bytes and is fully written.
void resampleBitmap2Bit(const uint8_t* src, const EpdGlyph& g, uint8_t* dst, const ScaledBox& box) const;

// Overflow context: glyphMissHandler needs to know which style it's serving
struct OverflowContext {
Expand Down Expand Up @@ -313,6 +413,26 @@ class SdCardFont {
void mergeIntoAdvanceTable(uint8_t styleIdx, const AdvanceEntry* sortedNew, uint32_t newCount);

Stats stats_;
uint32_t overflowLoads_ = 0;
uint32_t miniRebuilds_ = 0;
uint32_t miniRebuildMs_ = 0;
uint32_t prewarmEntryFails_ = 0;
uint32_t advanceFetchCalls_ = 0;
uint32_t advanceFetchMs_ = 0;
uint32_t advanceFullSkips_ = 0;
mutable uint32_t advanceOnlyCalls_ = 0;
mutable uint32_t advanceOnlySdReads_ = 0;
mutable uint32_t advanceOnlyMs_ = 0;
// Kept open across readAdvanceOnly calls while a build measures (closed by closeMeasureFile):
// opening the file by its long name was nearly all of each 3.9 ms read.
mutable std::unique_ptr<HalFile> measureFile_;
// Widths readAdvanceOnly already read from the card during this measuring pass, direct-mapped
// by codepoint (key = codepoint | style << 22 | valid bit). A chapter repeats its characters, so
// most calls past the full advance table are for a character read moments before. 3 KB, freed
// with the file.
static constexpr uint32_t MEASURE_MEMO_SLOTS = 512;
mutable std::unique_ptr<uint32_t[]> measureMemoKeys_;
mutable std::unique_ptr<uint16_t[]> measureMemoAdvances_;
uint32_t contentHash_ = 0;
bool loaded_ = false;

Expand All @@ -331,6 +451,13 @@ class SdCardFont {
void applyGlyphMissCallback(uint8_t styleIdx);
int32_t findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const;
int fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCount, uint8_t styleMask);
// prewarmStyle's return when the chunked arena could not hold the whole set (a chunk
// allocation failed or MINI_BM_MAX_CHUNKS was reached). The style is left freed and
// arenaFitGlyphs_ holds how many of its validGlyphs_ glyphs were placed first, which
// prewarm() uses to size a smaller retry.
static constexpr int PREWARM_ARENA_TOO_LARGE = -2;
uint32_t arenaFitGlyphs_ = 0;
uint32_t arenaValidGlyphs_ = 0;
int prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint32_t cpCount, bool metadataOnly, bool loadKernLig,
bool accumulate);

Expand Down
Loading
Loading