使用 Apache POI 在 Word Doc 表中创建无法解释的间距

问题描述 投票:0回答:2

我正在尝试使用 Apache POI 库 (v5.2.3) 在 Word 文档中创建标题。我正在使用表格,这样我就可以获得我正在寻找的对齐方式。这是我得到的结果:

enter image description here

我不会保留表格/单元格边框,但我保留它们来解释问题。表格最左边和“部分标题”文本之间存在一些边距/填充/间距,右侧也存在类似问题。我不知道是什么造成了这个间距或如何删除它。我尝试在 LibreOffice 中调查 word doc 结果,但我不明白是什么元素导致了间距,即表格、单元格或文本的段落包装。文本标签本身绝对不包含任何额外的空白字符。当表格/单元格边框被删除时,虚拟缩进不会消失。

int TWIPS_PER_INCH = 1440;

XWPFHeader header = doc.createHeader(HeaderFooterType.DEFAULT);
XWPFTable table = header.createTable(1, 2);
//table.removeBorders();
table.setWidth("100%");
table.setCellMargins(0, 0, 0, 0);

/*
 * Create CTTblGrid for this table with widths of the 2 columns. 
 * Necessary for Libreoffice/Openoffice to accept the column widths.
 */
table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(3 * TWIPS_PER_INCH));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(3 * TWIPS_PER_INCH));

/*
 * Left-Hand Cell
 */
XWPFTableRow tableRow = table.getRow(0);
XWPFTableCell cell = tableRow.getCell(0); 

cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.BOTTOM);
cell.setWidth("50%");

XWPFParagraph paragraph = doc.createParagraph();
paragraph = cell.getParagraphArray(0);
if (paragraph == null) paragraph = cell.addParagraph();

paragraph.setAlignment(ParagraphAlignment.LEFT);
paragraph.setSpacingBefore(0);
paragraph.setSpacingAfter(0);
paragraph.setFirstLineIndent(0);
paragraph.setIndentationLeft(0);

XWPFRun run = paragraph.createRun();
run = paragraph.createRun();
run.setFontFamily("Helvetica");
run.setFontSize(18);
run.setText("Section Title");

/*
 * Right-Hand Cell
 */
cell = tableRow.getCell(1); 
cell.setWidth("50%");
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.BOTTOM);

paragraph = cell.getParagraphArray(0);
if (paragraph == null) paragraph = cell.addParagraph();

paragraph.setAlignment(ParagraphAlignment.RIGHT);
paragraph.setSpacingBefore(0);
paragraph.setSpacingAfter(0);
paragraph.setFirstLineIndent(0);
paragraph.setIndentationRight(0);

run = paragraph.createRun();
run.setFontFamily("Helvetica");
run.setFontSize(15);
run.setText("Sub-Title");

logger.log( String.format("BOTTOM BORDER SPACE: [%s]\n", table.getBottomBorderSpace()) );
logger.log( String.format("TOP BORDER SPACE: [%s]\n", table.getTopBorderSpace()) );
logger.log( String.format("LEFT BORDER SPACE: [%s]\n", table.getLeftBorderSpace()) );
logger.log( String.format("RIGHT BORDER SPACE: [%s]\n", table.getRightBorderSpace()) );
logger.log( String.format("INSIDE HOR BORDER SPACE: [%s]\n", table.getInsideHBorderSpace()) );
logger.log( String.format("INSIDE VERT BORDER SPACE: [%s]\n", table.getInsideVBorderSpace()) );

logger.log( String.format("BOTTOM CELL MARGIN: [%s]\n", table.getCellMarginBottom()) );
logger.log( String.format("TOP CELL MARGIN: [%s]\n", table.getCellMarginTop()) );
logger.log( String.format("LEFT CELL MARGIN: [%s]\n", table.getCellMarginLeft()) );
logger.log( String.format("RIGHT CELL MARGIN: [%s]\n", table.getCellMarginRight()) );

这是上面日志记录的输出:

BOTTOM BORDER SPACE: [-1]
TOP BORDER SPACE: [-1]
LEFT BORDER SPACE: [-1]
RIGHT BORDER SPACE: [-1]
INSIDE HOR BORDER SPACE: [-1]
INSIDE VERT BORDER SPACE: [-1]
BOTTOM CELL MARGIN: [0]
TOP CELL MARGIN: [0]
LEFT CELL MARGIN: [0]
RIGHT CELL MARGIN: [0]
java apache-poi
2个回答
2
投票

红色箭头所指的那些间隙是不可移除的。唯一可以为表格设置的是负左缩进,并另外将表格大小设置为大于左右页边距之间的可用大小。之后,表格左侧悬挂在左页边距中,右侧悬挂在右页边距中。

为此,必须知道页面尺寸和左右页边距。另外,表格宽度需要设置为绝对宽度(以 TwIps 为单位),而不是相对百分比。

