Module: VivlioStarter::CLI::Build::RotateTableImages
- Defined in:
- lib/vivlio_starter/cli/build/rotate_table_images.rb
Overview
回転テーブルのページ画像化(Kindle 専用の劣化経路)
Constant Summary collapse
- OUT_DIR_NAME =
生成物の置き場(ワークスペース直下)。final clean がまとめて掃除する。
'rotate-tables'- CONTENT_WIDTH_PX =
版面幅が何 px になるようにラスタライズするか。
HeadingImageComposer::RENDER_WIDTHと揃えてある(Kindle 端末幅 1072px を上回り、拡大しても粗が出ない)。 1400- TRIM_FUZZ =
余白の切り詰め。版面の中で表は centering + scale されており、素のままでは 周囲が大きく空く。Kindle は画像を画面幅へ伸ばすので、詰めるほど字が大きくなる。
'1%'- BORDER_PX =
12
Class Method Summary collapse
- .arm!(enabled) ⇒ Object
- .armed? ⇒ Boolean
- .available?(anchor_id) ⇒ Boolean
-
.collect_anchor_ids(dir = Common::BUILD_HTML_DIR) ⇒ Hash{String => String}
html/ の全 HTML から回転テーブルの内部 ID を集める。.
-
.crop(source, destination, geometry) ⇒ Object
版面(柱・ノンブルの内側)だけを切り出し、余白を詰めて細い白縁を付ける。.
-
.crop_geometry ⇒ Hash?
版面の切り出し寸法(px)と、そのために必要な解像度。.
-
.extract!(pdf_path) ⇒ Integer
回転テーブルのページを切り出して画像化する。.
- .extract_subset(pdf_path, pages, output) ⇒ Object
- .image_path(anchor_id) ⇒ Object
- .mm(value) ⇒ Object
- .output_dir ⇒ Object
-
.rasterize_pages(pdf_path, pages) ⇒ Object
必要なページだけを 1 本の小さな PDF へ抜いてからラスタライズする。 100MB 級の本文 PDF を表の数だけ開き直すのは無駄が大きいため。.
-
.release! ⇒ Object
待っている枝を解放する。PDF 枝が例外で落ちても必ず呼ぶこと(デッドロック防止)。.
-
.render_one(subset_pdf, index, anchor_id, geometry, tmp) ⇒ Object
1 ページを PNG 化し、版面の外(柱・ノンブル)を落として余白を詰める。.
-
.wait_until_ready! ⇒ Object
待った時間を必ず報告する。.
-
.warn_missing_pdf(ids) ⇒ Object
縮退の案内(warning-messages-actionable の方針) =============================================================.
- .warn_unresolved(ids) ⇒ Object
Class Method Details
.arm!(enabled) ⇒ Object
75 76 77 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 75 def arm!(enabled) @gate = enabled ? Queue.new : nil end |
.armed? ⇒ Boolean
79 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 79 def armed? = !@gate.nil? |
.available?(anchor_id) ⇒ Boolean
59 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 59 def available?(anchor_id) = File.exist?(image_path(anchor_id)) |
.collect_anchor_ids(dir = Common::BUILD_HTML_DIR) ⇒ Hash{String => String}
html/ の全 HTML から回転テーブルの内部 ID を集める。
107 108 109 110 111 112 113 114 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 107 def collect_anchor_ids(dir = Common::BUILD_HTML_DIR) prefix = PreProcessCommands::TableConverter::ROTATE_ID_PREFIX Dir.glob(File.join(dir, '*.html')).each_with_object({}) do |path, found| File.read(path, encoding: 'utf-8').scan(/\bid="(#{prefix}[^"]+)"/o).flatten.each do |id| found[id] ||= path end end end |
.crop(source, destination, geometry) ⇒ Object
版面(柱・ノンブルの内側)だけを切り出し、余白を詰めて細い白縁を付ける。
左右は inner/outer の狭いほうで対称に切る。柱とノンブルは天地の余白に 入るので、左右の切り幅は「版面を欠かない」ことだけ満たせばよく、 ページの表裏(recto/verso で inner と outer が入れ替わる)を判定しなくて済む。
180 181 182 183 184 185 186 187 188 189 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 180 def crop(source, destination, geometry) command = %(magick "#{source}" ) + %(-crop #{geometry[:width]}x#{geometry[:height]}+#{geometry[:left]}+#{geometry[:top]} +repage ) + %(-fuzz #{TRIM_FUZZ} -trim +repage ) + %(-bordercolor white -border #{BORDER_PX} "#{destination}" > /dev/null 2>&1) return true if system(command) && File.exist?(destination) Common.log_warn("[rotate-table] 画像の切り出しに失敗しました: #{File.basename(destination, '.png')}") false end |
.crop_geometry ⇒ Hash?
版面の切り出し寸法(px)と、そのために必要な解像度。
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 193 def crop_geometry page = Common.configured? ? Common::CONFIG.page.to_h : nil return nil unless page page_w, page_h = Common.resolve_page_size(page).map { Units.length_to_mm(it) } side = [mm(page[:margin_inner]), mm(page[:margin_outer])].min top = mm(page[:margin_top]) bottom = mm(page[:margin_bottom]) return nil unless page_w && page_h content_w = page_w - mm(page[:margin_inner]) - mm(page[:margin_outer]) return nil unless content_w.positive? dpi = (CONTENT_WIDTH_PX / (content_w / Units::MM_PER_INCH)).round px = ->(value) { (value * dpi / Units::MM_PER_INCH).round } { dpi:, left: px.call(side), top: px.call(top), width: px.call(page_w - (side * 2)), height: px.call(page_h - top - bottom) } end |
.extract!(pdf_path) ⇒ Integer
回転テーブルのページを切り出して画像化する。
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 120 def extract!(pdf_path) ids = collect_anchor_ids.keys.sort return 0 if ids.empty? unless File.exist?(pdf_path) warn_missing_pdf(ids) return 0 end pages = PdfPageMapExtractor.new(pdf_path).pages_for(ids) warn_unresolved(ids - pages.keys) if pages.size < ids.size return 0 if pages.empty? rasterize_pages(pdf_path, pages) end |
.extract_subset(pdf_path, pages, output) ⇒ Object
154 155 156 157 158 159 160 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 154 def extract_subset(pdf_path, pages, output) command = %(qpdf "#{pdf_path}" --pages "#{pdf_path}" #{pages.join(',')} -- "#{output}" > /dev/null 2>&1) return true if system(command) && File.exist?(output) Common.log_warn('[rotate-table] 回転テーブルのページを取り出せませんでした(qpdf)') false end |
.image_path(anchor_id) ⇒ Object
57 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 57 def image_path(anchor_id) = File.join(output_dir, "#{anchor_id}.png") |
.mm(value) ⇒ Object
212 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 212 def mm(value) = Units.length_to_mm(value) || 0.0 |
.output_dir ⇒ Object
55 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 55 def output_dir = File.join(Common::BUILD_DIR, OUT_DIR_NAME) |
.rasterize_pages(pdf_path, pages) ⇒ Object
必要なページだけを 1 本の小さな PDF へ抜いてからラスタライズする。 100MB 級の本文 PDF を表の数だけ開き直すのは無駄が大きいため。
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 138 def rasterize_pages(pdf_path, pages) FileUtils.mkdir_p(output_dir) geometry = crop_geometry return 0 unless geometry Dir.mktmpdir('vs-rotate') do |tmp| ordered = pages.values.uniq.sort subset = File.join(tmp, 'pages.pdf') return 0 unless extract_subset(pdf_path, ordered, subset) pages.count do |anchor_id, page| render_one(subset, ordered.index(page) + 1, anchor_id, geometry, tmp) end end end |
.release! ⇒ Object
待っている枝を解放する。PDF 枝が例外で落ちても必ず呼ぶこと(デッドロック防止)。
82 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 82 def release! = @gate&.close |
.render_one(subset_pdf, index, anchor_id, geometry, tmp) ⇒ Object
1 ページを PNG 化し、版面の外(柱・ノンブル)を落として余白を詰める。
163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 163 def render_one(subset_pdf, index, anchor_id, geometry, tmp) raw = File.join(tmp, "#{anchor_id}-raw") rasterize = %(pdftoppm -png -r #{geometry[:dpi]} -f #{index} -l #{index} -singlefile ) + %("#{subset_pdf}" "#{raw}" > /dev/null 2>&1) unless system(rasterize) && File.exist?("#{raw}.png") Common.log_warn("[rotate-table] ページの画像化に失敗しました: #{anchor_id}") return false end crop("#{raw}.png", image_path(anchor_id), geometry) end |
.wait_until_ready! ⇒ Object
待った時間を必ず報告する。
待っているのは本文 PDF が組み上がることであって、画像化そのものではない (実測 1.8 秒)。この待ちは PDF 枝が長い構成では枝の陰に隠れて壁時計に現れないが、 索引・用語集を切ると PDF 枝が半分になって臨界経路が EPUB 側へ移り、そのまま 壁時計に乗る。ステップ計時には現れない時間なので、ここで出さないと 「EPUB 枝が遅い」と読み違える。
91 92 93 94 95 96 97 98 99 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 91 def wait_until_ready! return unless @gate Common.log_info('[rotate-table] 本文 PDF の完成を待っています…') started = Process.clock_gettime(Process::CLOCK_MONOTONIC) @gate.pop waited = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started Common.log_info(format('[rotate-table] 本文 PDF を %.2f 秒待ちました', waited)) end |
.warn_missing_pdf(ids) ⇒ Object
=============================================================
縮退の案内(warning-messages-actionable の方針)
218 219 220 221 222 223 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 218 def warn_missing_pdf(ids) Common.log_warn("回転テーブル #{ids.size} 件を Kindle 用に画像化できませんでした(本文 PDF がありません)", detail: "対処: config/book.yml の output.targets に pdf を足すと画像化されます。\n" \ '(例: targets: pdf, epub, kindle)そのままでも Kindle は素の表として読めますが、' \ '回転せず横長の表は見切れます。') end |
.warn_unresolved(ids) ⇒ Object
225 226 227 228 229 |
# File 'lib/vivlio_starter/cli/build/rotate_table_images.rb', line 225 def warn_unresolved(ids) Common.log_warn("回転テーブルのページ位置を特定できませんでした: #{ids.join(', ')}", detail: '対処: vs build --clean で中間生成物を作り直してください。' \ '解消しない場合は該当の :::{.rotate-table} ブロックをお知らせください。') end |