jinye_huang 2d21647f10 fix(poster_v2): 确保 Fabric JSON 和 PNG 位置一致
- 在所有布局类中添加 _add_object() 调用
- 渲染时同时记录元素位置到 _fabric_objects
- V2 引擎直接从布局类获取 Fabric 对象
- 移除硬编码的 fallback 位置
2025-12-10 16:28:25 +08:00

105 lines
2.8 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
布局抽象基类
"""
from abc import ABC, abstractmethod
from typing import Tuple, Optional
from PIL import Image, ImageDraw
from ..schemas.content import PosterContent
from ..schemas.theme import Theme
from ..renderers.text import TextRenderer
from ..renderers.shape import ShapeRenderer
from ..renderers.effect import EffectRenderer
class BaseLayout(ABC):
"""
所有布局的抽象基类
布局负责:
1. 计算内容区域位置和尺寸
2. 协调各渲染器绘制内容
3. 生成最终海报图像
4. 记录元素位置 (用于 Fabric.js)
"""
# 默认尺寸 (小红书 3:4)
DEFAULT_SIZE = (1080, 1440)
# 默认边距
MARGIN = 48
PADDING = 32
def __init__(self, size: Tuple[int, int] = None):
self.width, self.height = size or self.DEFAULT_SIZE
self.size = (self.width, self.height)
# 渲染器
self.text = TextRenderer()
self.shape = ShapeRenderer()
self.effect = EffectRenderer()
# 元素记录 (用于 Fabric.js)
self._fabric_objects = []
def _reset_objects(self):
"""重置元素记录"""
self._fabric_objects = []
def _add_object(self, obj: dict):
"""添加元素"""
self._fabric_objects.append(obj)
def get_fabric_objects(self) -> list:
"""获取 Fabric.js 对象列表"""
return self._fabric_objects.copy()
@abstractmethod
def generate(self, content: PosterContent, theme: Theme) -> Image.Image:
"""
生成海报
Args:
content: 海报内容
theme: 主题配置
Returns:
生成的海报图像
"""
pass
@abstractmethod
def calculate_content_height(self, content: PosterContent, theme: Theme) -> int:
"""
计算内容区域高度 (用于动态适配)
Args:
content: 海报内容
theme: 主题配置
Returns:
内容区域高度
"""
pass
def create_canvas(self, background: Tuple[int, int, int] = (255, 255, 255)) -> Image.Image:
"""创建画布"""
return Image.new("RGBA", self.size, (*background, 255))
def create_gradient_background(self, theme: Theme) -> Image.Image:
"""创建渐变背景"""
colors = theme.gradient_rgb
return self.effect.create_gradient(self.size, colors[0], colors[1])
def paste_with_alpha(self, canvas: Image.Image, layer: Image.Image, pos: Tuple[int, int]):
"""粘贴带透明度的图层"""
canvas.paste(layer, pos, layer)
@property
def layout_name(self) -> str:
"""布局名称"""
return self.__class__.__name__.replace("Layout", "").lower()