feat: 实现 Pandoc DOCX 转换服务

This commit is contained in:
SkyJourney
2026-07-30 15:05:50 +08:00
parent fa159d916f
commit bf43433d38
32 changed files with 2908 additions and 61 deletions
@@ -0,0 +1,110 @@
local map_path = os.getenv("MD_TO_PDF_DOCX_MEDIA_MAP")
if map_path == nil or map_path == "" then
error("DOCX media map path is missing")
end
local map_file, open_error = io.open(map_path, "rb")
if map_file == nil then
error("DOCX media map cannot be opened: " .. tostring(open_error))
end
local map_content = map_file:read("*a")
map_file:close()
local media_map = pandoc.json.decode(map_content, false)
local counters = {
image = 0,
mermaid = 0,
echarts = 0
}
local function contains_class(classes, expected)
for _, class_name in ipairs(classes) do
if class_name == expected then
return true
end
end
return false
end
local function next_media(kind)
counters[kind] = counters[kind] + 1
local media = media_map[kind][counters[kind]]
if media == nil then
error(
"DOCX media map does not contain "
.. kind
.. " #"
.. tostring(counters[kind])
)
end
return media
end
local function media_image(media)
return pandoc.Image(
pandoc.Inlines { pandoc.Str(media.alt_text) },
media.path,
"",
{
width = media.width,
height = media.height
}
)
end
local function replace_image(image)
local media = next_media("image")
image.src = media.path
image.caption = pandoc.Inlines { pandoc.Str(media.alt_text) }
image.attributes.width = media.width
image.attributes.height = media.height
return image
end
local function replace_code_block(block)
local kind
if contains_class(block.classes, "mermaid") then
kind = "mermaid"
elseif contains_class(block.classes, "echarts") then
kind = "echarts"
else
return block
end
local media = next_media(kind)
local image = media_image(media)
if media.caption ~= nil and media.caption ~= "" then
return pandoc.Figure(
{ pandoc.Plain { image } },
pandoc.Caption {
pandoc.Plain { pandoc.Str(media.caption) }
}
)
end
return pandoc.Para { image }
end
local function validate_counts()
for _, kind in ipairs({ "image", "mermaid", "echarts" }) do
if counters[kind] ~= #media_map[kind] then
error(
"DOCX media map contains unused "
.. kind
.. " resources"
)
end
end
end
return {
Pandoc = function(document)
local transformed = document:walk {
Image = replace_image,
CodeBlock = replace_code_block
}
validate_counts()
return transformed
end
}