curlkun commited on
Commit
8aa4b24
·
verified ·
1 Parent(s): 1ab9321

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -113
app.py CHANGED
@@ -1,50 +1,42 @@
1
  # verify_highlight_annotation.py
2
- # 実行: python verify_highlight_annotation.py
3
- # 出力: output_highlighted.pdf (カレントディレクトリ)
4
- # 確認: Adobe Acrobat等で開き、ハイライトが「注釈」として認識されるか確認
5
 
6
  import io
7
- import os
8
  from reportlab.lib.pagesizes import A4
9
  from reportlab.pdfgen import canvas as rl_canvas
10
  from reportlab.pdfbase import pdfmetrics
11
  from reportlab.pdfbase.ttfonts import TTFont
12
  import fitz # PyMuPDF
 
 
13
 
14
- # =====================
15
- # 設定
16
- # =====================
17
  FONT_NAME = "NotoSansJP"
18
- FONT_FILE = "NotoSansJP-Regular.ttf" # カレントディレクトリに配置すること
19
- OUTPUT_FILE = "output_highlighted.pdf"
20
 
21
- # ハイライト色 (RGB 0.0-1.0)
22
  COLORS = {
23
- "color1": (1.0, 0.6, 0.6), # 赤系
24
- "color2": (0.6, 0.8, 1.0), # 青系
25
- "mono": (0.5, 0.5, 0.5), # グレー(モノクロ代替)
26
  }
27
 
28
  # =====================
29
  # Step1: reportlabでベースPDF生成
30
  # =====================
31
- def build_base_pdf(font_file: str) -> bytes:
32
- """テスト用テキストを含むPDFをreportlabで生成してbytesで返す"""
33
-
34
- pdfmetrics.registerFont(TTFont(FONT_NAME, font_file))
35
-
36
  buffer = io.BytesIO()
37
  PAGE_W, PAGE_H = A4
38
  MARGIN = 20
39
  c = rl_canvas.Canvas(buffer, pagesize=A4)
40
  c.setFont(FONT_NAME, 12)
41
 
42
- # テスト文字列(差分あり想定)
43
  lines = [
44
- "吾輩は猫である。名前はまだない。", # 行1: 差分なし想定
45
- "吾輩は犬である。名前はもうある。", # 行2: 差分あり想定(「犬」「もうある」)
46
- "abcXYZdef", # 行3: 差分あり(「XYZ」)
47
- "テスト スペース あり", # 行4: 差分なし想定
48
  ]
49
 
50
  y = PAGE_H - MARGIN - 20
@@ -55,120 +47,72 @@ def build_base_pdf(font_file: str) -> bytes:
55
  c.save()
56
  return buffer.getvalue()
57
 
58
-
59
  # =====================
60
- # Step2: PyMuPDFでHighlight Annotation付与
61
  # =====================
62
  def add_highlight_annotations(pdf_bytes: bytes, color_mode: str = "color1") -> bytes:
63
- """
64
- PyMuPDFを使い、指定テキストにHighlight Annotationを付与する。
65
- テキスト検索 → quads取得 → add_highlight_annot の流れを検証。
66
- """
67
  hl_color = COLORS.get(color_mode, COLORS["color1"])
68
-
69
  doc = fitz.open(stream=pdf_bytes, filetype="pdf")
 
70
 
71
- # 検証用: ハイライトしたいテキストのリスト
72
- highlight_targets = [
73
- "犬",
74
- "もうある",
75
- "XYZ",
76
- ]
77
-
78
- page = doc[0] # 1ページ目
79
 
80
  for target in highlight_targets:
81
- # テキスト検索でquadsを取得
82
  areas = page.search_for(target, quads=True)
83
  if not areas:
84
- print(f"[WARNING] テキストが見つかりません: '{target}'")
85
  continue
86
-
87
  for quad in areas:
88
  annot = page.add_highlight_annot(quad)
89
  annot.set_colors(stroke=hl_color)
90
  annot.update()
91
- print(f"[OK] ハイライト注釈追加: '{target}' at {quad}")
92
 
93
  result = io.BytesIO()
94
  doc.save(result)
95
  doc.close()
96
  return result.getvalue()
97
 
98
-
99
  # =====================
100
- # Step3: 文字座標から直接Annotationを付与する方式の検証
101
  # =====================
