Skip to content
 
 

Repository files navigation

CrossPoint Reader OST 改造版

XTEINK X3上で日本語EPUBファイルを読むために、CrossPoint Reader を改造しています。 命名は面倒くさくなったのでOSakanaTaro版ってことでOST版です。

2026/09/29時点の開発方針

  • XTEINK X3とXTEINK X4 Pro向けに開発
    • Xteink X3はaliexpressで買ったUSB接続ができないバージョンを使用
    • Xteink X4 Proは Xteink公式で購入したバージョンを使用
  • Claude Codeで開発する
    • コンパイルなどはUbuntu 24.04仮想マシン上で実施
  • 本家をベースにする
    • X3の最近のバージョンは液晶パネルが違う件への対応などが本家には入っているため
    • 2026/08/09から1.5.0ベースで開発開始。本家にXteink x4 pro対応が入ったあたりで乖離が始まった
    • 2026/08/20から1.6.0rcをベースにして再構成開始
    • 2026/09/06から1.6.0リリース版をベースに再構成
    • 2026/09/28から1.6.5リリース版をベースに再構成(ブランチ vertical-1.6.5)
  • ブランチの使い分け(2026/09/30〜)
    • 既定のブランチは vertical-1.6.5review。CodeRabbit のレビューを通った状態で、先行版・リリースはここから作る
    • 日々の細かい修正は vertical-1.6.5 で行う
    • 1日1回程度、vertical-1.6.5 から vertical-1.6.5review への PR を作って CodeRabbit のレビューを受け、指摘を直してから反映する
  • 日本語訳は付けない
  • フォントはSDカード上に配置する
    • 容量食うのでSDカード上に置く
  • ページめくりボタンは左が進むで、右が戻る
    • 縦書きフォーマット(RTL)のEPUBを開いた時だけその動作をする
  • 青空文庫ダウンロードなどの機能は付けない
  • 時計表示機能を追加
    • 磁石で貼り付けることができるので作業時の時計表示に使えるな、と実装
    • [Settings]-[System]-[Clock]の[Timezone]を「Tokyo / Seoul」とすることで日本時間表示に(1.6.5 から。1.6.0 ベースの版では[Settings]-[Reader]-[Customize Status Bar]の[Clock UTC Offset]を「UTC+9:00」)
  • テスト用EPUBファイルにて動作を確認
    • 一部、特殊なフォントを必要とする文字以外について表示できることが期待される
  • ルビ表示について対応
    • 横書きであればルビに対応してるなら縦書きでも対応しておくかな、って
  • 電書連 EPUB 3 制作ガイド ver.1.1.4に準拠したEPUB表示についての対策
    • CSSが巨大すぎてメモリクラッシュを起こす件についての対応をやる必要があるのかという問題
    • [Setings]-[Reader]-[Text Settings]にある[Style]にて「Embedded Style」を「OFF」にするとCSSを使わないため回避できる
    • こんな小さな端末で著者がやりたいという複雑な表現をやって期待通りの表示になるのか?と思いつつ対応
  • 本家との付き合い方
    • UIの大きな変更は個別に追随しない。本家のタグが更新された時点でforkしなおし再実装する
    • 基本的に縦書き・挿絵・フォントに関わる本家修正だけ取り込む
  • フォントの明朝/ゴシック混植は実施しない
    • 1冊のなかでフォント指定を途中から変える機能について
    • 商業EPUBの91%が指定を持つが1冊あたり中央値40箇所で、2書体常駐のメモリ代償に見合わない
  • 組版やレイアウトについての出典として下記を採用している
  • 開発に際して下記URLをClaude codeに読み込ませている(実装内容まとめで使う略語)

各フォークが本家のどのリリースまで取り込んでいるか(2026/09/22時点、GitHubの履歴を照合)。

