Cell width is changed by Document.save

I’m in the process of migrating the code base from a previous version of Aspose.Words to the current version. However, when I update Aspose.Words from v21.1 to v21.2, several system tests are failing.

I’m in the process of narrowing it down to a small, self-contained example. Until I have that ready, I’m hoping you can point me in the right direction or maybe give me some hints about what to look for.

The issue so far comes from a bit of code that slices columns out of existing tables. These columns may intersect with “merged cells”.

Since DOC files don’t actually use the merge flags, I first traverse the table and replace all the very long cells (aka fake merged cells) with empty cells, resize them appropriately, and add the correct merge flags. After slicing out the cells, I then go back and replace the cells with actual merge flags with very long cells (aka fake merged cells).

This all seems to work really well - at least until v21.2. Now, when I save the document, some of these cells are resized. It seems that they grow by the width of the next cell in the row… though I’m still validating that.

Do you have any advice on tracking down this issue?

Thanks,
Drew

Just to be clear - this code works fine when using Aspose.Words 21.1 and fails when using Aspose.Words 21.2. This isn’t a simple coding mistake or algorithmic error. Some change in Aspose.Words is causing different behavior.

I’m happy to change my code to match the new behavior provided in Aspose.Words 21.2+ … I just need to know how to change it. :slight_smile:

@drewstovall Could you please provide us a code example along with input, output and expected output documents? We will check the issue and provide you more information.
By the way, there is a built-in method for converting “fake” merged cells to horizontally merged cells, see Table.ConvertToHorizontallyMergedCells.
Also, you can try using Table.AutoFit method with AutoFitBehavior.FixedColumnWidths.

files.zip (91.0 KB)

In the attached zip file, you will find input.doc, expected.doc, and actual.doc.

The code that processes input.doc into actual.doc is large and proprietary. I’m trying to extract a small version of it, but it’s taking a lot of time and effort.

In the meantime, maybe these documents will show you a bit about what I’m talking about. The code takes in input.doc, replaces all the long cells with merged cells, deletes the third column, then replaces the merged cells with long cells again. This all seems to work fine.

When I call Document.save(…), the cell formats are changed and the resulting document reflects the changed values (which are wrong).

I’ve been printing the values from cell format to the screen just before and just after the call to Document.save(…). Note that the Width of Cell[1,1], Cell[1,2], and Cell[1,3] all change:

Before Document.save(…)

===== Table[0] =====
 ---- Row[0] ----
Cell[0,0] - 	Width: 36.0 	PrefWidth: 36.0 	Width/PrefWidth: 1.0 	Merge:  0 	... A1a
Cell[0,1] - 	Width: 36.0 	PrefWidth: 36.0 	Width/PrefWidth: 1.0 	Merge:  0 	... B1a
Cell[0,2] - 	Width: 31.7 	PrefWidth: 31.7 	Width/PrefWidth: 1.0 	Merge:  0 	... D1a
Cell[0,3] - 	Width: 39.6 	PrefWidth: 39.6 	Width/PrefWidth: 1.0 	Merge:  0 	... E1a
Cell[0,4] - 	Width: 47.5 	PrefWidth: 47.5 	Width/PrefWidth: 1.0 	Merge:  0 	... F1a
 ---- Row[1] ----
Cell[1,0] - 	Width: 36.0 	PrefWidth: 36.0 	Width/PrefWidth: 1.0 	Merge:  0 	... A3a
Cell[1,1] - 	Width: 36.0 	PrefWidth: 36.0 	Width/PrefWidth: 1.0 	Merge:  0 	... B3/C3a
Cell[1,2] - 	Width: 31.7 	PrefWidth: 31.7 	Width/PrefWidth: 1.0 	Merge:  0 	... D3a
Cell[1,3] - 	Width: 39.6 	PrefWidth: 39.6 	Width/PrefWidth: 1.0 	Merge:  0 	... E3a
Cell[1,4] - 	Width: 47.5 	PrefWidth: 47.5 	Width/PrefWidth: 1.0 	Merge:  0 	... F3a

After Document.save(…)

===== Table[0] =====
 ---- Row[0] ----