102
- def add_highlight_by_position(pdf_bytes: bytes, color_mode: str = "color1") -> bytes:
103
- """
104
- reportlabで描画した文字位置を座標で指定してHighlight Annotationを付与。
105
- (テキスト検索不要の方式 - 本番実装候補)
106
- reportlabのy座標(下原点) → PyMuPDFのy座標(上原点)変換を検証。
107
- """
108
- hl_color = COLORS.get(color_mode, COLORS["color1"])
109
- PAGE_W, PAGE_H = A4 # (595.27, 841.89)
110
- MARGIN = 20
111
- FONT_SIZE = 12
112
- LINE_HEIGHT = 20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
- doc = fitz.open(stream=pdf_bytes, filetype="pdf")
115
- page = doc[0]
116
-
117
- # reportlabで描画した行2の「犬」の座標を手動計算
118
- # reportlab: y = PAGE_H - MARGIN - 20 - LINE_HEIGHT * 1 = 841.89 - 20 - 20 - 20 = 781.89
119
- # PyMuPDF: y_mupdf = PAGE_H - y_rl = 841.89 - 781.89 = 60.0 ← ベースライン
120
- # Highlight用rect: x0, y0(上端), x1, y1(下端)
121
-
122
- # 行インデックス1(0始まり)= 「吾輩は犬である...」行
123
- line_idx = 1
124
- y_rl = PAGE_H - MARGIN - 20 - LINE_HEIGHT * line_idx # reportlab y(ベースライン)
125
- y_mupdf_base = PAGE_H - y_rl # PyMuPDF y(上原点換算)
126
-
127
- # 「犬」は「吾輩は」(3文字)の後 → x方向はフォント幅が必要だが近似で指定
128
- # 実際はreportlab側でstringWidthを使って正確に計算する
129
- x0 = MARGIN + 3 * FONT_SIZE * 0.9 # 近似(全角文字)
130
- x1 = x0 + FONT_SIZE
131
-
132
- rect = fitz.Rect(x0, y_mupdf_base - FONT_SIZE, x1, y_mupdf_base + 2)
133
- quad = rect.quad
134
-
135
- annot = page.add_highlight_annot(quad)
136
- annot.set_colors(stroke=hl_color)
137
- annot.update()
138
- print(f"[OK] 座標指定ハイライト注釈追加: rect={rect}")
139
 
140
- result = io.BytesIO()
141
- doc.save(result)
142
- doc.close()
143
- return result.getvalue()
144
 
 
145
 
146
- # =====================
147
- # メイン
148
- # =====================
149
- if __name__ == "__main__":
150
- # フォントファイル確認
151
- if not os.path.isfile(FONT_FILE):
152
- print(f"[ERROR] フォントファイルが見つかりません: {FONT_FILE}")
153
- print("カレントディレクトリに NotoSansJP-Regular.ttf を配置してください。")
154
- exit(1)
155
-
156
- print("=== Step1: reportlabでベースPDF生成 ===")
157
- base_pdf = build_base_pdf(FONT_FILE)
158
- print(f"[OK] ベースPDF生成完了 ({len(base_pdf)} bytes)")
159
-
160
- print("\n=== Step2: テキスト検索によるHighlight Annotation ===")
161
- highlighted_pdf = add_highlight_annotations(base_pdf, color_mode="color1")
162
- print(f"[OK] Annotation付与完了 ({len(highlighted_pdf)} bytes)")
163
-
164
- print("\n=== Step3: 座標指定によるHighlight Annotation ===")
165
- highlighted_pdf2 = add_highlight_by_position(highlighted_pdf, color_mode="color1")
166
- print(f"[OK] 座標指定Annotation付与完了 ({len(highlighted_pdf2)} bytes)")
167
-
168
- with open(OUTPUT_FILE, "wb") as f:
169
- f.write(highlighted_pdf2)
170
- print(f"\n=== 出力完了: {OUTPUT_FILE} ===")
171
- print("Adobe Acrobat等で開き、以下を確認してください:")
172
- print(" 1. 「犬」「もうある」「XYZ」がハイライト注釈として表示される")
173
- print(" 2. 注釈パネルでHighlight Annotationとして認識されている")
174
- print(" 3. 座標指定のハイライトも正しい位置に表示される")
 
1
  # verify_highlight_annotation.py
2
+ # Hugging Face Spaces上で実行する検証用アプリ
3
+ # フォントファイル NotoSansJP-Regular.ttf を同じフォルダに配置すること
 
4
 
5
  import io
6
+ import gradio as gr
7
  from reportlab.lib.pagesizes import A4
8
  from reportlab.pdfgen import canvas as rl_canvas
