From 6c86657baa299c7a4e71c6600cc05d0085c521a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E5=90=91=E5=A4=9C?= Date: Thu, 7 Mar 2024 12:25:57 +0800 Subject: [PATCH] feat(docx): add docx renderer --- src/diceutils/renderer/docx.py | 102 +++++++++++++++++++++++---------- tests/test_renderer.py | 6 +- 2 files changed, 77 insertions(+), 31 deletions(-) diff --git a/src/diceutils/renderer/docx.py b/src/diceutils/renderer/docx.py index cfca61c..83387b4 100644 --- a/src/diceutils/renderer/docx.py +++ b/src/diceutils/renderer/docx.py @@ -1,52 +1,96 @@ -from typing import Optional -from diceutils.renderer._models import Renderer, Message - +from diceutils.renderer._models import Renderer, Message, Role, Element from pathlib import Path +from typing import Optional from docx.enum.style import WD_STYLE_TYPE -from docx import Document -from docx.shared import Pt +from docx.document import Document +from docx.text.paragraph import Paragraph +from docx.shared import Pt, RGBColor from docx.oxml.ns import qn +import docx + class DocxRenderer(Renderer): + document: Document def __init__(self) -> None: - self.document = Document() + self.document = docx.Document() self._add_chinese_font_style("微软雅黑") - self._add_chinese_font_style("SimSun", "SimSun-ExtB") + self._add_chinese_font_style("SimSun", "SimSun") + self._add_chinese_font_style("楷体") - def _add_chinese_font_style(self, style_name: str, font_name: Optional[str] = None, font_size: float = 11.0): + def _add_chinese_font_style( + self, style_name: str, font_name: Optional[str] = None, font_size: float = 12 + ): font_name = font_name or style_name - style_song = self.document.styles.add_style(style_name, WD_STYLE_TYPE.CHARACTER) - style_song.font.name = font_name - style_song.font.size = Pt(font_size) - style_song.element.rPr.rFonts.set(qn('w:eastAsia'), font_name) + style_cn = self.document.styles.add_style(style_name, WD_STYLE_TYPE.CHARACTER) + style_cn.font.name = font_name # type: ignore + style_cn.font.size = Pt(font_size) # type: ignore + style_cn.element.rPr.rFonts.set(qn("w:eastAsia"), font_name) - def export(self, filename: str) -> Path: - filepath = Path("./exports/" + filename + ".docx").resolve() + def _render_keeper(self, element: Element, paragraph: Paragraph) -> None: + paragraph.add_run(element.content, style="楷体") - filepath.parent.mkdir(parents=True, exist_ok=True) - self.document.save(str(filepath)) + def _render_act( + self, element: Element, paragraph: Paragraph, is_first: bool = False + ) -> None: + content = "#" + element.content.strip("##") if is_first else element.content + run = paragraph.add_run(content, style="SimSun") + run.font.italic = True - return filepath + def _render_dicer(self, element: Element, paragraph: Paragraph) -> None: + paragraph.add_run(element.content, style="微软雅黑") + + def _render_command(self, element: Element, paragraph: Paragraph) -> None: + run = paragraph.add_run(element.content, style="微软雅黑") + run.font.name = "Cascadia Mono" + + def _render_speak(self, element: Element, paragraph: Paragraph) -> None: + content = "“" + element.content.strip('"“”') + "”" + paragraph.add_run(content, style="SimSun") + + def _render_outside(self, element: Element, paragraph: Paragraph) -> None: + content = "(" + element.content.strip("()()") + ")" + run = paragraph.add_run(content, style="SimSun") + run.font.color.rgb = RGBColor(207, 210, 210) + + def _render_image(self, image: Element, paragraph: Paragraph) -> None: + run = paragraph.add_run(f"[IMAGE '{image.content}']") + run.font.name = "Cascadia Mono" def render_message(self, message: Message) -> None: paragraph = self.document.add_paragraph() - for element in message.elements: + date = paragraph.add_run(message.date) + date.font.name = "Cascadia Mono" + date.font.color.rgb = RGBColor(207, 210, 210) + paragraph.add_run("<" + message.nickname + ">", style="微软雅黑") + for index, element in enumerate(message.elements): + is_first = index == 0 if element.type == "text": - if element.tag == "act": - run = paragraph.add_run(element.content, style="SimSun") + if message.role == Role.DICER: + self._render_dicer(element, paragraph) + continue + elif message.role == Role.OB: + self._render_outside(element, paragraph) + continue + if element.tag == "act": + if message.role == Role.GM: + self._render_keeper(element, paragraph) + else: + self._render_act(element, paragraph, is_first=is_first) elif element.tag == "outside": - content = element.content.strip("()()") - run = paragraph.add_run(content, style="SimSun") - + self._render_outside(element, paragraph) elif element.tag == "speak": - run = paragraph.add_run(f"“{element.content}”", style="微软雅黑") - - else: - # self.document.add_picture() - ... + self._render_speak(element, paragraph) + elif element.tag == "command": + self._render_command(element, paragraph) + elif element.type == "image": + self._render_image(element, paragraph) - return + def export(self, filename: str) -> Path: + filepath = Path("./exports/" + filename + ".docx").resolve() + filepath.parent.mkdir(parents=True, exist_ok=True) + self.document.save(str(filepath)) + return filepath diff --git a/tests/test_renderer.py b/tests/test_renderer.py index 17d5094..4b47d14 100644 --- a/tests/test_renderer.py +++ b/tests/test_renderer.py @@ -1,4 +1,5 @@ from diceutils.renderer import Renderer, Messages, Role +from diceutils.renderer.docx import DocxRenderer from diceutils.renderer.html import HTMLRenderer import datetime @@ -61,5 +62,6 @@ def test_render(): ], ) - renderer = Renderer.render(messages, HTMLRenderer()) - renderer.export("测试") + # Renderer.render(messages, HTMLRenderer()).export("测试") # type: ignore + Renderer.render(messages, DocxRenderer()).export("测试") + raise