# mark2pdf 路径处理说明（基于当前实现）

> 修订日期: 2026-01-01  
> 范围: `src/mark2pdf`, `src/helper_workingpath`, `src/markdown2pdf`, `src/helper_typst`

本文描述的是当前代码的真实行为，不包含旧版本或计划中的逻辑。

## 1. 根目录角色

| 名称 | 来源 | 用途 |
| --- | --- | --- |
| **data_root** | `ConfigManager._find_config_root()` | 工作区根目录：从 CWD 向上查找 `mark2pdf.config.toml`，命中目录即为 data_root |
| **code_root** | `config_loader.get_code_root()` | 代码根目录：从 `src/mark2pdf/config_loader.py` 向上查找 `pyproject.toml` 或 `package.json` |
| **project_root** | `helper_workingpath.get_project_root()` | 与 code_root 相同算法，但入口文件不同；用于无配置时的回退 |

说明：开发仓库中 `data_root` 往往和 `code_root` 一致；作为已安装包时二者通常不同。

## 2. 工作区解析（ConfigManager）

当前逻辑只有“向上查找配置文件”，没有 CLI 参数或环境变量覆盖。

1. 从 `Path.cwd()` 向上查找 `mark2pdf.config.toml`
2. 找到则 `data_root = 该目录`，`standalone = False`
3. 未找到则进入独立模式：
   - `data_root = Path.cwd()`
   - `paths.input = "."`
   - `paths.output = "."`
   - `paths.tmp = ".mark2pdf_tmp"`
   - `standalone = True`

## 3. 路径配置来源与解析规则

`mark2pdf.config.toml` 的 `[paths]` 字段映射到代码中的 `PathsConfig`：

```
[paths]
in = "in"          -> config.paths.input
out = "out"        -> config.paths.output
tmp = "tmp"        -> config.paths.tmp
template = "template" -> config.paths.template
```

解析规则：
- 相对路径会拼接到 `data_root`
- 绝对路径保持不变
- `Mark2pdfConfig.input_dir/output_dir/tmp_dir/template_dir` 直接返回上述拼接后的绝对路径
- `frontmatter.yaml` 固定从 `data_root/frontmatter.yaml` 读取

## 4. 单文件输入/输出路径解析

单文件转换使用 `helper_workingpath.resolve_inout_paths()`。

处理流程：
1. **输入文件名必须是纯文件名**（不能包含 `/` 或 `\`），否则抛出异常
2. 若缺少扩展名，默认补 `.md`
3. 根目录选择：
   - 若 `ConfigManager` 可用且 `data_root` 已设置：以 `data_root` 为基准解析 `indir/outdir`
   - 否则回退到 `project_root` + `in/out`
4. 确认输入文件存在
5. 生成输出路径：
   - `outfile` 为空：`<outdir>/<input_stem>.<ext>`
   - `outfile` 有扩展名：`<outdir>/<outfile>`
   - `outfile` 无扩展名：`<outdir>/<outfile>.<ext>`
   - 若目标已存在，`safesave_path` 会追加时间戳

后续覆盖逻辑：
- 当 `options.overwrite=True` 时，`markdown2pdf.convert_file` 会重新计算输出路径，不使用时间戳。

输出文件名还可能被以下机制覆盖：
- `filename_with_title=True`：使用 frontmatter 的 `title` 生成安全文件名
- `exportFilename`：只保留文件名部分，拒绝包含 `..`

## 5. 目录模式与批量模式

- **目录模式**：`convert_directory(directory=...)` 将 `directory` 视为 `indir` 的子目录  
  输出到 `outdir/<directory>.pdf`（或 `.typ`）
- **批量模式**：对 `input_dir` 内的每个 `.md` 逐一转换  
  `directory != "."` 时，内部会传入 `indir = "<paths.input>/<directory>"`，但文件名仍为纯文件名

图片路径约定：
- 单文件模式：图片目录为 `<input_dir>/images`
- 目录模式：图片目录为 `<input_dir>/<directory>/images`

## 6. 模板选择与路径解析

模板名称的优先级（`resolve_template`）：
```
CLI --template > frontmatter.template > config.build.default_template > "nb.typ"
```

模板文件路径解析（`resolve_template_path`）：
1. 模板名必须是纯文件名（禁止包含路径分隔符）
2. 若 `data_root` 可用，优先查找 `data_root/template/<template>`
3. 否则回退 `project_root/template/<template>`
4. 找不到则抛 `FileNotFoundError`

注意：当前转换流程调用 `resolve_template_path()` 时使用默认 `template_dir="template"`，**不会读取** `config.paths.template`。除非调用方显式传入 `template_dir`，否则模板目录固定为 `template/`。

## 7. 临时目录与沙箱

临时目录来源：
- 有 `data_root`：`config.tmp_dir`
- 无 `data_root`：
  - 单文件：`project_root/tmp`
  - 目录模式：`cwd/tmp`

转换时会在 `tmp_dir` 下创建沙箱目录（如 `md2pdf_XXXX`）：
- 写入临时 Markdown 文件
- 在沙箱中创建 `images` 的符号链接（若存在）
- 传给 pandoc 进行转换

## 8. Typst 模板依赖与资产

模板依赖处理（`helper_typst`）：
- `#import` / `#include` 的 `.typ` 文件会被解析为模板目录下的真实路径
- `image("...")` 资产若在子目录，会复制整个目录到沙箱
- 运行时会复制模板文件及依赖/资产到沙箱目录，再以模板文件名执行 pandoc

当前实现的一个重要特性：
- `.typ` 依赖文件**会被复制到沙箱根目录（只保留文件名）**
- 资产目录会保留目录名

因此，模板中引用 `.typ` 依赖时，建议使用同级文件；若引用子目录路径，需要确保沙箱中能找到对应路径。

## 9. 安全与限制（当前实现）

- 输入文件名和模板名禁止包含路径分隔符（防止路径穿越）
- `exportFilename` 会被裁剪为文件名；包含 `..` 会被忽略
- 输出路径允许使用绝对路径（通过 `outdir`/`outfile` 传入），否则默认落在 `data_root` 的输出目录下

## 解决方案（建议改进项）

以下是基于当前实现的可执行改进方向，按影响优先级排序：

1. **模板目录与配置一致**  
   让模板解析尊重 `config.paths.template`：  
   - 在调用 `resolve_template_path()` 时传入 `template_dir=config.paths.template`  
   - 或在 `resolve_template_path()` 内部若 `config` 存在则读取 `config.paths.template`

2. **统一 tmp 目录来源**  
   目录模式与单文件模式当前回退逻辑不一致（`cwd/tmp` vs `project_root/tmp`），建议统一为：  
   - 有 `data_root`：`config.tmp_dir`  
   - 无 `data_root`：`project_root/tmp`

3. **Typst 依赖路径保持结构**  
   目前 `.typ` 依赖复制到沙箱根目录会丢失子目录结构。可改为：  
   - 按相对路径复制到沙箱，或  
   - 重写 import/include 路径以匹配扁平化结构

4. **工作区解析扩展（如需）**  
   若需要 CLI/环境变量覆盖工作区，可在 `ConfigManager._resolve_data_root()` 中加入：  
   - `--dir` 参数  
   - `MARK2PDF_DATA_DIR` 环境变量  
   并同步更新本文档。
