Spire.Doc is a professional Word .NET library specifically designed for developers to create, read, write, convert and print Word document files. Get free and professional technical support for Spire.Doc for .NET, Java, Android, C++, Python.

Mon Nov 29, 2021 2:25 pm

I load a docx template from Stream

Code: Select all
        final Document doc = new Document();
        doc.loadFromStream(input, FileFormat.Docx);

and fill a table with column and data using Spire.Office 4.11.3 (see document Etude.docx, page 22 & 23).

After, I tried to save the document directly to PDF

Code: Select all
        final UUID uuid = UUID.randomUUID();
        doc.saveToFile(uuid.toString() + ".pdf", FileFormat.PDF); // see fab534ab-e0be-47a3-93ea-ee8b13544050_saveToFile.pdf


but the tables are not generated correctly : see fab534ab-e0be-47a3-93ea-ee8b13544050_saveToFile2.pdf (the second column is too big) !

If I try to save to docx first, and after loadFromStream from the generated docx file, and try to export by this way to PDF : the generated PDF and the tables are not the same, but still not the result expected.

Code: Select all
        doc.saveToFile(uuid.toString() + ".docx", FileFormat.Docx); // see fab534ab-e0be-47a3-93ea-ee8b13544050.docx

        try {
            final Document doc2 = new Document();
            doc2.loadFromStream(new FileInputStream(uuid.toString() + ".docx"), FileFormat.Docx);
            doc2.saveToFile(uuid.toString() + "_2.pdf", FileFormat.PDF); // see fab534ab-e0be-47a3-93ea-ee8b13544050_saveToFile2.pdf

        } catch(FileNotFoundException e) {

        }

QuentinSup
 
Posts: 46
Joined: Mon Oct 18, 2021 9:18 am

Tue Nov 30, 2021 3:31 am

Hello,

Thanks for your inquiry!

I noticed that the input file “Etude.docx” is totally different with the “e0fab625-c77a-4650-971f-5d2e39c8bc67.docx” since you have inserted some data and change the table format. Sorry that if there is only your input file, and without your specific operation code, I'm afraid I can't reproduce your issue accurately. Please provide us with your completely code (a java class is better) for further investigation. Thanks in advance.

Sincerely,
Marcia
E-iceblue support team
User avatar

Marcia.Zhou
 
Posts: 858
Joined: Wed Nov 04, 2020 2:29 am

Tue Nov 30, 2021 10:56 am

Hi,

here an extract of the code used to manipulate table and cells :


import com.example.demo.CellTypeEnum;
import com.example.demo.CellValue;
import com.spire.doc.*;
import com.spire.doc.documents.BorderStyle;
import com.spire.doc.documents.Paragraph;
import com.spire.doc.fields.TextRange;
import com.spire.doc.formatting.CharacterFormat;

import java.awt.*;
import java.io.*;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;