Cell[0,0] - 	Width: 36.0 	PrefWidth: 36.0 	Width/PrefWidth: 1.0 	Merge:  0 	... A1a
Cell[0,1] - 	Width: 36.0 	PrefWidth: 36.0 	Width/PrefWidth: 1.0 	Merge:  0 	... B1a
Cell[0,2] - 	Width: 31.7 	PrefWidth: 31.7 	Width/PrefWidth: 1.0 	Merge:  0 	... D1a
Cell[0,3] - 	Width: 39.6 	PrefWidth: 39.6 	Width/PrefWidth: 1.0 	Merge:  0 	... E1a
Cell[0,4] - 	Width: 47.5 	PrefWidth: 47.5 	Width/PrefWidth: 1.0 	Merge:  0 	... F1a
 ---- Row[1] ----
Cell[1,0] - 	Width: 36.0 	PrefWidth: 36.0 	Width/PrefWidth: 1.0 	Merge:  0 	... A3a
Cell[1,1] - 	Width: 67.7 	PrefWidth: 36.0 	Width/PrefWidth: 1.8805555555555555 	Merge:  0 	... B3/C3a
Cell[1,2] - 	Width: 39.6 	PrefWidth: 31.7 	Width/PrefWidth: 1.249211356466877 	Merge:  0 	... D3a
Cell[1,3] - 	Width: 47.5 	PrefWidth: 39.6 	Width/PrefWidth: 1.1994949494949494 	Merge:  0 	... E3a
Cell[1,4] - 	Width: 47.5 	PrefWidth: 47.5 	Width/PrefWidth: 1.0 	Merge:  0 	... F3a

@drewstovall Thank you for additional information. I tried to emulate your scenario on my side, but cannot reproduce the problem. Here is a simple code I used for testing:

Document doc = new Document(@"C:\Temp\input.doc");
            
Table table = doc.FirstSection.Body.Tables[0];
           
// Convert wide cells to merged cells.
table.ConvertToHorizontallyMergedCells();

// Remove thrd column.
foreach (Row r in table.Rows)
    r.Cells[2].Remove();

// Convert merged cells back to simply wide cells.
foreach (Row r in table.Rows)
{
    // Get cells that are first in the merged range.
    List<Cell> firstCells = r.Cells.Cast<Cell>().Where(c => c.CellFormat.HorizontalMerge == CellMerge.First).ToList();

    foreach (Cell first in firstCells)
    {
        Cell nexCell = (Cell)first.NextSibling;
        while (nexCell != null && nexCell.CellFormat.HorizontalMerge == CellMerge.Previous)
        {
            first.CellFormat.Width += nexCell.CellFormat.Width;
            nexCell.Remove();
            nexCell = (Cell)first.NextSibling;
        }
    }
}

// Optionaly you can call this to auto fit the table to content.
//table.AutoFit(AutoFitBehavior.AutoFitToContents);

doc.Save(@"C:\Temp\out.doc");

Also, the last step (conversion merged cells with simply wide cells) looks redundant. Since using horizontally merged cells is fully valid from MS Word point of view.

I’ve got a reproduction case… though it’s so simple, I’m very suspicious of it…

In this code, I simply set the width of Cell A2 (the first cell in the second row) to 10. Then I save the document. Saving the document causes the cell width to revert back to its original value.

File inputFile = getFile("input.doc");
File outputFile = getFile("output.doc");

Document document = new Document(inputFile.getAbsolutePath(), new LoadOptions(LoadFormat.DOC, "", ""));

// Set the width of Cell A2 to 10
((Table)document.getChildNodes(NodeType.TABLE, true).get(0))
    .getRows().get(1)
    .getCells().get(0)
    .getCellFormat().setWidth(10);

double cellA2WidthBeforeSave = ((Table)document.getChildNodes(NodeType.TABLE, true).get(0))
    .getRows().get(1)
    .getCells().get(0)
    .getCellFormat()
    .getWidth();

document.save(outputFile.getAbsolutePath(), SaveFormat.DOC);

double cellA2WidthAfterSave = ((Table)document.getChildNodes(NodeType.TABLE, true).get(0))
    .getRows().get(1)
    .getCells().get(0)
    .getCellFormat()
    .getWidth();

assertEquals(cellA2WidthBeforeSave, cellA2WidthAfterSave);
assertEquals(10.0, cellA2WidthAfterSave);

