著作一覧 |
pdf-readerを使って久々に処理をしている。
1つバグ(というか考慮不足)を見つけたが、問題となるPDFを生成できない=スペックを書けないので、PRしてないのがあってパッチして(というか派生クラスを作って)利用しているが忘れそうなのでメモ。
現象は、メディアボックスに余白がたくさんある場合(たとえばYのトップが600、ボトムが200)に発生する。
この例だとテキストが表示される領域の高さは400となる。フォントサイズを仮りに20と置くと20行の領域をpdf-readerは確保する。
row_multiplierは、(600-200)/20の20となる。
ここでY座標580(テキストの座標はトップが大)のテキストがあると、pdf-readerは20 - (580 / 20) から-9行目にテキストを配置しようとする。もちろん負値の座標には配置できないので該当テキストは非表示扱いとなり、page#textでは取得できない。
解決させるには、マージンボトムに相当する200をオフセットとしてテキスト座標から引けば良い。そうすると 20 - ((580 - 200) / 20) で1が求められ、0から数えて1行目というそれなりに正しい位置にテキストが出力される。
class MyLayout < PDF::Reader::PageLayout def initialize(runs, mediabox) super @y_offset = mediabox[1] # メディアボックスのトップ座標をオフセットとして保持 end def to_s return "" if @runs.empty? page = row_count.times.map { |i| " " * col_count } @runs.each do |run| x_pos = ((run.x - @x_offset) / col_multiplier).round y_pos = row_count - ((run.y - @y_offset) / row_multiplier).round # puts "x: #{x_pos}, y: #{y_pos}, #{run.y}, #{row_multiplier}, r_count: #{row_count}, c_count: #{col_count}, text: #{run.text}" if y_pos <= row_count && y_pos >= 0 && x_pos <= col_count && x_pos >= 0 local_string_insert(page[y_pos-1], run.text, x_pos) end end interesting_rows(page).map(&:rstrip).join("\n") end end class MyReceiver < PDF::Reader::PageTextReceiver def content MyLayout.new(@characters, @mediabox).to_s end end def page_pdf_reader_text(page) receiver = MyReceiver.new # おれさまレイアウターを使う page.walk(receiver) receiver.content end
で、原因はわかったのでPR作ろうかとWordで上下にたくさんマージンをとったページを作ってPDF化したが、Wordは意味的に正しいメディアボックス(要はデバイス自身のサイズということでA4相当)を作っているので再現できない。
再現ファイルを作って提供もできないし、元ネタのPDFはスペック用に提供するにはでかすぎる(上にフォントが日本語だから視認も難しかろう)なので何か手段を見つけるまでは、放置せざるを得なくなった。X座標については誰かが見つけたらしくオフセット処理が入っているだけに惜しい。
別件として、ToUnicodeを持たないフォントであっても、OrderingがJapanの場合の簡単な変換方法を見つけた。以前メモした覚えがあるが見当たらないので再度書くと、popplerの変換ファイルを利用すると楽。
適当にインストールしたUbuntuだと /usr/share/poppler/cidToUnicode/にAdobe-Japan1 というファイルがあって、これが単純に00が1行目のコードとなっている。
0000 00a0 0021 0022 0023 ...
なので、これを使う。pdf-readerのFontをオーバーライドすれば良いのだが、面倒なのでraw_contentをそのまま利用して、
JAPAN = [] Patname.new('/usr/share/poppler/cidToUnicode/Adobe-Japan1').each_line do |line| JAPAN << line.to_i(16) end JAPAN.freeze def to_text(hs) hs.split('').each_slice(4).map(&:join).map{|o| JAPAN[o.hex]}.pack('U*') end def page_text(page) content = page.raw_content all = [] pos = 0 while m = /(\[?<.+?)T(J|j)/.match(content, pos) do texts = $1 if texts[0] == '[' pos0 = 0 while m0 = /<([^>]+)>/.match(texts, pos0) do all << to_text($1) pos0 = m0.end(0) end else all << to_text(texts[1..-2]) end pos = m.end(0) end all.join('') end
(テキストのポジションが飛びまくるPDFの場合は、この方法で得たテキストは無茶苦茶なので、やはりpdf-readerのtext-runnerに任せるようにしたほうが良い)
ジェズイットを見習え |