Skip to content

将 Markdown 和 LaTeX 转为图片的脚本开发

Published: at 23:54

前几天写了个 QQ 的机器人玩玩,接了一个第三方的 AI 接口。虽然在 LiteLoaderQQNT 中有相关的插件可以渲染 Markdown 和 LaTeX 等格式的文本,但大部分人是没有这些插件的。因此,我想着能否写一个将含有 Markdown 和 LaTeX 格式的文本转化为图片的脚本,于是便有了以下操作。

实现思路

经过查阅资料,了解到可以通过以下步骤实现:

  1. 将生成的文本转化为 HTML 网页;
  2. 使用 MathJax 将其中的 LaTeX 格式渲染出来;
  3. 对页面进行截图并保存。

JavaScript 脚本:网页截图

首先,写一个 JS 脚本,用于将给定的 HTML 网页进行截图,代码如下:

const puppeteer = require('puppeteer');
const path = require('path');
const fs = require('fs');

// 从命令行参数获取输入和输出路径
const args = process.argv.slice(2);
const inputPath = args[0];
const outputPath = args[1];

// 检查是否提供了参数
if (!inputPath || !outputPath) {
  console.error('用法: node script.js <输入路径> <输出路径>');
  process.exit(1);
}

// 将输入路径解析为绝对路径
const resolvedInputPath = path.resolve(__dirname, inputPath);

// 检查输入文件是否存在
if (!fs.existsSync(resolvedInputPath)) {
  console.error('输入文件未找到:', resolvedInputPath);
  process.exit(1);
}

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setViewport({ width: 1200, height: 100 });

  // 打开页面
  await page.goto('file://' + resolvedInputPath, { waitUntil: 'networkidle0' });

  // 尝试等待 MathJax 渲染完成,最多等待 2 秒
  try {
    await page.waitForFunction(
      'window.MathJax && window.MathJax.Hub && window.MathJax.Hub.getAllJax().length > 0',
      { timeout: 2000 }
    );
  } catch (e) {
    console.log('MathJax 未完全加载,但继续生成图片...');
  }

  // 截图并保存到指定输出路径
  const resolvedOutputPath = path.resolve(__dirname, outputPath);
  await page.screenshot({ path: resolvedOutputPath, fullPage: true });

  console.log('Screenshot Successfully:', resolvedOutputPath);

  await browser.close();
})();

将上述代码保存为 screenshot.js

配置依赖环境

上述代码依赖 Node.js 环境,首先要配置好 Node.js 的环境变量,同时使用以下命令在相同文件夹下安装 Puppeteer 包:

npm install puppeteer

Python 脚本:Markdown 转 HTML

在相同文件夹下新建一个 md2img.py 的 Python 程序,用于将 Markdown 和 LaTeX 转换为图片。

import markdown
from markdown.extensions import Extension
from markdown.treeprocessors import Treeprocessor

# MathJax
class MathJaxExtension(Extension):
    def extendMarkdown(self, md):
        md.treeprocessors.register(MathJaxProcessor(md), 'mathjax', 175)

class MathJaxProcessor(Treeprocessor):
    def run(self, root):
        for element in root.iter():
            if element.tag == 'span' and 'class' in element.attrib and 'math' in element.attrib['class']:
                element.tag = 'script'
                element.attrib['type'] = 'math/tex'
                element.text = element.text

def replace_brackets(input_string):
    input_string = input_string.replace('\[', '?gzl?')
    input_string = input_string.replace('\]', '?gzr?')
    input_string = input_string.replace('\(', '?gxl?')
    input_string = input_string.replace('\)', '?gxr?')
    return input_string

def covert_brackets(input_string):
    input_string = input_string.replace('?gzl?', '\[')
    input_string = input_string.replace('?gzr?', '\]')
    input_string = input_string.replace('?gxl?', '\(')
    input_string = input_string.replace('?gxr?', '\)')
    return input_string

def convert_markdown_to_html_with_latex(md_text):
    md = markdown.Markdown(extensions=['codehilite', 'fenced_code', MathJaxExtension()])
    html = md.convert(md_text)
    html = covert_brackets(html)
    return f"""
    <html>
    <head>
        <meta charset="UTF-8">
        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
        <script>hljs.highlightAll();</script>
        <style>body {{ font-size: 30px; }}</style>
    </head>
    <body>
        {html}
    </body>
    </html>
    """

def markdown_to_html(md_text, html_out_path):
    html_output = convert_markdown_to_html_with_latex(replace_brackets(md_text))
    with open(html_out_path, 'w', encoding='utf-8') as f:
        f.write(html_output)
    print("HTML has finished.")

def html_to_image(html_path, img_path):
    import subprocess
    import os

    html_path = os.path.abspath(html_path)
    img_path = os.path.abspath(img_path)

    if not os.path.exists(html_path):
        raise FileNotFoundError(f"HTML 文件未找到: {html_path}")

    node_command = ["node", "screenshot.js", html_path, img_path]

    try:
        result = subprocess.run(
            node_command,
            check=True,
            text=True,
            capture_output=True
        )
        print("screenshot.js 输出:", result.stdout)
    except subprocess.CalledProcessError as e:
        print("screenshot.js 执行失败:", e.stderr)
        raise

    if not os.path.exists(img_path):
        raise FileNotFoundError(f"图片生成失败: {img_path}")

    print(f"图片生成成功: {img_path}")

import os
import time

def markdown_to_image(md_text, img_out_path):
    tmp_dir = os.path.join(os.getcwd(), 'tmp', 'cache')
    os.makedirs(tmp_dir, exist_ok=True)
    timestamp = int(time.time())
    html_file_path = os.path.join(tmp_dir, f"temp_{timestamp}.html")
    markdown_to_html(md_text, html_file_path)
    try:
        html_to_image(html_file_path, img_out_path)
        print(f"图片生成成功: {img_out_path}")
    except Exception as e:
        print(f"图片生成失败: {e}")
        raise
    finally:
        if os.path.exists(html_file_path):
            os.remove(html_file_path)

配置依赖环境

上述 Python 程序需要安装 markdown 包,运行以下命令安装:

pip install markdown

调用示例

新建一个 test.py,在里面调用 md2img.markdown_to_image(md_text, img_out_path) 即可实现 Markdown 和 LaTeX 转图片。


上一篇文章
测试 latex 是否可以正常渲染