from enum import Enum from typing import Annotated, List, Literal, Optional from annotated_types import Len from pydantic import BaseModel from pptx.util import Pt from pptx.enum.text import PP_ALIGN from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, MSO_CONNECTOR_TYPE class PptxBoxShapeEnum(Enum): RECTANGLE = "rectangle" CIRCLE = "circle" class PptxObjectFitEnum(Enum): CONTAIN = "contain" COVER = "cover" FILL = "fill" class PptxSpacingModel(BaseModel): top: int = 0 bottom: int = 0 left: int = 0 right: int = 0 @classmethod def all(cls, num: int): return PptxSpacingModel(top=num, left=num, bottom=num, right=num) class PptxPositionModel(BaseModel): left: int = 0 top: int = 0 width: int = 0 height: int = 0 @classmethod def for_textbox(cls, left: int, top: int, width: int): return cls(left=left, top=top, width=width, height=100) def to_pt_list(self) -> List[int]: return [Pt(self.left), Pt(self.top), Pt(self.width), Pt(self.height)] def to_pt_xyxy(self) -> List[int]: return [ Pt(self.left), Pt(self.top), Pt(self.left + self.width), Pt(self.top + self.height), ] class PptxFontModel(BaseModel): name: str = "Inter" size: int = 16 italic: bool = False color: str = "000000" font_weight: Optional[int] = 400 underline: Optional[bool] = None strike: Optional[bool] = None class PptxFillModel(BaseModel): color: str opacity: float = 1.0 class PptxStrokeModel(BaseModel): color: str thickness: float opacity: float = 1.0 class PptxShadowModel(BaseModel): radius: int offset: int = 0 color: str = "000000" opacity: float = 0.5 angle: int = 0 class PptxTextRunModel(BaseModel): text: str font: Optional[PptxFontModel] = None class PptxParagraphModel(BaseModel): spacing: Optional[PptxSpacingModel] = None alignment: Optional[PP_ALIGN] = None font: Optional[PptxFontModel] = None line_height: Optional[float] = None text: Optional[str] = None text_runs: Optional[List[PptxTextRunModel]] = None class PptxObjectFitModel(BaseModel): fit: Optional[PptxObjectFitEnum] = None focus: Optional[ Annotated[List[Optional[float]], Len(min_length=2, max_length=2)] ] = None class PptxPictureModel(BaseModel): is_network: bool path: str class PptxChartDataModel(BaseModel): """Inline chart data for native PPTX chart rendering.""" chart_type: str = "column" # bar, column, line, pie, doughnut, area, scatter, gantt, waterfall title: str = "Chart" categories: List[str] = [] series: List[dict] = [] # [{name: str, values: [float]}] unit: Optional[str] = None class PptxShapeModel(BaseModel): shape_type: Literal["textbox", "autoshape", "picture", "connector", "chart"] class PptxTextBoxModel(PptxShapeModel): shape_type: Literal["textbox"] = "textbox" margin: Optional[PptxSpacingModel] = None fill: Optional[PptxFillModel] = None position: PptxPositionModel text_wrap: bool = True paragraphs: List[PptxParagraphModel] class PptxAutoShapeBoxModel(PptxShapeModel): shape_type: Literal["autoshape"] = "autoshape" type: MSO_AUTO_SHAPE_TYPE = MSO_AUTO_SHAPE_TYPE.RECTANGLE margin: Optional[PptxSpacingModel] = None fill: Optional[PptxFillModel] = None stroke: Optional[PptxStrokeModel] = None shadow: Optional[PptxShadowModel] = None position: PptxPositionModel text_wrap: bool = True border_radius: Optional[int] = None paragraphs: Optional[List[PptxParagraphModel]] = None class PptxPictureBoxModel(PptxShapeModel): shape_type: Literal["picture"] = "picture" position: PptxPositionModel margin: Optional[PptxSpacingModel] = None clip: bool = True opacity: Optional[float] = None invert: bool = False border_radius: Optional[List[int]] = None shape: Optional[PptxBoxShapeEnum] = None object_fit: Optional[PptxObjectFitModel] = None picture: PptxPictureModel class PptxConnectorModel(PptxShapeModel): shape_type: Literal["connector"] = "connector" type: MSO_CONNECTOR_TYPE = MSO_CONNECTOR_TYPE.STRAIGHT position: PptxPositionModel thickness: float = 0.5 color: str = "000000" opacity: float = 1.0 class PptxChartBoxModel(PptxShapeModel): shape_type: Literal["chart"] = "chart" position: PptxPositionModel chart_data: PptxChartDataModel brand_colors: Optional[List[str]] = None font_name: Optional[str] = None class PptxSlideModel(BaseModel): background: Optional[PptxFillModel] = None note: Optional[str] = None shapes: List[ PptxTextBoxModel | PptxAutoShapeBoxModel | PptxConnectorModel | PptxPictureBoxModel | PptxChartBoxModel ] class PptxPresentationModel(BaseModel): name: Optional[str] = None shapes: Optional[List[PptxShapeModel]] = None slides: List[PptxSlideModel]