2024-12-10 19:49:11 +08:00
|
|
|
|
from collections.abc import Callable
|
2024-02-25 03:18:34 +08:00
|
|
|
|
from io import BytesIO
|
|
|
|
|
|
from pathlib import Path
|
2024-12-10 19:49:11 +08:00
|
|
|
|
import random
|
2024-02-25 03:18:34 +08:00
|
|
|
|
|
2025-06-23 15:33:46 +08:00
|
|
|
|
from nonebot_plugin_htmlrender import md_to_pic, template_to_pic
|
2024-08-30 23:50:45 +08:00
|
|
|
|
from PIL.ImageFont import FreeTypeFont
|
2024-12-10 19:49:11 +08:00
|
|
|
|
from pydantic import BaseModel
|
2024-02-25 03:18:34 +08:00
|
|
|
|
|
2025-06-23 15:33:46 +08:00
|
|
|
|
from zhenxun.configs.path_config import TEMPLATE_PATH
|
|
|
|
|
|
|
2024-02-25 03:18:34 +08:00
|
|
|
|
from ._build_image import BuildImage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RowStyle(BaseModel):
|
|
|
|
|
|
font: FreeTypeFont | str | Path | None = "HYWenHei-85W.ttf"
|
|
|
|
|
|
"""字体"""
|
|
|
|
|
|
font_size: int = 20
|
|
|
|
|
|
"""字体大小"""
|
|
|
|
|
|
font_color: str | tuple[int, int, int] = (0, 0, 0)
|
|
|
|
|
|
"""字体颜色"""
|
|
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
|
arbitrary_types_allowed = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ImageTemplate:
|
2024-08-30 23:50:45 +08:00
|
|
|
|
color_list = ["#C2CEFE", "#FFA94C", "#3FE6A0", "#D1D4F5"] # noqa: RUF012
|
2024-02-27 16:12:56 +08:00
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
async def hl_page(
|
|
|
|
|
|
cls,
|
|
|
|
|
|
head_text: str,
|
2024-08-30 23:50:45 +08:00
|
|
|
|
items: dict[str, str],
|
2024-02-27 16:12:56 +08:00
|
|
|
|
row_space: int = 10,
|
|
|
|
|
|
padding: int = 30,
|
|
|
|
|
|
) -> BuildImage:
|
2024-02-28 00:38:54 +08:00
|
|
|
|
"""列文档 (如插件帮助)
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
head_text: 头标签文本
|
|
|
|
|
|
items: 列内容
|
|
|
|
|
|
row_space: 列间距.
|
|
|
|
|
|
padding: 间距.
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
BuildImage: 图片
|
|
|
|
|
|
"""
|
2024-02-27 16:12:56 +08:00
|
|
|
|
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
|
|
|
|
|
|
width, height = BuildImage.get_text_size(head_text, font)
|
|
|
|
|
|
for title, item in items.items():
|
|
|
|
|
|
title_width, title_height = await cls.__get_text_size(title, font)
|
|
|
|
|
|
it_width, it_height = await cls.__get_text_size(item, font)
|
|
|
|
|
|
width = max([width, title_width, it_width])
|
|
|
|
|
|
height += title_height + it_height
|
|
|
|
|
|
width = max([width + padding * 2 + 100, 300])
|
|
|
|
|
|
height = max([height + padding * 2 + 150, 100])
|
|
|
|
|
|
A = BuildImage(width + padding * 2, height + padding * 2, color="#FAF9FE")
|
|
|
|
|
|
top_head = BuildImage(width, 100, color="#FFFFFF", font_size=40)
|
|
|
|
|
|
await top_head.line((0, 1, width, 1), "#C2CEFE", 2)
|
2024-02-28 00:38:54 +08:00
|
|
|
|
await top_head.text((15, 20), head_text, "#9FA3B2", "center")
|
2024-02-27 16:12:56 +08:00
|
|
|
|
await top_head.circle_corner()
|
|
|
|
|
|
await A.paste(top_head, (0, 20), "width")
|
|
|
|
|
|
_min_width = top_head.width - 60
|
|
|
|
|
|
cur_h = top_head.height + 35 + row_space * len(items)
|
|
|
|
|
|
for title, item in items.items():
|
|
|
|
|
|
title_width, title_height = BuildImage.get_text_size(title, font)
|
|
|
|
|
|
title_background = BuildImage(
|
|
|
|
|
|
title_width + 6, title_height + 10, font=font, color="#C1CDFF"
|
|
|
|
|
|
)
|
|
|
|
|
|
await title_background.text((3, 5), title)
|
|
|
|
|
|
await title_background.circle_corner(5)
|
|
|
|
|
|
_text_width, _text_height = await cls.__get_text_size(item, font)
|
|
|
|
|
|
_width = max([title_background.width, _text_width, _min_width])
|
|
|
|
|
|
text_image = await cls.__build_text_image(
|
|
|
|
|
|
item, _width, _text_height, font, color="#FDFCFA"
|
|
|
|
|
|
)
|
|
|
|
|
|
B = BuildImage(_width + 20, title_height + text_image.height + 40)
|
|
|
|
|
|
await B.paste(title_background, (10, 10))
|
|
|
|
|
|
await B.paste(text_image, (10, 20 + title_background.height))
|
|
|
|
|
|
await B.line((0, 0, 0, B.height), random.choice(cls.color_list))
|
|
|
|
|
|
await A.paste(B, (0, cur_h), "width")
|
|
|
|
|
|
cur_h += B.height + row_space
|
|
|
|
|
|
return A
|
|
|
|
|
|
|
2024-02-25 03:18:34 +08:00
|
|
|
|
@classmethod
|
|
|
|
|
|
async def table_page(
|
|
|
|
|
|
cls,
|
|
|
|
|
|
head_text: str,
|
|
|
|
|
|
tip_text: str | None,
|
|
|
|
|
|
column_name: list[str],
|
2024-10-29 08:26:41 +08:00
|
|
|
|
data_list: list[list[str | int | tuple[Path | BuildImage, int, int]]],
|
2024-02-25 03:18:34 +08:00
|
|
|
|
row_space: int = 35,
|
|
|
|
|
|
column_space: int = 30,
|
|
|
|
|
|
padding: int = 5,
|
|
|
|
|
|
text_style: Callable[[str, str], RowStyle] | None = None,
|
|
|
|
|
|
) -> BuildImage:
|
|
|
|
|
|
"""表格页
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
head_text: 标题文本.
|
|
|
|
|
|
tip_text: 标题注释.
|
|
|
|
|
|
column_name: 表头列表.
|
|
|
|
|
|
data_list: 数据列表.
|
|
|
|
|
|
row_space: 行间距.
|
|
|
|
|
|
column_space: 列间距.
|
|
|
|
|
|
padding: 文本内间距.
|
|
|
|
|
|
text_style: 文本样式.
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
BuildImage: 表格图片
|
|
|
|
|
|
"""
|
2024-07-27 04:30:03 +08:00
|
|
|
|
font = BuildImage.load_font(font_size=50)
|
|
|
|
|
|
min_width, _ = BuildImage.get_text_size(head_text, font)
|
2024-02-25 03:18:34 +08:00
|
|
|
|
table = await cls.table(
|
2024-07-27 04:30:03 +08:00
|
|
|
|
column_name,
|
|
|
|
|
|
data_list,
|
|
|
|
|
|
row_space,
|
|
|
|
|
|
column_space,
|
|
|
|
|
|
padding,
|
|
|
|
|
|
text_style,
|
2024-02-25 03:18:34 +08:00
|
|
|
|
)
|
|
|
|
|
|
await table.circle_corner()
|
2024-07-27 04:30:03 +08:00
|
|
|
|
table_bk = BuildImage(
|
|
|
|
|
|
max(table.width, min_width) + 100, table.height + 50, "#EAEDF2"
|
|
|
|
|
|
)
|
2024-02-25 03:18:34 +08:00
|
|
|
|
await table_bk.paste(table, center_type="center")
|
|
|
|
|
|
height = table_bk.height + 200
|
|
|
|
|
|
background = BuildImage(table_bk.width, height, (255, 255, 255), font_size=50)
|
|
|
|
|
|
await background.paste(table_bk, (0, 200))
|
|
|
|
|
|
await background.text((0, 50), head_text, "#334762", center_type="width")
|
|
|
|
|
|
if tip_text:
|
|
|
|
|
|
text_image = await BuildImage.build_text_image(tip_text, size=22)
|
|
|
|
|
|
await background.paste(text_image, (0, 110), center_type="width")
|
|
|
|
|
|
return background
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
async def table(
|
|
|
|
|
|
cls,
|
|
|
|
|
|
column_name: list[str],
|
2024-10-29 08:26:41 +08:00
|
|
|
|
data_list: list[list[str | int | tuple[Path | BuildImage, int, int]]],
|
2024-02-25 03:18:34 +08:00
|
|
|
|
row_space: int = 25,
|
|
|
|
|
|
column_space: int = 10,
|
|
|
|
|
|
padding: int = 5,
|
|
|
|
|
|
text_style: Callable[[str, str], RowStyle] | None = None,
|
|
|
|
|
|
) -> BuildImage:
|
|
|
|
|
|
"""表格
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
column_name: 表头列表
|
|
|
|
|
|
data_list: 数据列表
|
|
|
|
|
|
row_space: 行间距.
|
|
|
|
|
|
column_space: 列间距.
|
|
|
|
|
|
padding: 文本内间距.
|
|
|
|
|
|
text_style: 文本样式.
|
2024-07-27 04:30:03 +08:00
|
|
|
|
min_width: 最低宽度
|
2024-02-25 03:18:34 +08:00
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
BuildImage: 表格图片
|
|
|
|
|
|
"""
|
|
|
|
|
|
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
|
|
|
|
|
|
column_data = []
|
|
|
|
|
|
for i in range(len(column_name)):
|
|
|
|
|
|
c = []
|
2024-11-16 17:59:15 +08:00
|
|
|
|
for item in data_list:
|
|
|
|
|
|
if len(item) > i:
|
|
|
|
|
|
c.append(
|
|
|
|
|
|
item[i] if isinstance(item[i], tuple | list) else str(item[i])
|
|
|
|
|
|
)
|
2024-02-25 03:18:34 +08:00
|
|
|
|
else:
|
|
|
|
|
|
c.append("")
|
|
|
|
|
|
column_data.append(c)
|
|
|
|
|
|
build_data_list = []
|
|
|
|
|
|
_, base_h = BuildImage.get_text_size("A", font)
|
|
|
|
|
|
for i, column_list in enumerate(column_data):
|
2024-07-27 04:30:03 +08:00
|
|
|
|
name_width, _ = BuildImage.get_text_size(column_name[i], font)
|
2024-02-25 03:18:34 +08:00
|
|
|
|
_temp = {"width": name_width, "data": column_list}
|
|
|
|
|
|
for s in column_list:
|
|
|
|
|
|
if isinstance(s, tuple):
|
|
|
|
|
|
w = s[1]
|
|
|
|
|
|
else:
|
2024-10-29 08:26:41 +08:00
|
|
|
|
w, _ = BuildImage.get_text_size(str(s), font)
|
2024-02-25 03:18:34 +08:00
|
|
|
|
if w > _temp["width"]:
|
|
|
|
|
|
_temp["width"] = w
|
|
|
|
|
|
build_data_list.append(_temp)
|
|
|
|
|
|
column_image_list = []
|
2024-08-24 19:32:52 +08:00
|
|
|
|
column_name_image_list: list[BuildImage] = []
|
2024-02-25 03:18:34 +08:00
|
|
|
|
for i, data in enumerate(build_data_list):
|
|
|
|
|
|
column_name_image = await BuildImage.build_text_image(
|
|
|
|
|
|
column_name[i], font, 12, "#C8CCCF"
|
|
|
|
|
|
)
|
2024-08-24 19:32:52 +08:00
|
|
|
|
column_name_image_list.append(column_name_image)
|
2024-08-30 23:50:45 +08:00
|
|
|
|
max_h = max(c.height for c in column_name_image_list)
|
2024-08-24 19:32:52 +08:00
|
|
|
|
for i, data in enumerate(build_data_list):
|
|
|
|
|
|
width = data["width"] + padding * 2
|
|
|
|
|
|
height = (base_h + row_space) * (len(data["data"]) + 1) + padding * 2
|
|
|
|
|
|
background = BuildImage(width, height, (255, 255, 255))
|
|
|
|
|
|
column_name_image = column_name_image_list[i]
|
2024-02-25 03:18:34 +08:00
|
|
|
|
await background.paste(column_name_image, (0, 20), center_type="width")
|
2024-08-24 19:32:52 +08:00
|
|
|
|
cur_h = max_h + row_space + 20
|
2024-02-25 03:18:34 +08:00
|
|
|
|
for item in data["data"]:
|
|
|
|
|
|
style = RowStyle(font=font)
|
|
|
|
|
|
if text_style:
|
|
|
|
|
|
style = text_style(column_name[i], item)
|
2024-11-16 17:59:15 +08:00
|
|
|
|
if isinstance(item, tuple | list):
|
2024-02-25 03:18:34 +08:00
|
|
|
|
"""图片"""
|
|
|
|
|
|
data, width, height = item
|
2024-08-24 19:32:52 +08:00
|
|
|
|
image_ = None
|
2024-02-25 03:18:34 +08:00
|
|
|
|
if isinstance(data, Path):
|
|
|
|
|
|
image_ = BuildImage(width, height, background=data)
|
|
|
|
|
|
elif isinstance(data, bytes):
|
|
|
|
|
|
image_ = BuildImage(width, height, background=BytesIO(data))
|
|
|
|
|
|
elif isinstance(data, BuildImage):
|
|
|
|
|
|
image_ = data
|
2024-08-24 19:32:52 +08:00
|
|
|
|
if image_:
|
|
|
|
|
|
await background.paste(image_, (padding, cur_h))
|
2024-02-25 03:18:34 +08:00
|
|
|
|
else:
|
|
|
|
|
|
await background.text(
|
|
|
|
|
|
(padding, cur_h),
|
|
|
|
|
|
item if item is not None else "",
|
|
|
|
|
|
style.font_color,
|
|
|
|
|
|
font=style.font,
|
|
|
|
|
|
font_size=style.font_size,
|
|
|
|
|
|
)
|
|
|
|
|
|
cur_h += base_h + row_space
|
|
|
|
|
|
column_image_list.append(background)
|
|
|
|
|
|
return await BuildImage.auto_paste(
|
|
|
|
|
|
column_image_list, len(column_image_list), column_space
|
|
|
|
|
|
)
|
2024-02-27 16:12:56 +08:00
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
async def __build_text_image(
|
|
|
|
|
|
cls,
|
|
|
|
|
|
text: str,
|
|
|
|
|
|
width: int,
|
|
|
|
|
|
height: int,
|
|
|
|
|
|
font: FreeTypeFont,
|
|
|
|
|
|
font_color: str | tuple[int, int, int] = (0, 0, 0),
|
|
|
|
|
|
color: str | tuple[int, int, int] = (255, 255, 255),
|
|
|
|
|
|
) -> BuildImage:
|
|
|
|
|
|
"""文本转图片
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
text: 文本
|
|
|
|
|
|
width: 宽度
|
|
|
|
|
|
height: 长度
|
|
|
|
|
|
font: 字体
|
|
|
|
|
|
font_color: 文本颜色
|
|
|
|
|
|
color: 背景颜色
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
BuildImage: 文本转图片
|
|
|
|
|
|
"""
|
|
|
|
|
|
_, h = BuildImage.get_text_size("A", font)
|
|
|
|
|
|
A = BuildImage(width, height, color=color)
|
|
|
|
|
|
cur_h = 0
|
|
|
|
|
|
for s in text.split("\n"):
|
|
|
|
|
|
text_image = await BuildImage.build_text_image(
|
|
|
|
|
|
s, font, font_color=font_color
|
|
|
|
|
|
)
|
|
|
|
|
|
await A.paste(text_image, (0, cur_h))
|
|
|
|
|
|
cur_h += h
|
|
|
|
|
|
return A
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
async def __get_text_size(
|
|
|
|
|
|
cls,
|
|
|
|
|
|
text: str,
|
|
|
|
|
|
font: FreeTypeFont,
|
|
|
|
|
|
) -> tuple[int, int]:
|
|
|
|
|
|
"""获取文本所占大小
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
text: 文本
|
|
|
|
|
|
font: 字体
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
tuple[int, int]: 宽, 高
|
|
|
|
|
|
"""
|
|
|
|
|
|
width = 0
|
|
|
|
|
|
height = 0
|
|
|
|
|
|
_, h = BuildImage.get_text_size("A", font)
|
|
|
|
|
|
for s in text.split("\n"):
|
|
|
|
|
|
s = s.strip() or "A"
|
|
|
|
|
|
w, _ = BuildImage.get_text_size(s, font)
|
2024-08-30 23:50:45 +08:00
|
|
|
|
width = max(width, w)
|
2024-02-27 16:12:56 +08:00
|
|
|
|
height += h
|
|
|
|
|
|
return width, height
|
2025-06-23 15:33:46 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MarkdownTable:
|
|
|
|
|
|
def __init__(self, headers: list[str], rows: list[list[str]]):
|
|
|
|
|
|
self.headers = headers
|
|
|
|
|
|
self.rows = rows
|
|
|
|
|
|
|
|
|
|
|
|
def to_markdown(self) -> str:
|
|
|
|
|
|
"""将表格转换为Markdown格式"""
|
|
|
|
|
|
header_row = "| " + " | ".join(self.headers) + " |"
|
|
|
|
|
|
separator_row = "| " + " | ".join(["---"] * len(self.headers)) + " |"
|
|
|
|
|
|
data_rows = "\n".join(
|
|
|
|
|
|
"| " + " | ".join(map(str, row)) + " |" for row in self.rows
|
|
|
|
|
|
)
|
|
|
|
|
|
return f"{header_row}\n{separator_row}\n{data_rows}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Markdown:
|
|
|
|
|
|
def __init__(self, data: list[str] | None = None):
|
|
|
|
|
|
if data is None:
|
|
|
|
|
|
data = []
|
|
|
|
|
|
self._data = data
|
|
|
|
|
|
|
|
|
|
|
|
def text(self, text: str) -> "Markdown":
|
|
|
|
|
|
"""添加Markdown文本"""
|
|
|
|
|
|
self._data.append(text)
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def head(self, text: str, level: int = 1) -> "Markdown":
|
|
|
|
|
|
"""添加Markdown标题"""
|
|
|
|
|
|
if level < 1 or level > 6:
|
|
|
|
|
|
raise ValueError("标题级别必须在1到6之间")
|
|
|
|
|
|
self._data.append(f"{'#' * level} {text}")
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def image(self, content: str | Path, add_empty_line: bool = True) -> "Markdown":
|
|
|
|
|
|
"""添加Markdown图片
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
content: 图片内容,可以是url地址,图片路径或base64字符串.
|
|
|
|
|
|
add_empty_line: 默认添加换行.
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
Markdown: Markdown
|
|
|
|
|
|
"""
|
|
|
|
|
|
if isinstance(content, Path):
|
|
|
|
|
|
content = str(content.absolute())
|
|
|
|
|
|
if content.startswith("base64"):
|
|
|
|
|
|
content = f"data:image/png;base64,{content.split('base64://', 1)[-1]}"
|
|
|
|
|
|
self._data.append(f"")
|
|
|
|
|
|
if add_empty_line:
|
|
|
|
|
|
self._add_empty_line()
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def quote(self, text: str | list[str]) -> "Markdown":
|
|
|
|
|
|
"""添加Markdown引用文本
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
text: 引用文本内容,可以是字符串或字符串列表.
|
|
|
|
|
|
如果是列表,则每个元素都会被单独引用。
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
Markdown: Markdown
|
|
|
|
|
|
"""
|
|
|
|
|
|
if isinstance(text, str):
|
|
|
|
|
|
self._data.append(f"> {text}")
|
|
|
|
|
|
elif isinstance(text, list):
|
|
|
|
|
|
for t in text:
|
|
|
|
|
|
self._data.append(f"> {t}")
|
|
|
|
|
|
self._add_empty_line()
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def code(self, code: str, language: str = "python") -> "Markdown":
|
|
|
|
|
|
"""添加Markdown代码块"""
|
|
|
|
|
|
self._data.append(f"```{language}\n{code}\n```")
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def table(self, headers: list[str], rows: list[list[str]]) -> "Markdown":
|
|
|
|
|
|
"""添加Markdown表格"""
|
|
|
|
|
|
table = MarkdownTable(headers, rows)
|
|
|
|
|
|
self._data.append(table.to_markdown())
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def list(self, items: list[str | list[str]]) -> "Markdown":
|
|
|
|
|
|
"""添加Markdown列表"""
|
|
|
|
|
|
self._add_empty_line()
|
|
|
|
|
|
_text = "\n".join(
|
|
|
|
|
|
f"- {item}"
|
|
|
|
|
|
if isinstance(item, str)
|
|
|
|
|
|
else "\n".join(f"- {sub_item}" for sub_item in item)
|
|
|
|
|
|
for item in items
|
|
|
|
|
|
)
|
|
|
|
|
|
self._data.append(_text)
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def _add_empty_line(self):
|
|
|
|
|
|
"""添加空行"""
|
|
|
|
|
|
self._data.append("")
|
|
|
|
|
|
|
|
|
|
|
|
async def build(self, width: int = 800, css_path: Path | None = None) -> bytes:
|
|
|
|
|
|
"""构建Markdown文本"""
|
|
|
|
|
|
if css_path is not None:
|
|
|
|
|
|
return await md_to_pic(
|
|
|
|
|
|
md="\n".join(self._data), width=width, css_path=str(css_path.absolute())
|
|
|
|
|
|
)
|
|
|
|
|
|
return await md_to_pic(md="\n".join(self._data), width=width)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Notebook:
|
|
|
|
|
|
def __init__(self, data: list[dict] | None = None):
|
|
|
|
|
|
self._data = data if data is not None else []
|
|
|
|
|
|
|
|
|
|
|
|
def text(self, text: str) -> "Notebook":
|
|
|
|
|
|
"""添加Notebook文本"""
|
|
|
|
|
|
self._data.append({"type": "paragraph", "text": text})
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def head(self, text: str, level: int = 1) -> "Notebook":
|
|
|
|
|
|
"""添加Notebook标题"""
|
|
|
|
|
|
if not 1 <= level <= 4:
|
|
|
|
|
|
raise ValueError("标题级别必须在1-4之间")
|
|
|
|
|
|
self._data.append({"type": "heading", "text": text, "level": level})
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def image(
|
|
|
|
|
|
self,
|
|
|
|
|
|
content: str | Path,
|
|
|
|
|
|
caption: str | None = None,
|
|
|
|
|
|
) -> "Notebook":
|
|
|
|
|
|
"""添加Notebook图片
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
content: 图片内容,可以是url地址,图片路径或base64字符串.
|
|
|
|
|
|
caption: 图片说明.
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
Notebook: Notebook
|
|
|
|
|
|
"""
|
|
|
|
|
|
if isinstance(content, Path):
|
|
|
|
|
|
content = str(content.absolute())
|
|
|
|
|
|
if content.startswith("base64"):
|
|
|
|
|
|
content = f"data:image/png;base64,{content.split('base64://', 1)[-1]}"
|
|
|
|
|
|
self._data.append({"type": "image", "src": content, "caption": caption})
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def quote(self, text: str | list[str]) -> "Notebook":
|
|
|
|
|
|
"""添加Notebook引用文本
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
text: 引用文本内容,可以是字符串或字符串列表.
|
|
|
|
|
|
如果是列表,则每个元素都会被单独引用。
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
Notebook: Notebook
|
|
|
|
|
|
"""
|
|
|
|
|
|
if isinstance(text, str):
|
|
|
|
|
|
self._data.append({"type": "blockquote", "text": text})
|
|
|
|
|
|
elif isinstance(text, list):
|
|
|
|
|
|
for t in text:
|
|
|
|
|
|
self._data.append({"type": "blockquote", "text": text})
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def code(self, code: str, language: str = "python") -> "Notebook":
|
|
|
|
|
|
"""添加Notebook代码块"""
|
|
|
|
|
|
self._data.append({"type": "code", "code": code, "language": language})
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def list(self, items: list[str], ordered: bool = False) -> "Notebook":
|
|
|
|
|
|
"""添加Notebook列表"""
|
|
|
|
|
|
self._data.append({"type": "list", "data": items, "ordered": ordered})
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def add_divider(self) -> None:
|
|
|
|
|
|
"""添加分隔线"""
|
|
|
|
|
|
self._data.append({"type": "divider"})
|
|
|
|
|
|
|
|
|
|
|
|
async def build(self) -> bytes:
|
|
|
|
|
|
"""构建Notebook"""
|
|
|
|
|
|
return await template_to_pic(
|
|
|
|
|
|
template_path=str((TEMPLATE_PATH / "notebook").absolute()),
|
|
|
|
|
|
template_name="main.html",
|
|
|
|
|
|
templates={"elements": self._data},
|
|
|
|
|
|
pages={
|
|
|
|
|
|
"viewport": {"width": 700, "height": 1000},
|
|
|
|
|
|
"base_url": f"file://{TEMPLATE_PATH}",
|
|
|
|
|
|
},
|
|
|
|
|
|
wait=2,
|
|
|
|
|
|
)
|