Skip to content

Commit

Permalink
Develop/v0.1.6 (#7)
Browse files Browse the repository at this point in the history
* mupdf free & more readme

* line_margin option

* refactoring
  • Loading branch information
ktaaaki authored Oct 14, 2020
1 parent d5d0cc0 commit ab0717e
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 57 deletions.
53 changes: 33 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,40 @@
[![Python Version](https://img.shields.io/badge/python-3.5|3.7|3.8-blue)](https://github.com/ktaaaki/paper2html)
[![Platform](https://img.shields.io/badge/platform-windows|macos|ubuntu-blue)](https://github.com/ktaaaki/paper2html)

It will convert a pdf paper to html pages. Only the format of single or double column is supported.
It will convert a pdf paper to html pages & show them using pdf-miner & poppler. Only the format of single or double column is supported.

2段組の論文をhtml表示するツールです
pdf-miner.sixとpopplerを使用して(2段組を含む)論文等をhtml表示するツールです.論文調のマニュアルでもきれいに表示できることもあります

<img width="1633" alt="demo" src="https://user-images.githubusercontent.com/4715386/94166499-54ecb480-fec6-11ea-8155-d44d192445fa.png">
Albanie, Samuel, Sébastien Ehrhardt, and Joao F. Henriques. "Stopping gan violence: Generative unadversarial networks." arXiv preprint arXiv:1703.02528 (2017).

## 依存環境のインストール
popplerとmu-pdfに依存しているので,環境に合わせてインストールしてください.
popplerに依存しているので,環境に合わせてインストールしてください.

### windowsの場合
`http://blog.alivate.com.au/poppler-windows/`

`https://mupdf.com/downloads/`
からpopplerをダウンロード+解凍して,環境変数にexeファイルのある場所のPathを通してください.

からpopplerとmupdfをダウンロード+解凍して,環境変数にexeファイルのある場所のPathを通してください.
例えば最新のバイナリ:poppler-0.68.0_x86を`C:¥Users¥YOUR_NAME¥Downloads`にダウンロードして展開した場合は,
システムの詳細設定の表示>システムのプロパティ>詳細設定>環境変数(N)...>ユーザー環境変数のPathを編集して
`C:¥Users¥YOUR_NAME¥Downloads¥poppler-0.68.0¥bin`を新規に追加してください.

### ubuntuの場合
```
> sudo apt install mupdf mupdf-tools poppler-utils poppler-data
> sudo apt install poppler-utils poppler-data
```
### macの場合
brew + pyenv + condaの環境でのみ動作確認しています.
anaconda(miniconda)の場合
```
> brew install mupdf-tools
> conda install -c conda-forge poppler
> conda install poppler
```
homebrewの場合
```
> brew install poppler
```
## 本体のインストール
python3とgitをインストールした後,
以下のコマンドで,作業ディレクトリにクローンしたpaper2htmlをインストールできます.
```
> git clone https://github.com/ktaaaki/paper2html.git
Expand All @@ -50,12 +56,6 @@ ipythonから
>>> import paper2html
>>> paper2html.open_paper_htmls("path-to-paper-file.pdf")
```
mac,linuxでは下記のインストールを済ませれば,右クリックメニューまたは自動化から

- 変換したいpdfを選択して,`open pdf as html`を選択する
- `~/paper2html/downloads`にブラウザからpdfを保存する(自動的に変換が起動)

ことで利用可能です.

開くブラウザは以下のように指定可能です.
```
Expand All @@ -68,14 +68,21 @@ mac,linuxでは下記のインストールを済ませれば,右クリック
>>> paper2html.paper2html("path-to-paper-file or directory")
```

さらに,下記のインストールを済ませれば,

- 右クリックメニューから変換したいpdfを選択して,`open pdf as html`を選択する (macのみ)
- ブラウザからpdfを保存する(自動的に変換が起動)

ことで変換したhtmlを自動的に開くことが可能です.

## フォルダアクションと右クリックメニューの作成
一部のOSではさらに操作を短縮するツールが利用できます.

### 右クリックメニューのインストール(mac用)
クローンしたソースフォルダから`paper2html/open pdf as html.workflow`
をダブルクリックしてautomatorに登録します.

`.zshrc`で自動的に有効になるconda環境でない場合は
`.zshrc`で自動的に有効になるpython環境 (≒ zshでデフォルトのpython)でない場合は
automatorからインストールされたworkflow内部のシェルスクリプトを変更してください.

MacOSがCatalina以上であれば,設定>セキュリティとプライバシー>フルディスクアクセス にFinder.appの追加が必要です.
Expand All @@ -86,7 +93,7 @@ MacOSがCatalina以上であれば,設定>セキュリティとプライバ
クローンしたソースフォルダから`paper2html/open_downloaded.workflow`
をダブルクリックしてautomatorに登録します.

`.zshrc`で自動的に有効になるconda環境でない場合は
`.zshrc`で自動的に有効になるpython環境 (≒ zshでデフォルトのpython)でない場合は
automatorからインストールされたworkflow内部のシェルスクリプトを変更してください.

MacOSがCatalina以上であれば,設定>セキュリティとプライバシー>フルディスクアクセス にFinder.appの追加が必要です.
Expand All @@ -95,7 +102,9 @@ MacOSがCatalina以上であれば,設定>セキュリティとプライバ

次に,pdfのダウンロード先のフォルダを右クリックし,右クリックメニュー>サービス>"フォルダアクションを設定.."を選択し,
"サービスを確認"を押すと,"Finder"が制限されたサービス"フォルダアクションを設定..."を使おうとしています.とメッセージが出ます.
サービスの実行を押し, +でスクリプトを追加,`open_downloaded.workflow`を選択し,関連付ける,を選択すれば完了です.
サービスの実行を押し, +でスクリプトを追加(下図参照),`open_downloaded.workflow`を選択し,関連付ける,を選択すれば完了です.

<img width="483" alt="accept_folder_action" src="https://user-images.githubusercontent.com/4715386/94677454-dffefc00-0357-11eb-8948-be0ea6c8f137.png">

### ディレクトリ監視スクリプト(ubuntu用)
以下のように監視用のツールを導入し,
Expand All @@ -109,15 +118,19 @@ bash paper2html/open_downloaded.sh
```
このディレクトリにダウンロードを行えば自動的にブラウザが起動します.

(デフォルトのブラウザ以外で開きたい場合は,持っているブラウザに合わせて`BROWSER_PATH`も適宜変更してください.)

### フォルダ監視スクリプト(windows用)
※ フォルダ監視が一部環境で機能しないバグが報告されています.

`paper2html/open_downloaded.ps1``"C:\MyDownloads"`を適当なフォルダパスに書き換えた後,
`paper2html/open_downloaded.ps1``"${HOME}/Downloads"`を適当なフォルダパスに書き換えた後,
`paper2html/open_downloaded.ps1`の右クリックメニュー>`power shellを実行`を選択すると,
フォルダが監視されます
指定したフォルダが監視されます

このフォルダにダウンロードを行えば自動的にブラウザが起動します.

(デフォルトのブラウザ以外で開きたい場合は,持っているブラウザに合わせて`$browser_path`も適宜変更してください.)

## トラブルシューティング
`which pdfinfo`(またはwindowsでは`where.exe pdfinfo`)とコマンド入力して何も出力されない場合は,popplerが実行環境から見えていません.
popplerのインストール場所を確認してください.
2 changes: 1 addition & 1 deletion paper2html/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .commands import open_paper_htmls, message_for_automator, paper2html

name = "paper2html"
__version__ = '0.1.5'
__version__ = '0.1.6'
__all__ = ['open_paper_htmls', 'message_for_automator', 'paper2html']

26 changes: 17 additions & 9 deletions paper2html/commands.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding:utf-8 -*-
import math
import os
from os.path import join as pjoin
from shutil import rmtree
Expand Down Expand Up @@ -73,25 +72,28 @@ def clean_pdf(pdf_filename, working_dir):
import subprocess
_, base_name = os.path.split(pdf_filename)
new_pdf_filename = pjoin(working_dir, base_name)
subprocess.run(['mutool', 'clean', pdf_filename, new_pdf_filename])
subprocess.run(['pdftocairo', '-pdf', pdf_filename, new_pdf_filename])
return new_pdf_filename


def message_for_automator(msg):
webbrowser.open("https://www.google.com/search?q={}".format(quote(msg)))


def paper2html(target_path: str, working_dir: str = None, verbose: bool = False) -> list:
def paper2html(target_path: str, working_dir: str = None, line_margin_rate: float = None, verbose: bool = False) -> list:
"""
Generate paper htmls from a pdf file.
@param target_path:
The target pdf file or the directory of pdf files.
@param working_dir:
The working directory contains output directory and html files.
Default is the same directory as pdf_filename.
@param line_margin_rate:
line margin rate = (line margin) / (line hight).
This is optionally used for pdfminer.Lparams.line_margin. It affects paragraph detection.
@param verbose:
Whether to output files which indicate the visual recognition process.
@return:
@return:
List of url of generated htmls.
"""
if os.path.isdir(target_path):
Expand All @@ -109,7 +111,7 @@ def paper2html(target_path: str, working_dir: str = None, verbose: bool = False)
fixed_dir, image_dir, temp_dir = init_working_dir(working_dir, pdf_filename)
pdf_filename = clean_pdf(pdf_filename, fixed_dir)
pdf2image.convert_from_path(pdf_filename, output_folder=image_dir, output_file='pdf', paths_only=True)
urls = read_by_extended_pdfminer(pdf_filename, verbose)
urls = read_by_extended_pdfminer(pdf_filename, line_margin_rate, verbose)

if not verbose:
rmtree(temp_dir)
Expand All @@ -118,7 +120,7 @@ def paper2html(target_path: str, working_dir: str = None, verbose: bool = False)


def open_paper_htmls(pdf_filename: str, working_dir: str = None, browser_path: str = None,
n_div_paragraph: int = 800, verbose: bool = False):
n_div_paragraph: int = 800, line_margin_rate: float = None, verbose: bool = False):
"""
Open generated paper htmls from a pdf file with a browser.
@param pdf_filename:
Expand All @@ -130,12 +132,18 @@ def open_paper_htmls(pdf_filename: str, working_dir: str = None, browser_path: s
The browser to open the file with.
@param n_div_paragraph:
Number of paragraphs to combine in one output html.
@param line_margin_rate:
line margin rate = (line margin) / (line hight).
This is optionally used for pdfminer.Lparams.line_margin. It affects paragraph detection.
@param verbose:
Whether to output files which indicate the visual recognition process.
"""
Paper.n_div_paragraph = n_div_paragraph
for url in paper2html(pdf_filename, working_dir, verbose):
open_by_browser(url, browser_path)
try:
Paper.n_div_paragraph = n_div_paragraph
for url in paper2html(pdf_filename, working_dir, line_margin_rate, verbose):
open_by_browser(url, browser_path)
except Exception as e:
message_for_automator(str(e))


if __name__ == '__main__':
Expand Down
40 changes: 19 additions & 21 deletions paper2html/paper.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,6 @@ def wrapped_init(self, *args, **kwargs):
return target_cls


def format4gt2(txt):
result = txt
patt = '([^(.|\n)])- ([^\n])'
result = re.sub(patt, r'\1\2', result)
patt = '([^(.|\n)])-\n([^\n])'
result = re.sub(patt, r'\1\2', result)
# patt = '\.\n'
# result = re.sub(patt, r'.\n\n', result)
patt = '([^(.|\n)])\n([^\n])'
result = re.sub(patt, r'\1 \2', result)
result = re.sub("([^\\.])\n", r"\1 ", result)
return result + "\n"


def unify_bboxes(bboxes):
bbox = [math.inf, math.inf, -math.inf, -math.inf]
for item_bbox in bboxes:
Expand Down Expand Up @@ -546,17 +532,29 @@ def _arrange_paragraphs(self):
self.arranged_paragraphs.extend(page.footers)

def _paragraph2txt(self, paragraph):
eng_txt = format4gt2("".join([item.text for item in paragraph]))
return eng_txt

def _paragraph2img_line(self, paragraph, i):
"""
段落内の改行を取り除く.pdfからのコピペの整形に使用していたころの名残.
"""
result = "".join([item.text for item in paragraph])
patt = '([^(.|\n)])- ([^\n])'
result = re.sub(patt, r'\1\2', result)
patt = '([^(.|\n)])-\n([^\n])'
result = re.sub(patt, r'\1\2', result)
# patt = '\.\n'
# result = re.sub(patt, r'.\n\n', result)
patt = '([^(.|\n)])\n([^\n])'
result = re.sub(patt, r'\1 \2', result)
result = re.sub("([^\\.])\n", r"\1 ", result)
return result + "\n"

def _paragraph2img_elem(self, paragraph, i):
img_template = '<p id="img{}"><img alt="Figure" src="./{}" /></p>\n'
if paragraph[0].type == PaperItemType.Figure:
return img_template.format(i, os.path.relpath(paragraph[0].url, self.output_dir))
else:
return "\n".join([img_template.format(i, os.path.relpath(item.url, self.output_dir)) for item in paragraph])

def _paragraph2txt_line(self, paragraph, i):
def _paragraph2txt_elem(self, paragraph, i):
txt_template = '<p id="txt{}">{}</p>\n'
if len(paragraph) == 0:
return ""
Expand Down Expand Up @@ -614,8 +612,8 @@ def chunks(list, n):
'''
html_pages = []
for paragraphs in chunks(self.arranged_paragraphs, self.n_div_paragraph):
img_content = "\n\n\n\n\n".join([self._paragraph2img_line(paragraph, i) for i, paragraph in enumerate(paragraphs)])
txt_content = "\n\n\n\n\n".join([self._paragraph2txt_line(paragraph, i) for i, paragraph in enumerate(paragraphs)])
img_content = "\n\n\n\n\n".join([self._paragraph2img_elem(paragraph, i) for i, paragraph in enumerate(paragraphs)])
txt_content = "\n\n\n\n\n".join([self._paragraph2txt_elem(paragraph, i) for i, paragraph in enumerate(paragraphs)])

html_pages.append([img_content, txt_content])
css_content = '''
Expand Down
25 changes: 19 additions & 6 deletions paper2html/paper_miner.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from .paper import Paper, PaperItemType, PaperItem, PaperPage, unify_bboxes


def read_by_extended_pdfminer(pdf_filename, verbose=False):
paper = PaperReader(pdf_filename).read()
def read_by_extended_pdfminer(pdf_filename, line_margin_rate=None, verbose=False):
paper = PaperReader(pdf_filename, line_margin_rate).read()
if verbose:
paper.show_layouts()

Expand All @@ -22,20 +22,22 @@ def read_by_extended_pdfminer(pdf_filename, verbose=False):


class PaperReader:
def __init__(self, pdf_filename):
def __init__(self, pdf_filename, line_margin_rate=None):
self.pdf_filename = pdf_filename
self.laparams = LAParams()
# laparams.line_margin = 0.3
self.laparams.boxes_flow = 1.0 # 1.0: vertical order, -1.0: horizontal order
# self.laparams.detect_vertical = True
# laparams.all_texts = True
self._zap()
if not line_margin_rate:
self._zap()
else:
self.laparams.line_margin = line_margin_rate

def pdf_pages(self):
with open(self.pdf_filename, 'rb') as fp:
parser = PDFParser(fp)
doc = PDFDocument(parser)
# doc.initialize('password') # leave empty for no password

for page in PDFPage.create_pages(doc):
yield page
Expand All @@ -47,7 +49,6 @@ def lt_pages(self):

for page in self.pdf_pages():
interpreter.process_page(page)
# receive the LTPage object for this page
yield device.get_result()

def read(self):
Expand All @@ -61,6 +62,9 @@ def read(self):
return paper

def _zap(self):
"""
先頭2ページを調べ,行の高さと行間の最頻値からpdfminer.Lparams.line_margin = 行間/行の高さ を設定します.
"""
line_margin_counts = {}
line_height_counts = {}
zap_max = 2
Expand All @@ -79,6 +83,9 @@ def _zap(self):
print('line_height: {}\nline_margin: {}'.format(self.line_height, self.line_margin))

def _count_something(self, item, line_margin_counts, line_height_counts):
"""
検出されたTextLineに対していくつかの統計を取る
"""
if isinstance(item, LTPage):
for child in item:
self._count_something(child, line_margin_counts, line_height_counts)
Expand All @@ -100,10 +107,16 @@ def _count_something(self, item, line_margin_counts, line_height_counts):

@staticmethod
def _char_is_horizontal(char):
"""
文字の配置行列から横書きの文字であるかを判定する
"""
return abs(char.matrix[0] - char.matrix[3]) < 0.1 and abs(char.matrix[1] - 0) < 0.1 \
and abs(char.matrix[2] - 0) < 0.1 and char.matrix[0] > 0 and char.matrix[3] > 0

def _textbox_is_vertical(self, text_box):
"""
英文テキストが縦書きされているか判定する
"""
# detect vertical text box
MAX_CHECK_NUM = 10
checked_char_num = 0
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def get_version(*file_paths):
classifiers=[
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
'Operating System :: Microsoft :: Windows :: Windows 10',
'Operating System :: MacOS :: MacOS X',
Expand Down

0 comments on commit ab0717e

Please sign in to comment.