トップ «前の日記(2021-07-31) 最新 次の日記(2021-08-15)» 編集

日々の破片

著作一覧

2021-08-13

_ 久々にpdf-reader

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に任せるようにしたほうが良い)


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|

ジェズイットを見習え