ppt-tool/backend/models/pptx_models.py
Vadym Samoilenko a2bd4cfefa Phase 3: Content Pipeline — file parsing, content intelligence, slide mapping, native charts
- 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>
2026-02-26 15:54:04 +00:00

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]