You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Docx4j转PDF时空格/制表符丢失问题求助

解决方案

1. 调整setSpace("preserve")的执行顺序

VariablePrepare.prepare()会重新处理文档中的文本节点,可能覆盖你设置的空格保留属性。必须在变量准备完成后再设置文本的space属性:

InputStream isNodo = new ByteArrayInputStream(contentNodoTempl);
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(isNodo);

// 先执行变量准备,再处理文本节点
VariablePrepare.prepare(wordMLPackage);
findAndReplace(wordMLPackage);

ByteArrayOutputStream bos = new ByteArrayOutputStream();
Docx4J.toPDF(wordMLPackage, bos);
bos.flush();
isNodo.close();
return bos.toByteArray();

2. 配置FO导出参数强制保留空格和制表符

Docx4j依赖Apache FOP进行PDF转换,默认会合并连续空格。需要通过FOSettings配置参数禁用该行为:

ByteArrayOutputStream bos = new ByteArrayOutputStream();
FOSettings foSettings = Docx4J.createFOSettings();

// 配置保留连续空格和制表符
foSettings.getProperties().put("org.apache.fop.areas.inline.space-treatment", "preserve");
foSettings.getProperties().put("org.apache.fop.areas.inline.tab-treatment", "preserve");

// 使用自定义配置导出PDF
Docx4J.toPDF(wordMLPackage, bos, foSettings);

3. 完善制表符的处理逻辑

制表符的保留还需要段落设置对应的制表位,否则FOP无法正确解析:

public static void findAndReplace(WordprocessingMLPackage doc){
    List<Object> paragraphs = getAllElementFromObject(doc.getMainDocumentPart(), P.class);
    for(Object par : paragraphs){
        P p = (P) par;
        
        // 为段落添加制表位(示例:1英寸宽度的左对齐制表位)
        if (p.getPPr() == null) {
            p.setPPr(Context.getWmlObjectFactory().createPPr());
        }
        Tabs tabs = Context.getWmlObjectFactory().createTabs();
        Tab tab = Context.getWmlObjectFactory().createTab();
        tab.setVal(STTabJc.LEFT);
        tab.setPos(BigInteger.valueOf(1440)); // 1440 twips = 1英寸
        tabs.getTab().add(tab);
        p.getPPr().setTabs(tabs);
        
        // 处理文本节点
        List<Object> texts = getAllElementFromObject(p, Text.class);
        for(Object text : texts){
            Text t = (Text)text;
            t.setSpace("preserve");
            // 确保制表符被正确识别
            t.setValue(t.getValue().replace("\t", "&#x0009;"));
        }
    }
}

4. 优化文本节点遍历逻辑

原遍历方法可能因节点类型判断不严谨漏掉部分Text节点,修改为更通用的递归遍历:

public static List<Object> getAllElementFromObject(Object obj, Class<?> toSearch) {
    List<Object> result = new ArrayList<>();
    if (obj == null) return result;
    
    if (obj instanceof JAXBElement) {
        obj = ((JAXBElement<?>) obj).getValue();
    }
    
    if (toSearch.isInstance(obj)) {
        result.add(obj);
    }
    
    if (obj instanceof ContentAccessor) {
        for (Object child : ((ContentAccessor) obj).getContent()) {
            result.addAll(getAllElementFromObject(child, toSearch));
        }
    }
    return result;
}

内容的提问来源于stack exchange,提问作者Guglio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 13:05:57