Metadata-Version: 2.4
Name: md2image
Version: 0.1.4
Summary: Render Markdown/Image to PIL Images with flexible layout composition
Author: Xiang Li
Project-URL: Homepage, https://github.com/lix8886/md2img
Project-URL: Repository, https://github.com/lix8886/md2img
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: pillow>=9.0

# md2img

Render Markdown or Image to PIL Images with flexible layout composition.

## Features

- Render Markdown to PNG: headings, bold/italic, code blocks, lists, tables, blockquotes, links
- Compose multiple images horizontally or vertically with optional Markdown panels and description bars
- Three built-in themes: `default`, `dark`, `warm`
- Pure Python (PIL), no heavy dependencies

## Install

```bash
pip install md2image
```

Requires Python 3.8+ and Pillow >= 9.0.

## Quick Start

```python
from md2image import render_md, compose_h, compose_v

# Pure Markdown rendering
img = render_md("# Hello\n\n**Bold** and *italic* text.", width=800, theme="dark")
img.save("output.png")

# Compose images horizontally with a Markdown header
from PIL import Image
imgs = [Image.new("RGB", (300, 200), c) for c in ["#4F46E5", "#059669", "#DC2626"]]
result = compose_h(imgs, top_md="# Experiment Results\nThree runs compared.", target_height=220)
result.save("comparison.png")
```

## API

```python
from md2image import render_md, compose_h, compose_v

# Render markdown string to image
render_md(markdown, width=800, theme="default", padding=40)

# Horizontal composition
compose_h(images, target_height=240, top_md=..., bottom_md=..., top_desc=..., bottom_desc=..., theme="default")

# Vertical composition
compose_v(images, target_width=440, left_md=..., right_md=..., md_panel_width=280, theme="default")
```

Themes: `default` | `dark` | `warm`

### Examples

````python
import os
from PIL import Image, ImageDraw
from md2image import (
    render_md,
    compose_h,
    compose_v,
    MarkdownRenderer,
    ImageCompositor,
    THEMES,
)

OUT = "./output"
os.makedirs(OUT, exist_ok=True)