public class ExportPdfIndexOutOfBounds {

protected static NumberFormat currencyFormater;
protected static NumberFormat percentFormater;
protected static NumberFormat currencyCompactFormater;
protected static boolean formatCurrencyWithSymbol = true;
protected static NumberFormat numberFormater;
public static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");

{
percentFormater = NumberFormat.getPercentInstance(Locale.FRANCE);
percentFormater.setMaximumFractionDigits(2);
currencyFormater = NumberFormat.getCurrencyInstance(Locale.FRANCE);
currencyCompactFormater = DecimalFormat.getCompactNumberInstance(Locale.FRANCE, NumberFormat.Style.SHORT);
numberFormater = NumberFormat.getNumberInstance(Locale.FRANCE);
numberFormater.setMaximumFractionDigits(2);
}

public static void main(String[] args) throws Exception {

String inputFile = "document.docx";

//create word document
FileInputStream stream1 = new FileInputStream(new File(inputFile));
Document document = new Document();
document.loadFromStream(stream1, FileFormat.Auto);

Map<String, TagValue<?>> rows = new HashMap<>();

rows.put("period", new TagValue<>(List.of("1", "2", "3", "Total"), TagValueTypeEnum.ROW));
rows.put("rentAmount", new TagValue<>(List.of("1000", "2000", "3000", "6000"), TagValueTypeEnum.ROW));
/**
* @TODO More example
*/
fillTables(rows, document);

ByteArrayOutputStream stream = new ByteArrayOutputStream();
document.saveToStream(stream, FileFormat.PDF);

FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream("topdf.pdf");
fileOutputStream.write(stream.toByteArray());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* Traite les tableaux dynamiques d'un document WORD
*
* @param mapList values
* @param doc document
*/
public static void fillTables(Map<String, TagValue<?>> mapList, Document doc) {
if (doc.getSections() != null && doc.getSections().getCount() > 0) {
for (Section section : (Iterable<Section>) doc.getSections()) {
if (section.getTables() != null && section.getTables().getCount() > 0) {
for (int i = 0; i < section.getTables().getCount(); i++) {
int maxColumnCount = fillTable(mapList, section.getTables().get(i));
if (maxColumnCount > 0) {
formatTable(section.getTables().get(i), maxColumnCount);
}
}
}
}
}
}

/**
* Complète un tableau WORD par les valeurs correspondantes de la liste formatée et supprime les tags traités du référentiel de tags si non null
*
* @param map values
* @param table table
*/
private static int fillTable(Map<String, TagValue<?>> map, Table table) {
boolean isReplaced;
int maxColumnCount = 0;

// replace values from map list
for (Object o : table.getRows()) {
final TableRow row = ((TableRow) o);
for (Map.Entry<String, TagValue<?>> entry : map.entrySet()) {
final String formatedKey = "${" + entry.getKey() + "[n]}";
if (row.getCells().getCount() > 0) {
if (row.getCells().get(row.getCells().getCount() - 1).getParagraphs().get(0).getText()
.contains(formatedKey))
{
isReplaced = false;
final List<?> values = (List<?>) entry.getValue().getValue();
maxColumnCount = Math.max(values.size(), maxColumnCount);
for (Object v : values) {
final String value = formatValue(mapToTagValue(v));
if (!isReplaced) {
row.getCells().get(row.getCells().getCount() - 1).getFirstParagraph()
.replace(formatedKey, value, true, true);
isReplaced = true;
} else {
row.addCell().addParagraph().appendText(value);
}
}
}
}
}
}
return maxColumnCount;
}

/**
* return appropriate representation oif tag value depending on type
*
* @param o Object value
*/
protected static TagValue<?> mapToTagValue(Object o) {

if (o instanceof TagValue) {
return (TagValue<?>) o;
}

if (o instanceof CellValue) {
final CellValue<?> cellValue = (CellValue<?>) o;
if (cellValue.getType() == CellTypeEnum.CURRENCY) {
return new TagValue<>(cellValue.getValue(), TagValueTypeEnum.CURRENCY);
}
if (cellValue.getType() == CellTypeEnum.CURRENCY_COMPACT) {
return new TagValue<>(cellValue.getValue(), TagValueTypeEnum.CURRENCY_COMPACT);
}
if (cellValue.getType() == CellTypeEnum.NUMBER) {
return new TagValue<>(cellValue.getValue(), TagValueTypeEnum.NUMBER);
}
if (cellValue.getType() == CellTypeEnum.PERCENT) {
return new TagValue<>(cellValue.getValue(), TagValueTypeEnum.PERCENT);
}
if (cellValue.getType() == CellTypeEnum.DATE) {
return new TagValue<>(cellValue.getValue(), TagValueTypeEnum.DATE);
}
}

if (o instanceof Long || o instanceof Integer || o instanceof Float) {
return new TagValue<>(o, TagValueTypeEnum.NUMBER);
}

if (o instanceof Double) {
return new TagValue<>(o, TagValueTypeEnum.PERCENT);
}

if (o instanceof List) {
return new TagValue<>(((List<?>) o).stream().map(ExportPdfIndexOutOfBounds::mapToTagValue).collect(Collectors.toList()),
TagValueTypeEnum.LIST);
}

return new TagValue<>(o, TagValueTypeEnum.TEXT);

}

public enum TagValueTypeEnum {
TEXT, CURRENCY, CURRENCY_COMPACT, NUMBER, PERCENT, DATE, IMAGE_BYTE, IMAGE_BASE64, IMAGE_PATH, MERGE_FIELD, CHART, LIST, ROW
}

public static class TagValue<T> {
private T value;
private TagValueTypeEnum type = TagValueTypeEnum.TEXT;

public TagValue(T value) {
this.value = value;
}

public TagValue(T value, TagValueTypeEnum type) {
this.value = value;
this.type = type;
}

public boolean isPrimitive() {
return type.equals(TagValueTypeEnum.TEXT) || type.equals(TagValueTypeEnum.DATE) || type.equals(
TagValueTypeEnum.CURRENCY) || type.equals(TagValueTypeEnum.CURRENCY_COMPACT) || type.equals(
TagValueTypeEnum.NUMBER) || type.equals(TagValueTypeEnum.PERCENT);
}

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}

public TagValueTypeEnum getType() {
return type;
}

public void setType(TagValueTypeEnum type) {
this.type = type;
}
}

/**
* Mise en forme des lignes d'un tableau WORD
*
* @param table table
* @param maxColumnCount max column count
*/
private static void formatTable(Table table, int maxColumnCount) {
TableRow row;
TableCell cell, firstCell;
Paragraph cellParagraph, firstParagraph;
TextRange cellText;
Color borderColor;

// format table rows
final int totalColumnCount = maxColumnCount;
int columnCount;
int rowCount = 1;
final PreferredWidth tableWidth = new PreferredWidth(WidthType.Percentage, (short) 100);
table.setPreferredWidth(tableWidth);
table.getTableFormat().getPaddings().setLeft(5);
table.getTableFormat().getPaddings().setRight(5);
float columnWidth =
(table.getWidth() - table.getFirstRow().getCells().get(0).getWidth()) / (totalColumnCount + 1);
table.setDefaultColumnWidth(columnWidth);
for (Object o1 : table.getRows()) {
row = ((TableRow) o1);

while (row.getCells().getCount() <= totalColumnCount) {
row.addCell().addParagraph().appendText("");
}

firstCell = row.getCells().get(1);

firstParagraph = (Paragraph) firstCell.getFirstParagraph();
final CharacterFormat characterFormatModel = firstParagraph.getChildObjects().getCount() > 0 ?
((TextRange) firstParagraph.getChildObjects().get(0)).getCharacterFormat() :
firstParagraph.getBreakCharacterFormat();

columnCount = 1;
for (Object o2 : row.getCells()) {

if (columnCount == 1) {
columnCount++;
continue;
}

borderColor = rowCount == 1 ? Color.WHITE : Color.BLACK;
cell = (TableCell) o2;

// TODO -> appliquer le format de la première ligne cell et paragraph à la dernière colonne de chaque ligne
// condition : rowCount != 2 && columnCount == row.getCells().getCount()

if (columnCount == totalColumnCount + 1) {
cell.setCellWidth((float) (columnWidth * 1.20), CellWidthType.Point);
} else {
cell.setCellWidth(columnWidth, CellWidthType.Point);
}

// cell format
cell.getCellFormat().setBackColor(firstCell.getCellFormat().getBackColor());
cell.getCellFormat().setVerticalAlignment(firstCell.getCellFormat().getVerticalAlignment());
cell.getCellFormat().setVerticalMerge(firstCell.getCellFormat().getVerticalMerge());
cell.getCellFormat().setTextWrap(false);

// right border all 5 year period ; no border for line 2
cell.getCellFormat().getBorders().getBottom()
.setBorderType(firstCell.getCellFormat().getBorders().getBottom().getBorderType());
cell.getCellFormat().getBorders().getTop()
.setBorderType(firstCell.getCellFormat().getBorders().getTop().getBorderType());
cell.getCellFormat().getBorders().getLeft().setBorderType(
rowCount != 2 && columnCount <= 2 ? BorderStyle.Basic_Thin_Lines : BorderStyle.Cleared);
cell.getCellFormat().getBorders().getRight().setBorderType(
rowCount != 2 && ((columnCount - 1) % 5 == 0 || columnCount == row.getCells().getCount()) ?
BorderStyle.Basic_Thin_Lines : BorderStyle.Cleared);
cell.getCellFormat().getBorders().getRight().setColor(borderColor);
cell.getCellFormat().getBorders().getLeft().setColor(borderColor);

// paragraph format
cellParagraph = cell.getParagraphs().getCount() > 0 ? (Paragraph) cell.getFirstParagraph() :
cell.addParagraph();

cellParagraph.applyStyle(firstParagraph.getStyleName());
cellParagraph.getFormat().setHorizontalAlignment(firstParagraph.getFormat().getHorizontalAlignment());
cellParagraph.getFormat().setTextAlignment(firstParagraph.getFormat().getTextAlignment());
cellParagraph.getFormat().setBeforeSpacing(firstParagraph.getFormat().getBeforeSpacing());
cellParagraph.getFormat().setAfterSpacing(firstParagraph.getFormat().getAfterSpacing());
cellText = cellParagraph.getChildObjects().getCount() > 0 ?
(TextRange) cellParagraph.getChildObjects().get(0) : cellParagraph.appendText("");

if (characterFormatModel != null) {
cellText.getCharacterFormat().setTextColor(characterFormatModel.getTextColor());
cellText.getCharacterFormat().setFontName(characterFormatModel.getFontName());
cellText.getCharacterFormat().setFontSize(characterFormatModel.getFontSize());
cellText.getCharacterFormat().setBold(characterFormatModel.getBold());
cellText.getCharacterFormat().setUnderlineStyle(characterFormatModel.getUnderlineStyle());
}

if (cellText.getText().isBlank()) {
cellText.getCharacterFormat().setTextColor(Color.WHITE);
// Mandatory to have a character (not space) for the style to be applied correctly
cellText.setText("-");
cellText.getCharacterFormat().setFontSize(1);
}

columnCount++;
}
//row.setHeight(initialRowHeight);

rowCount++;
}
}

/**
* Format tag value ; if null, return ""
*
* @param tag value to format
*/
protected static String formatValue(TagValue<?> tag) {
Object tagValue = tag.getValue() != null ? tag.getValue() : null;
if (tagValue != null) {
if (TagValueTypeEnum.CURRENCY == tag.getType()) {
tagValue = currencyFormat(String.valueOf(tagValue), formatCurrencyWithSymbol);
} else if (TagValueTypeEnum.CURRENCY_COMPACT == tag.getType()) {
tagValue = currencyCompactFormat(String.valueOf(tagValue), formatCurrencyWithSymbol);
} else if (TagValueTypeEnum.NUMBER == tag.getType()) {
tagValue = numberFormat(tagValue);
} else if (TagValueTypeEnum.PERCENT == tag.getType()) {
tagValue = percentFormat(tagValue);
} else if (TagValueTypeEnum.DATE == tag.getType()) {
assert tagValue instanceof Date;
tagValue = dateFormat.format((Date) tagValue);
}
}
return getFixedValue(tagValue);
}

/**
* Return string value with fixed chars
*
* @param tagValue the object
* @return the fixed string value
*/
protected static String getFixedValue(Object tagValue) {
return tagValue == null ? "" :
(tagValue instanceof String ? (String) tagValue : tagValue.toString()).replace((char) 8239, (char) 160);
}

/**
* Formattage du prix
*
* @param amount value to format
* @param currencySymbol boolean
* @return String
*/
protected static String currencyFormat(String amount, boolean currencySymbol) {
return currencyFormat(amount, currencySymbol, currencyFormater);
}

/**
* Formattage du prix
*
* @param amount value to format
* @param currencySymbol boolean
* @return String
*/
protected static String currencyCompactFormat(String amount, boolean currencySymbol) {

NumberFormat numberFormat;
if (isNullOrZeroValue(amount)) {
numberFormat = currencyFormater;
} else {
final double value = Double.parseDouble(amount);
if (value > -1000000. && value < 1000000.) {
numberFormat = currencyFormater;
} else {
numberFormat = (NumberFormat) currencyCompactFormater.clone();
double absValue = Math.abs(value);
while (absValue > 1000) {
double r = absValue % 1000;
if (r != 0) {
numberFormat.setMinimumFractionDigits(String.valueOf(r).length());
break;
}
absValue = absValue / 1000;
}
}
}

String value = currencyFormat(amount, false, numberFormat);
if(currencySymbol) {
value = value.concat(Currency.getInstance(Locale.FRANCE).getSymbol());
}
return value;
}

public static boolean isNullOrZeroValue(String value) {
return (value == null || "".equals(value) || "null".equals(value));
}

/**
* Formattage du prix
*
* @param amount value to format
* @param currencySymbol boolean
* @return String
*/
protected static String currencyFormat(String amount, boolean currencySymbol, final NumberFormat numberFormat) {

NumberFormat format = numberFormat;

if (isNullOrZeroValue(amount)) {
return format.format(0);
}

if (!currencySymbol) {
if (format instanceof DecimalFormat) {
final DecimalFormat decimalFormat = (DecimalFormat) format.clone();
final DecimalFormatSymbols decimalFormatSymbols = decimalFormat.getDecimalFormatSymbols();
decimalFormatSymbols.setCurrencySymbol("");
decimalFormat.setDecimalFormatSymbols(decimalFormatSymbols);
format = decimalFormat;
}
}

return format.format(Double.parseDouble(amount));

}

/**
* Formattage d'un nombre
*
* @param number value to format
* @return String
*/
protected static String numberFormat(Object number) {
if (number == null || "".equals(number) || "null".equals(number)) {
return numberFormater.format(0);
}
return numberFormater.format(number);
}

/**
* Formattage d'un nombre
*
* @param number value to format
* @return String
*/
protected static String percentFormat(Object number) {
if (number == null || "".equals(number) || "null".equals(number)) {
return percentFormater.format(0);
}
return percentFormater.format(number);
}

}

[/code]

QuentinSup
 
Posts: 46
Joined: Mon Oct 18, 2021 9:18 am

Wed Dec 01, 2021 5:58 am

Hello,

Thanks for sharing more information!

I have reproduced your issue and logged it in our issue tracking system with the ticket SPIREDOC-7052 for further investigation.

We will let you know if there is any update. Sorry for the inconvenience caused.

Sincerely,
Marcia
E-iceblue support team
User avatar

Marcia.Zhou
 
Posts: 858
Joined: Wed Nov 04, 2020 2:29 am

Tue Jan 25, 2022 10:13 am

Hello,

Thanks for your patience!

Glad to inform you that we just released Spire.Office for Java 5.1.5 Version:5.1.5 which fixes the issue SPIREDOC-7052.

Please download the fix version from the following links to test.

Website link: https://www.e-iceblue.com/Download/office-for-java.html

Sincerely,
Marcia
E-iceblue support team
User avatar

Marcia.Zhou
 
Posts: 858
Joined: Wed Nov 04, 2020 2:29 am

Wed Feb 09, 2022 7:49 am

Hello,

Hope you are doing well!

Has the issue been solved now? Could you please give us some feedback at your convenience?

Thanks in advance.

Sincerely,
Marcia
E-iceblue support team
User avatar

Marcia.Zhou
 
Posts: 858
Joined: Wed Nov 04, 2020 2:29 am

Tue Jun 28, 2022 12:10 pm

Hi,

We have upgraded lib to last 5.4.5 release.

There is still an issue on table generation with PDF : the table result heigth is larger and the table is splitted into two pages instead of one (see Example.pdf)

QuentinSup
 
Posts: 46
Joined: Mon Oct 18, 2021 9:18 am

Wed Jun 29, 2022 11:13 am

Hi,

Thanks for your inquiry.

I compared the attached docx and pdf documents, but I found that when opening your docx document with MS Word, the tables are splited into two pages(see the screenshot) too which are the same as the PDF. Therefore, I guess this is caused by inconsistent viewing effects of different versions of Microsoft Word. According to your description, the tables should be one page when you open the docx document on your side, but in mine it was two pages.
Could you please tell me what version of MS Office are you using? Thanks in advance.
Sincerely,
Andy
E-iceblue support team
User avatar

Andy.Zhou
 
Posts: 483
Joined: Mon Mar 29, 2021 3:03 am

Return to Spire.Doc