Word document font replacement fails

I want to replace a font in a Word document. The font is not installed on my computer and I want to replace it with an installed font.
version:aspose-words-25.9-jdk17

FontSettings fontSettings = new FontSettings();
fontSettings.setFontsSources(new FontSourceBase[]{
    new SystemFontSource(),
    new FolderFontSource(getFontPath(), true)
});
setupFontSubstitution(fontSettings, wpd, "方正仿宋_GB2312", "KaiTi");
public static void setupFontSubstitution(FontSettings fontSettings, Document doc, String sourceFont, String targetFont) throws Exception {
    Set<String> usedFonts = getUsedFonts(doc);
 
    if (usedFonts.contains(sourceFont)) {
        fontSettings.getSubstitutionSettings().getFontConfigSubstitution().setEnabled(true);
        fontSettings.getSubstitutionSettings().getTableSubstitution().setEnabled(true);
        fontSettings.getSubstitutionSettings()
                .getTableSubstitution()
                .setSubstitutes(sourceFont, new String[]{targetFont});
    } 
}
PdfSaveOptions opt = new PdfSaveOptions();    opt.getMetafileRenderingOptions().setRenderingMode(MetafileRenderingMode.VECTOR_WITH_FALLBACK);
doc.save(dstFile, opt);

@serendipity.zhq

To replace a font in a Word document when the original font is not installed on your computer, you can use the FontSettings class in Aspose.Words. This allows you to specify a substitution font that is installed on your system.

Here’s a simple example of how to do this:

using Aspose.Words;
using Aspose.Words.Fonts;

Document doc = new Document("input.docx");

// Create a FontSettings object
FontSettings fontSettings = new FontSettings();

// Specify the substitution font
fontSettings.SubstitutionSettings.DefaultFontSubstitution = new DefaultFontSubstitution("YourInstalledFontName");

// Apply the font settings to the document
doc.FontSettings = fontSettings;

// Save the document
doc.Save("output.docx");

Make sure to replace "YourInstalledFontName" with the name of the font you want to use as a replacement. This way, when the document is processed, it will use the specified installed font instead of the missing one.

If you are working with a plain text file (.txt), note that font substitution is not applicable. Instead, you may need to manually edit the text or use a different approach to ensure the content is displayed correctly.

I replaced a font used in the document with another font

@serendipity.zhq FontSettings and SubstitutionSettings has effect only while converting document to fixed page formats, like PDF, XPS, Image etc. Also, you should note that if the font is not present on the machine where the document is rendered, Aspose.Words perform font substitution in several stages. Please see our documentation for more information:
https://docs.aspose.com/words/java/manipulate-and-substitute-truetype-fonts/#font-availability-and-substitution

In your case even if you configured a custom TableSubstitution rule, font substitution can be performed before TableSubstitution stage.

If your goal to change font in the document and save the result in DOCX or other flow document format, you can try using DocumentVisitor. For example:

Document doc = new Document("C:\\Temp\\in.docx");
        
// Change font to SimSun 10.5
doc.accept(new FontChanger("SimSun", 10.5));
        
doc.save("C:\\Temp\\out.docx");
public class FontChanger extends DocumentVisitor {
    
    public FontChanger(String fontName)  {
        mFontName = fontName;
        mFontSize = 0;
    }
    
    public FontChanger(String fontName, double fontSize)
    {
        mFontName = fontName;
        mFontSize = fontSize;
    }

    /// <summary>
    /// Called when a FieldEnd node is encountered in the document.
    /// </summary>
    @Override
    public int visitFieldEnd(FieldEnd fieldEnd)
    {
        //Simply change font name
        resetFont(fieldEnd.getFont());
        return VisitorAction.CONTINUE;
    }

    /// <summary>
    /// Called when a FieldSeparator node is encountered in the document.
    /// </summary>
    @Override
    public int visitFieldSeparator(FieldSeparator fieldSeparator)
    {
        resetFont(fieldSeparator.getFont());
        return VisitorAction.CONTINUE;
    }

    /// <summary>
    /// Called when a FieldStart node is encountered in the document.
    /// </summary>
    @Override
    public int visitFieldStart(FieldStart fieldStart)
    {
        resetFont(fieldStart.getFont());
        return VisitorAction.CONTINUE;
    }

    /// <summary>
    /// Called when a Footnote end is encountered in the document.
    /// </summary>
    @Override
    public int visitFootnoteEnd(Footnote footnote)
    {
        resetFont(footnote.getFont());
        return VisitorAction.CONTINUE;
    }