input.doc.zip (7.3 KB)

@drewstovall Thank you for additional information. You are using CellFormat.Width property. This property is mainly left over from previous versions, it acts differently depending on which of the other width properties already exist on the table. Please see our documentation to learn more:
https://docs.aspose.com/words/java/specifying-table-and-cell-widths/#using-cell-width

While I’m not explicitly setting the preferred width, it appears to be set correctly before saving the document, yet it’s ignored and the cell width is changed (maybe auto-fitted?)

Before I call save:

  • Table Autofit is not allowed
  • Both the preferred width value and the cell format width value are 10 Points

After I call save:

  • Table Autofit is not allowed
  • The preferred width value remains at 10 (Good)
  • The cell format width value changes to 36 Points (Bad)

If I set the preferred width to AUTO before the save, both the preferred width value and the cell format width value are 36 points, but I wanted them both to be 10.

How do I cause cell A2 to be 10 points wide?


Debug Output from latest run:

PreferredWidthType.Auto:    1
PreferredWidthType.PERCENT: 2
PreferredWidthType.POINTS:  3

BEFORE: CellA2 CellFormat.PreferredWidth.Type:  3
BEFORE: CellA2 CellFormat.PreferredWidth.Value: 10.0
BEFORE: Table.AllowAutoFit: false
BEFORE: CellA2 CellFormat.Width: 10.0
--- Saving ---
AFTER: CellA2 CellFormat.PreferredWidth.Type:  3
AFTER: CellA2 CellFormat.PreferredWidth.Value: 10.0
AFTER: Table.AllowAutoFit: false
AFTER: CellA2 CellFormat.Width: 36.0

@drewstovall The Width property value is calculated by Aspose.Words on document loading and saving. Currently, not every combination of table, cell and document properties is supported. The returned value may not be accurate for some documents. It may not exactly match the cell width as calculated by MS Word when the document is opened in MS Word.

Setting this property is not recommended. There is no guarantee that the cell will actually have the set width. The width may be adjusted to accommodate cell contents in an auto-fit table layout. Cells in other rows may have conflicting width settings. The table may be resized to fit into the container or to meet table width settings. Consider using PreferredWidth for setting the cell width. Setting Width property sets PreferredWidth implicitly since version 15.8.

If I understand correctly, there is no way to use Aspose.Words v21.2+ to create a table that MSWord will render like this:

 * +----------+---------------------+
 * | A1       | B1                  |
 * +----------+----------+----------+
 * | A2                  | B2       |
 * +---------------------+----------+

Is that correct?

@drewstovall Of course, you can create such table using Aspose.Words. For example see the following code:

Document doc = new Document();
DocumentBuilder builder = new DocumentBuilder(doc);

builder.startTable();
builder.insertCell();
builder.getCellFormat().setWidth(50);
builder.write("A1");
builder.insertCell();
builder.getCellFormat().setWidth(100);
builder.write("B1");
builder.endRow();

builder.insertCell();
builder.getCellFormat().setWidth(100);
builder.write("A2");
builder.insertCell();
builder.getCellFormat().setWidth(50);
builder.write("B2");
builder.endRow();

Table table = builder.endTable();
table.autoFit(AutoFitBehavior.FIXED_COLUMN_WIDTHS);

doc.save("C:\\Temp\\out.docx");

Here is the output document produced by this code: out.docx (7.4 KB)

Ah Ha! Adding the autoFit(AutoFitBehavior.FIXED_COLUMN_WIDTHS) to the original code has caused it to produce the expected results again…

… for some cases.

It fixes the tables that were created by Word, but now the tables in my RTF documents don’t span the entire width of the document. This is to be expected, I suppose, because I’m setting the table behavior to fixed column widths.

I’m getting closer, for sure. I think I need to know the AutoFitBehavior before I start mutating the table so that I can restore it after I’m done.

Is there an accessor to retrieve a table’s AutoFitBehavior (i.e., where is Table.getAutoFit())?

@drewstovall Table.AutoFit method recalculates table grid of the table according to the specified AutoFitBehavior. I am afraid, there is no way to get AutoFitBehavior previously used for table grid recalculation.
Could you please attach your document with problematic table? We will check the issue and provide you more information.