Skip to content

Commit fd3ca6c

Browse files
committed
feat: 添加 HTML 图片和换行标签支持
1 parent 3b67a3a commit fd3ca6c

5 files changed

Lines changed: 116 additions & 6 deletions

File tree

.claude/settings.local.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
"Bash(cargo fmt --check)",
1717
"Bash(cargo fmt)",
1818
"Bash(cargo test)",
19-
"Bash(cargo run -- /Users/boran/Program/rust/md2tex-converter/test/simple_rawdoc.md)"
19+
"Bash(cargo run -- /Users/boran/Program/rust/md2tex-converter/test/simple_rawdoc.md)",
20+
"Bash(cargo run -- test/simple_rawdoc.md test/output.tex)",
21+
"Bash(tectonic --version)",
22+
"Bash(tectonic output.tex)"
2023
]
2124
}
2225
}

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "md2tex"
3-
version = "0.3.0"
3+
version = "0.3.1"
44
edition = "2024"
55

66
[dependencies]

src/main.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,21 @@ fn latex_template(content: &str) -> String {
144144
/// 在程序生命周期内只编译一次,类似 C++ 中静态编译的正则模式
145145
static MATH_TOKEN_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\{MATH\d+\})").unwrap());
146146

147+
/// HTML 标签正则表达式
148+
/// 匹配 <br> 或 <br/> 换行标签
149+
static HTML_BR_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"<br\s*/?>").unwrap());
150+
151+
/// 匹配 <img ...> 标签
152+
/// 提取 src、alt、width 属性
153+
/// 使用非贪婪匹配 .*? 捕获属性值,支持双引号或单引号
154+
/// C++ 中类似的模式匹配需要手写解析器,而 Rust 可以直接用正则
155+
static HTML_IMG_REGEX: Lazy<Regex> =
156+
Lazy::new(|| Regex::new(r#"<img\s+[^>]*src\s*=\s*["']([^"']+)["'][^>]*>"#).unwrap());
157+
158+
/// 提取 img 标签中所有属性的正则(用于获取 alt 和 width)
159+
static HTML_IMG_ATTR_REGEX: Lazy<Regex> =
160+
Lazy::new(|| Regex::new(r#"(\w+)\s*=\s*["']([^"']*)["']"#).unwrap());
161+
147162
/// 将 Markdown 事件转换为 LaTeX
148163
/// 这类似于 C++ 中的 Visitor 模式
149164
fn convert_markdown_to_latex(
@@ -310,6 +325,84 @@ fn convert_markdown_to_latex(
310325
Event::End(TagEnd::Link) => {
311326
latex_content.push('}');
312327
}
328+
// HTML 标签处理:<br>、<img> 等
329+
Event::Html(html) | Event::InlineHtml(html) => {
330+
let html_str = html.trim();
331+
332+
// 处理 <br> 换行标签
333+
if HTML_BR_REGEX.is_match(html_str) {
334+
latex_content.push_str("\\newline ");
335+
continue;
336+
}
337+
338+
// 处理 <img> 标签
339+
if let Some(caps) = HTML_IMG_REGEX.captures(html_str) {
340+
let src = caps.get(1).map(|m| m.as_str()).unwrap_or("");
341+
342+
// 使用另一个正则提取所有属性
343+
let mut alt = String::new();
344+
let mut width = String::new();
345+
346+
for attr_cap in HTML_IMG_ATTR_REGEX.captures_iter(html_str) {
347+
let attr_name = attr_cap.get(1).map(|m| m.as_str()).unwrap_or("");
348+
let attr_value = attr_cap.get(2).map(|m| m.as_str()).unwrap_or("");
349+
350+
match attr_name {
351+
"alt" => alt = attr_value.to_string(),
352+
"width" => width = attr_value.to_string(),
353+
_ => {}
354+
}
355+
}
356+
357+
// 转换宽度值
358+
let width_bracket = if !width.is_empty() {
359+
// 处理不同的 width 格式:
360+
// - "50%" -> 转换为 0.5
361+
// - "0.5" -> 直接使用
362+
// - "100px" -> 忽略单位,简单处理
363+
let clean_width = width
364+
.trim_end_matches("px")
365+
.trim_end_matches("%")
366+
.trim()
367+
.to_string();
368+
369+
if width.ends_with('%') {
370+
// 百分比格式:50% -> 0.5
371+
if let Ok(pct) = clean_width.parse::<f64>() {
372+
format!("[width={}\\textwidth]", pct / 100.0)
373+
} else {
374+
"[width=0.8\\textwidth]".to_string()
375+
}
376+
} else if let Ok(num) = clean_width.parse::<f64>() {
377+
// 纯数字:0.5 -> [width=0.5\textwidth]
378+
if num <= 1.0 {
379+
format!("[width={}\\textwidth]", num)
380+
} else {
381+
// 如果 >1,可能是像素值或其他单位,默认使用 0.8
382+
"[width=0.8\\textwidth]".to_string()
383+
}
384+
} else {
385+
// 无法解析,默认使用 0.8
386+
"[width=0.8\\textwidth]".to_string()
387+
}
388+
} else {
389+
// 没有 width 属性,默认使用 0.8
390+
"[width=0.8\\textwidth]".to_string()
391+
};
392+
393+
// 生成 LaTeX 图片环境
394+
latex_content.push_str("\n\\begin{figure}[htbp]\n\\centering\n");
395+
latex_content
396+
.push_str(&format!("\\includegraphics{}{{{}}}", width_bracket, src));
397+
398+
if !alt.is_empty() {
399+
latex_content.push_str(&format!("\n\\caption{{{}}}", escape_latex(&alt)));
400+
}
401+
402+
latex_content.push_str("\n\\end{figure}\n\n");
403+
}
404+
// <u> 和 </u> 标签暂时忽略
405+
}
313406
// 其他事件:忽略
314407
_ => {}
315408
}

test/simple_rawdoc.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,21 @@ a^2 + b^2 = c^2
5757

5858
![项目印象图](Gemini_Generated_Image_a40x9qa40x9qa40x.png)
5959

60+
下面是不同大小的图片测试:
61+
62+
<img src="Gemini_Generated_Image_a40x9qa40x9qa40x.png" alt="图片测试1" width="200"/>
63+
64+
<img src="Gemini_Generated_Image_a40x9qa40x9qa40x.png" alt="图片测试2" width="300"/>
65+
66+
其他的html width属性测试:
67+
68+
<img src="Gemini_Generated_Image_a40x9qa40x9qa40x.png" alt="图片测试3" width="50%"/>
69+
70+
<img src="Gemini_Generated_Image_a40x9qa40x9qa40x.png" alt="图片测试4" width="100px"/>
71+
72+
73+
6074
## 链接测试
61-
这是一个链接:[点击这里访问 OpenAI](https://www.openai.com)
62-
这是一个链接:[点击这里访问 GitHub](https://github.com)
63-
这是一个链接:[点击这里访问 Rust 官网](https://www.rust-lang.org)
75+
- 这是一个链接:[点击这里访问 OpenAI](https://www.openai.com)
76+
- 这是一个链接:[点击这里访问 GitHub](https://github.com)
77+
- 这是一个链接:[点击这里访问 Rust 官网](https://www.rust-lang.org)

0 commit comments

Comments
 (0)