9
  from reportlab.pdfbase import pdfmetrics
10
  from reportlab.pdfbase.ttfonts import TTFont
11
  import fitz # PyMuPDF
12
+ import tempfile
13
+ import os
14
 
 
 
 
15
  FONT_NAME = "NotoSansJP"
16
+ FONT_FILE = "NotoSansJP-Regular.ttf"
 
17
 
 
18
  COLORS = {
19
+ "color1": (1.0, 0.6, 0.6),
20
+ "color2": (0.6, 0.8, 1.0),
21
+ "mono": (0.5, 0.5, 0.5),
22
  }
23
 
24
  # =====================
25
  # Step1: reportlabでベースPDF生成
26
  # =====================
27
+ def build_base_pdf() -> bytes:
28
+ pdfmetrics.registerFont(TTFont(FONT_NAME, FONT_FILE))
 
 
 
29
  buffer = io.BytesIO()
30
  PAGE_W, PAGE_H = A4
31
  MARGIN = 20
32
  c = rl_canvas.Canvas(buffer, pagesize=A4)
33
  c.setFont(FONT_NAME, 12)
34
 
 
35
  lines = [
36
+ "吾輩は猫である。名前はまだない。",
37
+ "吾輩は犬である。名前はもうある。",
38
+ "abcXYZdef",
39
+ "テスト スペース あり",
40
  ]
41
 
42
  y = PAGE_H - MARGIN - 20
 
47
  c.save()
48
  return buffer.getvalue()
49
 
 
50
  # =====================
51
+ # Step2: テキスト検索によるHighlight Annotation
52
  # =====================
53
  def add_highlight_annotations(pdf_bytes: bytes, color_mode: str = "color1") -> bytes:
 
 
 
 
54
  hl_color = COLORS.get(color_mode, COLORS["color1"])
 
55
  doc = fitz.open(stream=pdf_bytes, filetype="pdf")
56
+ page = doc[0]
57
 
58
+ highlight_targets = ["犬", "もうある", "XYZ"]
 
 
 
 
 
 
 
59
 
60
  for target in highlight_targets:
 
61
  areas = page.search_for(target, quads=True)
62
  if not areas:
 
63
  continue
 
64
  for quad in areas:
65
  annot = page.add_highlight_annot(quad)
66
  annot.set_colors(stroke=hl_color)
67
  annot.update()
 
68
 
69
  result = io.BytesIO()
70
  doc.save(result)
71
  doc.close()
72
  return result.getvalue()
73
 
 
74
  # =====================
75
+ # メイン処理
76
  # =====================
77
+ def run_verify():
78
+ log = []
79
+
80
+ try:
81
+ # Step1
82
+ base_pdf = build_base_pdf()
83
+ log.append(f"[OK] Step1: ベースPDF生成完了 ({len(base_pdf)} bytes)")
84
+ except Exception as e:
85
+ return f"[ERROR] Step1失敗: {e}", None
86
+
87
+ try:
88
+ # Step2
89
+ highlighted_pdf = add_highlight_annotations(base_pdf, color_mode="color1")
90
+ log.append(f"[OK] Step2: Highlight Annotation付与完了 ({len(highlighted_pdf)} bytes)")
91
+ except Exception as e:
92
+ return "\n".join(log) + f"\n[ERROR] Step2失敗: {e}", None
93
+
94
+ try:
95
+ # 一時ファイルに保存してGradioのFileで返す
96
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
97
+ tmp.write(highlighted_pdf)
98
+ tmp.close()
99
+ log.append(f"[OK] PDF出力完了: {tmp.name}")
100
+ except Exception as e:
101
+ return "\n".join(log) + f"\n[ERROR] ファイル出力失敗: {e}", None
102
+
103
+ return "\n".join(log), tmp.name
104
 
105
+ # =====================
106
+ # Gradio UI
107
+ # =====================
108
+ with gr.Blocks(title="Highlight Annotation 検証") as demo:
109
+ gr.Markdown("## Highlight Annotation 検証ツール")
110
+ gr.Markdown("ボタンを押すとPDFを生成します。ダウンロードして注釈が認識されるか確認してください。")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ run_btn = gr.Button("検証実行", variant="primary")
113
+ log_out = gr.Textbox(label="実行ログ", lines=8)
114
+ pdf_out = gr.File(label="PDFダウンロード")
 
115
 
116
+ run_btn.click(fn=run_verify, inputs=[], outputs=[log_out, pdf_out])
117
 
118
+ demo.launch()