import mammoth from io import BytesIO from docx import Document from docx.shared import Pt, Inches from docx.enum.text import WD_ALIGN_PARAGRAPH from app.services.file_storage import get_file_content async def docx_to_html(file_content: bytes) -> str: result = mammoth.convert_to_html(BytesIO(file_content)) return result.value def html_to_docx_bytes(html_content: str) -> bytes: from html.parser import HTMLParser doc = Document() style = doc.styles["Normal"] font = style.font font.name = "Arial" font.size = Pt(11) class RichHTMLParser(HTMLParser): def __init__(self): super().__init__() self.paragraphs: list[dict] = [] self.current = {"runs": []} self.in_paragraph = False self.current_run = {"text": "", "bold": False, "italic": False, "underline": False} self.tag_stack: list[str] = [] self.heading_level = 0 def handle_starttag(self, tag, attrs): tag_lower = tag.lower() if tag_lower in ("p", "div", "li"): self.in_paragraph = True self.current_run = {"text": "", "bold": False, "italic": False, "underline": False} elif tag_lower in ("h1", "h2", "h3", "h4", "h5", "h6"): self.in_paragraph = True self.heading_level = int(tag_lower[1]) self.current_run = {"text": "", "bold": False, "italic": False, "underline": False} elif tag_lower in ("strong", "b"): self.current_run["bold"] = True elif tag_lower in ("em", "i"): self.current_run["italic"] = True elif tag_lower == "u": self.current_run["underline"] = True elif tag_lower in ("br",): if self.in_paragraph: self.current["runs"].append(dict(self.current_run)) self.current_run = {"text": "", "bold": False, "italic": False, "underline": False} self.tag_stack.append(tag_lower) def handle_endtag(self, tag): tag_lower = tag.lower() if tag_lower in ("p", "div", "li", "h1", "h2", "h3", "h4", "h5", "h6"): if self.current_run["text"].strip(): self.current["runs"].append(dict(self.current_run)) if self.current["runs"]: p = dict(self.current) p["heading"] = self.heading_level self.paragraphs.append(p) self.current = {"runs": []} self.current_run = {"text": "", "bold": False, "italic": False, "underline": False} self.in_paragraph = False self.heading_level = 0 if self.tag_stack: self.tag_stack.pop() def handle_data(self, data): if self.in_paragraph: self.current_run["text"] += data parser = RichHTMLParser() parser.feed(html_content) for para_data in parser.paragraphs: heading = para_data.get("heading", 0) if heading > 0: p = doc.add_heading(level=min(heading, 9)) else: p = doc.add_paragraph() for run_data in para_data.get("runs", []): run = p.add_run(run_data["text"]) if run_data.get("bold"): run.bold = True if run_data.get("italic"): run.italic = True if run_data.get("underline"): run.underline = True output = BytesIO() doc.save(output) return output.getvalue() def docx_to_pdf_bytes(file_content: bytes) -> bytes: doc = Document(BytesIO(file_content)) from io import BytesIO as Bio from reportlab.lib.pagesizes import A4 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer from reportlab.lib.enums import TA_LEFT buffer = Bio() pdf_doc = SimpleDocTemplate(buffer, pagesize=A4) styles = getSampleStyleSheet() story = [] for para in doc.paragraphs: if para.text.strip(): p = Paragraph(para.text, styles["Normal"]) story.append(p) story.append(Spacer(1, 6)) pdf_doc.build(story) return buffer.getvalue()