示例:

import java.io.*;

import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.wp.usermodel.HeaderFooterType;

import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.math.BigInteger;

public class CreateWordHeaderTable {
    
 static void setTableIndent(XWPFTable table, int tblIndW) {
  if (table.getCTTbl().getTblPr() == null) table.getCTTbl().addNewTblPr();  
  if (table.getCTTbl().getTblPr().getTblInd() == null) table.getCTTbl().getTblPr().addNewTblInd();  
  table.getCTTbl().getTblPr().getTblInd().setW(tblIndW);
  table.getCTTbl().getTblPr().getTblInd().setType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth.DXA);
 }
 
 static void setDefaultPageSettings(XWPFDocument doc, int pageWidth, int pageHeight, int pageLeftMargin , int pageRightMargin) {
  CTSectPr sectPr = doc.getDocument().getBody().getSectPr();
  if (sectPr == null) sectPr = doc.getDocument().getBody().addNewSectPr();
  CTPageSz pageSz = sectPr.addNewPgSz();
  pageSz.setW(BigInteger.valueOf(pageWidth));
  pageSz.setH(BigInteger.valueOf(pageHeight));
  CTPageMar pageMar = sectPr.getPgMar();
  if (pageMar == null) pageMar = sectPr.addNewPgMar();
  pageMar.setLeft(BigInteger.valueOf(pageLeftMargin)); 
  pageMar.setRight(BigInteger.valueOf(pageRightMargin));
  //pageMar.setTop(BigInteger.valueOf(720)); //720 TWentieths of an Inch Point (Twips) = 720/20 = 36 pt = 36/72 = 0.5"
  //pageMar.setBottom(BigInteger.valueOf(720));
  //pageMar.setFooter(BigInteger.valueOf(720));
  //pageMar.setHeader(BigInteger.valueOf(720));
  //pageMar.setGutter(BigInteger.valueOf(720));
 }

 public static void main(String[] args) throws Exception {
     
  int TWIPS_PER_INCH = 1440;
  
  try (
   XWPFDocument doc = new XWPFDocument();
   FileOutputStream out = new FileOutputStream("./CreateWordHeaderTable.docx");
   ) {
       
   int pageWidth = (int)Math.round(8.5 * TWIPS_PER_INCH); // page size letter
   int pageHeight = (int)Math.round(11 * TWIPS_PER_INCH); // page size letter
   int pageLeftMargin = (int)Math.round(1 * TWIPS_PER_INCH);
   int pageRightMargin = (int)Math.round(1 * TWIPS_PER_INCH);

   setDefaultPageSettings(doc, pageWidth, pageHeight, pageLeftMargin, pageRightMargin);
   
   // the body content
   XWPFParagraph paragraph = doc.createParagraph();
   XWPFRun run = paragraph.createRun();  
   run.setText("The Body... lorem ipsum...");

   // create header
   XWPFHeader header = doc.createHeader(HeaderFooterType.DEFAULT);
   XWPFTable table = header.createTable(1, 2);
   //table.removeBorders();
   table.setCellMargins(0, 0, 0, 0);
   
   int leftTableIndent = (int)Math.round(-0.025 * TWIPS_PER_INCH);
   setTableIndent(table, leftTableIndent);
   //table.setWidth("100%");
   int tableOversize = (int)Math.round(0.15 * TWIPS_PER_INCH);
   int tableWidth = pageWidth - pageLeftMargin - pageRightMargin + tableOversize;
   table.setWidth(tableWidth);
   
   /*
    * Create CTTblGrid for this table with widths of the 2 columns. 
    * Necessary for Libreoffice/Openoffice to accept the column widths.
    */
   table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(1 * TWIPS_PER_INCH));
   table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1 * TWIPS_PER_INCH));
  
   /*
    * Left-Hand Cell
    */
   XWPFTableRow tableRow = table.getRow(0);
   XWPFTableCell cell = tableRow.getCell(0); 

   cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.BOTTOM);
   cell.setWidth("50%");

   paragraph = doc.createParagraph();
   paragraph = cell.getParagraphArray(0);
   if (paragraph == null) paragraph = cell.addParagraph();

   paragraph.setAlignment(ParagraphAlignment.LEFT);
   run = paragraph.createRun();
   run = paragraph.createRun();
   run.setFontFamily("Helvetica");
   run.setFontSize(18);
   run.setText("Section Title");

   /*
    * Right-Hand Cell
    */
   cell = tableRow.getCell(1); 
   cell.setWidth("50%");
   cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.BOTTOM);

   paragraph = cell.getParagraphArray(0);
   if (paragraph == null) paragraph = cell.addParagraph();

   paragraph.setAlignment(ParagraphAlignment.RIGHT);
   run = paragraph.createRun();
   run.setFontFamily("Helvetica");
   run.setFontSize(15);
   run.setText("Sub-Title");

   paragraph = header.createParagraph();
   paragraph.setAlignment(ParagraphAlignment.LEFT);
   run = paragraph.createRun();
   run.setText("|...Next Line in Header...|");
  
   doc.write(out);
   
  }
 }
}

