Finding a suitable font substitution when converting a word document to PDF

Hi there

When trying to convert a word document to a PDF, where the fonts used within the word document are not part of the underlying system, Aspose tries to find a suitable substitution font (as stated at https://docs.aspose.com/words/java/using-truetype-fonts/).

Code Snippet:

public static void convertToPdf(File inputFile, File outputFile) throws Exception {
    try (FileInputStream fis = new FileInputStream(inputFile); FileOutputStream fos = new FileOutputStream(outputFile)) {
        Document doc = new Document(fis);
        doc.setWarningCallback(new HandleDocumentWarnings());
        doc.save(fos, new PdfSaveOptions());
    }
}

Warning callback is implemented as described in https://docs.aspose.com/words/java/manipulate-and-substitute-truetype-fonts/

Output for System 1
Font substitution: Font ‘Calibri’ has not been found. Using ‘Cousine’ font instead. Reason: first available font.
Font substitution: Font ‘Times New Roman’ has not been found. Using ‘Cousine’ font instead. Reason: first available font.

Output for System 2
Font substitution: Font ‘Calibri’ has not been found. Using ‘Symbol Neu’ font instead. Reason: first available font.
Font substitution: Font ‘Times New Roman’ has not been found. Using ‘Symbol Neu’ font instead. Reason: first available font.

Tested with Aspose Words 16.2 and 16.4.
Both systems have Ubuntu 14.04.4 LTS (server edition) with only the following three fonts package installed (see https://en.wikipedia.org/wiki/Croscore_fonts):
fonts-croscore
fonts-crosextra-caladea
fonts-crosextra-carlito

Questions:

  1. How is “most suitable font” defined (point 4) [PANOSE?]? Is there a way to give hints to Aspose for finding a suitable font? I don’t use addFontSubstitutes because the font substitution would be checked right after Point 1, which is to early because embedded fonts would be omitted. I would expect that Carlito would be a suitable substitution for Calibri for example.

  2. “First available font” doesn’t seem to be deterministic because resulting in different fonts on different systems. Is there a way that I can assure a “last” font option? Setting FontSettings.DefaultFontName would always use the default font instead of finding a more suitable one, thus not really a preferable solution. Font “Symbol” might always be a bad choice

A callback to allow specific fonts to be chosen dynamically as stated in https://docs.aspose.com/words/java/manipulate-and-substitute-truetype-fonts/ would be really great.

Kind regards and thanks
Stephan

Hi Stephan,

Thanks for your inquiry. If your testing environment have same fonts settings, the font substitution warnings should be same.

You can use FontSettings.GetFontsSources method to get a copy of the array that contains the list of sources where Aspose.Words looks for TrueType fonts. After getting the font sources, please add more font sources in the list. You can use FontSettings.SetFontsSources method to update the final font source. Finally, you may use FontSettings.AddFontSubstitutes method.

Please check following code example code example. Hope this helps you.

Document doc = new Document(*getMyDir*() + "Rendering.doc");
// Retrieve the array of environment-dependent font sources that are searched by default. 
// We add this array to a new ArrayList to make adding or removing font entries much easier.
ArrayList fontSources = new ArrayList(Arrays.*asList*(FontSettings.*getDefaultInstance*().getFontsSources()));
// Add a new folder source which will instruct Aspose.Words to search the following folder for fonts.
FolderFontSource folderFontSource = new FolderFontSource("/usr/myfonts/", true);
// Add the custom folder which contains our fonts to the list of existing font sources.
fontSources.add(folderFontSource);
// Convert the Arraylist of source back into a primitive array of FontSource objects.
FontSourceBase[] updatedFontSources = (FontSourceBase[])fontSources.toArray(new FontSourceBase[fontSources.size()]);
// Apply the new set of font sources to use.
FontSettings.*getDefaultInstance*().setFontsSources(updatedFontSources);
doc.save(*getMyDir*() + "Rendering.SetFontsFolders Out.pdf");

Hi Tahir

Thank you for your response.

I output the font sources to the console by iterating over FontSettings.getDefaultInstance().getFontsSources() and by using SystemFontSource.getSystemFontFolders()). They are identical on both systems and already contain the path where the croscore fonts are located (subfolder of /usr/share/fonts/ in my case). I still used your code and added two paths explicitly (/usr/share/fonts/truetype/croscore/, /usr/share/fonts/truetype/crosextra/), the result is the same. One system uses “Symbol Neu” as fallback, the other ones uses “Cousine”.

By using FontSettings.AddFontSubstitutes it works as expected but this isn’t a preferable solution due to the reason mentioned in the first post (see question 1). Because of this a new question (or maybe more a feature request) arises beside the 2 stated in the first post already.

Question 3 (related to https://docs.aspose.com/words/java/using-truetype-fonts/):
Wouldn’t it be in general more useful if the explicitly defined font substitutions would be checked after step #2 (instead of after step #1)? If the font is provided within the document, I assume it’s better to take that one as to take a substitution font).

Kind regards
Stephan

Hi Stephan,

Thanks for your inquiry.

Currently the fonts, which are set by SetFontSubstutites/AddFontSubstitutes method, are checked after the step #1. As per my understanding you want to check this after step #2. Do you also want to substitution suitable font from fonts embedded in the document? Please confirm, we will then log this feature in our issue tracking system.

You may use FontSourceBase.Priority for your scenario. This property is used when there are fonts with the same family name and style in different font sources. In this case Aspose.Words selects the font from the source with the higher priority value.

// Aspose.Words will prefer fonts from "Folder1" over the fonts from "Folder2".
FolderFontSource folder1 = new FolderFontSource("Folder1", false, 2);
FolderFontSource folder2 = new FolderFontSource("Folder2", false, 1);
FontSettings.setFontsSources(new FontSourceBase[] {folder1, folder2});

Hi Tahir

SetFontSubstutites/AddFontSubstitutes
Exactly, I assume that checking if the required font is part of the embedded fonts of the document is preferred over applying font substitution first. I recommend thus applying font substitution after step #2 and not after step #1 as it currently is. I’m not completely sure what you mean by your question, so here is an example:

  • document with font Calibri
  • font Calibri is embedded within Word document
  • no font Calibri is installed
  • font Carlito is installed
  • font substitution Calibri → Carlito is added via addFontSubstitutes

When I run this example, the following will happen (according to https://docs.aspose.com/words/java/using-truetype-fonts/)
#1: No exact font match
Font substitution is applied, thus Carlito is used

Desired behavior
#1: No exact font match
#2: Font Carlibri is found within document → Calibri is used

“most suitable font” (question 1 from first post)
Is there any way where this will not result in the reason ‘first available font’?
If not, then question 2 from the first post is obsolete.

FontSourceBase.Priority
Thanks for the information about FontSourceBase.Priority. The priority value seems to be ignored at least for ‘first available font’, but the order in which the font sources are added to the list seems to be relevant according to my tests (maybe priority works as expected when used for fonts with same family name and style as you mentioned):

*Test run #1
Folder: /usr/share/fonts/truetype/crosextra/ (recursive: true, priority 2)
Folder: /usr/share/fonts/truetype/croscore/ (recursive: true, priority 1)
→ Font substitution: Font ‘Calibri’ has not been found. Using ‘Carlito’ font instead. Reason: first available font.

Test run #2
Folder: /usr/share/fonts/truetype/crosextra/ (recursive: true, priority 1)
Folder: /usr/share/fonts/truetype/croscore/ (recursive: true, priority 2)
→ Font substitution: Font ‘Calibri’ has not been found. Using ‘Carlito’ font instead. Reason: first available font.

Test run #3
Folder: /usr/share/fonts/truetype/croscore/ (recursive: true, priority 2)
Folder: /usr/share/fonts/truetype/crosextra/ (recursive: true, priority 1)
→ Font substitution: Font ‘Calibri’ has not been found. Using ‘Symbol Neu’ font instead. Reason: first available font.

Test run #4
Folder: /usr/share/fonts/truetype/croscore/ (recursive: true, priority 1)
Folder: /usr/share/fonts/truetype/crosextra/ (recursive: true, priority 2)
→ Font substitution: Font ‘Calibri’ has not been found. Using ‘Symbol Neu’ font instead. Reason: first available font.*

Kind regards and thanks again
Stephan

Hi Stephan,

Thanks for sharing the detail.

*bsiagch:
#1: No exact font match
Font substitution is applied, thus Carlito is used

Desired behavior
#1: No exact font match
#2: Font Carlibri is found within document -> Calibri is used*

We have logged a feature request as WORDSNET-13817 to check font substitution from embedded fonts in document. Our product team will look into the possibility of implementation of this feature. Once we have any information about this feature, we will update you via this forum thread.

bsiagch:
The priority value seems to be ignored at least for ‘first available font’, but the order in which the font sources are added to the list seems to be relevant according to my tests (maybe priority works as expected when used for fonts with same family name and style as you mentioned):

This value of FontSourceBase.Priority property is used when there are fonts with the same family name and style in different font sources.

Thank you very much Tahir for the feedback and logging the feature request.

A final question remains:
How is “most suitable font” defined (point 4)? Is there any way where this will not result in the reason ‘first available font’?

Kind regards
Stephan

Hi Stephan,

Thanks for your inquiry. If the fonts used in input document are not available and font defined under FontSettings.DefaultFontName is also not available, Aspose.Words tries to match the most suitable font from available fonts on the system. If Aspose.Words cannot find any fonts on the file system, it renders the document using the free Gentium font that is embedded into the Aspose.Words assembly.

We would like to share with you that the feature (WORDSNET-13817) has been resolved and this feature will be available in Aspose.Words v16.7.0.

Please let us know if you have any more queries.

Thanks for the response and the fix. I will test it as soon as 16.7 is released.

Kind regards
Stephan

The issues you have found earlier (filed as WORDSNET-13817) have been fixed in this .NET update and this Java update.

This message was posted using Notification2Forum from Downloads module by aspose.notifier.

Thanks, tested successfully with 16.8.0

Kind regards
Stephan