    /// <summary>
    /// Called when a FormField node is encountered in the document.
    /// </summary>
    @Override
    public int visitFormField(FormField formField)
    {
        resetFont(formField.getFont());
        return VisitorAction.CONTINUE;
    }

    /// <summary>
    /// Called when a Paragraph end is encountered in the document.
    /// </summary>
    @Override
    public int visitParagraphEnd(Paragraph paragraph)
    {
        resetFont(paragraph.getParagraphBreakFont());
        return VisitorAction.CONTINUE;
    }

    /// <summary>
    /// Called when a Run node is encountered in the document.
    /// </summary>
    @Override
    public int visitRun(Run run)
    {
        resetFont(run.getFont());
        return VisitorAction.CONTINUE;
    }

    /// <summary>
    /// Called when a SpecialChar is encountered in the document.
    /// </summary>
    @Override
    public int visitSpecialChar(SpecialChar specialChar)
    {
        resetFont(specialChar.getFont());
        return VisitorAction.CONTINUE;
    }

    private void resetFont(Font font)
    {
        font.setName(mFontName);
        if(mFontSize > 0)
            font.setSize(mFontSize);
    }

    private String mFontName = "Arial";
    private double mFontSize = 0;
}

I am trying to convert a word file to pdf, but it is not working. Is there something wrong with my code?

@serendipity.zhq Have you tried implementing IWarningCallback to get notifications about font substitutions? What is the reason of font substitution in the shown warnings?

You seem to have misunderstood what I meant. I was actively trying to replace a font with another font but failed, rather than passively replacing the font in the code to find the reason.

@serendipity.zhq Yes, I understood your requirement. You have a document where font is set to 方正仿宋_GB2312, which is not installed in your environment, You would like this font to be substituted with KaiTi, which, I suppose, is installed in your environment. In your code you have configured TableSubstitution.
Upon performing font substitutions Aspose.Words generates warnings where you can see the reason of font substitution. Since there are several font substitution rules, one of the rule can be applied before TableSubstitution rule and TableSubstitution is not applied. So my question is the same - Have you tried implementing IWarningCallback to get notifications about font substitutions? What is the reason of font substitution in the shown warnings?

setupFontSubstitution(fontSettings, wpd, "方正仿宋_GB2312", "KaiTi");
doc.setWarningCallback(new FontSubstitutionWarningCallback());
public class FontSubstitutionWarningCallback implements IWarningCallback {
    @Override
    public void warning(WarningInfo info) {
        if (info.getWarningType() == WarningType.FONT_SUBSTITUTION) {
            String desc = info.getDescription();
            String missingFont = extractBetween(desc, "Font '", "'");
            String substituteFont = extractBetween(desc, "Using '", "' instead");
            String reason = extractAfter(desc, "Reason:");

            System.out.println("warning:");
            System.out.println(missingFont);
            System.out.println(substituteFont);
            System.out.println(reason.trim());

        } else {
            System.out.println(info.getDescription());
        }
    }

This is my code, which does not capture the relevant information of the font I want to replace

@serendipity.zhq Could you please attach your problematic input, output and expected output document along with the desired substitution font file? We will test your scenario on our side and provide you more information.

My file:https://drive.google.com/file/d/1FQvgZCk80y8FbJMxVd0PO5scD-45BCGD/view?usp=sharing

@serendipity.zhq Some fonts used in your input document are embedded into the document. So no font substitution is required. Aspose.Words simply use embedded fonts. In your expected output all fonts are replaced with KaiTi. To achieve this you should use the code suggested in my initial reply. Alternatively, you can remove embedded fonts in your document and set one file font source to force Aspose.Words to use the specified font for rendering:

Document doc = new Document("C:\\Temp\\in.doc");
// Disable font embedding in the document.
doc.getFontInfos().setEmbedTrueTypeFonts(false);
// Save open the document to remove embedded fonts.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
doc.save(baos, SaveFormat.DOC);
doc = new Document(new ByteArrayInputStream(baos.toByteArray()));
        
doc.setFontSettings(new FontSettings());
// Use one file font source for document rendering.
doc.getFontSettings().setFontsSources(new FontSourceBase[] { new FileFontSource("C:\\Temp\\simkai.ttf") });
doc.save("C:\\Temp\\out.pdf");

out.pdf (45.8 KB)