# ─────────────────────────────────────────────
# Helper: generate a solid-color placeholder image
# ─────────────────────────────────────────────
def make_placeholder(w: int, h: int, color: str, label: str) -> Image.Image:
    img = Image.new("RGB", (w, h), color)
    draw = ImageDraw.Draw(img)
    from md_image_renderer import get_font, fw, fh

    font = get_font("bold", 20)
    tw, th = int(fw(font, label)), fh(font)
    draw.text(((w - tw) // 2, (h - th) // 2), label, font=font, fill="#FFFFFF")
    return img


# ─────────────────────────────────────────────
# 1. Pure Markdown rendering (all element types)
# ─────────────────────────────────────────────
FULL_MD = """\
# Markdown 渲染演示

## 文字样式

普通段落文字。**粗体文字**，*斜体文字*，***粗斜体***，`行内代码`，
以及 [超链接](https://example.com) 都支持。

## 标题层级

### H3 三级标题
#### H4 四级标题
##### H5 五级标题
###### H6 六级标题

---

## 列表

### 无序列表

- 苹果 — 富含维生素C
- 香蕉 — 富含钾元素
  - 香蕉船
  - 香蕉蛋糕
- 橙子 — 富含维生素C

### 有序列表

1. 第一步：安装依赖
2. 第二步：配置环境
3. 第三步：运行程序
   - 子步骤 A
   - 子步骤 B

## 引用块

> **引用示例**
>
> 这是一段 *blockquote* 文字，支持内部的 **Markdown** 格式。
> 可以换行继续引用内容。

## 代码块

```python
def fibonacci(n: int) -> list[int]:
    a, b = 0, 1
    result = []
    for _ in range(n):
        result.append(a)
        a, b = b, a + b
    return result

print(fibonacci(10))
```

## 表格

| 语言       |  类型  |        用途 | 流行度 |
| :--------- | :----: | ----------: | :----- |
| Python     | 解释型 | AI/数据分析 | ★★★★★  |
| JavaScript | 解释型 |     Web前端 | ★★★★★  |
| Rust       | 编译型 |    系统编程 | ★★★★   |
| Go         | 编译型 |    后端服务 | ★★★★   |
| TypeScript | 解释型 |    大型项目 | ★★★★   |

"""

print("生成 1. 完整 Markdown 渲染图 (default / dark / warm theme)...")

for theme*name in ("default", "dark", "warm"):
img = render_md(FULL_MD, width=860, theme=theme_name, padding=48)
img.save(f"{OUT}/01_full_md*{theme*name}.png")
print(f" ✓ 01_full_md*{theme_name}.png ({img.width}×{img.height})")

# ─────────────────────────────────────────────

# 2. 横向拼接 — 顶部 MD + 底部描述

# ─────────────────────────────────────────────

print("\n生成 2. 横向拼接图 (顶部MD + 底部描述)...")

imgs_h = [
make_placeholder(320, 220, "#4F46E5", "图片 A"),
make_placeholder(280, 220, "#059669", "图片 B"),
make_placeholder(300, 220, "#DC2626", "图片 C"),
]

TOP_MD = """\

## 横向拼接示例

三张图片**横向排列**，顶部放置渲染后的 Markdown 说明文字。
支持 `inline code`、_斜体_、**粗体** 等格式。
"""

result_h1 = compose_h(
imgs_h,
theme="default",
gap=12,
outer_padding=20,
target_height=240,
top_md=TOP_MD,
bottom_desc="图 1：三张横向拼接图片 — 顶部 Markdown 说明 + 底部描述栏",
)
result_h1.save(f"{OUT}/02_horizontal_top_md_bottom_desc.png")
print(
f" ✓ 02_horizontal_top_md_bottom_desc.png ({result_h1.width}×{result_h1.height})"
)

# 3. 横向拼接 — 顶部描述 + 底部 MD

print("\n生成 3. 横向拼接图 (顶部描述 + 底部MD)...")

BOTTOM_MD = """\

### 数据分析结果

| 指标     |  数值 | 说明       |
| :------- | ----: | :--------- |
| 准确率   | 96.4% | 测试集表现 |
| 召回率   | 94.1% | 正样本覆盖 |
| F1 Score | 0.952 | 综合评分   |

> 模型在验证集上的综合表现 **优秀**，可以部署。
> """

result_h2 = compose_h(
imgs_h,
theme="dark",
gap=16,
outer_padding=24,
target_height=200,
top_desc="实验组 A · B · C 对比结果",
bottom_md=BOTTOM_MD,
)
result_h2.save(f"{OUT}/03_horizontal_top_desc_bottom_md.png")
print(
f" ✓ 03_horizontal_top_desc_bottom_md.png ({result_h2.width}×{result_h2.height})"
)

# 4. 横向拼接 — 全配置（顶部MD + 顶部描述 + 底部描述 + 底部MD）

print("\n生成 4. 横向拼接全配置...")

result_h3 = compose_h(
imgs_h,
theme="warm",
gap=10,
target_height=180,
top_md="## 顶部 Markdown 标题\n项目**对比图**，三组实验并排展示。",
top_desc="⬆ 实验说明",
bottom_desc="⬇ 图 4：横向全配置 Demo",
bottom_md="### 小结\n- 图A：基线模型\n- 图B：改进版本\n- 图C：最终方案",
)
result_h3.save(f"{OUT}/04_horizontal_full.png")
print(f" ✓ 04_horizontal_full.png ({result_h3.width}×{result_h3.height})")

# ─────────────────────────────────────────────

# 5. 竖向拼接 — 左侧MD + 右侧描述

# ─────────────────────────────────────────────

print("\n生成 5. 竖向拼接图 (左侧MD + 右侧描述)...")

imgs_v = [
make_placeholder(400, 180, "#7C3AED", "Step 1"),
make_placeholder(400, 180, "#1D4ED8", "Step 2"),
make_placeholder(400, 180, "#0F766E", "Step 3"),
]

LEFT_MD = """\

## 流程说明

本图展示三个
处理步骤。

**Step 1** — 数据预处理

_Step 2_ — 模型训练

`Step 3` — 结果评估

---

> 每步之间有
> **严格依赖**关系
> """

result_v1 = compose_v(
imgs_v,
theme="default",
gap=12,
outer_padding=20,
target_width=440,
left_md=LEFT_MD,
right_desc="图 5：三步处理流程竖向拼接 — 左侧 Markdown 说明",
md_panel_width=260,
)
result_v1.save(f"{OUT}/05_vertical_left_md_right_desc.png")
print(f" ✓ 05_vertical_left_md_right_desc.png ({result_v1.width}×{result_v1.height})")

# 6. 竖向拼接 — 左侧描述 + 右侧MD

print("\n生成 6. 竖向拼接图 (左侧描述 + 右侧MD)...")

RIGHT_MD = """\

## 参数配置

```python
config = {
  "lr": 1e-4,
  "epochs": 50,
  "batch": 32,
}
```

| 参数   |   值 |
| :----- | ---: |
| lr     | 1e-4 |
| epochs |   50 |
| batch  |   32 |

"""

result_v2 = compose_v(
imgs_v,
theme="dark",
gap=10,
outer_padding=20,
target_width=420,
left_desc="实验日志 2024-01",
right_md=RIGHT_MD,
md_panel_width=270,
)
result_v2.save(f"{OUT}/06_vertical_left_desc_right_md.png")
print(f" ✓ 06_vertical_left_desc_right_md.png ({result_v2.width}×{result_v2.height})")

# 7. 竖向拼接 — 双侧MD

print("\n生成 7. 竖向拼接全配置 (左侧描述+MD + 右侧MD+描述)...")

result_v3 = compose_v(
imgs_v,
theme="warm",
gap=14,
target_width=380,
left_desc="← 左侧面板",
left_md="## 左侧说明\n\n- **输入**：原始图像\n- **输出**：特征图\n\n> 注意图像分辨率",
right_md="## 右侧注记\n\n1. 验证阶段\n2. 测试阶段\n\n`accuracy > 95%`",
right_desc="右侧面板 →",
md_panel_width=240,
)
result_v3.save(f"{OUT}/07_vertical_full.png")
print(f" ✓ 07_vertical_full.png ({result_v3.width}×{result_v3.height})")

# 8. 单张图片 + MD

print("\n生成 8. 单张图片横向(顶MD+底描述)...")
single = make_placeholder(600, 400, "#B45309", "单张图片")
result_s = compose_h(
[single],
theme="default",
top_md="# 单张图片示例\n\n这是一张**单独**的图片，顶部有 Markdown 标题，底部有描述栏。\n\n- 特性 A\n- 特性 B\n- 特性 C",
bottom_desc="图 8：单张图片 + 顶部 Markdown + 底部描述",
)
result_s.save(f"{OUT}/08_single_image_h.png")
print(f" ✓ 08_single_image_h.png ({result_s.width}×{result_s.height})")

print("\n✅ 所有示例图片已生成至:", OUT)
print("文件列表：")
for f in sorted(os.listdir(OUT)):
if f.endswith(".png"):
path = os.path.join(OUT, f)
size = os.path.getsize(path)
print(f" {f} ({size//1024} KB)")

````

## License

MIT
