- Step 10: Extended file upload for Excel/CSV/images/URLs (openpyxl, trafilatura) - Step 11: Content intelligence service with rule-based + LLM classification - Step 12: Slide mapping engine mapping content blocks to master deck layouts - Step 13: Chart data extractor, native PPTX chart service (bar/line/pie/gantt/waterfall), ChartDataEditor skeleton Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
189 lines
4.9 KiB
Python
189 lines
4.9 KiB
Python
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]
|