diff --git a/app/pandoc/pandoc-resources/pandoc_color_filter.lua b/app/pandoc/pandoc-resources/pandoc_color_filter.lua
index d67bf68dd..1f12de25d 100644
--- a/app/pandoc/pandoc-resources/pandoc_color_filter.lua
+++ b/app/pandoc/pandoc-resources/pandoc_color_filter.lua
@@ -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('&', '&'):gsub('<', '<'):gsub('>', '>')
+ if el.attributes.style then
+ local style = el.attributes.style
- -- 3. 构造完整的 OpenXML 运行块 (Run)
- -- 这种结构 Word 识别率 100%,且不会产生标签嵌套冲突
- local run_xml = string.format(
- '%s',
- 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
\ No newline at end of file
+ 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("&", "&")
+ s = s:gsub("<", "<")
+ s = s:gsub(">", ">")
+ s = s:gsub('\r\n', '\n')
+ s = s:gsub('\r', '\n')
+ s = s:gsub('"', """)
+ s = s:gsub("'", "'")
+ 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 = ''
+ if text_hex then
+ run = run .. ''
+ end
+ if bg_hex then
+ run = run .. ''
+ end
+ run = run .. '' .. xml_escape(text) .. ''
+
+ return { pandoc.RawInline('openxml', run) }
+ end
+ end
+
+ return el
+end
diff --git a/kernel/model/export.go b/kernel/model/export.go
index 270bb309b..9843c5267 100644
--- a/kernel/model/export.go
+++ b/kernel/model/export.go
@@ -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)