Release standalone PPT template workflow 0.5.0
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
"""Slide dimensions, format detection, EMU conversion, and constants."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from ..canvas_contract import (
|
||||
CanvasContractError,
|
||||
ProjectViewBox,
|
||||
parse_project_viewbox,
|
||||
read_project_viewbox,
|
||||
require_consistent_project_viewboxes,
|
||||
)
|
||||
|
||||
# Import project utility modules
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
try:
|
||||
from project_utils import get_project_info
|
||||
from config import CANVAS_FORMATS
|
||||
except ImportError:
|
||||
CANVAS_FORMATS = {
|
||||
'ppt169': {'name': 'PPT 16:9', 'dimensions': '1280×720', 'viewbox': '0 0 1280 720'},
|
||||
}
|
||||
|
||||
def get_project_info(path: str) -> dict:
|
||||
return {'format': 'unknown', 'name': Path(path).name}
|
||||
|
||||
# EMU conversion constants
|
||||
EMU_PER_INCH = 914400
|
||||
EMU_PER_PIXEL = EMU_PER_INCH / 96
|
||||
|
||||
# XML namespaces
|
||||
NAMESPACES = {
|
||||
'a': 'http://schemas.openxmlformats.org/drawingml/2006/main',
|
||||
'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
||||
'p': 'http://schemas.openxmlformats.org/presentationml/2006/main',
|
||||
'asvg': 'http://schemas.microsoft.com/office/drawing/2016/SVG/main',
|
||||
}
|
||||
|
||||
# Register namespaces for ElementTree output
|
||||
for prefix, uri in NAMESPACES.items():
|
||||
ET.register_namespace(prefix, uri)
|
||||
|
||||
|
||||
def get_slide_dimensions(
|
||||
canvas_format: str,
|
||||
custom_pixels: tuple[int, int] | None = None,
|
||||
) -> tuple[int, int]:
|
||||
"""Get slide dimensions in EMU units.
|
||||
|
||||
Args:
|
||||
canvas_format: Canvas format key (e.g. 'ppt169').
|
||||
custom_pixels: Optional custom pixel dimensions override.
|
||||
|
||||
Returns:
|
||||
(width_emu, height_emu) tuple.
|
||||
"""
|
||||
if custom_pixels:
|
||||
width_px, height_px = custom_pixels
|
||||
else:
|
||||
if canvas_format not in CANVAS_FORMATS:
|
||||
canvas_format = 'ppt169'
|
||||
|
||||
dimensions = CANVAS_FORMATS[canvas_format]['dimensions']
|
||||
match = re.match(r'(\d+)[×x](\d+)', dimensions)
|
||||
if match:
|
||||
width_px = int(match.group(1))
|
||||
height_px = int(match.group(2))
|
||||
else:
|
||||
width_px, height_px = 1280, 720
|
||||
|
||||
return int(width_px * EMU_PER_PIXEL), int(height_px * EMU_PER_PIXEL)
|
||||
|
||||
|
||||
def get_pixel_dimensions(
|
||||
canvas_format: str,
|
||||
custom_pixels: tuple[int, int] | None = None,
|
||||
) -> tuple[int, int]:
|
||||
"""Get canvas pixel dimensions.
|
||||
|
||||
Args:
|
||||
canvas_format: Canvas format key.
|
||||
custom_pixels: Optional custom pixel dimensions override.
|
||||
|
||||
Returns:
|
||||
(width_px, height_px) tuple.
|
||||
"""
|
||||
if custom_pixels:
|
||||
return custom_pixels
|
||||
|
||||
if canvas_format not in CANVAS_FORMATS:
|
||||
canvas_format = 'ppt169'
|
||||
|
||||
dimensions = CANVAS_FORMATS[canvas_format]['dimensions']
|
||||
match = re.match(r'(\d+)[×x](\d+)', dimensions)
|
||||
if match:
|
||||
return int(match.group(1)), int(match.group(2))
|
||||
return 1280, 720
|
||||
|
||||
|
||||
def get_viewbox_dimensions(svg_path: Path) -> tuple[float, float]:
|
||||
"""Extract pixel dimensions from SVG viewBox.
|
||||
|
||||
Args:
|
||||
svg_path: Path to the SVG file.
|
||||
|
||||
Returns:
|
||||
(width, height) in SVG pixels.
|
||||
|
||||
Raises:
|
||||
CanvasContractError: The root canvas is missing or invalid.
|
||||
"""
|
||||
return read_project_viewbox(svg_path).pixel_dimensions
|
||||
|
||||
|
||||
def detect_format_from_svg(svg_path: Path) -> str | None:
|
||||
"""Detect canvas format from an SVG file's viewBox.
|
||||
|
||||
Args:
|
||||
svg_path: Path to the SVG file.
|
||||
|
||||
Returns:
|
||||
Canvas format key (e.g. 'ppt169'), or None if not detected.
|
||||
"""
|
||||
viewbox = read_project_viewbox(svg_path)
|
||||
for fmt_key, fmt_info in CANVAS_FORMATS.items():
|
||||
expected = parse_project_viewbox(
|
||||
fmt_info['viewbox'],
|
||||
context=f"registered canvas {fmt_key!r}",
|
||||
)
|
||||
if viewbox == expected:
|
||||
return fmt_key
|
||||
return None
|
||||
|
||||
|
||||
def resolve_svg_canvas(
|
||||
svg_files: list[Path],
|
||||
*,
|
||||
canvas_format: str | None = None,
|
||||
expected_viewbox: str | None = None,
|
||||
) -> tuple[ProjectViewBox, str | None]:
|
||||
"""Resolve one fail-closed canvas for every public/internal SVG."""
|
||||
format_viewbox: str | None = None
|
||||
if canvas_format is not None:
|
||||
if canvas_format not in CANVAS_FORMATS:
|
||||
raise CanvasContractError(f"Unsupported canvas format: {canvas_format}")
|
||||
format_viewbox = CANVAS_FORMATS[canvas_format]['viewbox']
|
||||
|
||||
if expected_viewbox is not None and format_viewbox is not None:
|
||||
locked = parse_project_viewbox(
|
||||
expected_viewbox,
|
||||
context="locked canvas viewBox",
|
||||
)
|
||||
selected = parse_project_viewbox(
|
||||
format_viewbox,
|
||||
context=f"canvas format {canvas_format!r}",
|
||||
)
|
||||
if locked != selected:
|
||||
raise CanvasContractError(
|
||||
f"canvas format {canvas_format!r} ({selected.canonical}) conflicts "
|
||||
f"with the locked canvas ({locked.canonical})"
|
||||
)
|
||||
|
||||
required = expected_viewbox if expected_viewbox is not None else format_viewbox
|
||||
if expected_viewbox is not None:
|
||||
expected_label = "the locked canvas"
|
||||
elif canvas_format is not None:
|
||||
expected_label = f"canvas format {canvas_format!r}"
|
||||
else:
|
||||
expected_label = "the first SVG canvas"
|
||||
viewbox = require_consistent_project_viewboxes(
|
||||
svg_files,
|
||||
expected_viewbox=required,
|
||||
expected_label=expected_label,
|
||||
)
|
||||
detected_format = canvas_format
|
||||
if detected_format is None:
|
||||
for fmt_key, fmt_info in CANVAS_FORMATS.items():
|
||||
registered = parse_project_viewbox(
|
||||
fmt_info['viewbox'],
|
||||
context=f"registered canvas {fmt_key!r}",
|
||||
)
|
||||
if viewbox == registered:
|
||||
detected_format = fmt_key
|
||||
break
|
||||
return viewbox, detected_format
|
||||
Reference in New Issue
Block a user