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)