Signed-off-by: Daniel <845765@qq.com>
This commit is contained in:
Daniel 2026-01-23 18:46:09 +08:00
parent c516e38007
commit fb5d170df2
No known key found for this signature in database
GPG key ID: 86211BA83DF03017
2 changed files with 97 additions and 20 deletions

View file

@ -1,25 +1,92 @@
function Span(el)
local style = el.attributes.style
if not style then return nil end
-- 辅助:解析颜色字符串为 6 位大写十六进制(不带 #),支持颜色名、#hex、rgb()/rgba()
local function parse_color(s)
if not s then return nil end
s = s:gsub("%s+", "")
local lower = s:lower()
-- 1. 提取 Hex 颜色 (支持 #FFF 和 #FFFFFF)
local hex = style:match("color%s*:%s*#(%x+)")
if not hex then return nil end
if #hex == 3 then
hex = hex:sub(1,1):rep(2) .. hex:sub(2,2):rep(2) .. hex:sub(3,3):rep(2)
local named = {
red = "FF0000", blue = "0000FF", green = "008000",
yellow = "FFFF00", orange = "FFA500", purple = "800080",
black = "000000", white = "FFFFFF"
}
if named[lower] then return named[lower] end
local hex = s:match("#?(%x%x%x%x%x%x)")
if hex then return hex:upper() end
local r,g,b = s:match("rgb%((%d+),%s*(%d+),%s*(%d+)%)")
if r and g and b then
local function h(n) return string.format("%02X", tonumber(n) or 0) end
return h(r)..h(g)..h(b)
end
local ra,ga,ba,aa = s:match("rgba%((%d+),%s*(%d+),%s*(%d+),%s*([%d%.]+)%)")
if ra and ga and ba and aa then
local R = tonumber(ra) or 0
local G = tonumber(ga) or 0
local B = tonumber(ba) or 0
local A = tonumber(aa) or 1
if A < 0 then A = 0 end
if A > 1 then A = 1 end
local function comp(c)
local v = math.floor((A * c + (1 - A) * 255) + 0.5)
if v < 0 then v = 0 end
if v > 255 then v = 255 end
return string.format("%02X", v)
end
return comp(R)..comp(G)..comp(B)
end
return nil
end
-- 2. 提取文本内容并处理 XML 转义
-- 使用 stringify 快速获取 span 内部所有纯文本(丢弃内部嵌套样式以换取稳定性)
local txt = pandoc.utils.stringify(el.content)
txt = txt:gsub('&', '&amp;'):gsub('<', '&lt;'):gsub('>', '&gt;')
if el.attributes.style then
local style = el.attributes.style
-- 3. 构造完整的 OpenXML 运行块 (Run)
-- 这种结构 Word 识别率 100%,且不会产生标签嵌套冲突
local run_xml = string.format(
'<w:r><w:rPr><w:color w:val="%s"/></w:rPr><w:t xml:space="preserve">%s</w:t></w:r>',
hex:upper(), txt
)
local props = {}
for k, v in style:gmatch("([%w%-]+)%s*:%s*([^;]+)") do
local key = string.lower(k:gsub("^%s*(.-)%s*$", "%1"))
local val = v:gsub("^%s*(.-)%s*$", "%1")
props[key] = val
end
return pandoc.RawInline('openxml', run_xml)
end
local text_color_raw = props["color"]
local bg_color_raw = props["background-color"] or props["background"]
local text_hex = parse_color(text_color_raw)
local bg_hex = parse_color(bg_color_raw)
if text_hex or bg_hex then
-- 将 Span 的内容 stringify 为纯文本并做 XML 转义
local text = pandoc.utils.stringify(el)
local function xml_escape(s)
s = s:gsub("&", "&amp;")
s = s:gsub("<", "&lt;")
s = s:gsub(">", "&gt;")
s = s:gsub('\r\n', '\n')
s = s:gsub('\r', '\n')
s = s:gsub('"', "&quot;")
s = s:gsub("'", "&apos;")
return s
end
local need_preserve = text:match("^%s") or text:match("%s$") or text:match(" ")
local t_attr = need_preserve and ' xml:space="preserve"' or ''
local run = '<w:r><w:rPr>'
if text_hex then
run = run .. '<w:color w:val="' .. text_hex .. '"/>'
end
if bg_hex then
run = run .. '<w:shd w:val="clear" w:color="auto" w:fill="' .. bg_hex .. '"/>'
end
run = run .. '</w:rPr><w:t' .. t_attr .. '>' .. xml_escape(text) .. '</w:t></w:r>'
return { pandoc.RawInline('openxml', run) }
end
end
return el
end

View file

@ -773,7 +773,6 @@ func ExportDocx(id, savePath string, removeAssets, merge bool) (fullPath string,
"-f", "html+tex_math_dollars",
"--resource-path", tmpDir,
"-o", tmpDocxPath,
"--lua-filter", util.PandocColorFilterPath,
}
params := util.RemoveInvalid(Conf.Export.PandocParams)
@ -786,6 +785,17 @@ func ExportDocx(id, savePath string, removeAssets, merge bool) (fullPath string,
}
}
hasLuaFilter := false
for i := 0; i < len(args)-1; i++ {
if "--lua-filter" == args[i] {
hasLuaFilter = true
break
}
}
if !hasLuaFilter {
args = append(args, "--lua-filter", util.PandocColorFilterPath)
}
pandoc := exec.Command(Conf.Export.PandocBin, args...)
gulu.CmdAttr(pandoc)
pandoc.Stdin = bytes.NewBufferString(content)