版 取り込み済みの最新 本家との世代差 使用しているfreeink-sdk
本家 1.6.5rc(2026/09/14 先行版。developの先端はさらに31件先、うち #2332 画像まわりの断片化対策、#3600 一覧の行を必要な分だけ作る、#3618 組み込むUI言語を選べる、#3528 語間・字間の設定、#3608 転送後に Library の索引を作り直す) ― ffcbb1c
OST版 新ツリー feat/vertical-1.6.0based 1.6.0(2026/09/05) 1.6.5rc が未取り込み(1.6.0から31件、develop先端までさらに31件)。#2925(段階表示JPEGの分離走査)と #3581(合字表の宙づり参照)は 09/17 に個別に取り込み済み。残りで当版に効きそうなのは断片化対処の連作 #3518/#3521/#3527(章レイアウト前のフォントキャッシュ解放)・#3398(フォント目録を1領域に)・#2332(画像まわり)と、#3439(X3のアンチエイリアス安定化)。いずれも 1.6.5 起点の作り直しで本家のまま採る fde240f
抹茶版 1.6.0(2026/09/20、安定版。本家 develop 09/20 時点 8c84ef32 を含む) 1.5.0 以来の安定版。X4 Pro・X4C・Sticky 対応、辞書引き(長押し・ボタン選択・縦書き対応・ページ跨ぎの語)、漫画の吹き出しの語の引き当て(変換のやり直しが必要)。独自分は辞書引き(漫画の吹き出しの語の引き当て、触れる操作)が中心。#282 は日本語の添え書体の区間表を Regular/Bold で共有して読み込みの山を半分にし、読み込み前に字形の保存を捨てる(20pt NotoSansJP で 26.6KB×2 が入らなかった件。当版の書体は区間が密で1書体3KBのため無縁) 4b17a7bb
CJK版 本家タグを1つも含まない(0.4.2、2026/09/05) 本家と共通の祖先を持たない再構成履歴。以後の同期なし 自前fork 62976c5
JP版 1.2.0(2026/04/03) 1.3.0 以降すべて未取り込み。独自開発は活発(v0.3.2=2026/09/19: #149 眠り画面の動的壁紙、#150 ホームの読書進捗率と Vega テーマ。09/08以降: 禁則処理の行分割 #144、字形の鮮明さ設定 #145、UI書体のディセンダ #146、整形済みブロックの行間 #147、フォルダを開くたびの全走査をやめる #148/#136、横向きUI #138) e38baecc
Yomuka版 1.2.0(2026/04/03) JP版からの派生のため同じ。独自開発は活発(yomuka-v0.7.6.0=2026/09/17: SD書体の作業領域をページ間で使い回す(本家の「収まれば保持」方式の移植で当版は既に同等)、ルビ・上付きなどの小さい字に 10pt 専用の書体データを使う、縦中横の桁数を2/3桁で選べる、曲線引用符 ‘’“” を縦の升に正立で置く(3f631371/BIZUD向け補正 9dcfc3dc)、全冊の事前生成中のSD書き込み失敗を安全に止める) 30011399
crossmux版 1.6.0(2026/09/05) 本家 1.6.0 起点(1.6.5rc は未取り込み)で独自 618 件。中国語(簡体字)対応の community fork。Apps(ゲーム・道具)、微信読書、AirPage、読書統計、Bluetooth ページめくり、33 言語を1 firmware に、C3 の読書メモリの上限管理。SD 書体は本家と同じ cpfont v4。縦書き・禁則なし(2026/09/22 に確認対象へ追加) 自前fork 094976e

各版の最新リリース(先行版を含む。安定版だけを見るとJP版・Yomuka版の日付つき開発版や、 Inx版・witchhunt版・抹茶版の先行版を取り落とす):

版 最新 日付
本家 1.6.5rc(先行版) 2026/09/14
Yomuka版 yomuka-v0.7.6.0 2026/09/17
JP版 v0.3.2 2026/09/19
抹茶版 1.6.0 2026/09/20
Papyrix版 v1.31.0 2026/09/18
Inx版 1.0.20-BETA(先行版) 2026/09/11
witchhunt版 2.31(2.31.0-rc.1 も同日) 2026/09/20
OST版 20260929(先行版、1.6.5 ベース) 2026/09/29
crossmux版 1.6.0(stable/nightly タグは動く先行版) 2026/09/20
CJK版 0.4.2 2026/09/05

CJK版だけは freeink-sdk 自体も自前でforkしていて、SSD1677パネル向けの修正を独自に入れている。 他は本家のfreeink-sdkをそのまま参照している。

最近の更新内容について(2026/09/30)

  • 本家 1.6.5(2026/09/27 リリース)を起点に作り直しを開始(2026/09/28〜、ブランチ vertical-1.6.5)。新しい系統の最初の pre-release として OST版 2026093001-diag(タグ 20260929、診断版、X3 と X4 Pro の2本)を公開
    • Xteink X4 Pro 向けの版も作るようにした(2026/09/29〜)。X4 Pro は PSRAM があり読書中の空きが 130KB 以上、18pt の縦書きは 1 ページ 8 列(X3 は 9 列)
      • X4 Pro には Back ボタンが無いため、時計画面をホームキーで終了できるようにした(タップ・長押しとも。ダブルタップの前面ライト切り替えは時計画面のまま使える)
    • 挿絵が白いページになる件を本家に PR #3802 として提出(章の組み立て中に挿絵の大きさを読む処理へ画面用の記憶を貸す)。本家 1.6.5 で表紙・章の扉が枠もなく白くなることを実機で再現し、修正版で表示されることを確認。本文中の細い挿絵の件は本家 PR #3675 に実測をコメント
    • その PR のレビューで見つかった件を当版にも反映: 待ち時間の組み立てで画面用の記憶を貸したらページを描き直し、描き直すまではツールバーを今の画面に直接重ねない(重ね表示中は待ち時間の組み立てを止める)
    • 本家・他フォークから小さな修正を取り込み(2026093004-diag、X3・X4 Pro で実機確認済み)
      • 本家から: ボタン操作ごとの記憶の確保をやめる(#3698)、脚注の一覧で電源ボタンでも飛べる(#3766)、タッチ機で見出しの戻るボタンがキーボード画面でも効く、読み込み用ファイルの確保失敗で落ちない(#3419)、保存済みの文字列の長さを上限で打ち切る(#3452)
      • 抹茶版・Yomuka版から: 空きが少ないときにツールバーを開いても落ちない、本の終わりの位置で目次・進み具合を引いても落ちない
      • 自動ページ送りの間は自動スリープさせない
    • 当版の変更を本家 1.6.5 からの段階ごとに区切って fork 内の PR にし、CodeRabbit のレビューを受け始めた(土台の枝 vertical-1.6.5review、1本目は診断記録とビルド番号)。指摘2件(診断版の番号の見分け、待ち時間の組み立て失敗の記録)を修正
      • 残り全部(段階2〜9 と X4 Pro 対応)を1本にまとめてレビューを受け、指摘9件を修正(2026093006-diag、X3・X4 Pro で実機確認済み)。縦書きの本で区切り線(hr)と表の行の線がページ上端を横切る横線になっていた件(区切り線は空いた列1列に、表の行の線は出さない。章の版 56)、Font Copy in Flash が Off のとき書体・大きさの変更で書体ファイルを2回読んでいた件、保存済みの文字列が途中で切れていても読めた扱いだった件、本体の書き換えに失敗したとき書体の複製を読み続けるおそれ、など
      • その修正分もレビューを受け、書き換えの消去を書体の複製を読んでいる途中の処理が終わるまで待つようにした(2026093007-diag、実機確認済み)
      • 以後は既定のブランチを vertical-1.6.5review とし、日々の修正を積む vertical-1.6.5 から1日1回程度 PR で CodeRabbit のレビューを受けて反映する(段階ごとのレビュー用ブランチは削除)
    • 段階0(素の 1.6.5 のビルド)、段階1(診断)、段階2(時計と監視)、段階3(字形の計測と素の実測)まで完了。詳細は下の「1.6.5ベースへの作り直し」節
    • 段階9(実測②と補い)を実施中。漢字の多いページの 1 字ずつの読み直し(最大 16.5 秒)を字形の置き場の 4KB 分割で解消し、本文ページ内の JPEG 挿絵が出ない件を組み立て中の先回り展開で直した。組み立て中の空きの底を 2KB→17KB に引き上げ(章で使う CSS の規則だけ読む・設定一覧を常駐させない)、組み立て中にメニューを開くと落ちる件も直した
    • 段階8bまで完了。Library(書棚)の並び・見出し・検索を、本の読み(file-as)に基づく五十音順にした(本家に出している PR #3651 を先に取り込み)
    • 段階8aまで完了。書体を内蔵 flash に複製して読めるようにした(Text Settings → Style「Font Copy in Flash」、18pt は 16pt を 9/8 倍)。商業書籍で挿絵と表紙が白いページになる件も直した
    • 段階8まで完了。表紙が SVG の包みになっている本でも、本棚と眠り画面に表紙が出るようにした
    • 段階7まで完了。縦書きの本の挿絵と外字を表示できるようにした(挿絵は列を割り当てて置き、1字大の外字は本文の列に入れる)
    • 段階6まで完了。本家の Character spacing(字間)が縦書きでも効くようにした
    • 段階5(縦書きの中核)まで完了。縦書き検証テスト集の26試験を実機で確認。縦書きでも脚注の選択(Footnotes)が使えるようにした
    • 段階4(横書きの組版)まで完了。禁則を JLREQ の各類に広げ(本家 PR #3609 の形)、長い段落の途中に字下げや空きが入る件、章内の飛び先が1ページ手前を指す件を直した
    • 段階9から前倒しで、字の幅だけを読む経路を持ち込んだ。離れた章への移動(185ページの章)で組み立て 20.7 秒 → 12.6 秒、体感 21 秒 → 14 秒(2026092815-diag)
    • この時点のブランチには日本語フォントの扱い・縦書き・挿絵などはまだ入っていない(以下の各項目は 1.6.0 ベースの feat/vertical-1.6.0based の内容)
  • 章の境目の待ちを減らすため、読書中の待機時間に次の章を先に組み立てる(2026/09/09〜)
    • 商業書籍で章をまたぐたびに7秒前後待つのを、ページを表示して1.5秒以上操作が無い間に少しずつ済ませる
    • ボタンの読み取りを止めないよう1回の作業を25ms以内に区切り、字形の一時記憶を汚さないよう配置計算の前後で空にする
  • 縦書きの本文中の1文字大の画像(外字)は字として列に流す(2026/09/10〜)
    • 幅が列の間隔以内・高さが列の半分以内の画像は前後の字と同じ列の同じ位置に入り、ルビも付く。それより大きいものは従来どおり独立した列
  • 描画中のボタン操作は別の系で読み取って保持し、階調の描き分けは押されたら打ち切る(2026/09/10〜)
    • 効かない時間を「反応が遅れる時間」に変える。待ち表示(Indexing)は 1〜3 秒の章の組み立てでも出す
  • 章内リンクの飛び先は、その要素の行が実際に載ったページを指す(2026/09/10〜)
    • 段落の1行目が次ページへ送られる場合に1つ手前を指していた。章の保存の版を50に上げたので初回オープンで全書籍が作り直しになる
  • 語数の上限で分割された長い段落の続きに、一字下げを再び入れない(2026/09/10〜)
  • 配置に使うメモリが足りないときは落とさず、その章の組み立てを失敗させて作り直しに回す(2026/09/10〜)
  • 字形のその場読みの置き場を8→48枠に広げる(2026/09/10〜)
    • 階調は短冊ごとに同じページを描き直すため、8枠では二十数字が毎回押し出し合い、描き直すたびにSDから読み直していた。遅いページの表示が15.3秒→4.9秒
  • 字形の先読みの取り分を、一時記憶を保持する床と同じ値にする(2026/09/10〜)
    • 別々の数字だったため、空きを使い切って作った一時記憶をその場で捨てていた。字の大きいフォントに変えると露呈し、1ページ7.8秒+中断(abort)になっていた
  • 1行・1列を作る前に、必要な大きさの領域を試しに取って確かめる(2026/09/10〜)
    • 取れなければそのページを諦めて報告する。従来は確保に失敗すると firmware ごと停止していた
  • 縦書きの列送りを、フォント内部の行送りではなく全角の幅×1.5から求める(2026/09/10〜、章の版51)
    • 書体を変えると同じ設定でも1ページの文字数が変わっていた(BIZ 11列/Noto 7列)。全角を実測して揃えた
  • 挿絵のログは短冊の判定より後に出す/小さい外字もRAMに載せる(2026/09/11〜)
    • 前者は1ページ12行のログで診断の記録を潰していた。後者は297バイトの外字が24KBの門番に断られていた
  • 本家 develop を併合(2026/09/11〜、章の版53)
    • SDK の ed39106(X3の余計な電源投入を止める)でページ送りが 3,773ms → 1,601ms。#3439 でX3の階調を安定化
  • 縦書きの一番右の列のルビが画面端で切れないようにする(2026/09/11〜)
    • ルビは列の右隣に描かれるため右端の列だけ領域外に出ていた。画面余白で足りない分だけ確保する
  • 挿絵の初回展開の内訳を測り、SdFat の SPI をまとめ転送にする(2026/09/11〜)
    • 17.7秒のうち復号自体は16%で、残りは保管層。SdFat が ESP32 で1バイトずつ転送していた。保管層が2〜3倍になり挿絵1枚16秒→8.1秒
  • 本家から2件を取り込み(2026/09/17〜、2026091703-diag、実機確認済み)
    • #2925 輝度と色を別々の走査に分けた段階表示JPEGが真っ白になる修正(JPEGDEC への3つ目の補正)。image-format-test.epub の A3/A4 で退行なし
    • #3581 合字表を解放したあとも stubData/miniData が解放済みの表を指したままになる修正。当版は一覧画面に入るたび解放するので本家より踏みやすかった。日本語フォントは合字ゼロで無縁、欧文の本文フォント(Literata・Alegreya 等)で該当。Literata+horizontal-regression-test.epub で章一覧⇄読書を繰り返し、落ちないことを確認
  • 一覧画面に入るときの字形の解放を本家へ出すことを検討し、実測のうえ見送り(2026/09/17)
  • 横書きの日本語で長い段落を開くと落ちる件(2026/09/17〜、2026091705-diag、実機確認済み)
    • 横書きは空白の無い段落を1語として受け、字境を探す前処理が全文字ぶんの8バイトの記録を一塊で確保していた(本家 #2288 由来)。2,559 バイトの段落で 20.5KB の連続領域を章の組み立て中に要求し、最大連続 5KB で abort。1回の走査に直して 3.4KB にし、それも置けなければ分割を諦めて落ちない経路へ回す。縦書きは別経路で影響なし。配置の結果は同じなので章の版は据え置き
    • 併せて、本棚に入るときも一覧画面と同じく字形の保持を無条件に手放す。18pt の日本語書体では1ページぶんの保持が約35KB あり、読書から戻った本棚が 空き20KB/最大連続7KB になっていた(→ 57KB/45KB)
    • 本家 develop 3555ff55 の素のビルドに探針を入れて X3 で測ると、本を読んでいる最中でも章一覧に入る時点で最大連続 47KB が残り、章一覧の初回描画は解放の有無にかかわらず 641ms、戻った直後のページも 1.37 秒で差なし。当版で観測した 78 秒は 1.6.0 起点のツリーだけの症状で、本家は #3521/#3527 で既に解消済み。1.6.5 起点の作り直しではこの解放も最初は載せない
  • 配布: 本節までの内容を pre-release 20260917(2026091707、非DIAG、3a692cd9) として公開した(2026/09/17)。添付は crosspoint-OST-2026091707.bin のみ。フォントは変更が無いので 20260909 の fonts-202609080.zip をそのまま使う。ページ配置の保存形式 48→54・本の情報の保存形式 11→12 のため、初回オープンで全書籍の作り直しが走る
  • 商業書籍の挿絵が枠だけになる/本棚の縮小表紙が出ない(2026/09/18〜、2026091714-diag、実機確認済み)
    • 3段とも「大きい一塊をヒープに求めていた」のが原因だった。挿絵: 取り出しが 8KB の連続領域を2つ要り、最大連続 5,364 の章の組み立てで失敗(→ 512B まで半分ずつ下げて確保)。表紙の取り出し: deflate の窓 32,768 を一塊で要り、読書後の本棚は最大連続 20,468 で頭打ち(→ 画面用の記憶 48,000 を借りる)。表紙の復号: MCU 用の 23,040 を、直前に確保した復号器本体が割った穴から取ろうとして失敗(→ 同じく借りる)
    • 併せて、表紙作りの失敗を recent.json に恒久記録するのをやめた(本当に表紙が無い本だけ記録する)。従来は一度失敗すると再起動しても二度と試さなかった
    • 失敗点ごとの記録を足し、SDの image-diag.txt だけで場所が分かるようにした(jpg OOM mcu 23040 の1行で原因が確定した)
  • 配布: 本節までの内容を pre-release 20260918(2026091801、非DIAG、4ed08c40) として公開した(2026/09/18)。前日の 20260917 の差し替えで、挿絵と縮小表紙の3件だけが差分。添付は crosspoint-OST-2026091801.bin のみ、フォントは 20260909 の fonts-202609080.zip をそのまま使う
  • 配布版 2026091801 で15分読んだ末に章の境目で abort(2026/09/18〜、2026091803〜1807-diag、実機確認済み)
    • 番地を引くと addVerticalToken の std::deque::push_back。「配置の前に領域を試す関門」は配置時にしか無く、押し込み時に無かった。hasHeapForToken(空き4KB/最大連続2KB)を入口に置き、割ったら組み立てを失敗扱いに
    • 章の組み立てが極端に苦しい原因も1つ潰した。送り幅の表(768語)が満杯になると、表に無い字の幅を測るためだけに字形本体を予備リング(48枠≈17KB)へ読んでいた。readAdvanceOnly(常駐→予備リング→SDの12バイト)で幅だけ返し、MeasureOnlyScope で組み立て中に限る(描画中に使うと二度読みになり page_draw が 76→384ms に増えたので)。同じ本・同じ章で 組み立て中の最大連続 2,164→13,300、余裕の最低 7,432→20,436
    • 保存済みの章を開くと30KB台しか空かず1ページ分の字形が入らない(prewarm_entry_fails、その場読み888回、6秒/ページ)のは18ptの限界で以前からの挙動。別件
  • 横書きの禁則を JLREQ 3.1.7/3.1.8 の各類に広げる(2026/09/18〜、2026091807-diag、実機確認済み・章の版55)
    • 横書きは本家 #2288(韓国語対応)由来の集合のままで括弧と句読点しか知らなかった。JLREQ 原文で確認: 3.1.7 の本則が cl-02〜cl-11・cl-13 を挙げ、小書きの仮名(cl-11)も本則に含まれる(Note の「行頭禁則としない方法」は採らない)。文字は付属書 A のとおりで、〒 は cl-12 に無いので入れない。……――は同じ字が続く間は切らない
    • 横書きは「切ってよい組か」で判定して禁じられた組を1語に貼り合わせる方式なので、JP版 #144 のような追い出しの繰り返しは要らない(推移的に『「漢」』。は割れない)。縦書きの列末の戻しだけ1回→上限4回に(『「 の入れ子で「が列末に残っていた)
    • 出典は JIS X 4051 ではなく JLREQ(W3C、日英併記で公開)に統一。本家に PR #3609 として提出(2026/09/18、ブランチ fix/cjk-line-break-prohibitions、CjkLineBreak.h に切り出し+gtest 9件、本家 develop dc9c3eab 版でも実機確認済み。審査待ち)
    • 隣り合う組がすべて禁則になる人工的な試験文字列は1語が1行より長くなり「長い語を割る」予備の経路に落ちるので、現実的な語に差し替えた。<code> の境目で語が貼り合わされ「見ているも」が広がるのは本家由来の別件
  • 配布: 本節までの内容を pre-release 20260918-2(2026091808、非DIAG、15bdbaab) として公開した(2026/09/18)。同日の 2026091801 の差し替え。添付は crosspoint-OST-2026091808.bin のみ
  • 配布(テクニカルプレビュー): 下の flash 複製の仕組みを pre-release 20260923-tp(2026092204-diag、診断版のまま、0aaf2ca7) として公開した(2026/09/23)。これまでの pre-release とは別系統で、普段使いは 20260918-2 のまま。説明文に「microSD のルートに動作記録(input-diag.txt など 5 種)が書き出される」旨と、複製の所要 2 分・firmware 書き換えで消える・電池 20% 未満は複製しない・戻し用の旧 firmware が失われる、を明記
  • SD 書体を内蔵 flash に複製して読む(crossmux版 A の実装)(2026/09/22〜23、2026092204-diag、実機確認済み: 初回起動の自動複製 6,549,504B が約 2 分で完了し、以降 font=NotoSansJP_16.cpfont … src=flash flash_kb=6396 scale=18/16。Home の空きヒープ 56.6KB/最大連続 51KB、本を開く各段階のヒープは複製 On/Off で同じ(sect 45KB、page1 33〜34KB)で、読み経路の常駐メモリは増えていない。確認中に商業本の第 44 章の作り直しでヒープが尽きて落ちた 1 件は Off でも同じ段階で同じ値になる既知の型で、本件とは無関係と判断)
    • 仕組み: 使っていない側の firmware 領域(app0/app1 の空いている方、先頭 4KB のヘッダを除いて 6,549,504B)に選択中の書体ファイルの先頭部分を複製し、字形の読みを SD ではなく内蔵 flash から行う(SdCardFontCache、HalOtaSlot、SdCardFont 内の読み取り窓口 FontFile)。ファイル全体が入るなら全体、入らなければ入る分だけを複製し、Regular(style 0)の実体が丸ごと入ることを条件にする。当版の cpfont は Regular+Bold の 2 style で Regular が前半にあるので、複製の範囲外=Bold の字形だけが SD 読みになる。範囲外の読みは窓口が自動で SD に切り替える(SD のファイルは必要になるまで開かない)
    • 18pt: NotoSansJP_18 は Regular だけで 7,621,409B あり入らないので、16pt の実体を複製し 9/8 倍に拡大して 18pt を出す(前項の検証機構を本番化。倍率は「選んだ大きさ/実体の大きさ」で上限 9/8、配布の大きさでは 18→16 の組だけが該当)。BIZUDGothic_18/BIZUDMincho_18 は Regular が 4.0〜4.2MB で本物が入る。配布 zip(fonts-202609080)の実測: NotoSansJP_16 の Regular 終端 6,160,269B(入る)、NotoSansJP_14 5,035,418B、BIZUDGothic_16 は全体 6,527,441B が入る
    • 操作: Text Settings → Style に「Font Copy in Flash」を追加。On にすると複製を作る(進捗画面。SD→flash の複製と、flash からの読み戻し照合の 2 段で 6MB なら 1 分前後)。On のまま書体や大きさを変えると、変えた直後に複製を作り直す。失敗(入らない・書けない・照合不一致)は設定を Off に戻して理由を表示し SD 読みのまま。firmware を書き換えると複製は消える(書き換え先が同じ領域)ので、次の起動で設定が On なら自動で作り直してから Home に行く
    • 電池: 複製は SD の読みと flash の書き込みを 1 分ほど続けるので、電池 20% 未満(USB 給電中は除く)では複製を始めず設定を Off に戻す(起動時の自動作り直しは Home に行く、設定画面では理由を表示)。途中で電池が切れてもヘッダが書けていないので複製なし扱いになるが、次の起動でまた同じ複製を始めてしまうのを避けるため(2026/09/22 ユーザー指摘)
    • 診断: font= 行に src=flash|sd flash_kb=… scale=n/d を追加。font_copy= 行に起動時の判定(候補・倍率・容量・有効か・現在の読み元)と複製の結果(結果・大きさ・時間)を 3 件まで残し、複製の直後は強制で書き出す。18pt を選んで複製 On なら font=NotoSansJP_16.cpfont … src=flash flash_kb=6396 scale=18/16、Off なら font=NotoSansJP_18.cpfont … src=sd scale=1/1
    • 安全側: 先頭 sector の消去→本体の複製(CRC-32 を取りながら)→flash からの読み戻し照合→ヘッダ(OSTSDFC1、経路・大きさ・ヘッダと TOC の FNV-1a・CRC)の書き込みの順。途中で電源が落ちてもヘッダ不正=複製なしとして SD から読む。読み込み時は経路・ファイル長・ヘッダと TOC の hash を照合するだけで本体の CRC は取り直さない(crossmux版と同じ)。書き換え直後で確定していない firmware では書かない(当版は Arduino の初期化が起動時に確定させるので実際は常に可)。firmware の書き換え先が空き側なので、書き換え前の firmware への戻しは失われる(当版の復旧手順は update.bin 前提で支障なし)
    • 出典: crossmux版 PR #57(perf: cache SD fonts in inactive OTA slot)。当版は「先頭部分だけの複製」と「16pt の拡大」の組み合わせが違うので magic を別にした。前項の検証機構(OST_SCALE_TEST、内部値 118)は削除し、拡大の仕組みだけを残した
  • 章ごとの CSS の読み直しが空の一覧で走り、章から CSS が全部抜ける件(2026/09/25、2026092503-diag、実機確認済み・章の版 56)
    • KADOKAWA テンプレートのような大きな CSS は保存が「一部だけ」になり、章を組むたびにその章で使う規則だけを読み直す。本を開いた経路によっては CSS ファイルの一覧が空のままで、読み直しは何も読まずに規則ゼロで章を組んでいた。リアデイルの大地にて 8 巻で、1em 指定の外字画像(迸)が元の 128px のまま 1 列を占めた
    • 一覧が空なら ZIP から CSS を探して補う。CSS なしで組まれた章を作り直すため章の版 55→56
  • 16pt→18pt の拡大で、字形の箱と画素の引き伸ばしを正確に 9/8 倍に(2026/09/25、2026092501-diag、実機確認済み)
    • 寸法の切り上げと原点の四捨五入を別々に行い箱いっぱいに引き伸ばしていたため、実際の倍率が 1.13〜1.17 倍にばらつき、縦書きの 「」 が本物の 18pt より列の中心寄りに約 1px ずれ、墨も 3〜23% 多かった(線がやや太い)。元の墨の端を 9/8 倍して外側へ丸めた箱に、画素中心を正確な比で戻して補間する。墨の位置は理想から 0.1px 前後、墨の量は本物の 18pt とほぼ同じ
  • 読書の後の Home で表紙の縮小画像が作れない件(2026/09/25、2026092505-diag、実機確認済み)
    • Home のヒープを全部書き出して起動直後と比べると、読書は大きな物を何も残していない。本を開くたびに作り直される「最近の本」の文字列や、最初に使われた瞬間に作られる排他部品などの小物が、起動時の 60KB 前後の一続きの空きに散り、最大の塊が 17〜35KB に細っていた
    • 縮小画像づくりは画面用の記憶を借りて作業用の行だけをそこから取っていたが、JPEG の解読器(17,884B の一塊)は一般の空きから確保していて先に失敗していた。借りた領域の先頭に解読器を置き、残りを作業用の行に回す(1440 幅で計 40.9KB、借りられる約 48KB に収まる)
  • 診断の追加(2026/09/24〜25、診断版のみ): 空きメモリの最小値が下がった区間の記録(heap_min_log=、ページ描画の検査点 pg:load〜pg:msb)、Home に入った時点のヒープの全塊と作業単位の一覧(heap-map-boot.txt=起動後最初、heap-map.txt=以後)。最小値の底はページ描画中(白黒の描画中や階調の 1 面目)で 3KB 前後まで下がることが分かった(対策は上の 2026/09/26 の項)
  • ページ描画中の空きメモリの底を上げる(2026/09/26、2026092604-diag、実機確認済み)
    • 底は字形の予備置き場(その場読みの環)が空きを見ずに 48 枠まで育つところだった。空きが 16KB を割ったら 12 枠以上は増やさず古い枠を上書きして使い回す。描画中の最小の空きが 2.7KB 前後 → 6〜6.8KB
    • 挿絵の事前変換も、最大連続だけでなく空きの総量が足りないときに画面用の記憶を借りる
  • Home の空きが読書のあとで細切れになる件の根本対策(2026/09/26、2026092605-diag、実機確認済み)
    • 前項の診断でヒープの全塊に確保元の印を付けて調べると、読書中に初めて作られてそのまま残る小物が起動時の一続きの空きに散っていた。小数の書式化の作業領域(作業単位ごと)、共有ポインタ用の排他部品、時刻取得の鍵、C++ 例外の管理情報、縦書きの全角幅の記憶、「最近の本」の一覧の伸長など
    • これらを起動時と描画の作業単位の開始時に先に作らせる、固定の大きさにする、同じ内容なら書き換えない、のいずれかで読書中に新しく作らないようにした。読書のあとの Home の最大連続が 17〜35KB → 65.5KB(起動直後 69.6KB)
    • 診断版には確保元の印(1 塊 12 バイト増)と、排他部品・newlib の鍵の作成元の記録を追加(通常版には入らない)
  • 縦書きで入れ物(div)の上下の余白を、中の段落すべての列に効かせる(2026/09/26、2026092607-diag、実機確認済み・章の版 58)
    • 縦書きでは上の余白が列の頭、下の余白が列の足の空きになる。入れ物の上の余白を最初の段落にだけ渡していたため、商業書籍の場面の区切り(五文字下げた入れ物の中に「・」だけの段落が三つ)で、二つ目と三つ目の「・」が列の頭に上がっていた(うちうち『逃げる魔法使い』で発見)
    • 縦書きのときは段落が入れ物の上下の余白・詰め物(padding)を受け継ぐようにした。最初の段落に預けていた分とは大きい方を取り、二重に下げない(詰め物の入れ物で最初の段落だけ 4 文字下がる形で一度踏んだ)
    • vertical-test-suite.epub のテスト25(場面の区切り、長い段落、入れ子、段落自身の余白、列の足側、詰め物、入れ物の外)で 7 項目とも合格
  • Home 画面の本体を、前の画面を消した後で作る(2026/09/26、2026092608-diag、実機確認済み)
    • 前項の対策後も、読書のあとの Home で最大連続が 45〜51KB に割れることがあった。確保元を引くと Home 画面の本体(約 200 バイト)で、画面の切り替えは次の画面を作ってから前の画面を消すため、読書画面の物が小さな穴を埋めているうちに大きな空きの途中へ置かれていた
    • Home へ戻るときは作成要求だけを預かり、読書画面を消してから作る。読書のあとの Home のヒープが起動直後と一致するようになった(空き 72.9KB/最大連続 65.5KB)
  • 挿絵の事前変換の取り出しで、読み書きの区画も画面用の記憶を借りた残りに置く(2026/09/26、2026092609-diag、実機確認済み)
    • 伸長の状態と窓(約41KB)は画面用の記憶から借りていたが、読み込み用・書き出し用の 8KB 区画2つはヒープから取っており、章の組み立て中の空きを 22.7KB → 4.8KB まで下げていた
    • 借りた残り(約6.8KB)に区画を約3KB ずつ置くようにし、取り出しの前後の空きの減りが約18KB → 0.3KB、取り出し時間も 2.2〜2.3 秒 → 1.7 秒
  • 設定項目の一覧を常駐させず、使うときだけ作る(2026/09/26、2026092613-diag)
    • 本家は一覧を一度だけ作って起動中ずっと持っていたため、約13.9KB のヒープが読書中も埋まっていた(空き 5〜6KB で描画している場面がある)
    • 一覧が要るのは設定の読み書き・設定画面・Web の設定ページだけなので、そのたびに作り直す(数 ms)
  • 診断の追加(2026/09/26、診断版のみ): 空きが 16KB を割ったとき確保元ごとの持ち分を heap-low.txt(章の組み立て中は heap-low-build.txt)に書き出す。確保元の印からは operator new・malloc の共通部分を除き、呼び出し元が見えるようにした
  • 試験用 EPUB の整理(2026/09/26): test/epubs-ja の画像の試験4冊(表紙SVG・縦書きの中の画像・画素数・形式と書き方)を image-test.epub の1冊に統合し、vertical-test-suite.epub をテスト25まで入った版に差し替えた
  • 空きの塊が細った読書中に、挿絵が枠だけになる件(2026/09/23、2026092302-diag、実機確認済み)
    • JPEG の解読器(JPEGDEC)は X3 で 17,884B の一塊が要る。読書を続けて最大連続が 17,396B まで細ると、章の組み立て中の挿絵の事前変換が確保の段階で全部失敗し、表示時の再試行も書体の解放で塊が育たず代わりの枠のままだった(2 回とも 17,396B で失敗、19,444B 以上は成功)
    • 事前変換は画面を描かないので、塊が足りないときだけ画面用の記憶を借りて解読器を置く(PNG の逐次展開・ZIP の展開と同じ仕組み)。実機で最大連続 15,348B のまま 2 枚とも約 3 秒で変換でき、image-diag.txt に jpeg decoder in lent framebuffer が出る。表示時のその場の変換は画面を描いている最中なので従来どおり
  • 診断ファイル input-diag.txt の書き出しを分割(2026/09/23、2026092301-diag、実機確認済み)
    • 2,304B の枠 1 つに全体を組んでいたため、font_copy= の追加後は末尾の説明が毎回切れ、描画の記録が 12 件たまると枠を超えて書き出し自体を省いていた(長く読んだ後ほど古い記録が残る)。前半・後半を同じ枠で順に書き、説明文は flash 上の定数から直接書く。font_copy= の行も途中で切れていたので枠を広げた(常駐 +144B)
  • 拡縮の検証機構(2026/09/22、2026092201-diag、実機確認済み。-DOST_SCALE_TEST=1 のときだけ組み込まれ、通常版には入らない)
    • 目的: crossmux版由来の「SD 書体を空き側の firmware 領域(実効 6.5MB)に複製して flash から読む」案(A)に向け、18pt の実体(NotoSansJP_18 は 15.6MB で入らない)の代わりに 16pt の実体を 9/8 倍に拡大して 18pt を出す画質を実機で確かめる
    • 作り: Text Settings → Size に「18 pt (16 x9/8)」(内部値 118)。SdCardFont::load に倍率を持たせ、TOC の行送り・上端・下端を 9/8 倍、字形が置き場(mini 領域・予備の環)に入る瞬間に字形記録(幅・高さは切り上げ、寄せ・送り幅は四捨五入)と 2 階調 bitmap(双一次で拡大して 4 段階に丸め直す、整数演算)を拡大。送り幅表もその場読みも倍率適用。書体の識別子と章の保存は 118 として本物の 16/18 と別扱い
    • 実機: font=NotoSansJP_16.cpfont advY=54(本物の 18pt と同じ行送り)で起動、縦書き 146 升 174ms、先読み 225ms、その場読み 18 字、組版・禁則・約物・ルビに異常なし。画質は写真では本物と区別がつかず、最終判断はユーザーの目視待ち
    • 前段の検討: 10pt→18pt(1.81 倍)や 12pt→18pt(1.52 倍)は 2 階調化で画の太さが不揃いになり本文には不可、16pt→18pt(1.12 倍)だけが許容範囲(e-ink/pr-photos/font-scale-compare.png)。FreeType(本家 PR #3646 の方式)は cmap+loca を丸ごと RAM に置くため X3 では不可(BIZUDGothic 258KB、NotoSansJP 203KB)
  • 確認対象のフォークに crossmux版(0x1abin/crossmux、中国語対応の community fork)を追加(2026/09/22、ユーザー指示)。本家 1.6.0 起点、SDK も自前 fork、縦書き・禁則の実装なし。UI・機能面の参考(Apps、読書統計、C3 の読書メモリの上限管理)として見る
  • 本家とフォークの更新確認(2026/09/22): 本家 develop は 09/20〜21 に #3630(一覧の NFC 合成の復元)・#3528(語間・字間の設定、章の版 47)・#3608(転送後に Library の索引を作り直す)。#3528 で PR #3609 が衝突したため develop の上に積み直し(章の版 48、CI 通過)。抹茶版が 1.6.0 安定版(辞書引きと漫画が中心、当版に無縁)。witchhunt版 2.31 で当版に関係する知見が3つ: (1) 章ごとの style の記憶(inline style と tag|class の解決結果)を 64 件で打ち切り——PDF 由来の EPUB は語ごとに固有の inline style を持ち、記憶が 129KB まで育っていた(当版の CSS の省略と同じ問題の別の入口。1.6.5 起点の作り直し時に本家の ChapterHtmlSlimParser を見て要否判断)。(2) SD 書体を最も近い顔から拡大して 20〜26pt を出す(当版は 18pt が上限で、拡大は画質を落とすので採らない)。(3) SD の読み出しをセクタ単位で束ねる(当版は USE_SPI_ARRAY_TRANSFER で把握済み)。本家 PR #3646(TTF)は未マージ、#3469(X3 階調の縦縞)も未マージ
  • 本家の Library 表示を日本語で使えるようにする読み対応を本家に PR #3651 として提出(2026/09/21、枝 feat/library-file-as-reading、本家 develop ce2b4fcd 起点、当版のコードは含まない)
    • 本家 Library(#3366)を実機で試した結果「日本語では利点が薄い」(漢字の題名が符号順、見出しが1字ごと)。EPUB の読み(file-as、蔵書 2,256 冊中 1,943 冊が題名に持つ)を並び・見出し・著者の組にだけ使い、表示は本の題名・著者名のまま
    • 読み取り: refines の前後どちらの書き方も、EPUB 2 の opf:file-as 属性も読む。書誌の保存に読みを2欄追加(v11)
    • 折りたたみ: カタカナ→ひらがな、全角英数→半角、ー・々・ゝゞ は語の一部、見出しは五十音の行頭(が→か、ぱ→は)
    • 並びの鍵: 12 バイトの鍵にかなを1字1バイトで詰め(従来4字→12字)、それでも同じ題名は読みの全文で並べ直す(叢書の巻順のため)。著者順は読みが「姓 名」なので姓の推測をやめる。索引形式 3/折りたたみ版 4
    • 実機: Title で あ・か の見出し、ら でリアデイル 4→8 巻順、Author で読み シーズ の Ceez が さ〜た の間。読みの無い自作 EPUB は従来どおり漢字1字の見出し。host テスト 105 件通過
    • 別件として判明: 本家は著者欄「Ceez, てんまそ」(著者と絵師)を「姓, 名」と見なして「てんまそ Ceez」に反転する(著者2人の本すべて)。この PR には含めない
    • PR の写真は fork の孤立した枝 pr-assets に置いた(claudeworks は非公開のため)
  • 本家とフォークの更新確認(2026/09/20): 本家 develop は 09/18 以降 5件(#3613 網の画面を抜けるときに無線を切る、#3618 組み込むUI言語の選択、CI 2件、独語訳)で、当版に効くものなし。#3618 は custom_i18n_builtin_langs = en で 34 言語分の文字列をフラッシュから外せるので 1.6.5 起点の作り直しで使う。本家 SDK は当版の fde240f から 61件先(TTF 描画、一覧の行の要求方式 #105、X3 の絶対階調 #94 は当版に含まれ、本家側の PR #3469(X3 の階調画像の縦縞、BMP 表示と眠り画面が対象)は未マージのまま)。Yomuka版 v0.7.6.0 の候補は 2件: 曲線引用符 ‘’“” を縦の升に正立で置く(当版は横倒しのまま。要実機写真)、ルビ・上付きに 10pt 専用の書体データ(当版は本文書体の SUP 縮小。細い画の途切れが出ているか要写真)。抹茶版 #282(区間表の共有)・JP版 v0.3.2(動的壁紙・進捗率・テーマ)・witchhunt版 2.30(触れる操作・機種追加)は当版に無縁。当版の本家 PR は #3539・#3609 とも人のレビュー待ち
  • 本家 1.6.5rc の蔵書表示が 4096 冊で止まる報告(討議 #3544)について当版を確認(2026/09/17)
    • 上限は蔵書目録 lib/LibraryIndex/(#3366)の CLIX_MAX_RECORDS = 4096 で、当版にはその機能自体が無い。当版で冊数に効くのは Browse Files の1フォルダあたりの件数(本家 1.6.0 と同じ作りで、見積もり数百件が頭打ち)と、/.crosspoint/ 直下に1冊1フォルダで平らに増える作業用フォルダ(本家由来)。作り直しで蔵書表示を採ると 4096 の上限もそのまま入る
  • 内蔵字形のページ枠は同じ書体で二重に取らない/挿絵の保存が無いページの四角の下書きは描かない(2026/09/10〜、抹茶版から)
    • 前者は読まれない枠が4枠の1つを潰していた分の解消、後者は書き換え1回(約0.5秒)の削減
    • 挿絵ページが白黒→階調の2段階で出るのはX3の階調表示の仕組み上の挙動で、実用上の不都合が無いためこのままとする

1.6.5ベースへの作り直し(2026/09/28〜) by claude code

本家 1.6.5(タグ 1.6.5=93e98bb7、2026/09/27)から vertical-1.6.5 を作り、当版の機能を段階ごとに載せ直す。 前回(1.6.0)は机上で取捨を決めたが、今回は本家が断片化・入力の取りこぼしに正面から取り組んだ後なので、 本家の対処をまず素のまま採り、実測で足りない箇所だけ当版の実装を戻す。freeink-sdk は 1.6.5 が指す 111fdcc7 を使う(当版が使っていた fde240f とパネル待ちの修正 ed39106 を含むので、SDK で持ち込むものは無い)。

段階 中身 状態
0 素の 1.6.5 のビルド 完了(RAM 57,912B/flash 5,621,527B)
1 診断(INPUT_DIAG、ビルド番号) 完了(2026092803-diag、実機確認済み)
2 時計と監視(#3562 の地域・夏時間の設定に載せ替え) 完了(2026092806-diag、実機確認済み)
3 日本語フォント(生成フォントと描画の追加分。メモリ処理は入れない)+素の実測① 完了(2026092807-diag、実測済み)
4 横書きの組版(禁則、長い段落) 完了(2026092818-diag、実機確認済み)
5 縦書きの中核 完了(2026092901-diag、実機確認済み)
6 縦書きの字間(本家 #3528 の字間を縦書きの字送りにも効かせる、新規) 完了(2026092903-diag、実機確認済み)
7 挿絵(本家 #2332 の上に載せる) 完了(2026092908-diag、実機確認済み)
8 表紙(SVG の包み、縮小表紙) 完了(2026092910-diag、実機確認済み)
8a flash から書体を読む仕組み、16pt を 9/8 倍して 18pt に使う仕組み 完了(2026092913-diag、実機確認済み)
8b 書棚の読み順(本家 PR #3651) 完了(2026092914-diag、実機確認済み)
9 実測②と不足分の補い(断片化対策・描画中の入力・本棚での解放など) 実施中(2026092918-diag まで実機確認済み)
10 配布(非診断版、README の整理)

段階1: 診断(2026-09-28、2026092803-diag、実機確認済み)

  • src/util/InputDiag.* と scripts/ost_version.py は旧ツリーの最終形のまま持ち込み、呼び出しは本家 1.6.5 にある処理にだけ入れ直した
    • 主ループの読み取り間隔と書き出し(書き出しは、描画中に主ループが途中で戻る本家 #3652 の分岐より手前に置いた)、画面の描画時間、Home に入った時点のヒープの一覧、読書画面の開く各段階・閉じたときのヒープ・章の組み立ての計時・ページ描画の検査点、章の保存のページごとの書き出し時間、クラッシュ報告の先頭のビルド番号
    • 字形の読み込み回数と字形置き場の解放記録は段階3、縦書き・挿絵・先読みの計測点は各段階で入れる。一覧画面の表示範囲の記録は、本家が一覧の作りを変えたため入れる場所が無く見送り
  • 非診断版もビルドが通る(RAM は素の 1.6.5 と同じ、flash +236B)
  • 実機での最初の観察(素の 1.6.5 に診断だけ、日本語 SD 書体・商業書籍の横書き、文字のアンチエイリアス ON)
    • ページ送り 1.1〜1.3 秒、章の組み立て最長 3.5 秒(5回に分けて)、本を開く各段階の空き 95→70KB(最大連続 41KB のまま)、閉じると 96KB に戻る
    • 空きの底は 11.4KB(最大連続 4.6KB)で、アンチエイリアスの帯描画の 8KB 作業領域を取った直後。その時点の持ち主の上位は、画面用の記憶 53,248B(SDK が起動時にヒープに取る。旧ツリーでも同じ)、設定項目の一覧 9,216B(本家は起動中ずっと保持。旧ツリーでは 8cc5f333 で使うときだけ作る形にした)、読書中の CSS 6,912B+4,352B、SD 書体の一覧 5,376B と書体ファイル名の文字列 35 個 3,272B、章の組み立て中の XML 読み取り用 4,352B
    • Home の初回描画 7.1 秒は表紙の縮小画像の作成(823KB の JPEG の展開 3 秒+変換 2.5 秒)。初回だけ

段階2: 時計と監視(2026-09-28、2026092806-diag、実機確認済み)

  • 時計モードの画面・数字の画像・12時間表示は旧ツリーのまま。Home のメニューの末尾(Settings の次)に「Start clock mode」
  • 時刻は本家 #3562 の地域・夏時間の設定に従う。本家の時刻の読み出しは 10 秒ごとにしか RTC を読まず秒が古くなるため、時計の画面向けに RTC を毎回読んで地域の規則で変換する HalClock::getLocalDateTime() を足した。旧版の UTC の時差を自前で足す計算は廃止
    • 1.6.0 の「UTC+9」の設定は本家の移行処理で一覧の「UTC+9」として引き継がれ、表示は変わらなかった。「Tokyo / Seoul」に切り替えても同じ
  • 時計の描画と時計表示中のメイン処理を 30 秒の監視に登録(すべてのビルド)。CPU の速度切り替えの直列化、電池電圧の記録、「自動スリープさせない」と「速度を保つ」の区別(時計表示中は 10MHz)も旧ツリーのまま
  • 実機確認: わざと固まらせる検証ボタン(CLOCK_HANG_TRIGGERS)で、メイン処理(Confirm)・描画(側面ボタン)とも 30 秒以内に再起動し、crash_report.txt にそれぞれ loopTask/ActivityManager の名前が残った。時計表示中は cpu_mhz_min=10、ボタンの読み取り間隔 76ms 以内、空き 78〜80KB で一定

段階3: 字形の計測と素の実測①(2026-09-28、2026092807-diag)

  • 旧ツリーのフォント層の変更を仕分けた。SD に置く日本語書体はそのまま 1.6.5 で使え、生成フォントの定義はフォント用ブランチにあるので firmware で移すものは無い。メモリ処理(4KB 分割の置き場、予備枠 8→48、解放の経路など)は段階9、縦書き用は段階5、flash から読む仕組みは段階8a、挿絵・階調は段階7
  • 入れたもの: 字形を1つずつ読んだ回数・置き場の作り直し(回数と時間)・置き場の解放記録・描画前の走査の結果・幅の表の作業量・本文の書体を診断に出す計測(書体のメモリの扱いは変えない)。異体字選択子(VS1〜VS16、IVS)の読み飛ばし(章の版 48→49)
  • 素の実測①(商業書籍の横書き、NotoSansJP 18pt、アンチエイリアス ON)
    • 離れた章への移動が 39.2 秒(組み立て 34.6 秒)。本家は字の幅を 768 字分まで表に覚え、日本語の章はそれを超えるため、以降の幅の測定のたびに字形の画像まで SD から1字ずつ読む(1回の描画で 2,817 字)。旧ツリーの 15bdbaab が塞いでいた穴なので、段階9から前倒しして持ち込む
    • 同じ本を開き直した直後に空き 2.7KB(最大連続 1.2KB)。持ち主は本の CSS 36KB、設定項目の一覧 9KB、画面用の記憶 53KB など
    • 読んだ後の Home で空き 62.5KB に対し最大連続 17.4KB(起動直後 102KB/98KB)。旧ツリーの 7239d0bb/f3293ca7 が直した形と同じ
    • ボタンの取りこぼしは無し(押下 230 回すべて受け付け)
    • 2点目・3点目は縦書きと挿絵を載せた後の実測②(段階9)で判断する

段階3の続き: 幅だけを読む経路の前倒し(2026-09-28、2026092815-diag、実機確認済み)

  • 測り方: 商業書籍の横書き、キャッシュを消して電源を入れ直し、目次の8番目から1つ前のページへ戻る(前の章 177KB・約 180 ページを最初から組み立てる)
  • 段階3の 39.2 秒の大半は診断版の記録の重さだった(訂正)。メモリの持ち主の記録(1 塊ごとの印と呼び出し元の遡り)を外すと、同じ操作の組み立てが 31.4 秒 → 20.9 秒。持ち主の記録は INPUT_DIAG_ALLOC_TAGS を付けたときだけ有効にした。非診断版でも体感 21 秒で、残りは本物の遅さ
  • 入れたもの(旧ツリーの 15bdbaab を元に作り直し)
    • 幅の表(768 字)が満杯になった後は、字形の画像を読まずに幅だけを返す readAdvanceOnly(常駐の置き場 → 予備の環 → SD の 12 バイトの字の記録)。章の組み立ての間(MeasureOnlyScope)だけ使う
    • 組み立ての間は書体ファイルを開いたままにする(1 回の読みが 3.9ms → 1.15ms)
    • SD から読んだ幅を 512 字分の表(約 3KB)に覚える。組み立ての1回分(8 ページ)ごとにファイルと一緒に捨てるので、読書中・Home には残らない
    • 診断に build_adv_only=(呼ばれた回数・SD を読んだ回数・時間)を追加
  • 結果(同じ章): 組み立て 20.7 秒 → 12.6 秒、体感 → 14 秒。幅だけの読みは 2,734 回すべて SD・10.7 秒 → 2,580 回のうち SD は 1,269 回・2.3 秒。残りは配置の計算そのもの(約 6.6 秒)、ページの書き出し 2.3 秒、表の範囲の幅の読み 1.4 秒
  • 試して戻したもの: 組み立て中だけ幅の表を 2,048 字に広げる案は、時間が変わらず(21.3 秒)空きが減ったので取り消した
  • 本を開く途中の空き(open_heap の sect)が 93KB の回と 40〜55KB の回があるのは、今回の変更ではなく、起動直後に開いた章の組み立てが1回で終わるかどうかの差。終わらない間は XML の読み取り器を次の回まで持ち続ける(本家の作り)。その状態でのページ描画で空きの底 3.9KB(最大連続 2.2KB)があり、段階9で扱う

段階4: 横書きの組版(2026-09-28、2026092818-diag、実機確認済み・章の版 51)

  • 禁則: 本家に提出中の PR #3609(CjkLineBreak.h+試験 9 件)をそのまま持ち込んだ。行頭に ー・々〜%」。、と小書き仮名、行末に始め括弧が来ない。……―― は同じ字が続く間は切らない。字の集合は旧ツリーと同じ。1.6.5 で本家がハングルの行分けを別の関数に分けていたので、ハングルも同じ規則を通す
  • 長い段落: 字境の表を1回の走査で作る(段落のバイト数の8倍を一塊で求めない。旧ツリー b18977ad)
  • 長い段落は語数の上限(CSS のある本は 320 語)で分けて組む。その続きの回を段落の1行目と見なして字下げしていた(旧ツリー 82eed024 と同じ直し)。全角の空白で始まる段落には端末の字下げを重ねない
  • 新規: 段落の上の余白(CSS の margin-top)を最後の回でまとめて足していたため、分けて組んだ長い段落では途中に 0.5em の空きが入り、段落の頭には入っていなかった。最初に行を出す回の前に1回だけ足す。旧ツリーも同じ作りだった
  • 章内の飛び先は、目印の段落の1行目が実際に載ったページを指す(1.6.5 も旧ツリーの修正前と同じく、次ページへ送られると1ページ手前を指していた。旧ツリー 82eed024)
  • 実機(horizontal-regression-test.epub 第1・9・13章): 禁則の違反なし、異体字選択子の後に四角が出ない、第13章の 2,559 バイトの段落で落ちない、途中に字下げ・空きなし、「目印へ飛ぶ」が正しいページに着く。空きの最低は 37.6KB
  • 1行に入りきらない禁則の塊(例: 18pt の「名・ー・・・々・〜・%・……――」)を途中で割ると、英単語と同じく行末に「-」が付く(本家の作り)。割ったことが見て分かるので、このままとする(2026/09/28 判断)

段階5: 縦書きの中核(2026-09-29、2026092901-diag、実機確認済み・章の版 53)

  • 旧ツリーの縦書きを 1.6.5 の作りに合わせて載せ直した。文字の分類(UAX #50・約物・小書き仮名・禁則)、CSS の圏点・文字の向き・縦中横・二段の子孫指定子、横倒しの描画、列の描画(ルビ・傍線・二分アキ)、列組み、字ごとの分解、列のページ配置、入れ物の余白、右開きのページ送り
  • 1.6.5 に合わせて変えた点: 語の持ち方(WordStore)に合わせて列組みを書き直し、縦書きの字にも本文の位置を持たせた(読みかけの位置が字の位置で保存される。旧ツリーは段落の終わりの位置だった)。字形の置き方(Frame)に横倒しを1種類足した
  • 本家の「空き 48KB 未満で CSS を無視する」関門は外した。読書中の空きは 44〜62KB を行き来するので、縦書き・ルビ・圏点の指定が段落によって効いたり効かなかったりする
  • 長い段落の区切りを語を足す途中でも行う(段階9から前倒し)。縦書きは1字が1語なので、区切らないと 4KB 読むたびに 1,300 語ほどたまる
  • 新規: 縦書きの列にもリンクの範囲を持たせ、Footnotes で番号を選んで飛べるようにした(旧ツリーでは一覧は出ても選べなかった)。1 ページの脚注は本家の上限どおり 16 件まで
  • まだ入れていないもの: 縦書きの本の挿絵と1字大の外字(段階7)、縦書きの字間(段階6)
  • 実機(vertical-test-suite.epub の 26 試験、Extra Paragraph Spacing OFF): すべて期待どおり。漢字の多いページの階調描画は 1 ページ 4〜6 秒(字形の予備の置き場 8 枠の読み直し。段階9)。ルビ中の 2 桁の数字は斜めにずれて並ぶ(重なりではない、記録のみ)。列の末尾の中点は旧ツリー同様の既知の未対応

段階6: 縦書きの字間(2026-09-29、2026092903-diag、実機確認済み)

  • 本家 #3528 の Character spacing(-2〜+2px)を、縦書きでは字と字の縦の間隔に効かせる。1マスごとに足し、二分に詰めた約物・縦中横のマスも同じ。横倒しの欧文の並びは1マスとして1回だけ足す(横倒しの描画は字間を持たないため、1字ごとに足すと字との位置がずれる)
  • 旧ツリーの縦書き専用の字間(設定が無く常に0だった)は取り除いた。字間は本家の時点で章の保存の照合項目なので、設定を変えれば章は組み直される
  • 実機(テスト17): +2px→-2px で1列目が3字多く入り、章が3ページ→2ページ。-2px でも字は重ならず、ルビも本文の横の正しい位置

段階7: 挿絵(2026-09-29、2026092908-diag、実機確認済み)

  • 縦書きの本では画像を列の流れに置く。列の幅と画面の半分の高さに収まる画像は1字として本文の列に入れる(外字)。それ以外は右から左へ列を割り当てて上下中央に置き、入らなければ次のページへ送る。中身が画像1枚だけのページは左右も中央に置く
  • CSS の max-width / max-height を画像の大きさに効かせる(電書協テンプレートの max-width:100%; max-height:100% や四割指定が効く)
  • 章の組み立て中に、画面用の記憶を借りて画像を本から取り出し、PNG は少しずつ読む展開器で画素の保存まで作っておく。ページを描くときは保存を読むだけなので、字形で記憶が埋まったページでも外字が枠だけにならない(修正前は取り出しに要る 32KB の連続領域が 2KB しか残っていなかった)
  • 実機(image-test.epub 第2〜13章): 外字(1em・em単位の各大きさ・ルビ付き・Kobo形式)、低ビット深度 PNG、svg で包んだ挿絵、横組み指定の挿絵ページ、挿絵12枚の章まですべて表示。挿絵12枚の章を初めて開くのは 8.5 秒(組み立て 4.9 秒、うち挿絵1枚あたり約 0.3 秒の保存作り)
  • 残り: 挿絵のあるページは本家の既定で残像消しの書き換えになり 1 回 2.8 秒かかる。高さだけ1字分の横長の外字は独立した列になる(文の途中で列が切れる)

段階8: 表紙(2026-09-29、2026092910-diag、実機確認済み)

  • 本の情報が表紙として SVG ファイルを指す本(旧ツリーの蔵書調査で 2,256 冊中 56 冊)は、SVG の中の JPEG/PNG をたどって表紙に使う。1.6.5 のままでは本棚にも眠り画面にも表紙が出なかった
  • 表紙の取り出しに失敗したときは空のファイルを画像として扱わずに止め、本棚は一時的な失敗を「表紙なし」と記録しない(再起動しても二度と試さなくなっていた)
  • 旧ツリーで入れた「本棚の縮小表紙を画面用の記憶を借りて作る」等のメモリ対策は入れていない。1.6.5 の上では読書後の本棚でも最大連続が 61〜73KB 残り、縮小表紙がそのまま作れている
  • 実機(cover-svg-test.epub): 本棚に表紙が出る。記録で opf cover=item/image/cover.jpg → thumb jpg decode ok を確認

段階9: 実測②と不足分の補い(2026-09-29〜、実施中)

  • 字形の置き場を 4KB の塊に分けて確保するようにした(旧ツリーの 4KB 分割を移植)。章を移った直後の 1 ページが 16.5 秒かかっていた件(1 字ずつの読み直し 1,175 字)が 11 字に減った
  • 塊が足りないときは置き場をまるごと諦めず、置けた字数に縮めて作り直す(本家 1.6.5 #3126 と同じ考え方)。のんびり農家のページ送りが 1.0〜7.9 秒 → 0.84〜1.31 秒
  • 本からの取り出しの塊を空きに合わせて 512B まで半分ずつ縮める(旧ツリー c042f63c)。組み立て中の外字の取り出し失敗が解消
  • JPEG の挿絵も、PNG と同じく章の組み立て中に表示用の形(.pxc)まで展開しておく。本文ページ内の細長い挿絵が描画時の空き不足で出なかった件を解消(11 枚すべて展開成功を確認)
  • 実測: リアデイル 1 巻 17 章の組み立て 11.6 秒(旧ツリー 21.1 秒)、vertical-test-suite テスト14本文(異なる漢字 1,336 字)のページ送り 1.1〜1.9 秒(段階5 では 4〜6 秒)、Font Copy in Flash は On の方が速く、ヒープの差はほぼ無い
  • 組み立て中にメニューを開くと落ちる件を修正。組み立てが空き不足で止まったまま XML 読み取りと CSS の表を持ち続け、メニューの確保に失敗していた。メニューを開く前に組み立てを一時停止(組み立て済みのページは保存し、本文に戻ると続きから再開)
  • 章の組み立てでは、その章で当たりうる CSS の規則だけを読み込む(旧ツリーの CssSelectorUsage、元は JP版 #106)。出版社のひな形 CSS の全規則を読むと書式の表が 32KB の 1 塊になっていた。書式の表 32KB→2KB
  • 設定項目の一覧を常駐させず、設定の読み書き・設定画面を開くときだけ作る(旧ツリー 8cc5f333)。約 11KB が空きに戻る
  • 実測(のんびり農家、Delete Book Cache 後に挿絵の多い章を組み立て): 組み立て中の空きの底 2.0KB→17KB、空き全体の底 2.0KB→13.4KB、読書中の空き 47KB→54〜63KB、起動後の本棚 97KB→108KB
  • 残り: 挿絵ページの残像消し 2.8 秒、漢字の多いページで字形の置き場の作り直しが 1 ページ 5〜10 回

段階8b: Library の読み順(2026-09-29、2026092914-diag、実機確認済み)

  • 本家に出している PR #3651(未マージ)の 4 コミットを取り込んだ。Library の並び・見出し・検索の元に、EPUB の本の情報にある読み(file-as)を使う。表示する題名・著者名は変えない
  • 読みのカタカナはひらがなに、全角英数は半角にそろえ、見出しは五十音の行(が→か、ぁ→あ)でまとめる。読みの無い本は題名そのものから同じ規則で並べる
  • 本の情報の保存(BOOK_CACHE_VERSION)を 13 に上げた(本家 PR 側の番号 11 は当版の縦書き・SVG 表紙で使用済みのため)
  • 実機: 「異世界のんびり農家」01〜05 が「あ」に巻順、「リアデイルの大地にて」1〜8 が「ら」に巻順
  • 既知: 著者が 2 人の本で名前の並びが入れ替わって見えるのは本家の名前整形の元からの不具合

段階8a: flash から書体を読む、16pt を 9/8 倍して 18pt に(2026-09-29、2026092913-diag、実機確認済み)

  • Text Settings → Style「Font Copy in Flash」を On にすると、選んでいる書体を内蔵 flash の使っていない側(約 6.4MB)に複製し、字形を SD カードではなく flash から読む。firmware を書き換えると複製は消えるので、次の起動で自動で作り直してから本棚に入る(電池 20% 未満で USB 未接続なら Off に戻す)
  • NotoSansJP の 18pt は大きすぎて入らないので、16pt を複製して字形の箱と画素を正確に 9/8 倍し、18pt として使う
  • 旧ツリーの最終形を 1.6.5 に合わせた。1.6.5 は字形の画素を一塊の領域に置くので、拡大後の大きさで確保する
  • 実機: 複製 6,549,504 バイトが約 2 分、src=flash scale=18/16。ページ送りの体感は問題なし
  • 併せて直した不具合: 商業書籍で表紙・扉・挿絵がすべて白いページになっていた。章の組み立て中は最大連続が 19KB まで下がり、画像の大きさを読むための展開(32KB の窓が要る)が失敗して、画像がページから外れていた。大きさを読む展開にも画面用の記憶を貸すようにして、9 枚すべて表示を確認
  • 残り(段階9): JPEG の挿絵は初めて開くページで展開するため 1 ページ 8〜10 秒かかる。書き出しの瞬間に空きが 2KB まで下がることがある

1.6.0ベースへの作り直し(2026/09/06〜) by claude code

本家1.6.0が2026/09/05に正式リリースされたため、方針どおり1.6.0(54337e6d)から新ブランチ feat/vertical-1.6.0based を切り、旧ツリー feat/vertical-1.6 の成果を段階ごとに移し直している。 移す元は旧ツリーの完成形(68a4b4ca、1.6.0rcから124コミット)で、コミット単位の機械的な移し替えではない。 6段階の移植と実機確認を終え、新ツリー初の配布版を pre-release 20260906(2026090611、非DIAG)として公開した (2026-09-06)。旧ツリーの配布版(pre-release 20260905=2026090504)はそのまま残す。同日、GitHubのデフォルトブランチを feat/vertical-1.6 から feat/vertical-1.6.0based に切り替えた。

本家 1.6.0 との差分一覧(2026-09-08時点、c28f1cb5)

本家 v1.6.0(54337e6d)に対して、コミット21件、73ファイル、+9,601/−567行。領域ごとの内訳は次のとおり (行数は追加/削除。本家側は 1.6.0 以降 #3397 のビルド環境更新1件だけで、当版と重なるのは体裁変更の3ファイル)。

1. 縦書き組版(本体の中心)

ファイル 追加/削除 内容
lib/GfxRenderer/VerticalTextUtils.h +371/0 新規。UAX#50準拠の文字の向き分類、縦書き約物の位置表、小書き仮名の寄せ、禁則表(行頭・行末、〝〟や中点まで拡充済み)
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp/.h +1,043/−43 縦書きの字ごとの分解、縦中横の自動判定、ルビ・圏点の付け方、外字画像、変異選択子の読み飛ばし、文字の向き指定(VERTICAL_FLIP)、子孫指定子用の先祖要素の記録、列のページ配置
lib/Epub/Epub/ParsedText.cpp/.h +293/−24 右から左への列組み、列境界の禁則押し戻し、縦書きの字下げ(正負)、横書きの圏点
lib/Epub/Epub/blocks/TextBlock.cpp/.h +603/−5 縦書き列の描画(正立・横倒し・縦中横、ルビ、傍線・打ち消し線)、保存形式の縦書き拡張
lib/GfxRenderer/GfxRenderer.cpp/.h +206/−7 横倒し描画 drawTextSideways、縦書き字間設定、縦書き用の計測
lib/EpdFont/EpdFontFamily.h +5/0 書式ビット VERTICAL_FLIP
src/activities/reader/EpubReaderActivity.cpp/.h、ReaderUtils.h +325/−40 縦書き本の自動判別、右開きのページ送り(左が進む)、縦書き時の余白と状態表示
lib/Epub/Epub/ReaderRenderSpec.h、Section.cpp の一部 — 縦書きフラグと字間を配置の仕様に持たせる

2. 書式(CSS)解析の拡張

ファイル 追加/削除 内容
lib/Epub/Epub/css/CssParser.cpp/.h、CssStyle.h +331/−46 text-emphasis(圏点)、max-width/max-height、text-orientation/text-combine-upright(接頭辞付き含む)、二段の子孫指定子(.vrtl .x)、保存形式 v13
lib/Epub/Epub/css/CssSelectorUsage.cpp/.h +288/0 新規。章が実際に使う要素名・クラス名を集めて、読み込む規則を絞る
lib/Epub/Epub.cpp/.h、Section.cpp +161/−9 章ごとの絞り込み解析、保存が打ち切られた場合の読み直し、空きメモリ24KBでの再解析

3. 挿絵

ファイル 追加/削除 内容
lib/Epub/Epub/converters/PngStreamDecoder.cpp/.h +382/0 新規。逐次読みのPNG展開
converters/ImageToFramebufferDecoder、Jpeg…、PngToFramebufferConverter +39/−6 寸法指定(幅・高さ・上限)と構築時の画像キャッシュ
lib/Epub/Epub/blocks/ImageBlock.cpp/.h +107/−1 縦書きでの挿絵の置き方(列の流れに入れる、幅広は列を割り当てて上下中央)
lib/Epub/Epub/parsers/ContentOpfParser.cpp/.h +8/0 画像の探索用

4. SDフォントのメモリ管理

ファイル 追加/削除 内容
lib/EpdFont/SdCardFont.cpp/.h +196/−65 字形領域を4KB刻みで確保(連続領域が取れない状況の回避)、縦書き字形の存在確認
lib/GfxRenderer/FontCacheManager.cpp/.h +21/0 一覧画面での字形キャッシュ解放
src/activities/UiListActivity.cpp +16/−1 同上の呼び出し

5. 時計モードと固まり検知

ファイル 追加/削除 内容
src/activities/util/ClockActivity.cpp/.h、src/util/ClockFace.cpp/.h +484/0 新規。時計画面
src/images/ClockDigits.h、scripts/build_clock_digits.py +1,522/0 新規。時計の数字画像とその生成台本
lib/hal/HalClock.cpp/.h、HalPowerManager.cpp/.h、HalSystem.cpp、HalGPIO.cpp/.h +151/−12 UTC補正、時計中の省電力、30秒の監視タイマーと再起動理由の記録
src/activities/ActivityManager.cpp/.h、HomeActivity.cpp/.h、Activity.h、main.cpp +140/−12 Homeメニューの「Start clock mode」、監視の掛け外し、クラッシュ報告へのビルド番号
lib/I18n/translations/english.yaml +1/0 メニュー文言

6. 診断基盤

ファイル 追加/削除 内容
src/util/InputDiag.cpp/.h +741/0 新規。ボタン応答・画像処理・空きメモリをSDのファイルに書き出す(-DINPUT_DIAG=1 のときだけ組み込む)

7. ビルド・配布・資料

ファイル 追加/削除 内容
scripts/ost_version.py、platformio.ini +171/0 crosspoint-OST-YYYYMMDDNN[-diag].bin の命名とビルド番号
README.md — 当版の記録
test/epubs-ja/(EPUB 7冊+README) +549/0 と 858KB 縦書きテスト書、挿絵テスト書、寸法テスト書、横書き・縦書きの基本テスト
src/util/DictHtmlPages.cpp +1/−1 配置仕様の引数追加に伴う1行

2026-09-09 の追加分(上の表に未反映): EpubReaderActivity.cpp/.h(次の章の先組み +170)、Section.cpp/.h(組み立て1回の時間上限 +10)、 SdCardFont.cpp/.h(寸法だけの字形集合への絵付き合成の抑止、字形集合を捨てた記録 +40)、HalGPIO・MappedInputManager(押下中の判定 +20)、 InputDiag.cpp/.h(先組みと字形集合の診断行 +60)、test/epubs-ja/vertical-test-suite.epub にテスト22(42→46章)。

本家と意図的に違えている点(機能ではなく判断): UI文字の太さは本家どおり Regular(#3096 は採用しない)、 「Extra Paragraph Spacing」ONでも縦書きの明示の字下げは効かせる(横書きの本家挙動とは異なる)、表は縦書きでは 格子ではなく積み上げ、列より幅の広い外字画像は独立した列(1文字大のものは 2026-09-10 から字として列に流す)。


配布フォントの出典と同梱文書(2026-09-08)

SDカード用フォント fonts-YYYYMMDD<n>.zip の3書体は、feat/japanese-sd-fonts ブランチの lib/EpdFont/scripts/sd-fonts.yaml に書いた取得元から元フォントを取り、JIS X 0213 などの符号位置リストで収録範囲を絞って点画像形式(.cpfont)に変換したもの。 太字は元フォントの太字から作り、字形は変えていない。元フォントはすべて SIL Open Font License 1.1。

zip内の書体 元フォント(取得元) 2026-08-12 取得時の版 著作権表示
BIZUDGothic BIZ UDGothic Regular/Bold — github.com/googlefonts/morisawa-biz-ud-gothic(main) 1.051 2022 The BIZ UDGothic Project Authors(モリサワ)
BIZUDMincho BIZ UDMincho Regular/Bold — github.com/googlefonts/morisawa-biz-ud-mincho(main) 1.06 2022 The BIZ UDMincho Project Authors
NotoSansJP Noto Sans CJK JP Regular/Bold(OTF)— github.com/notofonts/noto-cjk(main) 2.004 2014–2021 Adobe
後詰め(共通) Noto Sans Math — github.com/notofonts/notofonts.github.io(main)、Noto Sans — 本家同梱 builtinFonts/source/NotoSans 3.000/— Google LLC/The Noto Project Authors

後詰めは元フォントに無い字(∼ ≈ などの記号)だけを補う。取得元は版を固定していないので、作り直すと元フォントの版が 上がっている可能性がある(NOTICE.txt に取得時の版を記録する)。

2026-09-08 から、配布 zip に fonts/NOTICE.txt(上の表に当たる出典・版・著作権表示・後詰め・変換内容)と fonts/OFL-1.1.txt(ライセンス本文)を同梱する(ユーザー指示)。OFL は変換物を配る際もライセンス本文と著作権表示を 添えることを求めているため。生成は同ブランチの lib/EpdFont/scripts/make-fonts-zip.py で、元フォントの名前表から 版・著作権・Reserved Font Name の有無を読んで書き出す(4つの元フォントのいずれも Reserved Font Name は宣言していない)。 最初の同梱版は fonts-202609080.zip(フォント本体は fonts-202608240.zip と1バイトも変わらない)。pre-release 20260909 に添付済み(2026-09-09。フォント本体は同一なので、fonts-202608240.zip を入れた microSD はそのままで足りる)。

本家 #3412 から挿絵の失敗記録の扱いを取り込み(2026-09-08、2026090801-diag、実機確認済み)

本家 develop の b7bceb56 #3412(KOSync のメモリ検査の修正)のうち、当版に効く部分だけを手で移した。展開に失敗した挿絵の記録を 「本を閉じるまで」から「そのページの描画の間だけ」に縮め、次にそのページを開いたときに読み解きをやり直す(ImageBlock の 改名と、renderContents の先頭で記録を消す1行)。X3 では一時的なメモリ不足で挿絵1枚が白い枠になることがあり、従来はその本を 閉じるまで戻らなかった。KOSync・PSRAM・表示用領域の貸し出しに関わる残りの部分は当版に無縁なので取り込まない。

vertical-image-test.epub は v12 として第13章「挿絵が多い章(展開の失敗と再挑戦)」を追加。480×360 の番号付き挿絵12枚を続けて置き、 白い枠が出たら1ページ戻って再度進めば絵が戻る、という見かたを解説に書いた。実機(2026-09-08): 12枚すべてが番号順に出て、 間の文も欠けなし。白い枠は一度も出なかったため再挑戦の経路そのものは未通過だが、退行なし。

次の章の先組みと、章の境目の描画の遅さの修正(2026-09-09、2026090908-diag、実機確認済み)

配布: 本節までの内容を pre-release 20260909(2026090909、非DIAG、e83d2122) として公開した(2026-09-09)。 添付は crosspoint-OST-2026090909.bin と fonts-202609080.zip(NOTICE.txt/OFL-1.1.txt 同梱、フォント本体は 202608240 と同一)。 非DIAG版は strings で input-diag が 0 件、時計の監視が入っていることを確認してから添付した。

参考3ツリー(Papyrix版・Inx版・witchhunt版)の表示高速化を見比べた結果、当版の実測(2026090609-diag)では通常のページ送り 約460msのうち描画は約30msで残りは電子ペーパーの書き換えそのものであり、witchhunt版の「次ページの先描き」を移しても 40ms程度しか縮まない。Papyrix版の全体 -O2 はフラッシュが 84.4→96.2% になるため不採用。本当に長いのは章の境目 (章の組み立て 4.6〜8.8秒を含めて 7〜17秒)、挿絵ページ(3.2秒)、章の最初のページの淡い階調(2.2秒)で、まず章の境目を 「次の章の先組み」で潰すことにした(ユーザー承認 2026-09-09)。

先組みの仕組み(EpubReaderActivity::tickLookaheadBuild): いま読んでいる章の組み立てが終わっていて、ページ表示から 1.5秒以上ボタン操作が無いとき、次の章の配置を少しずつ組み立ててSDの保存(sections/N.bin)に書く。章を進めると保存を 読むだけになる。空きメモリ 40KB 未満か最大の連続領域 20KB 未満なら手を付けず、途中で別の章へ跳んだら部分保存として残す。 章の元データの展開が要るときは章をまたぐときと同じ方法で表示用領域を一時的に借り、終わったら今のページを描き戻す (画面は変わらない)。診断ファイルに lookahead=starts N done N ticks N pages N total_ms N chunk_max_ms N (spine N at N MHz) font_releases N の行を追加。

実機で直した3件(すべて診断ファイルの数字で発見):

  1. 2026090902-diag: 先組みの1ページ分が 17.6 秒。本体は操作が3秒途切れると CPU を 160MHz→10MHz に落とす(X3 は外部 メモリ無しの設定)ため、その後の先組みが16分の1の速さで動いていた。0903 で、先組みの1回分に描画と同じ全速の保持を掛け、 進行中は主ループの待ちも省くようにした(終われば元どおり3秒で省電力へ)。
  2. 2026090903-diag: 11秒待ってからのページ送りが無視された。X3 のボタンは抵抗の分圧を主ループが読み、5ms以上離れた2回の 読み取りで同じ状態が続いて初めて確定する。先組みの1回分(1ページ=最長0.5秒)の間は読み取りが止まるので、その間に 押して離した操作は消える。0906 で、1回分を「元データ1KBぶんの処理」単位で 25ms 上限に区切り(Section::buildSomeMore の時間上限)、ボタンが押されている・確定待ち・押した直後・離した直後のいずれかなら1回休むようにした。実測の最長は 190ms (1単位が重いページ)。待機中の次ページの字形先読み(既存、最長0.7秒)にも同じ条件を付けた。
  3. 2026090906-diag/2026090907-diag: 目次から章を開いたときや、キャッシュ削除後の最初の開きで、1ページ目が出たあと 19〜21秒ボタンが効かない(描画 23〜25秒)。白黒の描画は 47ms で正常なのに、淡い階調の描き分け2回が字形を 812〜945 個 SD から1個ずつ読んでいた(8個しか入らない予備の置き場の使い回し)。原因は字形の一時記憶(ページ分の部分集合)の合成規則: 章の組み立て(配置計算)は字形の寸法だけを集めるが、その直後のページ描画はその集合に絵付きでページの字形を合成する ため、章ぶん(最大512字)の絵を読み込む大きな塊になり、直後の空き不足で塊ごと捨てられて階調の描き分けが裸で走っていた。 先組みでも同じ膨張が起き(本を閉じたあとの空きが 54→34KB)、後のページの字形予算が削られていた。0907 で先組みの配置計算の 前後に一時記憶を空にし、0908 で寸法だけの集合には絵付きの合成をしない(ページ分だけ作り直す)ようにした。あわせて 一時記憶を捨てた時刻・呼び出し元・空き・字数を mini_free= 行に最新4件残す(番地は dist/*.elf で addr2line)。 キャッシュ削除後の最初の開きは 5.9 秒(組み立て 1.2 秒+描画 4.1 秒。階調の1回目 2.2 秒は全面書き換えの完了待ち)で、 字形の個別読み込みは 0。

残る章の境目のコスト: 全面書き換え 1.05 秒+その完了待ち 2.2 秒+字形の先読み 0.4 秒。組み立ては先組みで隠れる。 次はこの完了待ち(gray_lsb に含まれる waitRefreshComplete)を見る。

テストEPUB: vertical-test-suite.epub にテスト22「章の切り替えと次の章の先組み」を追加(第43〜46章、42→46章)。 本文A(待つ場所、約950字)・本文B(先組みの対象、番号付き60段落・約13,000字)・本文C(Bと同じ長さの比較用、目次から 直接開く)。解説に見かた(1)効果(2)比較(3)取りこぼし(4)表示の正しさ(5)待機中の画面と、再試験時のキャッシュ削除を記載。 実機(2026-09-09、0906〜0908): 先組み 40〜48 ページを約1秒で完了、1回押しで確実に切り替わり、体感の違和感なし。

共有時の記録: 2026090902→0903→0906→0907→0908 の各 diag を claudeworks で入れ替えながら確認。診断ファイルは /input-diag.txt・/input-diag-log.txt・/open-heap.txt。

縦書きの外字画像を字として列に流す(2026-09-10、2026091002-diag、商業書籍で実機確認済み)

前節の「№2」の続き。1文字大に直っても、当版は縦書きの本文中の画像を独立した列の上下中央に置いていたため、文の途中の 「№2」が別の列の真ん中に浮いていた(既知の制限「外字画像は独立した列」)。JP版 #129 と同じ考え方で、幅が列の間隔 (getLineHeight)以内で高さが列の半分以内の画像は、文字と同じ扱いの1語として列に流すようにした。

  • 語の文字列に画像の寸法と保存先を埋め込む(先頭に私用領域の U+F8F0、続けて 幅,高さ,展開先<TAB>元の場所。 lib/Epub/Epub/InlineImageToken.h)。章の保存形式は変えず、描画時に語の文字列から読み解く(縦中横・横倒しの判定と同じ方式)。 VerticalBehavior::InlineImage を追加し、配置では高さの分だけ進む(字間の設定も同じく加える)
  • 描画は TextBlock::renderVertical で、列の幅に対して中央に寄せ、ImageBlock::render で挿絵と同じ経路(組み立て時に作る 縮小済みの保存 .pxc、無ければその場で展開)を使う。字形の一時記憶の走査中は描かない
  • ルビの親文字の範囲は語の数で数えているので、<ruby><img/><rt> の形はそのまま画像の横に読みが出る。ルビの範囲の長さと 列の末端の計算は画像の高さを使う
  • 解析器は画像の縮小済み保存を作ったあと、条件を満たせば段落を分けずに語として追加する(IMG_DIAG("inline WxH pitch=N"))。 幅が列より広い画像(幅1.5em・2em など)と高さが列の半分を超えるものは従来どおり独立した列。横書きは変えていない

実機(2026-09-10、2026091002-diag): 『リアデイルの大地にて5』の「スキルマスター№2! 九条様管理下!」が同じ列に 1文字大で並び、直後のルビも崩れない。vertical-image-test.epub の第5・8・10・12章の合格条件(「前後の文字と同じ列に並ぶ」)は 元からこの動きを求める書き方なので変更なし。第5章は5ページ全部合格(同日、写真で照合): 【外字1em】と【連続外字】が 前後の字と同じ列に同じ大きさで入り、【大画像を縮小】は枠・左上の丸・右下の三角がすべて見える1文字大、【文の連続】は画像の前後で 文が切れず、【ルビ付き外字】に読み「るび」が画像の横に出る(これまで「未対応として記録」だった項目。ルビの範囲を語の数で 数える仕組みがそのまま効いた)。第8章も合格(同日): 【外字・正方】と【外字・縦長】(幅1em・高さ自動)が前後の字と 同じ列に入り、【外字・横長】(高さ1em・幅自動=列より広い)は設計どおり独立した列で本文と重ならない。挿絵の【前】【後】・全面・ 横長・【指定なし】(原寸)は従来どおり。第10章も合格(同日): 【基準】の1em角だけが字として流れ、【幅1.5em】【幅2em】 【幅2em・横長】【高さ3em】(元が縦長で幅1.5em)【高さ4em】(正方形で幅4em)【幅6em】は幅が列の間隔(54px)を超えるので 設計どおり独立した列の上下中央。解説は「列を一本割り当てて置く実装は合格」としており本文とも重ならない。列の間隔+両側の 空き(38px の字幅に対して片側 8px)まで許せば幅1.5em(66px)も流せるが、ルビの置き場と当たるので今は間隔以内に留める。 第12章も合格(同日): Kobo形式の区切り要素に包まれた【文中外字】が同じ列に1文字大、【ルビ付き外字】に読み「るび」が出る (市販の本の912箇所の形)、【一字ずつの読み】と【区切りの境目】は従来どおり。これで第5・8・10・12章の外字に関わる項目は全部合格。 テスト書の解説にある「読みが出ていなければ未対応として記録」の文言は次にテスト書を触るときに改める。

描画中のボタンを別の系で読み取る/階調の描き分けの打ち切り/Indexing を早く出す(2026-09-10、2026091005-diag、実機確認済み)

「ボタンが効かない瞬間に待ち表示を出せないか」という問いへの答え。待ち表示は画面の書き換え(最短 0.3 秒)を処理の前に 先回りして出すしかなく、最も頻度の高い通常のページ送り(1.2〜4.5 秒)に付けると毎ページ遅くなってちらつく。そこで 表示ではなく、押されたことを取りこぼさない方向で実装した(ユーザー承認 2026-09-10)。

  1. 描画中のボタン読み取り(HalGPIO::startBackgroundSampling): 描画は専用の系(ActivityManagerRender、優先度1)で 走り、主ループはその間 RenderLock で止まるので分圧の読み取りも止まっていた。描画の間だけ優先度2の小さな系 (btnsample、5ms 周期、2回の ADC 読み)が InputManager::update() を代わりに呼び、押した・離したの立ち上がりを 保持する。主ループの次の HalGPIO::update() が保持分を「いま起きた」として渡す(wasPressed/wasReleased に OR)。 update() と読み取り系の排他は FreeRTOS の相互排他(ADC の読み取りは割り込み禁止の区間に入れない)。描画が終わると 読み取り系は通知待ちで眠り、待機中は従来どおり主ループが読むので省電力の動きは変わらない
  2. 階調の描き分けの打ち切り: 短冊ごと(50〜100ms)に保持分を見て、押されていれば残りの描き分けと階調の書き換えを 省き、白黒のページのまま cleanupGrayscaleWithFrameBuffer() で制御器の基準を戻して次へ進む。診断の aa_aborts=
  3. Indexing の条件緩和: 目標 20→10 ページ、章 96KB かつ展開あり→32KB(展開の有無を問わず)、途中で出す待ち時間 1→0.5 秒、組み立ての単位 8→4 ページ(判定を細かくするため)
  4. 章の組み立て中(Indexing)の押下も保持されるので組み立て後に効く。組み立て自体の打ち切りは未実装

実機(2026091005-diag、2分15秒): aa_aborts=4(ページを出した直後の押下で描き分けを打ち切って進んだ回数)、 押した回数と確定した数は読み取り系の分が確定側に加わる形(84 episodes/130 edges)で、取りこぼしなし。 主ループの読み取りの最長の空きは 20 秒→2 秒(描画中は読み取り系が受け持つので、この空きは取りこぼしにならない)。 誤動作(押していないのに進む、二重に進む)の有無はユーザーの体感で確認。

同じ診断から: 挿絵ページ(512×694)の階調の描き分けは短冊ごとに縮小済み保存を SD から読み直しており(1回の描き分けで 7回・1.7 秒、2回で 3.5 秒)、ページ全体で 5.0 秒。RAM の置き場(img_slot)はこの大きさでは使われない。次の高速化候補。

本家の更新確認と #3463 の取り込み(2026-09-10、2026091006-diag)

本家 develop は 1.6.0 に 09-09 の2件が加わった状態(リリースは 1.6.0 のまま)。

  • #3463(9ba16086、cherry-pick -x で取り込み → 5a3d3bfe): 操作が3秒途切れたあと主ループは 50ms 眠って読むため、 65ms 未満の押下が1回しか読まれず消えていた。眠りを 10ms 刻みにし、分圧が待機の電位(約4095)から離れた瞬間に起こす (HalGPIO::rawInputActive()、main.cpp の待機の眠り)。当版の「描画中は別の系で読む」(同日)は描画中、こちらは待機中を 受け持つ補完関係。本家の実測では押下の中央値 145ms・最短 31ms で、65ms 以下が 100% 消えていた
  • #3439(1f3d7458、X3 の文字の階調がかすれる・斑点が出る #3400 の修正): 見送り。新しいパネル(UC8279)の X3 での 症状で、手元の X3(UC8253)ではテスト14 本文を 8 回送っても黒が保たれ斑点も出ない(写真で確認)。SDK の更新 (cb9167d→7f6bd0f)を伴い、当版の階調の打ち切りと書き換えの順序が絡むため、次の本家のタグで fork し直す時点で一緒に入れる

他フォークの確認と、抹茶版から2件の取り込み(2026-09-10、2026091007-diag、実機確認済み)

9つのフォーク(本家・JP版・CJK版・Yomuka版・抹茶版・freeink-sdk・Papyrix版・Inx版・witchhunt版)の先端を取り直し、 当版に無い修正を洗った。当版のコードで該当を確認し、効く条件は商業EPUB 955冊+Kobo形式 2,256冊で数えてから選んだ。

取り込んだ2件(どちらも抹茶版。配置の結果は変わらないので章の保存の版は据え置き):

  • 内蔵字形のページ枠を同じ書体で二重に取らない(抹茶版 #218 相当、FontDecompressor::prewarmCache)。 getBitmap() は fontData が一致した最初の枠で探索を打ち切るため、同じ書体の2枚目の枠は永久に読まれないまま 4枠のうち1つを占め、別の書体が「全部埋まっている」で温められなくなる。1つの書体が見出し・本文・代替として 複数の font id から届くので実際に起きる。既に枠がある書体は温めずに戻す(8行)。当版は本文がSDフォントなので主にUI側に効く
  • 挿絵の保存が無いページで四角の下書きを描かない(抹茶版 #220 相当、EpubReaderActivity::renderContents)。 四角を描いて1回書き換え、すぐ本番の描画で上書きしていた。電子ペーパーは前のページを保持するので、その1回 (約0.5秒)は丸ごと省ける。当版は章の組み立て中に .pxc を先に作るので、この経路は先作りが失敗したページしか通らない (image-diag.txt に pregen ... FAIL が出るページ)。本家の API である renderWithImagePlaceholders() と hasImagesNeedingDecode() は定義を残してある

実機確認(2026091007-diag、挿絵20ページの縦書きテスト書): 退行なし。ui_prewarm_fail=0、prewarm_entry_fails=0、 glyph_ondemand_last=0 max=7、page_draw_ms=156(最大229)。挿絵は8枚とも pregen stream ok だったので、 下書きの経路は今回1度も通っておらず、こちらの効果の実測は取れていない。

挿絵ページが白黒→階調の2段階で出るのは仕組み上の挙動(今回の変更とは無関係)。同じ診断の Page render (tiled): display=1053ms gray_lsb=2158ms gray_msb=35ms gray_display=226ms total=3848ms がそのままで、 白黒の土台を出す→2.2秒かけて4階調の面を組む(この間ずっと白黒版が見えている)→階調を重ねる、という順序。 X3 のドライバは階調を当てる前に較正された白黒の土台を要求する(Uc8253X3Driver::displayGrayscaleBase のコメント)。 本家 #3439 を入れても変わらない——あれは本文のアンチエイリアス経路だけで、PR 本文にも 「retain existing image-page handling」とある。実用上の不都合は無いのでこのままとする(2026-09-10、ユーザー判断)。 縮めるなら (1) 土台を displayBuffer(HALF/FAST) から差分方式の displayGrayscaleBase() に替える (当版の今のSDKに X3 向けの実装がある)、(2) SDK の ed39106(下記)の2案があり、どちらも計測から。

見送り・不要と判断したもの(調べた根拠つき):

出どころ 内容 判断
freeink-sdk ed39106 X3(UC8253) の全面同期で電源が入っていても電源投入を送り、BUSY が下がらず約1秒空回りする 有力候補として保留。当版の SDK(cb9167d)にも同じコードがある。SDK main は本家の参照先より20件先で階調の大改造を含むため、丸ごと上げずに3行だけ当てるには SDK の fork が要る。診断で全面書き換えの時間を測ってから
witchhunt版 5698985b 章内の飛び先が1ページ手前を指す(段落の1行目が次ページに送られた場合) 該当あり・保留。当版も flushPendingAnchor() で completedPageCount をその場で記録している。章内リンクを持つ本は 1,349/3,211(42%)。章の保存の内容が変わるので版上げが要る
Inx版 #115 長い段落の途中で一字下げが再発する 該当あり・保留。語数 320(本の書式使用時)/750 で分割したあと、isFirstColumn/breakIndex==0 が再び真になる。320字超の段落を持つ本は 383/3,211(12%)。同じく版上げが要るので上と同時に
witchhunt版 d4547ae6 章の飛び先表をSDへ逃がしてRAMを空ける 不要。当版は span の id を記録しないので、記録対象は1章あたり最大82個(3,211冊、中央値2、p99 27)。上限1024には遠く2KB程度
Yomuka版 fddb339 縦書きで小書きの仮名を上へ10%寄せる 不採用。JLREQ は縦組みで「字面を天地中央で右寄り」。当版は右へ1/8だけ寄せ上下は中央=仕様どおり
witchhunt版 e0c99be6 ほか 書式の #id 解決を省いて再解決を減らす 該当なし。当版の resolveStyle は tag/.class/tag.class しか引かない(CssSelectorUsage)
JP版 #123 ルビが段落の最終行(縦書きは最終列)で消える 該当なし。当版は横書き側が存在する分だけ複写、縦書き側は addVerticalToken が常に長さを揃える
JP版 #129/Yomuka版 99d0754 縦書きの本文中画像を語として流す 実装済み(2026-09-10、2026091002-diag)
Yomuka版 2138120 書式の画像上限(max-width/max-height) 実装済み(保存形式 v13)
Yomuka版 1e65fe8 圏点のゴマ点 U+FE45/FE46 を日本語フォントに入れる 実装済み。cjk-symbols の FE30–FE46 に含まれており(08-23 追加)、配布中の fonts-202609080.zip の3書体×Regular/Bold 全部に実体を確認
Yomuka版 dd8b64d SVGで包まれた画像の寸法を親の svg から取る 保留。当版も <image xlink:href> は読み、電書協型は内在寸法+画面合わせで正しく出る。該当ページを持つ本が多い(通常EPUB 552/955、Kobo形式 1,712/2,256)ので、不具合の報告が出るまで触らない
Yomuka版 f8a9d0f PNG展開の作業領域をスタックからヒープへ 不要。当版の展開器は本体(約11KB+32KB)がヒープにあり、スタックに残るのは約3KB
CJK版 1d6776b6 zip の展開緩衝を 8KB→512B と段階的に縮める 不要。当版は 1KB で読んでいる
CJK版 e9c66ee6 字形の予備リングに参照順の更新を入れる 条件付き保留。当版も順送りのみだが、返した EpdGlyph* の番地で予備かどうかを判定しているため、入れ替えると描画中の参照が壊れる。添字で持つ作りに直してから
JP版 #124 zip の EOCD 解析の非整列読み 不要。X3(C3)で現に動作しており不具合ではない
抹茶版 #214/#216 章の組み立て中の周波数の上げ下げ ほぼ対策済み。当版は本組みが render() の中=電源保持の内側、先読みも 09-09 に保持を入れた
抹茶版 7523403a フォント配信画面が低メモリで異常終了する 該当あり・低優先。当版も onEnter() が無条件に WiFi.mode(WIFI_STA)(:73)。本を開いたまま入ると落ちうる
本家 #2560 HTTP配信のキャッシュ制御 無縁
UI系全般 JP版 #134–#138(横向き表示)、抹茶版 #230–#240(タッチ・辞書・図書館)、Papyrix版の時計とX3波形、CJK版のOTA、Inx版 非対象(方針どおり)

字形の読み直しで遅くなるページの調査と修正(2026-09-10、2026091021-diag、実機確認済み)

診断の mini_free(字形の一時記憶を捨てた記録)と glyph_rebuild … total_ms(積み直しに使った時間)が セッション合計で12〜17秒に達しており、ページによっては表示に5〜15秒かかっていた。原因を追って複数箇所を 直したが、効いたのは最後の1点だけだったので、経過も含めて残す。

決め手: その場読みの置き場(overflow ring)が8枠しかなかった

一時記憶に載らなかった字は「その場読み」でSDから1字ずつ読み、予備の枠に置く。この枠が8個しかなかった。 階調の描き分けは短冊ごとに同じページを10回前後描き直すので、載らなかった字が二十数種類あると毎回 押し出し合い、描き直すたびに全部読み直していた(1字約10ms)。枠を48に広げた(OVERFLOW_CAPACITY、 16pt CJKで約4KB。枠は描画のたびに clearCache() で空になるので抱えっぱなしにはならない)。

同じ章(テスト24 本文)での実測:

診断 前 後
aa_worst_lsb_ms … glyphs= 8,812ms / 699字 2,255ms / 0字
aa_worst_msb_ms … glyphs= 4,432ms / 378字 98ms / 0字
page_draw_ms の最大 4,497ms 464ms
render_max_ms 15,344ms 4,917ms
glyph_ondemand の最大 1,128 41
heap_min_free 5,364 5,604(悪化なし)

CJK版が同じ症状で 80→120 に広げていた(e9c66ee6)。当版は8のままだった。

同時に入れた変更(効果は測れていないが筋は通る)

  • 一時記憶の保持の床を 40KB→24KB(MINI_RETAIN_MIN_FREE_HEAP)。読書中の空きは36〜45KBで この床をまたぐため、ページを描くたびに(PrewarmScope の生成→clearCache())捨てていた。 メモリを本当に要求する経路(章の組み立て・先読み・メニュー・フォント配信)は明示的に解放している
  • 絵つきの一時記憶がある間は、metaOnly の要求(代替フォントに振り替わった文字列の測定)で 作り直さない。作り直すとページの絵が捨てられる
  • ページ自身の prewarm にも空きに応じた字数の上限を掛ける。従来は複数文字列の一括要求にしか 掛かっておらず、積む途中で失敗して全部捨てていた(空き5KBで76字ぶんを破棄した記録がある)
  • 余裕の見込み(PREWARM_HEAP_HEADROOM)を16KB→8KB。16KBだと空きが少ないとき上限が0になり、 一時記憶を一切積まなくなっていた
  • 走査の font 枠を 4→6(MAX_SCAN_FONTS)。上限を超えた font は黙って走査から落ちるため。 実測では font_slots_lost=0 で該当しなかったが、保険として残す

回り道の記録(同日、いずれも撤回・修正済み)

  • 階調の短冊用領域(7.9KB)を描画の先頭で確保するようにしたら、まさに一時記憶を積む時点で 競合して悪化した。元の位置(階調に入る直前)に戻した
  • 配置の門番(メモリ不足なら章の組み立てを失敗させる)が abort を誘発した。配置を諦めると 語が消費されないので解析器が字を足し続け、addVerticalToken の push で落ちる。失敗と決まったら 文字を受け取らないようにした。見積もりも過大で(380語に8.4KB要求)、縦は4B/語・床2KBに直した

診断に足した道具

  • glyph_miss= … その場読みした符号位置の直近24件。どの字が漏れているかで原因が割れる (本文の字=ページの積み込み失敗、数字やASCII=UI側、特定の書体だけ=走査の枠不足)
  • font_slots_lost= … 走査の font 枠が足りずに落ちた回数
  • 遅い描画の記録(input-diag-log.txt)の先頭にも glyph_miss= を出す。定期報告は採取時 (たいてい別の画面)の内容になってしまい、遅かったページの中身が分からなかった

残っている費用(今回は手を付けていない)

  • 階調の描き分けそのものが1ページ約2.3秒。字形とは無関係で、書き換えの仕組み側の話
  • UI画面(一覧・メニュー)には一括プリウォームが無く、行ごとに1文字列ずつ積み直している。 旧ツリーの UiGlyphPrewarm 相当の移植が保留のまま

フォントを変えたあとの中断と極端な遅さ(2026-09-10、2026091023-diag、実機確認済み)

前節の変更を入れた 2026091021-diag で、フォントを BIZ UDGothic に変えたところ中断(abort)した。 番地から引くと縦組みの列を作る ParsedText.cpp の colRuby.assign()、つまり ふりがなの配列の確保だった。

原因1: 確保の失敗が報告にならない経路が残っていた

例外を使わない設定なので std::vector の確保は失敗すると報告ではなく firmware ごと停止する。前節で 段落規模の配列(字の高さ)と列の入れ物は「失敗しても止まらない」形にしたが、1列ぶんの配列5本 (本文・ふりがな・字体・座標2本)は素通しのままだった。1行・1列を作る前に同じ大きさの領域を試しに 取って返す関門を入れ、取れなければそのページを諦めて報告する(横組みの1行も同じ。失敗した行の語は 消費せずに残し、呼び出し側も止まるようにした)。

原因2: 先読みの取り分と、保持の床が別々の数字だった

前節で PREWARM_HEAP_HEADROOM を16KB→8KB、MINI_RETAIN_MIN_FREE_HEAP を40KB→24KBにした。この2つが 食い違うと次のことが毎ページ起きる。

手順 結果
先読みが空き8KBまで使って一時記憶を作る SDから何百字も読む
ページを描き終える 空き9KB < 保持の床24KB → 作ったばかりの一時記憶を全部捨てる
次のページ また最初から読み直す

診断の Low heap opening menu (9184 free, 6388 max block) が8KBの取り分とほぼ一致し、glyph_miss=476、 1ページ7.8秒。払った費用が毎ページ捨てられていた。取り分を保持の床から導くようにして、二度と ずれないようにした(PREWARM_HEAP_HEADROOM = MINI_RETAIN_MIN_FREE_HEAP)。作った一時記憶は必ず保持 されるか、そもそも作らない。

実測(BIZ UDGothic 12、同じ書籍・第13章)

診断 2026091021-diag 2026091023-diag
中断 abort(colRuby.assign) なし(176秒・挿絵ページと20分割の章組み立てを含む)
ページ送り 4,565ms(打ち切りあり) 1,704〜2,455ms
aa_worst_lsb_ms … glyphs= 2,144ms / 0字(押下で打ち切り) 2,259ms / 0字
glyph_ondemand の直近 — 49(最大513は章の組み立てを含む初回)
heap_min_free — 716(最も細ったところ。関門が受け止めて中断せず)

分かったこと: このフォントは1ページ分の字形が物理的に入らない

新しい診断行が出したのはこの2行。

font=BIZUDGothic_12.cpfont styles=2 advY=25 glyphs=24519 resident=6144
prewarm_budget_min=66 wanted_then=67 free_then=47588 clips=9

空き47,588バイトから保持の床24KBを引いた23KBで66字しか置けない。つまり1字あたり約350バイト (advY=25の2階調字形の実測平均)。1ページの異なり字数は約130なので、必要なのは約45KB——空きとほぼ 同じで、描画に使う分を残すと入りきらない。入らなかった字はその場読みに回る(1ページ49字)。 clips=9 は先読みの上限に達したページ数。フォントを変える前に速かったのは、字形が小さく1ページ分が 収まっていたからで、これはフォントの大きさが決める限界であって直せる不具合ではない。

診断に足した道具

  • font= … 使用中の .cpfont の名前・字体数・字の高さ・収録字数・常駐量。「フォントを変えたら遅く なった」という報告が、これまで診断だけでは読めなかった
  • prewarm_budget_min= … 先読みが許された字数、必要だった字数、そのときの空き、上限に達した回数。 字形が大きくなったのか、組版が複雑になったのかを分ける数字

残っている費用(今回は手を付けていない)

  • 33×33の外字画像が1ページの描画で10回以上再描画されている(階調の短冊ごと)。1回約20msで約200ms
  • 大きな章の初回オープンが20.8秒(うち章の組み立て12.5秒/20分割)

書体を変えると1ページの文字数が変わる(2026-09-10、2026091025-diag、実機確認済み・章の版51)

フォント指定以外の設定をすべて同じにしても、1ページに入る文字数が書体で大きく違うという指摘。 同じ書籍の同じ章で NotoSansJP が32ページ、BIZUDGothic と ExGothic が20ページだった。

原因: 縦書きの列の幅に、フォント内部の行送りをそのまま使っていた

配布中の .cpfont を直接読んだ値。

ファイル advY(焼き込まれた行送り) 上へ 下へ
BIZUDGothic_12 25 22 −3
BIZUDMincho_12 25 22 −3
NotoSansJP_12 36 29 −8

同じ「12」でも1.44倍違う。三段階の仕様が重なってこうなる。

  1. 大きさの設定は <書体>_<pt>.cpfont を選ぶだけで、実寸は変換時に焼き込み済み。変換は face.set_char_size(size, 150dpi) で 全角25画素(どの書体も同じ)
  2. しかし advanceY = norm_ceil(face.size.height) を焼き込む。face.size.height は全角ではなく その書体自身が指定する推奨行送り(hhea の上端・下端の合計)で、Noto Sans CJK は全角の 約1.44倍、BIZ UD 系はほぼ全角ちょうど
  3. 縦書きでは columnWidth = getLineHeight()(=advanceY)、列間はその1/4。つまり 列送りが BIZ で全角の1.25倍、Noto で1.8倍になっていた

全角幅は同じなので 1列あたりの字数は変わらず(どちらも19字)、列数だけが減る。写真でもそのとおりだった。

直した内容: 列送りを全角基準にする

JLREQ 2.3 は行送りを字の大きさに対する倍率(本文で1.5〜1.75倍)で定める。フォント内部の行送りを 使う仕様ではない。

  • GfxRenderer::getCjkCellWidth() を新設し、全角の幅をフォントから実測する (U+3000 →「あ」→「ア」→「国」の順。書体ごとに1回だけ測って覚える。字形がまだ読めない状態なら 覚えずに次回やり直す)
  • 列送り=全角 × 1.5 × 行間設定(VERTICAL_COLUMN_PITCH_EM)。行間設定の四段階(0.95/1.0/1.1/1.2) と合わせて1.43〜1.8倍となり、JLREQ の範囲を覆う
  • 文字の列・挿絵の列(1文字大か独立した列かの判定を含む)・描画側の升の幅を、すべて同じ値から求める
  • 縦中横の判定にあった 行送り × 2/3 という全角幅の代用も本物に置き換え

実測(同じ書籍・プロローグ、章の保存を作り直した後)

書体 前 後
NotoSansJP 32ページ(7列) 28ページ(9列)
BIZUDGothic 20ページ(11列) 28ページ(9列)
ExGothic 20ページ(11列) 28ページ(9列)

列の切れ目・行頭行末・ふりがなの位置まで3書体で一致。12ptなら列送りは全書体38画素になる。 BIZ で読んでいた密度からは約19%ゆるくなる(1.25倍→1.5倍)ので、詰めたい場合は行間設定の「狭い」で 1.43倍まで戻せる。

横書きは変更していない。 欧文には全角という単位が無く、行送りをフォントの上下端から取るのは 横書きでは正しいため。縦書きは日本語専用なので全角基準にできる。

調べたが直す必要が無かった点: 場面の区切り(<br>)が足す余白はフォントの行送り基準のままだが、 縦書きでは verticalIndent = max(textIndent, -topInset) と負の値との比較にしか使われないため、 列の頭下げには影響しない(負の字下げを列の外へはみ出させない歯止めとしてのみ働く)。

挿絵のログが診断を潰す/小さい外字がRAMに載らない(2026-09-11、2026091101-diag、実機確認済み)

「挿絵が1ページで10回以上再描画されている」として調べたが、再描画はしていなかった。診断が費用を 直接測っていて、aa_worst_lsb … img_slot=0/0 stream=1 sd=8ms — 1面1回・8ms。ログの間隔15〜23msは その短冊全体を描く時間だった。短冊に掛からない挿絵は glyphIntersectsStrip() が既に飛ばしている。 かわりに2点見つかった。

① 「Rendering image at …」を短冊の判定より前に出していた

階調は短冊ごとに全画像を一巡するので、判定より前だと1ページで12行出る。ログ環は16行しかないので、 採取の目的だった内容がすべて押し出されていた(2026-09-10 の遅い描画の記録は16行中12行がこの1行 だった)。判定の後ろへ移した。

前 後
1ページの画像のログ 12行(描画は1回) 4行(描画4回)=1行につき必ず描画が伴う
記録に残った内訳 なし AA split / Page render (tiled) / Rendered page in / Progress saved

② RAM置き場の門番が、要求量に関係なく空き24KB・最大確保8KBを求めていた

置き場(pxcSlot)は画面いっぱいの挿絵(約93KB)を想定した数字で門番を作ってあった。ところが 33×33の外字は2階調で 297バイトしか要らない。空きの少ないページほど断られる——SDに戻る費用が 一番高い場面で断られていた。4KB以下の要求には4KBの余裕で足りるようにした(大きい画像の条件は不変)。

前: img_slot=0/0 stream=1     一度も載らない
後: img_slot=1/0 stream=4     白黒の回で載り、階調の回は当たる

残っている費用: 置き場は1枚しか持てないので、外字が複数あるページは2枚目以降がSDへ戻る (実測1面32ms、1ページ65ms=ページ全体4,156msの約1.6%)。数枚持てるようにすれば消せる(外字1枚 300バイト)。効果が小さいので保留。

この節の教訓: ログの間隔を費用と読み違えた。診断に数字がある場合はそちらを先に見る。

章の組み立てとページ送りの費用を測る(2026-09-11、2026091105-diag、実機測定のみ・機能変更なし)

「大きな章の初回オープンが20.8秒(章の組み立て12.5秒/20分割)」を追った。推測で手を付ける前に 内訳を出す方針で、診断に3行足して測った結果、組み立てには狙うべき一点が無く、ページ送りのほうの 最大の費用は計測の区切り方の誤りだった。

組み立て: 挿絵の展開が全部だった

同じ章(20ページ) 挿絵の画素保存なし 保存あり
build_total_max_ms 3,784ms 1,028ms
build_page_write 100ms 58ms
build_font(文字幅表のSD読み) 203ms 54ms
残り(構文解析・書式照合・配置) ~3,480ms ~916ms

差の2,756msは 480×360 の挿絵1枚あたり約470ms×8枚。設計どおり(描画時は空きが足りず失敗するので 組み立て時に作る)。本文側は1ページ約46msで、書き出しも文字幅表も誤差。

事前に立てた仮説「文字幅表が768字で打ち止めになり、以後は測るたびに1字ずつ読む」は外れだった。 実測は table 13/768 full_skips 0。打ち止めには全く届いていない。

商業書籍の127ページの章の12.5秒もこの単価の積み上げで、しかも章ごとに1回きり・次の章は待機中に 先組みしている。20.8秒になったのは章の版を50→51に上げて全部作り直させたため。ここで打ち切る。

ページ送り: gray_lsb の中に書き換え待ちが入っていた

1回目の階調が2,293ms、2回目が140ms。同じ処理なのに60倍という差が長く残っていて、コード中の注記も 「1回目が何かを暖め、2回目はそれを見つける」と書いていた。繰り返しを「短冊をRAMで組む」と 「パネルへ送る」に分けて測ると——

AA split:  lsb=2293ms | msb=140ms
AA phases: lsb draw=97ms push=36ms | msb draw=99ms push=41ms

2回目は 99+41=140 でぴったり。1回目は 97+36=133 しかなく、2,160msが繰り返しの外にあった。 その正体は階調に入る直前の renderer.waitRefreshComplete()——白黒の書き換えの完了待ちで、 これが gray_lsb の計測区間に含まれていた。1回目と2回目の処理量は同じで、差は最初から無かった。

読み直したページ送りの内訳:

項目 ms 種別
prewarm 619 CPU
bw_render 132 CPU
display 1,080 パネル
書き換え待ち 2,160 パネル
階調1回目 133 CPU
階調2回目 140 CPU
gray_display 228 パネル
cleanup 78 CPU
合計 4,570 パネル 3,468(76%)

階調の描画そのものは273ms。以前この節に書いた「階調の描き分けそのものが1ページ約2.3秒」は 誤りで、その2.3秒は書き換え待ちだった。

計測の区切りを直し、AA split と Page render (tiled) に wait= を分けて出す。診断にも aa_refresh_wait=<ms> (max <ms>) を追加。

次の行き先: CPU側でページ送りから削れるものはもう多くない(prewarm 619ms が最大)。パネル側の 3.5秒が本体で、後回しにしているSDKの ed39106(X3 UC8253 の余計な電源投入、全面書き換え1回あたり 約1秒)がそこに直接効く。なお全面書き換えを待たずに階調を組んでおく経路は既にあるが (lsbPlaneBuf)、1面47.5KB+余裕60KBで空き107KBを要求するため、読書中に40〜70KBしかない X3では一度も走らない。

この節の教訓: 計測の区切りが誤っていると、同じ誤りを何度でも追う。区間の中に待ちが入って いないかを最初に疑う。

本家 develop の併合と、右端のルビ(2026-09-11、2026091115-diag、実機確認済み・章の版53)

ページ送りの費用がパネル側に偏っていることが前節で分かり、そこに効く SDK 修正 ed39106 (X3 UC8253 の余計な電源投入)を取りに行った。調べると本家は 2026-09-10 に取り込み済みで、 Yomuka版・抹茶版は未取り込みだった(SDKの固定先が全て 08-23 以前)。当版が独自に判断する必要は なく、本家に追随すればよい話だった。

再fork は不要だった

方針は「本家のタグが更新された時点で fork し直す」だが、今回は素直な併合で足りた。

ファイル 行
当版が加えた分(1.6.0 から) 80 +10,586/−589
本家が進めた分 106 +2,236/−585
両方が触ったファイル 15 —

試験併合の結果は 104ファイルが自動併合、衝突は5ファイル8箇所・142行。縦書きの中核 (VerticalTextUtils TextBlock ParsedText Section CssParser PngStreamDecoder)は本家が 一切触っておらず衝突ゼロで、当版の1万行の大半は本家の変更と物理的に重ならない。

衝突の解決: Epub.h/ReaderUtils.h は当版の縦書き用の宣言と本家の引数変更を両立、 ImageBlock.cpp は当版の hasValidCache() 分離とログ位置を維持、ChapterHtmlSlimParser.cpp は 当版の診断と本家の hidden 属性対応を両方、EpubReaderActivity.cpp は本家の chapterPosition() の整理を採って当版のメニューの低メモリ保護と nothrow 生成を載せ直した。

文字として併合できても意味は保証されない: ビルドが HalGPIO::rawInputActive() の二重定義を 検出した。#3463 を当版が先に取り込んでいたため、本家側の同じ実装が別位置に入っていた。

入ったもの

  • SDK cb9167d → fde240fa(29コミット)。本命は ed39106「電源が入っているときは POWER_ON を繰り返さない」。余計な PON は BUSY が下がらないため待機が 1000ms の上限まで回る
  • #3439 X3の階調の安定化。X3 は asyncBase=false となり、階調の組み立てを白黒の書き換えに 重ねる経路が無効になる(X3では安全でないと本家が判定した。前節で「要求メモリを見直せば 使える」と書いたのは誤りで、使ってはいけない経路だった)。当版の短冊方式は stripUploads=true でそのまま残る
  • #3478 階調機能の判定を GrayscaleCapabilities に集約

実測(本文ページ)

項目 併合前 併合後
書き換え待ち(aa_refresh_wait) 2,120ms 1ms
白黒の表示 1,053ms 579ms
階調 1回目+2回目 2,192ms 220ms
ページ全体 3,773ms 1,601ms

見送った本家の更新: #3390(hidden 属性)は手持ちの商業書籍513冊を走査して0冊・0箇所、 効果がない。#3036(macOS由来のNFDファイル名)は中身がラテン系と結合ハングルのみで、日本語の 濁点(U+3099/U+309A)は対象外。同じ問題は日本語にもあるので、ファイル一覧で「か゛」を 見かけたら同じ関数を広げるのが対処になる。#3437 はUI(方針により見送り)。

右端のルビが画面端で切れていた

併合とは無関係の、縦書き移植(段階5)以来の不具合。ルビは列の右隣に描かれるが (TextBlock.cpp の rubyX = cellX + cellWidth)、ページの一番右の列は本文領域の右端に 密着していたため、そのルビが領域外に出ていた。

最初はルビ幅(升の半分)をそのまま本文領域から引いたが、診断で1ページから列を1つ丸ごと 奪っていることが分かった。

vert_layout=cell 38 pitch 57 ruby_reserve 19 viewport 512x756 -> 8 cols
  確保なし  (512-38)/57+1 = 9列
  確保19    (512-19-38)/57+1 = 8列   ← 境目は18。1画素超えて11%の文字数を失っていた

ルビは利用者が設定した画面余白の上に出て構わない(そこは実際にパネル上にある。枠の下に 隠れる bezel の分は使えない)。確保量を「ルビ幅 − 画面余白」にして、既定なら 19−5=14。 9列を保ったままルビが完全に出る。実機で確認済み(ページ数 206→153)。

この節の失敗: 確保量を直したとき、配置が変わるのに章の保存の版を据え置いた。「まだ配布して いないから」と判断したが一つ前の版は配布済みで、実機はその配置で保存を作っていた。修正が一度も 実行されず、診断の vert_layout=cell 0 … -> 0 cols(組み立てが走っていない)で発覚した。 配置に触ったら版を上げる。

診断に足した道具

  • vert_layout=cell <升> pitch <列送り> ruby_reserve <右端の確保> viewport <幅x高さ> -> <列数> 縦書きのページがどの寸法で組まれたか。フォントの大きさ・余白・行間の設定と列数を突き合わせられる
  • font= 行が「最後に読み込んだフォント」を出していたのを本文用に直した。UI用の代替サイズが 後から読み込まれるため、本文が18ptなのに font=ExGothic_12 と出ていた
  • layout_giveup=<回数> last=kind<K> … 配置を諦める6か所のどれが断ったか
  • 失敗時の記録が、書き出し待ちの「遅い描画」の記録に塞がれていたのを、追い出せるようにした (flushNow() で即座に書き出す)

挿絵の初回展開が1枚8〜17秒(2026-09-11、2026091121-diag、実機測定・上限は保管層と判明)

商業書籍の 1440×2048 の挿絵を初めて開くと1枚8〜17秒かかる。1画素あたり約3,700サイクルに見えたので JPEGの扱い方を疑ったが、復号そのものは妥当で、時間はファイルの読み書きに消えていた。

内訳を測る

pregen の ms が展開(zipからの取り出しとSDへの書き出し)と復号を1つの数字にしていたので分け、 復号の中も分けた。

pregen extract ok ms=8563
jpeg split total=8283 io=4270/846 draw=343 flush=901 prog=0 scale=1/2
項目 ms 割合
展開(zipから取り出してSDへ) 8,563 50%
JPEGDEC の読み込み(846回) 4,270 25%
JPEGDEC 自身の復号 2,769 16%
画素保存の書き戻し 901 5%
拡縮+網点 343 2%

累進形式ではなく(prog=0)縮尺 1/2 も妥当。JPEGの扱い方に問題は無かった。復号2,769msは 1画素あたり約600サイクルで、この機械としては妥当な値。

往復を減らしても変わらなかった

JPEGDEC は1KB前後ずつ要求してくるので、8KBの緩衝を1枚挟んだ。

前 後
読み込み 4,270ms/846回 3,825ms/163回
展開 8,563ms 7,914ms

回数は1/5になったのに時間は7%しか減らない。163回×8KB=1.3MBを3,825msで読んで340KB/s。 展開は1.4MBで7,914ms=180KB/s。緩衝の補充が毎回呼んでいた seek も外したが変わらなかった。

別の経路でも同じだった。表紙のサムネイル生成は SD への書き出しが無いのに——

[ZIP] Decompressed 633921 bytes into 633770 bytes   ← 3,886ms = 163KB/s

151バイトしか縮まない(=実質そのまま通すだけの)データで163KB/s。保管層そのものが 160〜340KB/sで、SDのSPIは既定の40MHz(5MB/s相当)なので数%しか出ていない。挿絵の経路を どう調整してもここが頭打ちで、同じ割合が字形の読み込み・画素保存・章の保存・サムネイルにも効く。

この節の失敗: 展開の塊を 4096→16384 に広げたら、deflate 済みの項目は塊を2つ malloc する ため32KB連続を要求し、最大32KBの状況で外字の取り出しが失敗した(pregen extract FAIL)。 実測で塊の大きさがほぼ効かないと分かっていたので 8192(extractItemToFile と同じ実績値)へ戻した。

あわせて直したもの: readerRenderSpec() の呼び出し3か所のうち rightMargin を設定していたのは 1か所だけで、待機中の先組みが組んだ章だけルビの確保が19(前景は14)になり、章によって1ページの 列数が変わっていた。設定由来の値なので readerRenderSpec() 自身に入れた。

残っている費用: 表紙のサムネイル生成が Home 画面を8.4秒止める(展開3.9秒+復号3.0秒)。 挿絵と同じ原因。保管層の速度は別途の調査対象。

保管層が遅い原因は SdFat の1バイト転送だった(2026-09-11、2026091124-diag、実機確認済み)

前節で挿絵の時間の75%がファイルの読み書きに消えており、経路を問わず160〜340KB/sしか出ていない ことが分かった。40MHz のバスは MB/s 級を運べるはずで、桁が合わない。

外したほうの仮説: X3 の SD は画面と同じ SPI バス(GPIO 8/10/7)を使い、C3 の SPI2 が直結で使える のは 2/6/7/10 なので SCLK 8 は GPIO マトリクス経由。ESP-IDF はこの経路を読み出し26.6MHzと定めており、 SDK は既定で40MHzを要求している——タイミング余裕が無く再試行しているのでは、と考えて20MHzを試した。

クロック 634KBの展開
40MHz 3,886ms
20MHz 4,342ms

半分にしても10%しか遅くならない。純粋にクロック律速なら半分になるので、転送時間は全体の約1割で 残り9割は1回ごとの手間、と分かった。既定に戻し、sd_clock_hz= を診断に残した(どのクロックで出た 数字かが分からないと比較できない)。

当たったほう: SdFatConfig.h は ESP32 では USE_SPI_ARRAY_TRANSFER を 0 にする (RP2040 と UNO R4 のみが例外)。0 のとき SdSpiArduinoDriver::receive は——

for (size_t i = 0; i < count; i++) buf[i] = m_spi->transfer(0XFF);

1バイトずつ転送する。Arduino-ESP32 の transfer(uint8_t) は毎回データレジスタを書いて起動し 完了を待つので1バイト約2.8µs、8KBで23ms——実測の23.5ms/8KB とぴたり一致する。40MHzのバス自体は 8KBを0.2msで運べるので、99%が1バイトごとの手間だった。クロックを半分にしても10%しか変わらなかった のも、転送が元々ごく一部だったからで辻褄が合う。

platformio.ini に -DUSE_SPI_ARRAY_TRANSFER=1 の1行(送信側で512バイトのスタック領域を使う)。

実測(同じ1440×2048の挿絵)

項目 前 後
展開(zip→SD) 7,914ms 3,089ms
JPEGDEC の読み込み 3,825ms/163回 1,335ms/163回
復号全体 7,882ms 4,979ms
挿絵1枚の合計 約16,000ms 約8,139ms
外字(128×128)の展開+変換 169ms 90ms
8KBあたりの読み 23.5ms(340KB/s) 8.2ms(1MB/s)

副次: page_prewarm_ms 363〜590 → 154、glyph_rebuild total_ms 400〜1,600 → 175。 保管層を通る全部(字形・挿絵・章の保存・zip の取り出し・表紙のサムネイル)に効く。

あわせて、表紙のサムネイルの取り出しだけ塊が 1024 だったのを 8192 に揃えた(634KB に対し620回の 往復になっていた。Home 画面を8.4〜9.2秒止めている経路)。

これは X3 固有ではなく ESP32 全般の話で、本家・他フォーク・SDK も同じ既定で動いているはず。

この節の失敗: 展開の塊を 4096→16384 に広げたら、deflate 済みの項目は塊を2つ malloc するため 32KB連続を要求し、最大32KBの状況で外字の取り出しが失敗した(pregen extract FAIL)。塊の大きさが ほぼ効かないことは実測で分かっていたので 8192 へ戻した。塊を増やす前に、その塊がいくつ確保されるかを 見る。

章内の飛び先のページと、長い段落の途中の字下げ(2026-09-10、2026091011-diag、実機確認済み・章の版50)

他フォークの確認で見つけた2件を直した。どちらも章の保存の中身が変わるので SECTION_FILE_VERSION を 49→50 に上げてある(更新後の初回オープンで全書籍の章が作り直しになる)。

1. 章内の飛び先が1ページ手前を指す(witchhunt版 5698985b と同じ不具合)

flushPendingAnchor() は飛び先(id)を「その要素が現れた時点の completedPageCount」で記録していた。 段落の1行目がページに入りきらず次へ送られる場合、記録は1つ手前のページのままになる。注のリンクから 飛ぶと目当ての段落が画面に無く、ページを1つ送って初めて現れる、という形で出る。目次の飛び先だけは 記録の前にページを切るので無事だった。

飛び先を待ち行列(上限16)に積み、addLineToPage/addColumnToPage/挿絵の配置2箇所/罫線の配置で 「その行が実際に載ったページ」として確定するようにした。1行も生まないまま章が終わった場合は最後の ページに割り当てる。上限1024の数え方も待ち行列のぶんを足す。縦書きの表は当版では各セルの行が addLineToPage を通るので追加の対応は不要だった。

2. 長い段落の途中で一字下げが再発する(Inx版 #115 と同じ不具合)

語数が上限(本の書式を使うとき320語、使わないとき750語)を超えた段落は何回かに分けて配置されるが、 続きの回も「段落の一行目」と見なされて字下げが入っていた。ParsedText に「この段落は既に行(縦書きは列) を出したか」を持たせ、横書きの resolveFirstLineIndent と縦書きの verticalIndent の両方で見る。 行分割の計算(computeLineBreaks/computeHyphenatedLineBreaks)も同じ判定を通るので、測る幅と描く幅が ずれない。

テスト資産: vertical-test-suite.epub にテスト23(長い段落の途中の字下げ)とテスト24(章内リンクの 飛び先ページ)を追加した(生成は test-assets/corpus-survey/patch_suite6.py、epubcheck 5.3.0 で追加分に エラーなし)。テスト23は書式指定 text-indent:3em の1,464字の段落と、自動の一字下げに任せる1,438字の段落の 2本立てで、読書設定の段落間の空きに左右されずに確かめられる。テスト24は最初のページに01〜24のリンク、 その下に長さを25〜104字で変えた24個の目印段落を置き、どれかの段落の頭がページの境目に当たるようにしてある。

実機確認(2026091011-diag):

  • テスト23: 1/24 で〔見本〕と〔1〕の2箇所だけが3文字ぶん下がる(16字=19字−3)。2/24 は先頭列が19字=満杯で、 ページと区切りをまたいでも下がらない
  • テスト24: 07 は 5/14、14 は 9/14 に着地。どちらも【NN】が着地ページの右端の列の先頭=以前ずれていた形。 列の途中から始まる06は元から正しく4/14
  • 注意: 最初の試行では07・14とも1つ手前に着いた。作り直し後は正しくなったので、前の状態の章の保存 (部分保存か、別ビルドで組んだ配置)が残っていたものと見られる。再発したら e-ink/dump_section_anchors.py で章の保存の飛び先表を直接読み、記録側か読み出し側かを切り分けられる

配置がメモリ不足で落ちるのを止める(2026-09-10、2026091011-diag)

2026091008 の実機でページ送り中に異常終了した。ELFで逆引きすると発生源は ParsedText::layoutVerticalColumns の wordHeights.reserve(ParsedText.cpp、2026-09-06 の縦書き移植から あるコード)で、-fno-exceptions では operator new が失敗時に nullptr を返さず std::terminate を呼ぶ。 直前のログに「1024バイトの確保に失敗」が出ており、原因は空きメモリの枯渇。26秒前には Low heap opening menu (5340 free, 2164 max block) も出ていた。

  • 配置に入る前に見積もりと空きを比べる。縦書き6バイト/語・横書き12バイト/語(行分割の dp と ans が 4バイトずつ要るため横が高い)+6KBの床。足りなければその章の組み立てを失敗として返し、abandonBuild() で 保存を書かずに終わる。**落ちる代わりに「次に開いたときに作り直し」**になる
  • wordHeights を nothrow の配列に、列の TextBlock も nothrow 生成+valid() 確認に
  • 区切り(soft flush)の取りこぼしを直した。判定を語を足す途中でも行う。従来は文字の塊を読み終えてから しか見ないため、上限320語に対して648語まで溜まり、配置が確保する山が倍になっていた。 最初の修正では語の切れ目(partWordBufferIndex == 0)に置いたが、空白の無い日本語は200バイトの 溜め込みが埋まったときにしか字を渡さず、しかも3バイトの字が境目をまたぐと溜め込みが空にならないため ほぼ発火していなかった(words_max=471 で判明)。溜め込みが埋まった直後にも判定するようにした
  • 診断版に build_headroom_min= を追加(配置に入る直前の空き・最大連続・語数の最小値と、扱った語数の最大値)

実機確認: 2026091010-diag(10分の読書)で build_headroom_min=13920 max_alloc_then=10228 words_then=447。 447語なら見積もりは8.8KBなので約5KBの余裕を残して通っており、門番が誤って章を拒否してはいない。落ちた章 (テスト23本文)が今度は最後まで組み上がった。続く 2026091011-diag でテスト23本文を組み直させたところ build_headroom_min=24360 max_alloc_then=18420 words_then=380 で words_max=471→380。上限320に対する 超過が151語から60語(溜め込み1杯=約66字)に収まり、区切りの修正が効いていることを確認した。 heap_min_free は 3,756/8,188/2,920 バイトまで落ちており、落ちなくなっただけで狭さ自体は残っている (OOM: grayscale strip scratch (7920 bytes); skipping AA this page や 132 glyphs loaded on demand in the BW pass; skipping AA this page が出る)。

同じ診断から次の候補: 字形の一時記憶の作り直しにセッション合計18.6秒(glyph_rebuild ... total_ms=18656、 mini_free=120)、および「BW描画で128字を1字ずつSDから読み階調を諦めた」6秒のページ(aa_aborts=2)。

章の配置の保存の版を上げる(2026-09-09、未配布・次のリリースに含める)

商業書籍の本文中の外字画像「№2」(img.gaiji、width:1em;height:1em)が X3 で元画像の 128px のまま出る報告。診断で追うと、 キャッシュ消去後の組み立てでは 44×44(1文字大)に置かれ(image-diag.txt: placed 44x44 … vert=1)、章ごとの絞り込み再解析も 8ファイル全部を読んで 9 規則で完了していた(css done rules=9 result=0 filtered=1)。つまり現行の解析は正しく、報告の表示は 09-06 夜の書式対応(tag.class・二段の指定子・縦書き字下げ)より前に組み立てた章の保存を読んでいたもの。章の保存 (sections/N.bin)には配置結果がそのまま入るので、書式の解釈が変わっても版を上げなければ古い配置が残る。09-06 は SECTION_FILE_VERSION を 48 に上げたのが昼(部分保存の再解析の修正)で、夜の書式対応では上げていなかった。pre-release 20260906/20260909 の「初回オープン時に配置の作り直しが走る」はスタイルの保存(CSS_CACHE_VERSION 13)の話で、章の配置は対象外 だった。SECTION_FILE_VERSION を 49 に上げ(部分保存の目印も連動)、次のリリースで全章が作り直されるようにする。 以後、配置の結果が変わる修正では必ずこの版を上げる。

同じ診断から: 保存の無い章の途中(第13章の126ページ目)を読書位置から開くと、そこまでの組み立てに 38.4 秒(16 回、最長 10.8 秒)、 描画まで 46.4 秒。続く描画で階調用の作業領域(7.9KB)が取れず、そのページだけ白黒で出た(OOM: grayscale strip scratch)。 先組みは章の途中を開く場合には効かない。外字画像そのものは「独立した列の上下中央」に置かれる既知の制限が残る(文の途中の 「№2」が別の列の真ん中に浮く)。1文字大の画像を字として列に流す実装(JP版 #129 と同じ考え方)が次の候補。

段階と進み具合(ユーザー指定の順序):

段階 内容 状態
1 1.6.0から feat/vertical-1.6.0based を作成、SDK cb9167d 完了(2026-09-06)
2 診断基盤(INPUT_DIAG、ビルド番号、クラッシュ報告の先頭行) 完了・実機確認済み(2026-09-06、2026090601-diag)
3 時計機能の移植と、固まった時の再起動の実機検証 完了・実機確認済み(2026-09-06、2026090602-diag)
4 フォント(SDフォントの字形領域の分割・キャッシュ解放・ヒープ門番) 完了・実機確認済み(2026-09-06、2026090604-diag)
5 縦書き(縦組版コア・電書協CSSのメモリ管理・ルビ/圏点/約物/UAX#50・傍線) 完了・実機確認済み(2026-09-06、2026090606-diag、テスト書16章すべて旧ツリーと一致)
6 挿絵(章内画像、寸法指定、ページ送りの速度) 完了・実機確認済み(2026-09-06、2026090609-diag〜0610-diag)。コードは段階5に同梱。商業書籍1冊の寸法指定が旧ツリーと同値、image-size-test.epub 13サイズ全部合格、vertical-image-test.epub 全章が旧ツリーと一致

旧ツリーとの照合(2026-09-06、6段階完了後)

旧ツリー feat/vertical-1.6 が本家 1.6.0rc から加えた行(生成フォントの表・README・テスト書を除く87ファイル、 5,443行)を、新ツリーの同じファイルに1行ずつ探した。見つからなかったのは384行で、内訳は全部が説明できる:

見つからなかった行 何か 判断
UIフォント関係のスクリプト3本(286行)、convert-builtin-fonts.sh、main.cpp の Medium フォント宣言、build-font-ids.sh 本家 #3096「UI文字を Ubuntu Medium にする」と、その手直し #3186・光学補正済み Noto UI 面。旧ツリーは採用していた 本家が 1.6.0 で #3096 を取り消した(#3314)。新ツリーは本家に従い Regular。旧ツリーの見た目に戻すなら移植が必要(下記)
CssParser.cpp/.h(32行)、Epub.cpp/.h(18行) 解析中の空き32KB門番、validateCache、cssComplete で保存を止める旧方式 本家 #3005 の「上限付き保存+部分キャッシュ」の上に作り直したので形が違う。機能は「部分なら章ごとに選別解析」で置き換え済み(段階6の項)
fontconvert.py の --mono/--autohint-font(18行) #3096 系の1bit描画オプション 上と同じ理由で不要。SDフォント側(fontconvert_sdcard.py・sd-fonts.yaml)は全行一致
ChapterHtmlSlimParser.cpp(7行) 圏点を先頭トークンにまとめて付ける旧形(本節で直した側) 新ツリーのほうが正しい
ReaderUtils.h(1行) 長押し設定OFF時に押下で送る判定 本家 1.6.0 が「長押し/離した」判定に変えたのを取り込み、usePress 分岐は残してある
HalGPIO.cpp・Section.cpp の各1〜2行 条件式の書き方、版番号(旧49/新48) 同等。版番号は違えば作り直すので混在しても問題ない

本家 1.6.0 の変更で、旧ツリーが取り込んでいた11件(#3107 #3009 #3122 #3134 #3102 #3191 #3244 #3119 #3222 #3144 #3186)は、#3096 の取り消しを除きすべて 1.6.0 本体に入っている。新ツリー側で改めて当てる必要はない。

新ツリーにだけあるもの: 本家 1.6.0 の79コミット(言語別フォント #3146、表の列描画 #2654、リンクのタッチ操作 #3296、SD電源断と単押し起動 #3341、冷間起動の電源ボタン誤判定 #3248、X4 Classic 対応 #3279、リーダーのツールバー メニュー #3182、辞書まわり、KOSync・OPDS 修正ほか)と、この作り直しで加えた部分キャッシュ時の章ごと選別解析 (95a7600c)、圏点の文字ごと付与(f1dd384f)、縦書き本での表の縦積みへの退避、wordLinkIds との歩調合わせ。

UI文字の太さは本家どおり Regular にする(2026-09-06 決定)。旧ツリー(公開中の 2026090504)は Ubuntu Medium だったが、本家が #3096 を採用しなかった(#3314 で取り消し)以上、新版でも採用しない。1bit描画で Regular は縦線が 2px、Medium は3pxになる差があり、本家は表示崩れ(#3186 で直した文字の重なり)を理由に戻している。

1.6.0で変わったビルドのしかた

1.6.0は firmware_tuned という設定でArduino基盤そのものを再構築し、ヒープを32〜37KB回収する (タスクの持ち分の縮小、WiFiの処理をIRAMから外す、使わない部品の除外)。初回ビルドで基盤を作り直すため 7分半かかり、ESP-IDF用のPython部品(idf-component-manager ほか)が環境に無いと設定段階で止まる。 以降のビルドは1分半〜3分半。素の1.6.0のX3向けビルドは RAM 17.1%・Flash 83.5%。

段階2: 診断基盤(2026-09-06、実機確認済み)

InputDiag 本体と scripts/ost_version.py は旧ツリーからそのまま複製し、差し込み口だけを1.6.0側の コードに合わせて置き直した: 主ループの入力採取と書き出し、画面描画ごとの所要時間と5秒超の記録採取、 一覧画面の行配置、読書画面の本を開く6段階のヒープ・章構築の所要・ページ描画の内訳、crash_report.txt 先頭のビルド番号。字形の読み込み回数などSDフォント側の計測値はフォント段階まで0のまま。

実機で採れた最初の数字が、そのまま後の段階の動機になっている。素の1.6.0に日本語SDフォントを載せると、 読書中の空きが最低2.3KB・最大連続4.8KBまで落ち、テスト書の第2章は「出力バッファのメモリ確保に失敗」で 3回再試行の末に章の構築に失敗した(診断が section-build-failed として直前ログ付きで捕捉)。 一覧画面の描画も空き7〜9KBで1.9〜4.8秒かかっている。旧ツリーではフォント段階(キャッシュ解放・領域分割)と 縦書き段階(CSSのメモリ管理)で解消していたものが、1.6.0素の状態では丸ごと残っていることの確認になった。 本を開く6段階は enter 93KB → page1 62KB(最大連続 71KB → 33KB)。

段階3: 時計機能と、固まった時の再起動(2026-09-06、実機検証済み)

旧ツリーの完成形をそのまま移した: 時計画面(ClockActivity/ClockFace/数字の図案)、RTCの日時取得 (HalClock::getLocalDateTime)、電池電圧の読み取り、CPU速度切り替えの直列化(HalPowerManager)、 待機中の降速を「入力の有無」で判定する主ループ、時計画面限定の30秒監視(主処理・描画処理、全ビルド)、 止まったタスク名をクラッシュ報告に残す処理、故意ハングの引き金(CLOCK_HANG_TRIGGERS/ DEBUG_RENDER_WATCHDOG の試験ビルドのみ)、Homeメニューの「Start clock mode」。 1.6.0側に #3009(起床時の残像取り)が既に入っていたため、旧ツリーで自前に持っていた goHome の引数は そのまま使えた。1.6.0が足したクラッシュ報告の「Reset reason」行とも共存する。

実機検証(2026090602-diag=配布版と同じ監視経路、DEBUG_RENDER_WATCHDOG なし):

  • 側面ボタン → forced hang: render task → 主処理は minute turn を記録し続け → 30秒で再起動、 理由欄 ActivityManager (CPU 0)、Reset reason: TASK_WDT
  • 前面ボタン → forced hang: main task → 30秒で再起動、理由欄 loopTask (CPU 0)
  • 時計は11分の連続表示で分更新445ms・残像取り3.19秒、空き76KB・最大連続71KBで一定。旧ツリーの 44KBより30KB以上多い(1.6.0の基盤再構築による回収分と、SDフォントの常駐がまだ無いことの両方)

前面ボタンの試験で1回引き金が引かれなかった。直前ログの周波数の上げ下げから押下は約1.2秒で、 X3の前面「決定」キーは電源ボタンと線を共有しており、SDKの入力処理が0.4秒以上の押下を電源ボタン の操作として扱う(CONFIRM_POWER_HOLD_MS)。電源短押しは既定で無視なので何も起きなかったように見えた。 短く叩いて再試験し合格。旧SDKも同じ判定なので、試験手順としては「前面は短く叩く」が正しい。

旧ツリーの配布版 2026090504(同じ監視・同じ時計)では給電なし11時間11分の連続表示で誤発火なし、 残量100%→96%、−14mV/h(2026-09-05〜06)。1.6.0側での長時間放置は次の段階のビルドと並行して行う。

段階4: フォント=SDフォントのメモリ管理(2026-09-06、実機確認済み)

旧ツリーのコミットを差分として1.6.0に当て直した(3方向マージ): 字形領域を4KB片で組み立てる方式、 符号位置の受け皿を必要量まで伸ばす方式、SDフォントのキャッシュ解放(一覧画面を開くとき・飢えたヒープで 本を開くとき・章の展開が断片化ヒープに当たったとき・低ヒープで読書メニューを開くとき)、読書画面の ページ表示と章構築のヒープ門番、診断への字形読み込み回数・領域作り直し回数・走査結果の接続。

本家1.6.0の #3126(字形領域が取れないとき半分ずつ縮めて再試行)は外した。同じ問題への別解で、 当版の分割方式が入れば再試行は決して走らない死んだ経路になるため、定数・欄・再試行ループを撤去した。 内蔵UIフォントは1.6.0のまま(本家が Ubuntu Medium を取り消した判断に合わせる)。SDフォントのファイル形式は 変わらず、fonts-202608240.zip がそのまま使える。

実機(2026090604-diag): 段階2で構築に失敗したテスト書の第2章が開く。本を開く6段階の最大連続ブロックが sect 33KB→55KB、page1 33KB→47KB に改善。字形の計測欄が生きた(page_scan_last=88b/3f)。 残る問題(旧ツリーから引き継ぎ、移植による後退ではない): 読書後にファイル一覧を開くと1画面の描画で 字形領域の作り直しが16〜21回走り、3.0〜4.5秒かかる(旧ツリーでも 3.9〜4.6秒・12〜14回の記録がある)。 一覧の行ごとに逐次で字形を読むためで、読書ページ式の一括事前読み込みを一覧画面にも適用する必要がある。 起動直後の一覧は0.5〜0.9秒なので、読書が残したヒープの状態に依存する。

段階5: 縦書き(2026-09-06、実機確認済み)

やり方を変えた。 旧ツリーの縦書き関連コミット(約25件)を順に当てると、本家1.6.0が同じファイルに入れた 変更(タッチでのリンク #3296、表の段組み #2654、CSS保存方式の書き換え #3005、RTL継承 #3198、 段落間隔 #3221、脚注の上付き #3355)と衝突を繰り返す。そこで旧ツリーの完成形をファイル単位で 1.6.0に3方向マージした(本家が触っていないファイルは丸ごと複製、両方が触ったファイルは git diff rc..old -- F | git apply -3)。衝突は10ファイル29箇所。解析器と章構築(ChapterHtmlSlimParser・ Section・ImageBlock)は縦書きと挿絵で共有しているため、段階6の挿絵のコードも同じ変更に入った。 検証だけを段階の順に分けている。

手を入れた箇所:

  • CSS解析器は本家1.6.0の新実装を土台に作り直した。 本家は規則の保存を可変長の連想配列から上限付きの 固定領域(選択子プール+スタイルの重複排除、-fno-exceptionsで落ちない)へ書き換えており、当版の 「メモリ不足で落ちない」目的と同じ。その上に当版の機能を載せ直した: 圏点(text-emphasis、-epub-/-webkit- 接頭辞込み)と max-width/max-height の解釈と保存形式(記録に1バイト+2長さ、定義ビット18〜20)、 章ごとの選別読み込み(登録時とキャッシュ読み込み時の両方でフィルタ)、描画時のヒープ門番の撤去。 本家が足した「部分キャッシュを残して後で再試行」「メモリ不足なら章構築を延期」はそのまま生きる。 CSSキャッシュの版は12(本家11と記録長が違う)
  • 章の版は46: 本家の40〜45と旧ツリーの40〜49のどちらの番号とも重ならない値。全書籍が初回オープンで 配置を作り直す
  • 語の並列配列: 本家の wordLinkIds と当版の wordVerticalBehaviors を両方保持し、縦書きの追加・削除経路 でも歩調を合わせた
  • 語の追加: 本家のリンク識別(語ごとのlink id)と当版の縦書き分岐を同じ関数で両立。縦書き本では本家の 表の段組みを使わず平坦化に固定(段組みは横書き座標で置かれるため)
  • ページ送りボタン: 本家の長押しスキップと、当版の右開き(RTL)での左右反転・側面ボタンの上書きを合成
  • 読書画面は本家の道具箱型メニューなど新UIをそのまま残し、縦書き判定・RTLページ送り・AA見送り・ 挿絵ページの残像取りだけを当てた

実機(2026090606-diag、vertical-test-suite.epub 全16章を写真で照合): テスト1〜16すべて旧ツリーと同じ 表示。約物・記号の向き(UAX#50)、補助面漢字、互換漢字の代替、圏点5種と入れ子解除、実体参照2経路、 欧文語間空白、小書き仮名の右寄せ、40行の長大段落と1300字の漢字連続、太字/下線/取り消し線のタグ版と クラス版。20分・177描画・368回のボタン操作で異常終了なし。診断: ページ描画1.2〜1.5秒(字形領域の 作り直しが毎ページ1〜4回、計39秒)、空き最低3.3KB(旧ツリーの同種の読書でも4.3〜16KBまで落ちていた)、 CSSは20規則を完全読み込み、resolveStyle の結果が診断に出る。

旧ツリーから引き継いだ既知の差(後退ではない): 縦書きの段落配置(中央/末尾寄せ)は未実装(text-indent は 2026-09-06 に対応、下の「縦書きの書式対応の拡充」)、Extra Paragraph Spacing ONでは自動字下げが無効、 中黒「・」(U+30FB/半角U+FF65)の行頭禁則(同日に表へ追加)、読書後の一覧画面が遅い。

段階6: 挿絵(2026-09-06、完了・商業書籍1冊とテスト書2冊で実機確認)

KADOKAWA テンプレートの縦書きライトノベル(旧ツリーが 2026-08-31 に検証したのと同じ本)で、 2026090606〜0608 は挿絵の寸法が開くたびに変わった(容器いっぱい/小さく/その中間)。診断で原因が 3つ重なっていると分かり、順に直した:

  1. 部分キャッシュを完全扱いしていた。本家1.6.0の保存方式は規則索引・選択子・書式を連続領域で倍々に 伸ばすため、最大連続ブロック18〜22KBの断片化ヒープでは32KBへの成長に失敗し、テンプレート 3,172規則のうち296件で打ち切る。本家はそれを「部分キャッシュ」として保存し、章構築はそれを 読み込み成功として使っていた。画像用クラスは切り落とされた側にあるので fit ... rules=0 になる。 → 部分キャッシュを読んだ章構築は、旧ツリーと同じく章ごとの選別解析をやり直す (CssParser::lastCacheLoadPartial)。章の版を47に上げ、規則なしで組んだ章を作り直させた
  2. 選別解析にも全体解析用の門番(空き64KB)が掛かっていた。章構築時の空きは47〜66KBなので、 6ファイル全部を読み飛ばす回と読める回が混在し、同じ挿絵が別の大きさになった。 → 選別解析は24KBで走らせる(登録は数十規則、初期領域は索引1KB・選択子4KB・書式16件、 ファイルは512バイトずつ流し読み)。全体解析の64KBはそのまま
  3. 診断が章構築を遅くしていた。Kobo由来の koboSpan が全文に付く本で、クラス付きspanごとの 診断1行がSDカードへの書き直し1回になり、1章39回で数秒を食い、CSSと画像の行を記録の輪から押し出した。 → 太字・斜体・線・圏点のどれかに解決したspanだけ書く

2026090609-diag の結果は旧ツリーの 08-31 の記録と同値: 3,172規則中7件だけ登録(result=0 filtered=1)、 166x180→70x76(max-height 10%)、195x244→60x76、199x1345→89x605(80%)、570x1600→269x756(fit のみ)。 章題ページの羊とロゴが1文字大なのは著者指定(高さ10%)で、0606で大きく見えていたほうが誤りだった。 章の初回表示は17秒(構築9.2秒+選別解析+挿絵5枚の前生成)で、その間は入力を受け付けない。 旧ツリーの「画像の多い章で11秒」より長く、選別解析の分が乗っている。改善候補。

テスト書2冊の結果(同じ 2026090609-diag):

  • image-size-test.epub(横書き、実書籍954冊の実測寸法13種、各ページ画像1枚のみ): 13ページ全部合格。 外枠の欠け・円の歪み・四隅の向きの異常なし。縦長9種(720×1024〜1748×2480)は全面に収まり、 横長4種(2048×1589・2868×2048・3049×1300 など)は横幅いっぱいで上寄せ、下は余白。 最大の5.87Mpx(2868×2048)と最長辺3049×1300も止まらず表示。1px縞は縮小率によって グレーに潰れるか、間引き位置しだいで真っ黒/薄いグレーの一様な塗りになる(2048×1589で観察)。 縮小器が近い画素を拾うだけで平均を取らないためで、線画のテスト図形でしか目立たず、対処しない。 3049×1300(約6分の1)では中央の小さな文字が読めなくなる。これは判定外の「実用下限」の観察項目。
  • vertical-image-test.epub(縦書き): 全章が旧ツリーの既知の挙動と一致 (外字画像は1マス幅の独立した列を占める、全面・40%・横長は正しく縮小、1bit/4bit PNG と svg包みは表示)。 第3章(同じ縞画像を4経路で参照)は4枚とも表示。画像は列の流れに入り、ページの右端に画像、その左に 次の見出しが続く並びで、読み順(見出し→画像→次の見出し)どおり。1ページに画像1枚ずつ、2〜5ページ目。
  • 縦書き本のページ送り(左が進む、側面ボタン)は想定どおり。章境界の描画は7.4秒 (章の組版5.2秒+字形再構築3回)、灰色階調の描画は約2.2秒で、いずれも旧ツリーと同じ傾向。

横書きの回帰確認で1件直した(2026090610-diag、章の版48)。横書きの圏点は、対象の文字列を1回で まとめて処理し先頭の1文字にまとめて全部の点をルビとして付けていたため、13文字の強調で先頭の 「は」に13個分のルビが乗り、ルビ幅に合わせて「は 一 文」と行が引き伸ばされた。横書きの本文は CJK文字を1文字1トークンに分けて積むので、トークンごとにその文字数分の点を付ける形に直した。 旧ツリーでも同じ挙動だった(移植で持ち込んだ既存の不具合)。

商業EPUB蔵書の取得し直しに伴うテストEPUB3冊の更新(2026-09-06、調査とテスト資産のみ・FW変更なし)

蔵書の取得し直しが完了し(旧 protect-epub は廃止、new-protect-epub に一本化)、2,256ファイル・識別子で重複を除いて **1,941冊(リフロー型878・固定レイアウト1,063、縦書き1,814)**を対象に、これまで3回に分けて行った調査 (挿絵の書き方955冊、画像寸法954冊、使用文字とフォント収録範囲955冊)を一つの集計器で取り直した。本文は保存も引用も していない(タグの形・クラス名・CSSの値・画像の見出し部・文字の出現数だけ)。集計器と数値だけの結果は e-ink/test-assets/corpus-survey/(リポジトリ外)に置いた。

蔵書の性質で新しく分かったこと: リフロー型はほぼ全冊がKobo書店の形式(KEPUB)で(ルビを持つ852冊の全部で確認)、本文のすべての文字列が読書位置記録用の <span class="koboSpan" id="kobo.N.M"> で包まれている。ルビの親文字・読み・外字画像も一つずつ包まれ、 ルビ160万箇所のうち約39万箇所(528冊)が親文字一字ごとに読みを分けた形(ruby の中に rt が複数)、 rp の括弧も span で包まれる(21冊・23,001箇所、XHTMLの規則上は不正だが実在する)。これまでのテスト書は どれもこの形を含んでいなかった。

数値の主な変化と新しい発見(リフロー型878冊、詳細は test/epubs-ja/README):

項目 前回 今回
挿絵の寸法指定が max-* クラス ほぼ全て 757冊(固定幅8・width属性2・style属性2)
svgで包んだ挿絵 527/955冊・約7万箇所 408/878冊・8,071箇所(コミックは全ページ)
無圧縮格納の画像 0/10万枚超 0/209,851枚
文庫(リフロー)画像の中央値/上位10% 844×1200/1120×1600 1135×1600/1440×2048(加わった出版社の口絵が大きい)
画素数最大 2868×2048(コミック) 2868×2048(文庫の見開き口絵に移った)
外字画像の大きさ指定 1em角のみ想定 幅2em 833・幅1.5em 74・幅6em 62・高さ3em 82・高さ4em 87箇所
挿絵だけのページ 未調査 vrtlファイル9,841・hltr(横組み指定)ファイル6,355ページ、hltrの画像ページを持つ本352冊
ルビ付き外字画像 1例を想定 1,329箇所・120冊
upright/sideways クラス 未調査 147冊・3,605箇所/226冊・451箇所。CSSは接頭辞付き(-webkit-/-epub-)だけで書かれる
段落の text-indent/text-align クラス 未調査 421冊/705冊。負の値(ぶら下げ、text-indent:-Nem; padding-top:Nem)1万箇所超
span の font-size クラス 未調査 641冊(90% 3,864・110% 2,087・0.7em 1,002箇所)
フォント未収録の文字 ℃・溺蓮煉・𠮟 など 3書体共通で未収録はなし(1冊限りのタイ文字・ヘブライ文字・合字を除く)。BIZ UD系のみ Ⓒ(56冊)・逬(4冊)・溺(11冊)・煉(3冊)が元フォント未収録。IVS異体字選択子 U+E0100 が1冊にあり、現状は読み飛ばさない
頻出記号 — ─(U+2500)637冊・31万回(ダッシュ「──」)、〝〟で始まる段落6,223(禁則表に未登録)、⁉634回・‼

テストEPUBへの反映(3冊とも epubcheck は従来からの意図的な不整合以外エラーなし。テスト17は実機で5項目すべて合格(2026-09-06、2026090611): 文の分割・ルビ・一字ずつの読み・rp の括弧・装飾クラスの内側の区切り要素のどれも「なし」と同じ見え方で、Kobo形式の本は現状の解析器でそのまま読める。テスト18も実機確認済み: 予想どおり upright/sideways の指定は無視され、指定あり・比較の両段落が同じ見え方(矢印は既定どおり下向き、全角の!?ーは正立のまま)。周りの字への影響は無い。テスト19・20も実機確認済み: 19は text-indent(正負)・padding-top・margin-top の全部が無視され、全段落が天から始まる(予想どおり)。ただし text-align:center の段落の最初の列で、末尾の1字の前に空きが入る現象を新しく確認した(同じ形の文で right では出ない。要調査)。20は span の font-size が全部無視され、同じ大きさで出る(予想どおり。ルビと列は乱れない)。テスト21も実機確認済み(NotoSansJP): ─―‐は縦の線(合格)、⁉‼♡♥※●◆①は正立(合格)だが Ⅱ(U+2161)は写真では横倒しに見えたが、2026-09-08 に拡大写真で正立を確認(セリフの無い2本の縦線が低解像度の写真で「=」に見えていただけ。変換済みフォントの字形も縦長で保存されていることを確かめた。規格どおりで問題なし)、Ⓒ・逬は表示(合格)、葛の直後に四角が出る=IVS未対応を確認(同日 2026090612-diag で修正・実機合格)、数字の桁数別の形は合格、〝〟の禁則は列の変わり目に来ず判定できず(文の長さを調節する)。image-size-test.epub は14ページ全部合格(2026-09-06、2026090611): 新しく入った 3360×1187・2868×2048(5.87Mpx)・2700×1410・1832×2600 を含め、どれも幅いっぱい・外枠四辺あり・正円で、横長は上寄せ。vertical-image-test.epub v11 の第10〜12章も実機確認済み: 第10章は幅1.5em・2em・2em横長・高さ3em・4em・幅6emの全部が指定どおりの大きさで本文と重ならない(列をまたぐ幅の絵は列を割り当てて上下中央に置かれる)。第11章は hltr+img/hltr+svg/vrtl+img の挿絵ページが全部表示され、送り順 A→B→C→D→第12章 も正しい。D の文字ページは既知の制限どおり縦で出る。第12章は区切り要素に包まれた外字・一字ずつの読み・分割された文が合格、ルビ付き外字の読みは既知の制限どおり付かない。3冊とも実機確認完了):

  • image-size-test.epub を新しい実測値で作り直した(13→14ページ)。文庫の中央値・上位10%・最長辺 3360×1187・ 最大画素 2868×2048、コミック現行の最長辺 2700×1410・最大画素 1748×2480、旧世代の最頻 710×1024、大判の最大 1832×2600。 まとめページに前回との比較表と測りかた(固定レイアウトは各本12枚前後の見出し部を読み、残りはsvgのimage属性。 読んだ46,166枚のうち属性と食い違い68枚)を載せた。生成器 test-assets/build_image_size_test.py を更新
  • vertical-image-test.epub を v11 に(9→18ページ)。第7・9章の数値を更新し、第10章「1em以外のem単位の外字・小画像」、 第11章「別ファイルの挿絵ページ(hltr+img/hltr+svg/vrtl+img/hltr文字ページの4ファイル、送り順の確認)」、 第12章「Kobo形式の区切り要素と外字・ルビ」を追加
  • vertical-test-suite.epub にテスト17〜21を追加(32→42章)。17: Kobo形式の区切り要素(同じ文の「あり/なし」を対にして 比較。文の分割・ルビ・一字ずつのルビ・rp・縦中横・傍点・太字)、18: upright/sideways の明示指定 (当版は未対応と分かっているので記録用)、19: 電書協テンプレートの字下げ・ぶら下げ・天アキ(同じく未対応の記録用)、 20: 文中の font-size 指定、21: 頻度順の記号(─―‐の向き、〝〟の禁則、⁉‼♡♥※●◆①Ⅱの正立、Ⓒ・逬の収録、 IVS の読み飛ばし、数字の桁数別の形)。テスト8・9の解説に再調査の冊数を1文ずつ追記

実装への含意(今回はFWを変えていない): 優先度順に、(1) IVS(U+E0100〜E01EF)をVS15/16と同じく読み飛ばす(2026-09-06 実施: 読み飛ばしを VS1〜VS16 の U+FE00〜FE0F 全部と IVS の U+E0100〜E01EF に広げた。2026090612-diag でテスト21の「葛󠄀」の四角が消えたのを実機確認)、 (2) 〝(U+301D)を行末禁則・〟(U+301F)を行頭禁則に加える、(3) 接頭辞付きの text-orientation を読んで upright/sideways を効かせる、(4) 縦書きの text-indent(正負)と margin-top の天アキ、(5) span の font-size、(6) text-align:center の列で末尾の1字の前に空きが入る件の原因調査。(3)〜(5) は市販の本での 使用冊数が多く、既知の制限として残していたものに実際の頻度が付いた形になる。

縦書きの書式対応の拡充(2026-09-06、前節の実装候補(2)〜(6)を優先度順に実施・2026090613-diag・実機確認済み)

前節の「実装への含意」(2)〜(6)を順に片付けた。(5)だけは見送り(理由は下)。

(2) 禁則表の拡充(VerticalTextUtils.h): 〝(U+301D)を行末禁則、〟(U+301F)を行頭禁則に加えた。同じ性質の “‘(行末)と ”’(行頭)も一緒に登録。あわせて JLREQ の行頭禁則のうち表に無かった類——ハイフン類「‐–〜゠~」 (cl-03)、中点類「・・」(cl-05。段階5の節で候補にしていた中黒の件はこれで解消)、分離禁止「‥…〳〴〵」(cl-08)、 繰返し記号「々〻ゝゞヽヾ」(cl-09)——を行頭禁則に加えた。

(6) text-align:center の列で末尾の1字の前に空きが見える件は、原因が分かり不具合ではなかった。 18pt NotoSansJP で 1マスは38px。写真の【中央】の第1列は 【中央】この段落は+半角17字+。+縦 で 715px、【右】の第1列は 686px、 【基準】の第1列は 684px(次の1字を足すと722pxで入らない)。つまり列の容量は715〜721pxで、【中央】の列だけが 容量ぎりぎりまで詰まっていた。「。」の後の空きを写真の画素で測ると【中央】も【右】も同じ47px——縦書きの句点は 縦書き用の字形「︒」で1マスを使い、点がマスの上端に付くので残りが空きに見える、いつもの形。最後の1字が 状態表示の直上まで下がったので目についただけで、中央揃え特有の現象ではない。中央揃え・右揃えは縦書きでは列の 位置を変えないという現状の扱いはそのまま。

(3) 文字の向きの明示指定(CssParser・ChapterHtmlSlimParser・TextBlock・EpdFontFamily.h): text-orientation(upright/sideways/sideways-right/mixed)を、市販の本が使う -webkit-/-epub- 接頭辞付きも 含めて読む。縦中横の text-combine-upright: all(電書協の tcy クラスが使う -webkit-text-combine: horizontal の形も)も同じ欄で持つ。仕組み: ページの保存形式に字ごとの向きの配列を足す代わりに、語の書式ビットの空きの1ビット (VERTICAL_FLIP)を「文字の種類から決まる向きを反転する」印にした。描画側は従来どおり文字の種類から向きを決め、 印が付いていれば逆にする。これで章の保存形式(SECTION_FILE_VERSION)は変えずに済む。

  • 正立指定: 半角の英数字・記号・矢印は1字ずつ1マスを占め、マスの中央に置く。もともと正立する字(漢字・かな・全角記号) はそのまま
  • 横倒し指定: もともと倒れる字(欧文・数字・括弧・長音)は従来どおりひとつながりの語に、正立する字(全角の!?、かな) は1字ずつ倒す。1〜2桁の数字と「!?」は自動で縦中横になる字なので、横倒し指定の中では印を付けて倒す
  • 縦中横指定: 自動でならない並び(3桁の数字、「EU」「3.5」)を1マスに正立で入れる。幅が1.5マスを超える並びは 横倒しのまま。マスの幅を超える分は左右に均等にはみ出す

(4) 縦書きの text-indent と天アキ(ParsedText.cpp・CssParser・CssSelectorUsage): 縦書きの列組みで 明示の text-indent を正負とも効かせる。負の値(ぶら下げ)は電書協テンプレートが padding-top と対で書くので、 列の天のずらし(前からあった)が空けた分だけ第1列を上に戻す形になる。対が無い場合は天のずらしの範囲で止める。 「Extra Paragraph Spacing」ONでも明示の指定は効かせる(横書きの本家実装はONのとき正の明示指定も捨てるが、縦書きの 市販本ではこのクラスが特定の段落だけに付いていて著者の意図があるため。自動の字下げは従来どおりONで無効)。 天アキ(margin-top/padding-top)は列の天のずらしとして前から入っていたのに効かなかった原因は、電書協テンプレートが これらのクラスを .vrtl .h-indent-1em、.hltr .start-1em のように html 要素の書字方向クラスを親に付けた二段の指定子で 書いていて、当版の書式解析器が空白を含む指定子を全部捨てていたこと。テスト19の指定は全部この形で、実物の テンプレート(1,905規則)では956規則がこの形だった。そこで二段の子孫指定子(a b、a・bは要素名/.クラス/要素名.クラス) を読むようにした。保存は「a b」の1個の空白区切り、照合はパーサが開いている先祖要素の要素名とクラスを積んでおいて (ancestorStack)、要素の書式を引くときに先祖ごとに「先祖 .クラス」「先祖 要素名」「先祖 要素名.クラス」を引く。 CSSの優先度どおり単段の規則より後に重ねる。章ごとの絞り込み(CssSelectorUsage)は二段とも章に現れるときだけ残す。 書式の保存形式は CSS_CACHE_VERSION 12→13(1バイト追加+空白付きの鍵)で、初回オープン時に書式の保存だけ作り直しが走る。

  • 副作用として横書きの本でも .hltr .h-indent-1em{text-indent:-1em; padding-left:1em} などが効くようになる(横書きの ぶら下げは本家の組版がもともと負の text-indent と padding-left を扱える)
  • メモリの面: 電書協テンプレートは単段625規則の時点で固有の書式本体が583種と上限256を超えていて、すでに 「保存は打ち切り、章ごとに絞り込んで読み直す」経路に乗っている。二段の規則が増えても同じ経路のままで、 章ごとの絞り込み後の規則数は変わらず少ない

(5) span の font-size は見送り。 当版のフォントは大きさごとに別ファイルの点画像で、同時に読み込むのは1つの 大きさだけ(メモリ)。ルビ・上付きの50%縮小だけが描画にあり、70/90/110/150%の拡縮を足すと点画像の拡縮で字が崩れる。 もう一つの大きさを読み込む案は空きメモリ(読書中48〜80KB)に対して大きすぎる。既知の制限として残す。

実機での見かた(vertical-test-suite.epub は解説を新しい合格条件に書き換え、テスト18に【縦中横】、テスト21に 【引用・連続】の段落を足した。epubcheck は従来の意図的な不整合のみ):

  1. テスト18: 見かた(1)〜(5)。正立の A x 3 & + と →、横倒しの!?ー、縦中横の 100・EU・3.5(1234 は横倒しのままが正) → 実機で5項目とも合格(2026-09-06、2026090613-diag、NotoSansJP 18pt)。A x 3 & + が1字1マスで正立、→が右向き、!?が横倒し(【比較】側は正立のまま)、100・EU・3.5 が1マスに横並び、1234 は横倒し
  2. テスト19: 見かた(1)〜(4)。字下げ1em、ぶら下げ1em/2em(一行目は天から、二行目以降が下がる)、天アキ1em/3em → 実機で(2)〜(4)合格、(1)は判定保留(2026-09-06、2026090613-diag)。ぶら下げは二行目以降(ページをまたぐ続きの列も)、天アキは全行が指定の文字数分下がり、中央・右揃えは列の位置が変わらない。二段の指定子 .vrtl .xxx が読めていることの確認を兼ねる。(1)は【字下げ1em】が一字分下がっているものの、端末の「Extra Paragraph Spacing」がOFFで【基準】にも当版の自動の字下げ(1マス)が入り、差が見えなかった(写真の見直しで判明。仕様どおりで不具合ではない)。テスト書に【字下げ2em】の段落を足し、再確認で (1)も合格: 【基準】(自動の1マス)≈【字下げ1em】<【字下げ2em】の順に下がる。なお em の換算は横書きと同じく字面の高さ(18pt NotoSansJP で44px)なので、1em は1マス(38px)より一割ほど深い。実用上の差は小さく、既知の癖として残す
  3. テスト21: 見かた(2)。【引用】【引用・連続】で〝が列末・〟が列頭に残っていないこと → 実機(18pt)では9か所の列の変わり目のどこにも引用符が来ておらず、字形の送り幅からの計算でも引用符が変わり目に掛かる位置に無かったため、禁則が働いたかは判定できず。三字ごとに引用符が来る【引用・密】(〝一〟〝二〟…)を足したが、実機では列が18マスで3マスの組が6組ずつ割り切れ、これも変わり目が全部組の境目に落ちて判定できず。そこで組の間に0〜2字のかなを挟み、列の長さが14〜26字のどれでも禁則なしなら必ず二か所以上で違反が出る並びを計算で作って差し替え、再確認で合格: 「…〝三〟い」の列と「…ちつ〝丑〟てと」の列がそれぞれ1マス空けて終わり、次の列が〝四〟・〝寅〟で始まる(列末に来るはずの〝が引き取られた形)。全部の列で三字の組が崩れず、列末の〝・列頭の〟は無い。ほかの段落(線・正立記号・収録・異体字・数字)は前回と同じ結果で退行なし
  4. 退行確認: テスト17(Kobo形式)が前回と同じこと、商業書籍1冊で会話文のぶら下げ・扉の天アキが崩れていないこと → 商業書籍1冊(電書協形式、KEPUB)で確認済み(2026-09-06): 会話文の「」の行は天から、地の文は一字下げ、章扉の挿絵ページと章の最初のページも従来どおりで、二段の指定子が効くようになった副作用は見えない。テスト20(font-size、見送り)は前回と同じ見え方で退行なし

About

XTEINK X3とX4 Proで縦書きEPUBを読むための改造版

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages