2

I'm facing a major issue with Eclipse BIRT. Our management team has created a report that works fine and pulls the correct data. However, when I integrate it into our web application, the fonts do not render in the generated PDF. It always falls back to the default font

I need to use the Sora font family, but no matter what I try, it refuses to render. I've verified that the relative path to the font configuration is correct and accessible. Still, the fonts are not applied in the output.

When generating as an HTML, it works just as expected

Here is a snippet of my setup and report generation code:

@PostConstruct
public void setup() throws BirtException {
    log.info("Configuration");
    EngineConfig config = new EngineConfig();
    config.getAppContext().put("spring", this.applicationContext);
    // Set the custom font config directory containing fontsConfig.xml
    log.info("Font");
    try {
        File fontDir = new File(fontConfigPath);
        if (!fontDir.exists()) {
            log.warn("Font config path [{}] does not exist. Skipping font config.", fontConfigPath);
        } else {
            config.setFontConfig(fontDir.toURI().toURL());
            log.info("BIRT font config loaded from: {}", fontDir.getAbsolutePath());
        }
        log.info("Font Success");
    } catch (Exception e) {
        log.error("Error setting BIRT font config from path: {}", fontConfigPath, e);
    }
    Platform.startup(config);
    IReportEngineFactory factory = (IReportEngineFactory) Platform.createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
    birtEngine = factory.createReportEngine(config);
}
@Override
public File generateReportFile(EclipseBirtDesignFileParameterData designFileParameterData) throws FileNotFoundException, EngineException {
    IReportRunnable report = birtEngine.openReportDesign(designFileParameterData.getReportDesignFile().getFileLink().toPath().toString());
    IRunAndRenderTask runAndRenderTask = birtEngine.createRunAndRenderTask(report);

    setParameterData(runAndRenderTask, designFileParameterData);

    File tempReportFile = FileUtils.generateTempReportFile("pdf");
    IRenderOption options = new RenderOption();
    PDFRenderOption pdfRenderOption = new PDFRenderOption(options);
    pdfRenderOption.closeOutputStreamOnExit(true);
    pdfRenderOption.setOutputFormat("pdf");
    pdfRenderOption.setEmbededFont(true);
    runAndRenderTask.setRenderOption(pdfRenderOption);

    try {
        Map<String, String> fontSubstitution = new HashMap<>();
        fontSubstitution.put("Sora", "Sora-Regular");
        fontSubstitution.put("Sora Bold", "Sora-Bold");
        fontSubstitution.put("Sora SemiBold", "Sora-SemiBold");
        fontSubstitution.put("Sora Medium", "Sora-Medium");
        fontSubstitution.put("Sora ExtraBold", "Sora-ExtraBold");
        fontSubstitution.put("Sora ExtraLight", "Sora-ExtraLight");
        fontSubstitution.put("Sora Light", "Sora-Light");
        fontSubstitution.put("Sora Thin", "Sora-Thin");
        log.debug("Using font substitutions: {}", fontSubstitution);

        pdfRenderOption.setOption(IPDFRenderOption.PDF_FONT_SUBSTITUTION, fontSubstitution);
        pdfRenderOption.setOutputStream(new FileOutputStream(tempReportFile));
        runAndRenderTask.run();
    } catch (Exception e) {
        tempReportFile.delete();
        throw new RuntimeException(e.getMessage(), e);
    } finally {
        runAndRenderTask.close();
    }
    return tempReportFile;
}

My fontsconfig.xml

<!-- Enable or disable advanced kerning and ligatures -->
<!-- Font alias mappings -->
<font-aliases>
    <mapping name="serif" font-family="Times-Roman" />
    <mapping name="sans-serif" font-family="Helvetica" />
    <mapping name="monospace" font-family="Courier" />
    <mapping name="cursive" font-family="Times-Roman" />
    <mapping name="fantasy" font-family="Times-Roman" />

    <!-- Sora mappings -->
    <mapping name="Sora" font-family="Sora-Regular" />
    <mapping name="Sora Bold" font-family="Sora-Bold" />
    <mapping name="Sora SemiBold" font-family="Sora-SemiBold" />
    <mapping name="Sora Medium" font-family="Sora-Medium" />
    <mapping name="Sora ExtraBold" font-family="Sora-ExtraBold" />
    <mapping name="Sora ExtraLight" font-family="Sora-ExtraLight" />
    <mapping name="Sora Light" font-family="Sora-Light" />
    <mapping name="Sora Thin" font-family="Sora-Thin" />
</font-aliases>

<!-- Fallback composite font -->
<composite-font name="all-fonts" font-family="Sora-Regular" />
<!-- Unified "Sora" family -->
<font name="Sora">
    <truetype path="/tmp/reports/fonts/global/Sora/Sora-Regular.ttf" isBold="false" isItalic="false"/>
    <truetype path="/tmp/reports/fonts/global/Sora/Sora-Bold.ttf" isBold="true" isItalic="false"/>
    <truetype path="/tmp/reports/fonts/global/Sora/Sora-Italic.ttf" isBold="false" isItalic="true"/>
    <truetype path="/tmp/reports/fonts/global/Sora/Sora-BoldItalic.ttf" isBold="true" isItalic="true"/>
</font>

<!-- Actual font file definitions -->
<font>
    <name>Sora-Regular</name>
    <path>/tmp/reports/fonts/global/Sora/Sora-Regular.ttf</path>
</font>
<font>
    <name>Sora-Bold</name>
    <path>/tmp/reports/fonts/global/Sora/Sora-Bold.ttf</path>
</font>
<font>
    <name>Sora-SemiBold</name>
    <path>/tmp/reports/fonts/global/Sora/Sora-SemiBold.ttf</path>
</font>
<font>
    <name>Sora-Medium</name>
    <path>/tmp/reports/fonts/global/Sora/Sora-Medium.ttf</path>
</font>
<font>
    <name>Sora-ExtraBold</name>
    <path>/tmp/reports/fonts/global/Sora/Sora-ExtraBold.ttf</path>
</font>
<font>
    <name>Sora-ExtraLight</name>
    <path>/tmp/reports/fonts/global/Sora/Sora-ExtraLight.ttf</path>
</font>
<font>
    <name>Sora-Light</name>
    <path>/tmp/reports/fonts/global/Sora/Sora-Light.ttf</path>
</font>
<font>
    <name>Sora-Thin</name>
    <path>/tmp/reports/fonts/global/Sora/Sora-Thin.ttf</path>
</font>

<!-- Directories BIRT will scan for fonts -->
<font-paths>
    <path path="/tmp/reports/fonts/global/Sora"/>
    <path path="C:/windows/fonts" />
    <path path="C:/WINDOWS/Fonts/Sora" />
    <path path="d:/windows/fonts" />
    <path path="e:/windows/fonts" />
    <path path="f:/windows/fonts" />
    <path path="g:/windows/fonts" />
    <path path="C:/WINNT/fonts" />
    <path path="/usr/share/fonts/TTF" />
    <path path="/usr/share/fonts/truetype" />
    <path path="/usr/X11R6/lib/X11/fonts/TTF" />
</font-paths>

<!-- Font encodings -->
<font-encodings>
    <encoding font-family="Sora-Regular" encoding="Cp1252" />
    <encoding font-family="Sora-Bold" encoding="Cp1252" />
    <encoding font-family="Sora-SemiBold" encoding="Cp1252" />
    <encoding font-family="Sora-Medium" encoding="Cp1252" />
    <encoding font-family="Sora-ExtraBold" encoding="Cp1252" />
    <encoding font-family="Sora-ExtraLight" encoding="Cp1252" />
    <encoding font-family="Sora-Light" encoding="Cp1252" />
    <encoding font-family="Sora-Thin" encoding="Cp1252" />
    <encoding font-family="Sora" encoding="Cp1252"/>
</font-encodings>

Here’s a summary of what I’ve done:

Verified that fontsConfig.xml is correctly located and readable. Set the font config path in EngineConfig. Made sure the fonts exists Used PDFRenderOption with setEmbededFont(true). Added font substitutions for all Sora variants. Despite this, the PDF still does not render the Sora fonts. fonts that need to be used

Below is the full code.

import lombok.extern.slf4j.Slf4j;
import org.eclipse.birt.report.engine.api.*;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import static za.co.reportgenerator.model.report.EclipseBirtReportGenerator.SupportedFileFormats.PDF_DEFAULT;
@Slf4j
@Component
public class EclipseBirtPdfDefaultFileGenerator extends EclipseBirtReportFileGenerator<EclipseBirtReportGenerator.SupportedFileFormats, EclipseBirtDesignFileParameterData> {
    public EclipseBirtReportGenerator.SupportedFileFormats getFileFormat() {
        return PDF_DEFAULT;
    }

    @Override
    public File generateReportFile(EclipseBirtDesignFileParameterData designFileParameterData) throws FileNotFoundException, EngineException {
        IReportRunnable report = birtEngine.openReportDesign(designFileParameterData.getReportDesignFile().getFileLink().toPath().toString());
        IRunAndRenderTask runAndRenderTask = birtEngine.createRunAndRenderTask(report);

        setParameterData(runAndRenderTask, designFileParameterData);

        File tempReportFile = FileUtils.generateTempReportFile("pdf");
        IRenderOption options = new RenderOption();
        PDFRenderOption pdfRenderOption = new PDFRenderOption(options);
        pdfRenderOption.closeOutputStreamOnExit(true);
        pdfRenderOption.setOutputFormat("pdf");
        pdfRenderOption.setEmbededFont(true);
        runAndRenderTask.setRenderOption(pdfRenderOption);

        try {
            pdfRenderOption.setOutputStream(new FileOutputStream(tempReportFile));
            runAndRenderTask.run();
        } catch (Exception e) {
            tempReportFile.delete();
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            runAndRenderTask.close();
        }
        return tempReportFile;
    }
}

This is the variables in application.properties

# App specific
reportgenerator.report-save-location = ${GENERATED_REPORT_SAVE_LOCATION:/tmp/report_generator/reports}
reportgenerator.report.design.file.root.path=${ECLIPSE_BIRT_REPORT_DESIGNS_RELATIVE_PATH:/tmp/report_generator/report_design}
reportgenerator.report.design.images.root.path=${ECLIPSE_BIRT_IMAGES_RELATIVE_PATH:/tmp/report_generator/report_design/images}
reportgenerator.report.font.config.path=${ECLIPSE_BIRT_REPORT_DESIGNS_RELATIVE_PATH:/tmp/reports/fonts/config}

import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import lombok.Setter;




import java.io.Serializable;

@MappedSuperclass
@Getter
@Setter
public abstract class BaseEntity implements Serializable {
    public abstract Long getId();
    public abstract void setId(Long id);
}

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class XmlUtils {

    public static List<Element> findElementsByTagValue(File xmlFile, String... byTagValues) throws DocumentException {
        Document xmlDocument = readXmlFile(xmlFile);
        return xmlDocument != null ? findElementsByTagValue(new ArrayList<>(),xmlDocument.getRootElement(),byTagValues) : new ArrayList<>();
    }

    public static List<Element> findElementsByTagValue(Document document, String... byTagValues) {
        return document != null ? findElementsByTagValue(new ArrayList<>(),document.getRootElement(),byTagValues) : new ArrayList<>();
    }

    public static List<Element> findElementsByTagValue(Element element, String... byTagValues) {
        return findElementsByTagValue(new ArrayList<>(),element,byTagValues);
    }

    public static List<Element> findElementsByTagValue(List<Element> currentFoundElements, Element element, String... byTagValues) {
        List<Element> childElements = element.elements();
        if(childElements != null && !childElements.isEmpty()) {
            for (Element childElement : childElements) {
                findElementsByTagValue(currentFoundElements, childElement, byTagValues);
            }
        } else if(element.getText() != null && Arrays.stream(byTagValues).anyMatch(byTagVal -> element.getText().contains(byTagVal))) {
            currentFoundElements.add(element);
        }
        return currentFoundElements;
    }

    public static List<Element> findElementsByTagName(Document document, String byTagName) {
        return findElementsByTagName(new ArrayList<>(),document.getRootElement(),byTagName);
    }

    public static List<Element> findElementsByTagName(Element element, String byTagName) {
        return findElementsByTagName(new ArrayList<>(),element,byTagName);
    }

    public static List<Element> findElementsByTagName(List<Element> currentFoundElements, Element elementToLookAt, String byTagName) {
        List<Element> childElements = elementToLookAt.elements();
        if(childElements != null && !childElements.isEmpty()) {
            for (Element childElement : childElements) {
                findElementsByTagName(currentFoundElements, childElement, byTagName);
            }
        } else if(elementToLookAt.getName() != null && elementToLookAt.getName().equalsIgnoreCase(byTagName)) {
            currentFoundElements.add(elementToLookAt);
        }
        return currentFoundElements;
    }

    public static void prefixElementTextValueByTagValues(Document document, String prefixValue, String... byTagValues) {
        prefixElementTextValueByTagValues(document.getRootElement(),prefixValue,false,byTagValues);
    }

    public static void prefixFilePathToElementTextValueByTagValues(Document document, String prefixValue, String... byTagValues) {
        prefixElementTextValueByTagValues(document.getRootElement(),prefixValue,true,byTagValues);
    }

    public static void prefixElementTextValueByTagValues(Document document, String prefixValue, boolean forFilePathPrefixing, String... byTagValues) {
        prefixElementTextValueByTagValues(document.getRootElement(),prefixValue,forFilePathPrefixing,byTagValues);
    }

    public static void prefixElementTextValueByTagValues(Element elementToLookAt, String prefixValue, boolean forFilePathPrefixing, String... byTagValues) {
        List<Element> childElements = elementToLookAt.elements();
        if(childElements != null && !childElements.isEmpty()) {
            for (Element childElement : childElements) {
                prefixElementTextValueByTagValues(childElement, prefixValue, forFilePathPrefixing, byTagValues);
            }
        } else if(elementToLookAt.getText() != null && Arrays.stream(byTagValues).anyMatch(byTagVal -> elementToLookAt.getText().contains(byTagVal))) {
            if(forFilePathPrefixing) {
                if(elementToLookAt.getText().startsWith("/") && !prefixValue.endsWith("/")) {
                    elementToLookAt.setText(prefixValue + elementToLookAt.getText());
                } else if(elementToLookAt.getText().startsWith("/") && prefixValue.endsWith("/")) {
                    elementToLookAt.setText(prefixValue + elementToLookAt.getText().substring(1));
                } else if(!elementToLookAt.getText().startsWith("/") && prefixValue.endsWith("/")) {
                    elementToLookAt.setText(prefixValue + elementToLookAt.getText());
                } else if(!elementToLookAt.getText().startsWith("/") && !prefixValue.endsWith("/")) {
                    elementToLookAt.setText(prefixValue + "/" + elementToLookAt.getText());
                }
            } else {
                elementToLookAt.setText(prefixValue + elementToLookAt.getText());
            }
        }
    }

    public static Document readXmlFile(File xmlFile) throws DocumentException {
        SAXReader reader = new SAXReader();
        return xmlFile != null ? reader.read(xmlFile) : null;
    }

}
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity 
@Getter
@Setter
public class ReportDesignParameter extends BaseEntity {

    @Id
    @Column(name="id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @Column(name="name")
    private String name;

    @Column(name="param_order")
    private int order;

    @Column(name="param_value")
    private String value;

    @Column(name="value_type")
    @Enumerated(EnumType.STRING)
    private ReportParamValType valueType;
    @Column(name="generated_from_design_file")
    private Boolean generatedFromDesignFile;

    public ReportDesignParameter() {}
    public ReportDesignParameter(String name) {
        this.name = name;
    }
    public ReportDesignParameter(String name, String value) {
        this.name = name;
        this.value = value;
    }

    public ReportDesignParameter(String name, String value, int order) {
        this.name = name;
        this.value = value;
        this.order = order;
    }

    public ReportDesignParameter withOrder(Integer order) {
        this.order = order;
        return this;
    }

    public ReportDesignParameter withReportDesignParameter(ReportParamValType reportParamValType) {
        this.valueType = reportParamValType;
        return this;
    }

    public ReportDesignParameter withGeneratedFromDesignFile(Boolean generatedFromDesignFile) {
        this.generatedFromDesignFile = generatedFromDesignFile;
        return this;
    }
}
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.framework.Platform;
import org.eclipse.birt.report.engine.api.*;
import org.eclipse.birt.report.engine.api.impl.ParameterDefnBase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

@Slf4j
@Component
public class EclipseBirtReportDesignFileHandler {
    @Value("${reportgenerator.report.design.file.root.path}")
    private String reportDesignsPath;
    @Value("${reportgenerator.report.design.images.root.path}")
    private String imagesRootPath;
    @Value("${reportgenerator.report.font.config.path}")
    private String fontConfigPath = '/tmp/reports/fonts/config';

    @Autowired
    private ApplicationContext applicationContext;

    private IReportEngine birtEngine;

    @PostConstruct
    public void setup() throws BirtException {
        log.info("Configuration");
        EngineConfig config = new EngineConfig();
        config.getAppContext().put("spring", this.applicationContext);
        // Set the custom font config directory containing fontsConfig.xml
        log.info("Font");
        try {
            File fontDir = new File(fontConfigPath);
            config.setFontConfig(fontDir.toURI().toURL());
            log.info("BIRT font config loaded from: {}", fontDir.getAbsolutePath());
            log.info("Font Success");

        } catch (Exception e) {
            log.warn("Font config path [{}] does not exist. Skipping font config.", fontConfigPath);
            log.error("Error setting BIRT font config from path: {}", fontConfigPath, e);
        }
        Platform.startup(config);
        IReportEngineFactory factory = (IReportEngineFactory) Platform.createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
        birtEngine = factory.createReportEngine(config);
    }

    public List<ReportDesignParameter> fetchReportDesignParametersFromReportDesignFile(File reportDesignFile) throws ReportDesignException {
        try {
            return fetchReportDesignParametersFromReportDesignFile(birtEngine.openReportDesign(reportDesignFile.getPath()));
        } catch (EngineException e) {
            throw new ReportDesignException(ReportDesignException.ReportDesignError.INTERNAL_SERVER_ERROR, " - error trying to fetch report design parameters");
        }
    }

    public List<ReportDesignParameter> fetchReportDesignParametersFromReportDesignFile(IReportRunnable reportDesignFile) throws ReportDesignException {
        List<ReportDesignParameter> result = new ArrayList<>();
        try {
            IGetParameterDefinitionTask task = birtEngine.createGetParameterDefinitionTask(reportDesignFile);
            Collection<ParameterDefnBase> params = task.getParameterDefns(true);

            int order = 0;
            for (ParameterDefnBase paramDef : params) {
                if (paramDef instanceof IParameterDefn) {
                    result.add(new ReportDesignParameter(paramDef.getName())
                            .withOrder(order++)
                            .withReportDesignParameter(determineReportParameterValueType((IParameterDefn) paramDef)));
                }
            }

        } catch (Exception e) {
            throw new ReportDesignException(ReportDesignException.ReportDesignError.INTERNAL_SERVER_ERROR, " - error trying to fetch report design parameters", e);
        }
        return result;
    }

    public List<File> getAllImagesFromReportDesignFile(File reportDesignFile) throws DocumentException {
        return getAllImagesFromReportDesignFile(XmlUtils.readXmlFile(reportDesignFile));
    }

    public List<File> getAllImagesFromReportDesignFile(Document reportDesignFile) {
        List<File> result = new ArrayList<>();

        final List<Element> xmlTagsWithImagePaths = new ArrayList<>();
        for (ImageType imageType : ImageType.values()) {
            xmlTagsWithImagePaths.addAll(XmlUtils.findElementsByTagValue(reportDesignFile, imageType.getFileExtension()));
        }

        for (Element tagWithImagePath : xmlTagsWithImagePaths) {
            final String elementImagePath = tagWithImagePath.getText();
            result.add(new File(elementImagePath));
        }

        return result;
    }

    public void prefixImageElementPathsWithImageRootPath(File reportDesignFile) throws DocumentException {
        prefixImageElementPathsWithImageRootPath(XmlUtils.readXmlFile(reportDesignFile));
    }

    public void prefixImageElementPathsWithImageRootPath(Document reportDesignFile) {
        // override image paths by prefixing it with the base image directory path
        XmlUtils.prefixFilePathToElementTextValueByTagValues(
                reportDesignFile,
                imagesRootPath,
                Arrays.stream(ImageType.values()).map(ImageType::getFileExtension).toList().toArray(new String[]{}));
    }

    public List<String> getAllDataSourceUrlsFromReportDesignFile(File reportDesignFile) throws DocumentException {
        return getAllDataSourceUrlsFromReportDesignFile(XmlUtils.readXmlFile(reportDesignFile));
    }

    public List<String> getAllDataSourceUrlsFromReportDesignFile(Document reportDesignFile) {
        return reportDesignFile != null ? getAllDataSourceUrlsFromElement(reportDesignFile.getRootElement()) : new ArrayList<>();
    }

    public List<String> getAllDataSourceUrlsFromElement(Element fromElement) {
        return XmlUtils.findElementsByTagName(fromElement, "property").stream()
                .filter(propElement ->
                        propElement.attribute("name") != null &&
                                propElement.attributeValue("name").equals("odaURL"))
                .map(Element::getText).toList();
    }

    private ReportParamValType determineReportParameterValueType(IParameterDefn paramDef) {
        return switch (paramDef.getDataType()) {
            case IParameterDefn.TYPE_STRING -> ReportParamValType.STRING;
            case IParameterDefn.TYPE_INTEGER -> ReportParamValType.NUMBER_NO_DECIMAL;
            case IParameterDefn.TYPE_FLOAT, IParameterDefn.TYPE_DECIMAL -> ReportParamValType.NUMBER_DECIMAL;
            case IParameterDefn.TYPE_DATE -> ReportParamValType.DATE;
            case IParameterDefn.TYPE_DATE_TIME -> ReportParamValType.DATETIME;
            case IParameterDefn.TYPE_BOOLEAN -> ReportParamValType.BOOLEAN;
            case IParameterDefn.TYPE_TIME -> ReportParamValType.TIME;
            default -> ReportParamValType.UNKNOWN;
        };
    }


}

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.