尽管如此,负左缩进的确切大小和表格的超大尺寸只能通过尝试和错误来获得。我怀疑所有能够渲染

*.docx
的文字处理应用程序都会将其渲染为相同的。所以我不会这样做并忽略那些小差距。文字处理是文字处理而不是图形处理。意思是:文字处理不是像素精确的。


-1
投票

您看到的间距可能是由于表格结构中的默认单元格边距和填充造成的。

这是代码的修改版本,使用

CTTcMar
解决了这个问题:


import org.apache.poi.wp.usermodel.HeaderFooterType;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblGrid;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcMar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;

import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;

public class WordTableSpacing {

    public static void main(String[] args) throws IOException {
        try (
                XWPFDocument doc = new XWPFDocument();
                FileOutputStream out = new FileOutputStream("fixed_header.docx")
        ) {

            XWPFHeader header = doc.createHeader(HeaderFooterType.DEFAULT);
            XWPFTable table = header.createTable(1, 2);
            table.setWidth("100%");

            // Set column widths (important for consistent behavior across different Word processors)
            CTTblGrid grid = table.getCTTbl().addNewTblGrid();
            grid.addNewGridCol().setW(BigInteger.valueOf(3 * 1440)); // 3 inches
            grid.addNewGridCol().setW(BigInteger.valueOf(3 * 1440)); // 3 inches

            XWPFTableRow tableRow = table.getRow(0);

            // Left Cell
            XWPFTableCell leftCell = tableRow.getCell(0);
            leftCell.setWidth("50%");
            leftCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.BOTTOM);
            addTextToCell(doc, leftCell, "Section Title", "Helvetica", 18, ParagraphAlignment.LEFT);
            removeCellPadding(leftCell);

            // Right Cell
            XWPFTableCell rightCell = tableRow.getCell(1);
            rightCell.setWidth("50%");
            rightCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.BOTTOM);
            addTextToCell(doc, rightCell, "Sub-Title", "Helvetica", 15, ParagraphAlignment.RIGHT);
            removeCellPadding(rightCell);

            doc.write(out);
        }
    }

    private static void addTextToCell(XWPFDocument doc, XWPFTableCell cell, String text, String fontFamily, int fontSize, ParagraphAlignment alignment) {
        XWPFParagraph paragraph = cell.getParagraphs().size() > 0 ? cell.getParagraphs().get(0) : cell.addParagraph();
        paragraph.setAlignment(alignment);
        // Reset all spacing and indentation for the paragraph
        paragraph.setSpacingBefore(0);
        paragraph.setSpacingAfter(0);
        paragraph.setSpacingBetween(1.0); // Use 1.0 or another suitable value
        paragraph.setIndentationLeft(0);
        paragraph.setIndentationRight(0);
        paragraph.setFirstLineIndent(0);

        XWPFRun run = paragraph.createRun();
        run.setFontFamily(fontFamily);
        run.setFontSize(fontSize);
        run.setText(text);
    }

    private static void removeCellPadding(XWPFTableCell cell) {
        CTTcPr tcpr = cell.getCTTc().addNewTcPr();
        CTTcMar margin = tcpr.addNewTcMar();
        margin.addNewLeft().setW(BigInteger.ZERO);
        margin.addNewTop().setW(BigInteger.ZERO);
        margin.addNewRight().setW(BigInteger.ZERO);
        margin.addNewBottom().setW(BigInteger.ZERO);
    }

}

变更和主要改进的说明:

  • removeCellPadding(XWPFTableCell cell)
    功能
    :这是核心改进。它直接操作 CTTcMar 对象将所有内边距(左、上、右、下)设置为零。这消除了单元内的内部间距。
  • addTextToCell()
    函数
    :此函数现在以更有条理的方式处理段落创建和样式设置。重要的是,它还会重置段落的所有间距和缩进设置,以确保干净的状态。明确设置文本行之间的间距以避免默认值影响布局
  • 列宽:使用 CTTblGrid 显式设置列宽至关重要,尤其是在 LibreOffice 或 OpenOffice 中打开文档时。这确保了更一致的渲染。
  • 简化代码:代码经过重构,更具可读性和可维护性,并具有用于添加文本和删除填充的辅助函数。

此修订后的方法提供了更强大的解决方案,用于控制使用 Apache POI 生成的 Word 表中的间距。通过解决表格单元格边距和内部单元格填充的问题,您将获得所需的布局,而不会出现无法解释的间距。请记住根据您的需要调整字体大小和列宽。

© www.soinside.com 2019 - 2024. All rights reserved.