doeqoth commited on
Commit
0bde93f
·
verified ·
1 Parent(s): a4e7427

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +91 -0
  2. README.md +95 -4
  3. app.py +580 -0
  4. requirements.txt +2 -0
Dockerfile ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Set environment variables
4
+ ENV PYTHONDONTWRITEBYTECODE=1
5
+ ENV PYTHONUNBUFFERED=1
6
+ ENV DEBIAN_FRONTEND=noninteractive
7
+
8
+ # Create non-root user for HF Spaces
9
+ RUN useradd -m -u 1000 user
10
+ ENV HOME=/home/user
11
+ ENV PATH=/home/user/.local/bin:$PATH
12
+
13
+ # Install system dependencies for pdf2htmlEX
14
+ RUN apt-get update && apt-get install -y --no-install-recommends \
15
+ wget \
16
+ curl \
17
+ git \
18
+ build-essential \
19
+ cmake \
20
+ pkg-config \
21
+ libfontforge-dev \
22
+ libcairo2-dev \
23
+ libpango1.0-dev \
24
+ libfreetype6-dev \
25
+ libglib2.0-dev \
26
+ libjpeg-dev \
27
+ libpng-dev \
28
+ libopenjp2-7-dev \
29
+ libtiff-dev \
30
+ libxml2-dev \
31
+ libpoppler-dev \
32
+ libpoppler-cpp-dev \
33
+ libpoppler-glib-dev \
34
+ poppler-utils \
35
+ fontforge \
36
+ fonts-dejavu \
37
+ fonts-liberation \
38
+ fonts-noto \
39
+ fonts-noto-cjk \
40
+ fonts-thai-tlwg \
41
+ ttf-mscorefonts-installer \
42
+ && rm -rf /var/lib/apt/lists/* \
43
+ && apt-get clean
44
+
45
+ # Install pdf2htmlEX from source
46
+ WORKDIR /tmp
47
+
48
+ # Clone and build pdf2htmlEX
49
+ RUN git clone --depth 1 https://github.com/pdf2htmlEX/pdf2htmlEX.git && \
50
+ cd pdf2htmlEX && \
51
+ mkdir build && cd build && \
52
+ cmake .. -DCMAKE_INSTALL_PREFIX=/usr && \
53
+ make -j$(nproc) && \
54
+ make install && \
55
+ cd /tmp && rm -rf pdf2htmlEX
56
+
57
+ # Verify pdf2htmlEX installation
58
+ RUN pdf2htmlEX --version || echo "pdf2htmlEX installed"
59
+
60
+ # Set working directory
61
+ WORKDIR $HOME/app
62
+
63
+ # Copy requirements first for caching
64
+ COPY --chown=user:user requirements.txt .
65
+
66
+ # Switch to non-root user
67
+ USER user
68
+
69
+ # Install Python dependencies
70
+ RUN pip install --no-cache-dir --upgrade pip && \
71
+ pip install --no-cache-dir -r requirements.txt
72
+
73
+ # Copy application code
74
+ COPY --chown=user:user . .
75
+
76
+ # Create necessary directories
77
+ RUN mkdir -p /home/user/app/temp /home/user/app/output
78
+
79
+ # Expose port for Gradio
80
+ EXPOSE 7860
81
+
82
+ # Set environment variables for Gradio
83
+ ENV GRADIO_SERVER_NAME=0.0.0.0
84
+ ENV GRADIO_SERVER_PORT=7860
85
+
86
+ # Health check
87
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
88
+ CMD curl -f http://localhost:7860/ || exit 1
89
+
90
+ # Run the application
91
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,10 +1,101 @@
1
  ---
2
- title: Export
3
- emoji: 📊
4
- colorFrom: red
5
  colorTo: purple
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: PDF to HTML Converter
3
+ emoji: 📄
4
+ colorFrom: blue
5
  colorTo: purple
6
  sdk: docker
7
  pinned: false
8
+ license: mit
9
  ---
10
 
11
+ # 📄 PDF to HTML Converter
12
+
13
+ แปลง PDF เป็น HTML ที่รักษา layout เหมือนต้นฉบับ พร้อม embedded fonts, images, CSS ในไฟล์เดียว
14
+
15
+ ## ✨ Features
16
+
17
+ - ✅ **รักษา Layout** - HTML ที่ได้เหมือน PDF ต้นฉบับ
18
+ - ✅ **Embed ทุกอย่าง** - Fonts, Images, CSS, JavaScript อยู่ในไฟล์เดียว
19
+ - ✅ **ข้อความเลือกได้** - Select/Copy ข้อความได้
20
+ - ✅ **เปิดได้ทุก Browser** - ไม่ต้องติดตั้งอะไรเพิ่ม
21
+ - ✅ **ปรับแต่งได้** - Zoom, DPI, Font Format
22
+
23
+ ## 🚀 วิธีใช้งาน
24
+
25
+ 1. **อัปโหลด PDF** - ลากไฟล์หรือคลิกเลือก
26
+ 2. **ปรับตั้งค่า** (optional)
27
+ - Zoom: ความคมชัด (1.5 = 150%)
28
+ - DPI: ความละเอียด (144 แนะนำ)
29
+ - Embed Fonts: ฝัง fonts ไว้ใน HTML
30
+ - Embed Images: ฝังรูปภาพเป็น Base64
31
+ 3. **กด "แปลงเป็น HTML"**
32
+ 4. **ดาวน์โหลด** ไฟล์ HTML
33
+
34
+ ## ⚙️ Options
35
+
36
+ | Option | Default | Description |
37
+ |--------|---------|-------------|
38
+ | Zoom | 1.5 | ความคมชัด (0.5 - 3.0) |
39
+ | DPI | 144 | ความละเอียด (72 - 300) |
40
+ | Embed Fonts | ✅ | ฝัง fonts ใน HTML |
41
+ | Embed Images | ✅ | ฝังรูปภาพเป็น Base64 |
42
+ | Font Format | woff | woff, woff2, ttf, svg |
43
+
44
+ ## 🔧 เทคโนโลยี
45
+
46
+ - **[pdf2htmlEX](https://github.com/pdf2htmlEX/pdf2htmlEX)** - แปลง PDF เป็น HTML
47
+ - **[Gradio](https://gradio.app)** - Web UI Framework
48
+ - **[Hugging Face Spaces](https://huggingface.co/spaces)** - Hosting
49
+
50
+ ## 📋 Tabs
51
+
52
+ ### 📄 PDF → HTML
53
+ แปลง PDF เป็น HTML ที่รักษา layout ทั้งหมด
54
+
55
+ ### 📝 ดึงข้อความ
56
+ ดึงเฉพาะข้อความจาก PDF (ไม่รักษา layout)
57
+
58
+ ## ⚠️ ข้อจำกัด
59
+
60
+ - ไฟล์ขนาดใหญ่ (>50 หน้า) อาจใช้เวลานาน
61
+ - PDF ที่เป็นรูปภาพ (scanned) จะไม่มีข้อความให้ดึง
62
+ - บาง fonts พิเศษอาจแสดงผลไม่ถูกต้อง
63
+ - ขนาดไฟล์ HTML อาจใหญ่กว่า PDF (เพราะ embed ทุกอย่าง)
64
+
65
+ ## 🐳 Local Development
66
+
67
+ ```bash
68
+ # Clone
69
+ git clone https://huggingface.co/spaces/YOUR_USERNAME/pdf2html
70
+
71
+ # Build Docker
72
+ docker build -t pdf2html .
73
+
74
+ # Run
75
+ docker run -p 7860:7860 pdf2html
76
+
77
+ # Open http://localhost:7860
78
+ ```
79
+
80
+ ## 📁 Files
81
+
82
+ ```
83
+ ├── app.py # Main Gradio application
84
+ ├── requirements.txt # Python dependencies
85
+ ├── Dockerfile # Docker configuration with pdf2htmlEX
86
+ └── README.md # This file
87
+ ```
88
+
89
+ ## 🔗 Links
90
+
91
+ - [pdf2htmlEX GitHub](https://github.com/pdf2htmlEX/pdf2htmlEX)
92
+ - [Gradio Documentation](https://gradio.app/docs/)
93
+ - [Hugging Face Spaces Docs](https://huggingface.co/docs/hub/spaces)
94
+
95
+ ## 📄 License
96
+
97
+ MIT License
98
+
99
+ ---
100
+
101
+ Made with ❤️ using Gradio & pdf2htmlEX
app.py ADDED
@@ -0,0 +1,580 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PDF to HTML Converter - Hugging Face Space
3
+ แปลง PDF เป็น HTML พร้อมรักษา layout และ text ที่เลือกได้
4
+
5
+ ใช้ PyMuPDF (fitz) - ไม่ต้องติดตั้ง pdf2htmlEX
6
+ """
7
+
8
+ import base64
9
+ import io
10
+ import os
11
+ import tempfile
12
+ import time
13
+ from pathlib import Path
14
+
15
+ import fitz # PyMuPDF
16
+ import gradio as gr
17
+
18
+ # ============ Configuration ============
19
+
20
+ TITLE = "📄 PDF to HTML Converter"
21
+ DESCRIPTION = """
22
+ แปลง PDF เป็น HTML ที่รักษา layout เหมือนต้นฉบับ พร้อม:
23
+ - ✅ รักษาตำแหน่งข้อความตาม PDF
24
+ - ✅ ข้อความเลือก/copy ได้
25
+ - ✅ เปิดได้ในทุก browser
26
+ - ✅ รองรับภาษาไทย
27
+
28
+ **วิธีใช้:** อัปโหลด PDF → ปรับตั้งค่า → กดแปลง → ดาวน์โหลด HTML
29
+ """
30
+
31
+ # ============ Helper Functions ============
32
+
33
+
34
+ def get_file_size(size_bytes):
35
+ """รับขนาดไฟล์แบบ human readable"""
36
+ for unit in ["B", "KB", "MB", "GB"]:
37
+ if size_bytes < 1024:
38
+ return f"{size_bytes:.1f} {unit}"
39
+ size_bytes /= 1024
40
+ return f"{size_bytes:.1f} TB"
41
+
42
+
43
+ def extract_text_with_positions(page, scale=1.5):
44
+ """ดึงข้อความพร้อมตำแหน่งจากหน้า PDF"""
45
+ blocks = []
46
+
47
+ # Get text blocks with positions
48
+ text_dict = page.get_text("dict", flags=fitz.TEXT_PRESERVE_WHITESPACE)
49
+
50
+ for block in text_dict.get("blocks", []):
51
+ if block.get("type") == 0: # Text block
52
+ for line in block.get("lines", []):
53
+ for span in line.get("spans", []):
54
+ text = span.get("text", "").strip()
55
+ if not text:
56
+ continue
57
+
58
+ bbox = span.get("bbox", [0, 0, 0, 0])
59
+ font_size = span.get("size", 12)
60
+ font_name = span.get("font", "sans-serif")
61
+ color = span.get("color", 0)
62
+
63
+ # Convert color to hex
64
+ if isinstance(color, int):
65
+ hex_color = f"#{color:06x}"
66
+ else:
67
+ hex_color = "#000000"
68
+
69
+ blocks.append(
70
+ {
71
+ "text": text,
72
+ "x": bbox[0] * scale,
73
+ "y": bbox[1] * scale,
74
+ "width": (bbox[2] - bbox[0]) * scale,
75
+ "height": (bbox[3] - bbox[1]) * scale,
76
+ "font_size": font_size * scale,
77
+ "font_name": font_name,
78
+ "color": hex_color,
79
+ }
80
+ )
81
+
82
+ return blocks
83
+
84
+
85
+ def render_page_as_image(page, scale=1.5, image_format="png"):
86
+ """Render หน้า PDF เป็นรูปภาพ"""
87
+ mat = fitz.Matrix(scale, scale)
88
+ pix = page.get_pixmap(matrix=mat, alpha=False)
89
+
90
+ if image_format == "png":
91
+ img_data = pix.tobytes("png")
92
+ else:
93
+ img_data = pix.tobytes("jpeg")
94
+
95
+ return base64.b64encode(img_data).decode("utf-8")
96
+
97
+
98
+ def generate_html(pages_data, title="PDF Document", include_background=True):
99
+ """สร้าง HTML จากข้อมูลหน้า PDF"""
100
+
101
+ html_pages = []
102
+
103
+ for i, page_data in enumerate(pages_data):
104
+ text_elements = []
105
+
106
+ for block in page_data["texts"]:
107
+ # Escape HTML
108
+ text = (
109
+ block["text"]
110
+ .replace("&", "&amp;")
111
+ .replace("<", "&lt;")
112
+ .replace(">", "&gt;")
113
+ )
114
+
115
+ style = (
116
+ f"""
117
+ position: absolute;
118
+ left: {block["x"]:.1f}px;
119
+ top: {block["y"]:.1f}px;
120
+ font-size: {block["font_size"]:.1f}px;
121
+ color: {block["color"]};
122
+ white-space: pre;
123
+ pointer-events: auto;
124
+ cursor: text;
125
+ user-select: text;
126
+ """.strip()
127
+ .replace("\n", "")
128
+ .replace(" ", " ")
129
+ )
130
+
131
+ text_elements.append(f'<span style="{style}">{text}</span>')
132
+
133
+ # Background style
134
+ if include_background and page_data.get("image"):
135
+ bg_style = f"background-image: url('data:image/png;base64,{page_data['image']}'); background-size: cover;"
136
+ else:
137
+ bg_style = "background: white;"
138
+
139
+ page_html = f"""
140
+ <div class="page" style="width: {page_data["width"]:.0f}px; height: {page_data["height"]:.0f}px; {bg_style}">
141
+ <div class="text-layer">
142
+ {"".join(text_elements)}
143
+ </div>
144
+ </div>
145
+ """
146
+ html_pages.append(page_html)
147
+
148
+ # Complete HTML document
149
+ html = f"""<!DOCTYPE html>
150
+ <html lang="th">
151
+ <head>
152
+ <meta charset="UTF-8">
153
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
154
+ <title>{title}</title>
155
+ <style>
156
+ * {{
157
+ box-sizing: border-box;
158
+ margin: 0;
159
+ padding: 0;
160
+ }}
161
+ body {{
162
+ font-family: 'Sarabun', 'Noto Sans Thai', sans-serif;
163
+ background: #e2e8f0;
164
+ padding: 20px;
165
+ display: flex;
166
+ flex-direction: column;
167
+ align-items: center;
168
+ gap: 20px;
169
+ }}
170
+ .page {{
171
+ position: relative;
172
+ background: white;
173
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
174
+ overflow: hidden;
175
+ }}
176
+ .text-layer {{
177
+ position: absolute;
178
+ inset: 0;
179
+ overflow: hidden;
180
+ }}
181
+ .text-layer span {{
182
+ position: absolute;
183
+ }}
184
+ .text-layer span::selection {{
185
+ background: rgba(37, 99, 235, 0.3);
186
+ }}
187
+ @media print {{
188
+ body {{
189
+ background: white;
190
+ padding: 0;
191
+ gap: 0;
192
+ }}
193
+ .page {{
194
+ box-shadow: none;
195
+ page-break-after: always;
196
+ }}
197
+ }}
198
+ </style>
199
+ <link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@400;600;700&display=swap" rel="stylesheet">
200
+ </head>
201
+ <body>
202
+ {"".join(html_pages)}
203
+ </body>
204
+ </html>
205
+ """
206
+ return html
207
+
208
+
209
+ def convert_pdf_to_html(
210
+ pdf_file, scale, include_background, selected_pages, progress=gr.Progress()
211
+ ):
212
+ """
213
+ แปลง PDF เป็น HTML โดยใช้ PyMuPDF
214
+ """
215
+ if pdf_file is None:
216
+ return None, "❌ กรุณาอัปโหลดไฟล์ PDF", "", None
217
+
218
+ progress(0.1, desc="กำลังเปิดไฟล์ PDF...")
219
+
220
+ try:
221
+ # Open PDF
222
+ input_path = pdf_file.name if hasattr(pdf_file, "name") else pdf_file
223
+ doc = fitz.open(input_path)
224
+
225
+ total_pages = len(doc)
226
+ pdf_name = os.path.basename(input_path)
227
+ pdf_base = os.path.splitext(pdf_name)[0]
228
+
229
+ # Parse selected pages
230
+ if selected_pages.strip():
231
+ try:
232
+ page_indices = []
233
+ for part in selected_pages.split(","):
234
+ part = part.strip()
235
+ if "-" in part:
236
+ start, end = map(int, part.split("-"))
237
+ page_indices.extend(range(start - 1, min(end, total_pages)))
238
+ else:
239
+ idx = int(part) - 1
240
+ if 0 <= idx < total_pages:
241
+ page_indices.append(idx)
242
+ page_indices = sorted(set(page_indices))
243
+ except:
244
+ page_indices = list(range(total_pages))
245
+ else:
246
+ page_indices = list(range(total_pages))
247
+
248
+ if not page_indices:
249
+ page_indices = list(range(total_pages))
250
+
251
+ progress(0.2, desc=f"กำลังประมวลผล {len(page_indices)} หน้า...")
252
+
253
+ pages_data = []
254
+ start_time = time.time()
255
+
256
+ for i, page_idx in enumerate(page_indices):
257
+ progress(
258
+ 0.2 + (0.7 * (i + 1) / len(page_indices)),
259
+ desc=f"กำลังแปลงหน้า {page_idx + 1}/{total_pages}...",
260
+ )
261
+
262
+ page = doc[page_idx]
263
+ rect = page.rect
264
+
265
+ # Get text with positions
266
+ texts = extract_text_with_positions(page, scale)
267
+
268
+ # Render page as image (optional)
269
+ image = None
270
+ if include_background:
271
+ image = render_page_as_image(page, scale)
272
+
273
+ pages_data.append(
274
+ {
275
+ "page_num": page_idx + 1,
276
+ "width": rect.width * scale,
277
+ "height": rect.height * scale,
278
+ "texts": texts,
279
+ "image": image,
280
+ }
281
+ )
282
+
283
+ doc.close()
284
+
285
+ progress(0.9, desc="กำลังสร้าง HTML...")
286
+
287
+ # Generate HTML
288
+ html_content = generate_html(
289
+ pages_data, title=pdf_base, include_background=include_background
290
+ )
291
+
292
+ # Save to temp file
293
+ temp_dir = tempfile.mkdtemp()
294
+ output_path = os.path.join(temp_dir, f"{pdf_base}.html")
295
+
296
+ with open(output_path, "w", encoding="utf-8") as f:
297
+ f.write(html_content)
298
+
299
+ elapsed = time.time() - start_time
300
+ input_size = os.path.getsize(input_path)
301
+ output_size = os.path.getsize(output_path)
302
+
303
+ # Count total text blocks
304
+ total_texts = sum(len(p["texts"]) for p in pages_data)
305
+
306
+ progress(1.0, desc="เสร็จสิ้น!")
307
+
308
+ # Status message
309
+ status = f"""✅ **แปลงสำเร็จ!**
310
+
311
+ 📊 **สถิติ:**
312
+ | รายการ | ค่า |
313
+ |--------|-----|
314
+ | หน้าที่แปลง | {len(page_indices)} / {total_pages} หน้า |
315
+ | ข้อความที่พบ | {total_texts} รายการ |
316
+ | ไฟล์ต้นฉบับ | {get_file_size(input_size)} |
317
+ | ไฟล์ HTML | {get_file_size(output_size)} |
318
+ | เวลาที่ใช้ | {elapsed:.1f} วินาที |
319
+ | Scale | {scale}x |
320
+ | รวม Background | {"✅" if include_background else "❌"} |
321
+ """
322
+
323
+ # Preview (first 50KB)
324
+ preview = html_content[:50000]
325
+ if len(html_content) > 50000:
326
+ preview += "\n\n... (truncated for preview)"
327
+
328
+ return output_path, status, preview, html_content
329
+
330
+ except Exception as e:
331
+ import traceback
332
+
333
+ error_detail = traceback.format_exc()
334
+ return None, f"❌ เกิดข้อผิดพลาด: {str(e)}\n\n```\n{error_detail}\n```", "", None
335
+
336
+
337
+ def extract_text_only(pdf_file, progress=gr.Progress()):
338
+ """ดึงเฉพาะข้อความจาก PDF"""
339
+ if pdf_file is None:
340
+ return "❌ กรุณาอัปโหลดไฟล์ PDF"
341
+
342
+ progress(0.2, desc="กำลังเปิดไฟล์...")
343
+
344
+ try:
345
+ input_path = pdf_file.name if hasattr(pdf_file, "name") else pdf_file
346
+ doc = fitz.open(input_path)
347
+
348
+ all_text = []
349
+ total_pages = len(doc)
350
+
351
+ for i, page in enumerate(doc):
352
+ progress(
353
+ 0.2 + (0.7 * (i + 1) / total_pages), desc=f"หน้า {i + 1}/{total_pages}"
354
+ )
355
+ text = page.get_text("text")
356
+ if text.strip():
357
+ all_text.append(f"--- หน้า {i + 1} ---\n{text}")
358
+
359
+ doc.close()
360
+
361
+ progress(1.0, desc="เสร็จสิ้น!")
362
+
363
+ if all_text:
364
+ return "\n\n".join(all_text)
365
+ else:
366
+ return "❌ ไม่พบข้อความใน PDF (อาจเป็นไฟล์ที่ scan มา)"
367
+
368
+ except Exception as e:
369
+ return f"❌ เกิดข้อผิดพลาด: {str(e)}"
370
+
371
+
372
+ def extract_as_json(pdf_file, scale, progress=gr.Progress()):
373
+ """ดึงข้อมูลเป็น JSON พร้อมพิกัด"""
374
+ if pdf_file is None:
375
+ return "❌ กรุณาอัปโหลดไฟล์ PDF"
376
+
377
+ progress(0.2, desc="กำลังเปิดไฟล์...")
378
+
379
+ try:
380
+ import json
381
+
382
+ input_path = pdf_file.name if hasattr(pdf_file, "name") else pdf_file
383
+ doc = fitz.open(input_path)
384
+
385
+ result = {
386
+ "filename": os.path.basename(input_path),
387
+ "total_pages": len(doc),
388
+ "pages": [],
389
+ }
390
+
391
+ for i, page in enumerate(doc):
392
+ progress(0.2 + (0.7 * (i + 1) / len(doc)), desc=f"หน้า {i + 1}/{len(doc)}")
393
+
394
+ rect = page.rect
395
+ texts = extract_text_with_positions(page, scale)
396
+
397
+ result["pages"].append(
398
+ {
399
+ "page_num": i + 1,
400
+ "width": rect.width * scale,
401
+ "height": rect.height * scale,
402
+ "text_count": len(texts),
403
+ "texts": texts,
404
+ }
405
+ )
406
+
407
+ doc.close()
408
+
409
+ progress(1.0, desc="เสร็จสิ้น!")
410
+
411
+ return json.dumps(result, ensure_ascii=False, indent=2)
412
+
413
+ except Exception as e:
414
+ return f"❌ เกิดข้อผิดพลาด: {str(e)}"
415
+
416
+
417
+ # ============ Gradio Interface ============
418
+
419
+ with gr.Blocks(
420
+ title=TITLE,
421
+ theme=gr.themes.Soft(),
422
+ css="""
423
+ .output-html { max-height: 400px; overflow: auto; font-family: monospace; font-size: 12px; }
424
+ .status-box { font-size: 14px; }
425
+ footer { display: none !important; }
426
+ """,
427
+ ) as demo:
428
+ gr.Markdown(f"# {TITLE}")
429
+ gr.Markdown(DESCRIPTION)
430
+
431
+ with gr.Tabs():
432
+ # ============ Tab 1: PDF to HTML ============
433
+ with gr.TabItem("📄 PDF → HTML", id="tab-html"):
434
+ with gr.Row():
435
+ with gr.Column(scale=1):
436
+ pdf_input = gr.File(
437
+ label="📁 อัปโหลด PDF", file_types=[".pdf"], type="filepath"
438
+ )
439
+
440
+ with gr.Accordion("⚙️ ตั้งค่า", open=True):
441
+ scale_slider = gr.Slider(
442
+ minimum=0.5,
443
+ maximum=3.0,
444
+ value=1.5,
445
+ step=0.1,
446
+ label="Scale (ความคมชัด)",
447
+ info="1.5 = 150%, 2.0 = 200%",
448
+ )
449
+
450
+ include_bg = gr.Checkbox(
451
+ value=True,
452
+ label="รวม Background (ภาพหน้า PDF)",
453
+ info="ปิดเพื่อได��ไฟล์เล็กลง แต่จะเห็นเฉพาะข้อความ",
454
+ )
455
+
456
+ pages_input = gr.Textbox(
457
+ label="เลือกหน้า (ว่าง = ทุกหน้า)",
458
+ placeholder="เช่น 1,3,5-10",
459
+ info="ระบุหน้าที่ต้องการ เช่น 1,2,3 หรือ 1-5,8,10-12",
460
+ )
461
+
462
+ convert_btn = gr.Button(
463
+ "🚀 แปลงเป็น HTML", variant="primary", size="lg"
464
+ )
465
+
466
+ with gr.Column(scale=2):
467
+ html_output = gr.File(label="📥 ดาวน์โหลด HTML")
468
+
469
+ status_output = gr.Markdown(
470
+ label="สถานะ", elem_classes=["status-box"]
471
+ )
472
+
473
+ with gr.Accordion("👁️ Preview HTML Code", open=False):
474
+ preview_output = gr.Code(
475
+ label="HTML Preview", language="html", elem_classes=["output-html"]
476
+ )
477
+
478
+ # Hidden state for full HTML
479
+ html_state = gr.State()
480
+
481
+ convert_btn.click(
482
+ fn=convert_pdf_to_html,
483
+ inputs=[pdf_input, scale_slider, include_bg, pages_input],
484
+ outputs=[html_output, status_output, preview_output, html_state],
485
+ )
486
+
487
+ # ============ Tab 2: Extract Text ============
488
+ with gr.TabItem("📝 ดึงข้อความ", id="tab-text"):
489
+ gr.Markdown("ดึงเฉพาะข้อความจาก PDF (เรียงตามหน้า)")
490
+
491
+ with gr.Row():
492
+ with gr.Column(scale=1):
493
+ pdf_text_input = gr.File(
494
+ label="📁 อัปโหลด PDF", file_types=[".pdf"], type="filepath"
495
+ )
496
+ extract_btn = gr.Button("📝 ดึงข้อความ", variant="primary")
497
+
498
+ with gr.Column(scale=2):
499
+ text_output = gr.Textbox(
500
+ label="ข้อความที่ดึงได้",
501
+ lines=20,
502
+ max_lines=50,
503
+ show_copy_button=True,
504
+ )
505
+
506
+ extract_btn.click(
507
+ fn=extract_text_only, inputs=[pdf_text_input], outputs=[text_output]
508
+ )
509
+
510
+ # ============ Tab 3: Extract JSON ============
511
+ with gr.TabItem("📊 Export JSON", id="tab-json"):
512
+ gr.Markdown("ดึงข้อมูลเป็น JSON พร้อมพิกัด (x, y, width, height, font_size)")
513
+
514
+ with gr.Row():
515
+ with gr.Column(scale=1):
516
+ pdf_json_input = gr.File(
517
+ label="📁 อัปโหลด PDF", file_types=[".pdf"], type="filepath"
518
+ )
519
+ json_scale = gr.Slider(
520
+ minimum=0.5, maximum=3.0, value=1.0, step=0.1, label="Scale"
521
+ )
522
+ json_btn = gr.Button("📊 Export JSON", variant="primary")
523
+
524
+ with gr.Column(scale=2):
525
+ json_output = gr.Code(
526
+ label="JSON Output", language="json", show_label=True
527
+ )
528
+
529
+ json_btn.click(
530
+ fn=extract_as_json,
531
+ inputs=[pdf_json_input, json_scale],
532
+ outputs=[json_output],
533
+ )
534
+
535
+ # ============ Tab 4: About ============
536
+ with gr.TabItem("ℹ️ เกี่ยวกับ", id="tab-about"):
537
+ gr.Markdown("""
538
+ ## 🔧 เทคโนโลยีที่ใช้
539
+
540
+ - **[PyMuPDF (fitz)](https://pymupdf.readthedocs.io/)** - อ่านและประมวลผล PDF
541
+ - **[Gradio](https://gradio.app)** - สร้าง Web UI
542
+ - **[Hugging Face Spaces](https://huggingface.co/spaces)** - Hosting ฟรี
543
+
544
+ ## 📋 Features
545
+
546
+ | Feature | Description |
547
+ |---------|-------------|
548
+ | **PDF → HTML** | แปลง PDF เป็น HTML ที่รักษา layout |
549
+ | **ดึงข้อความ** | ดึงเฉพาะ text จาก PDF |
550
+ | **Export JSON** | ดึงข้อมูลพร้อมพิกัด (x, y, size) |
551
+ | **เลือกหน้า** | เลือกแปลงเฉพาะหน้าที่ต้องการ |
552
+ | **ปรับ Scale** | ปรับความคมชัด 0.5x - 3.0x |
553
+
554
+ ## 💡 Tips
555
+
556
+ 1. **ไฟล์เล็กลง**: ปิด "รวม Background" จะได้ไฟล์ HTML เล็กลงมาก
557
+ 2. **เลือกหน้า**: ใส่ `1-5` หรือ `1,3,5,7-10` เพื่อแปลงเฉพาะบางหน้า
558
+ 3. **JSON**: ใช้สำหรับ import ข้อมูลไปใช้ในแอปอื่น
559
+
560
+ ## ���️ ข้อจำกัด
561
+
562
+ - PDF ที่เป็นรูปภาพ (scanned) จะไม่มีข้อความให้ดึง
563
+ - ไฟล์ขนาดใหญ่มากอาจใช้เวลานานหรือ timeout
564
+ - บาง fonts พิเศษอาจแสดงผลไม่ถูกต้อง
565
+
566
+ ## 📄 License
567
+
568
+ MIT License - ใช้งานได้ฟรี
569
+ """)
570
+
571
+ gr.Markdown("""
572
+ ---
573
+ <center>Made with ❤️ using PyMuPDF & Gradio</center>
574
+ """)
575
+
576
+
577
+ # ============ Launch ============
578
+
579
+ if __name__ == "__main__":
580
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=4.0.0
2
+ PyMuPDF>=1.23.0