Spaces:
Sleeping
Sleeping
File size: 5,382 Bytes
8cfc9fa a97da81 8cfc9fa 91226a9 8cfc9fa 7d63969 8cfc9fa 91226a9 8cfc9fa 91226a9 8cfc9fa 91226a9 ee04223 7d63969 8cfc9fa 7d63969 8cfc9fa 7d63969 8cfc9fa 7d63969 91226a9 7d63969 91226a9 7d63969 8cfc9fa 91226a9 8cfc9fa 91226a9 7d63969 8cfc9fa 7d63969 8cfc9fa 91226a9 6e80856 91226a9 6e80856 91226a9 8cfc9fa 7d63969 91226a9 6e80856 8cfc9fa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | # app.py
import tempfile
from pathlib import Path
from typing import Tuple
import gradio as gr
from docx import Document
from docx.shared import Inches
from pypdf import PdfReader
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import inch
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# Register a standard font (optional)
# pdfmetrics.registerFont(TTFont('DejaVu', 'DejaVuSans.ttf'))
# --- KDP Constants ---
TRIM_SIZES_INCHES = {
"5\" × 8\"": (5.0, 8.0),
"5.5\" × 8.5\"": (5.5, 8.5),
"6\" × 9\" (Most Popular)": (6.0, 9.0),
"7\" × 10\"": (7.0, 10.0),
"8.5\" × 11\"": (8.5, 11.0),
}
GUTTER_MARGINS_INCHES = [
(150, 0.375),
(300, 0.5),
(500, 0.625),
(700, 0.75),
(828, 0.875),
]
def extract_text_from_docx(docx_path: str) -> list:
"""Extract paragraphs from DOCX."""
doc = Document(docx_path)
return [para.text for para in doc.paragraphs if para.text.strip()]
def extract_text_from_pdf(pdf_path: str) -> list:
"""Extract text from PDF (simple)."""
reader = PdfReader(pdf_path)
text = []
for page in reader.pages:
page_text = page.extract_text()
if page_text:
text.extend([line for line in page_text.split("\n") if line.strip()])
return text
def apply_kdp_to_docx(docx_path: str, trim_w: float, trim_h: float, page_count: int, has_bleed: bool) -> str:
doc = Document(docx_path)
section = doc.sections[0]
section.page_width = Inches(trim_w)
section.page_height = Inches(trim_h)
outside = 0.375 if has_bleed else 0.25
gutter = 0.375
for max_p, g in GUTTER_MARGINS_INCHES:
if page_count <= max_p:
gutter = g
break
section.top_margin = Inches(outside)
section.bottom_margin = Inches(outside)
section.left_margin = Inches(gutter)
section.right_margin = Inches(outside)
output_path = Path(tempfile.gettempdir()) / f"KDP_formatted_{Path(docx_path).stem}.docx"
doc.save(output_path)
return str(output_path)
def create_pdf_from_text(text_lines: list, output_path: str, trim_w: float, trim_h: float, has_bleed: bool):
"""Create a simple PDF from text lines."""
page_size = (trim_w * inch, trim_h * inch)
outside_margin = 0.375 if has_bleed else 0.25
doc = SimpleDocTemplate(
output_path,
pagesize=page_size,
leftMargin=gutter_to_inch(len(text_lines)) * inch, # Approximate
rightMargin=outside_margin * inch,
topMargin=outside_margin * inch,
bottomMargin=outside_margin * inch
)
styles = getSampleStyleSheet()
story = []
for line in text_lines:
story.append(Paragraph(line, styles['Normal']))
story.append(Spacer(1, 6))
doc.build(story)
def gutter_to_inch(page_count: int) -> float:
gutter = 0.375
for max_p, g in GUTTER_MARGINS_INCHES:
if page_count <= max_p:
gutter = g
break
return gutter
def process_file(
input_file: str,
trim_size: str,
has_bleed: bool,
page_count: int,
) -> Tuple[str, str, str]:
file_path = Path(input_file)
trim_w, trim_h = TRIM_SIZES_INCHES[trim_size]
# Step 1: Extract text and get a working DOCX
if file_path.suffix.lower() == ".pdf":
text_lines = extract_text_from_pdf(str(file_path))
# Create a new DOCX from text
temp_doc = Document()
for line in text_lines:
temp_doc.add_paragraph(line)
docx_path = Path(tempfile.gettempdir()) / "temp_from_pdf.docx"
temp_doc.save(docx_path)
else:
text_lines = extract_text_from_docx(str(file_path))
docx_path = file_path
# Step 2: Format DOCX
formatted_docx = apply_kdp_to_docx(str(docx_path), trim_w, trim_h, page_count, has_bleed)
# Step 3: Create PDF from text
formatted_pdf = Path(tempfile.gettempdir()) / f"KDP_formatted_{file_path.stem}.pdf"
create_pdf_from_text(text_lines, str(formatted_pdf), trim_w, trim_h, has_bleed)
info = f"Page Count Used: {page_count}\n⚠️ PDF is text-only (no images/formatting)."
return formatted_docx, str(formatted_pdf), info
# --- UI ---
with gr.Blocks() as demo:
gr.Markdown("# 📚 KDP Formatter (HF Spaces Compatible)")
gr.Markdown("Upload `.docx` or `.pdf`. **PDF output is text-only** due to platform limits.")
with gr.Row():
with gr.Column():
input_file = gr.File(file_types=[".docx", ".pdf"])
trim_size = gr.Dropdown(choices=list(TRIM_SIZES_INCHES.keys()), value="6\" × 9\" (Most Popular)")
page_count = gr.Number(value=300, minimum=24, maximum=828, label="Page Count")
has_bleed = gr.Checkbox(label="Include Bleed")
submit = gr.Button("Format")
with gr.Column():
out_docx = gr.File(label="Formatted DOCX")
out_pdf = gr.File(label="Formatted PDF (Text Only)")
info = gr.Textbox(label="Info")
submit.click(
process_file,
[input_file, trim_size, has_bleed, page_count],
[out_docx, out_pdf, info]
)
gr.Markdown("💡 For full PDF formatting, run this app locally with LibreOffice installed.")
if __name__ == "__main__":
demo.launch() |