PDFsharp / MigraDoc font resolver for embedded fonts: System.ArgumentException: Font 'Ubuntu' cannot be found
Asked Answered
R

1

7

I am using MigraDoc to generate PDFs in my ASP.NET5 MVC6 Web Application, which is deployed to the Azure cloud. I am using version 1.50 beta-2, but I have also tried using v1.50 beta-1 and v1.32.

I have had success generating PDFs when the app is running locally. However, I have been having significant trouble generating PDFs when the app is running on the cloud server due to not having access to any fonts. Following the PDFsharp docs, I tried creating a "private font" by embedding a font within my code.

I had success generating PDFs on the cloud using PDFsharp directly

    public static MyResolver FontResolver = new MyResolver();
    public void RenderPdf(CreateDocumentViewModel viewModel)
    {
        GlobalFontSettings.FontResolver = FontResolver;
        //...
        XFont font = new XFont("times-roman", 12, XFontStyle.Regular);
        //This font is then used in DrawString.
    }

However, I would now like to take advantage of MigraDoc so that I don't have to do all the typesetting myself.

I followed the excellent guide on Thomas Hövel's blog here (this is the new one for beta-2, although I had also previously followed his earlier post for beta-1). His project works perfectly for me, as-is, locally.

I implemented the example for use in my Web App project. It's exactly the same as Thomas' code except main is a method in my controller that is run on a button click:

Font Resolver Classes:

public class DemoFontResolver : IFontResolver
{
    public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
    {
        // Ignore case of font names.
        var name = familyName.ToLower();

        // Deal with the fonts we know.
        switch (name)
        {
            case "ubuntu":
                if (isBold)
                {
                    if (isItalic)
                        return new FontResolverInfo("Ubuntu#bi");
                    return new FontResolverInfo("Ubuntu#b");
                }
                if (isItalic)
                    return new FontResolverInfo("Ubuntu#i");
                return new FontResolverInfo("Ubuntu#");

            case "janitor":
                return new FontResolverInfo("Janitor#");
        }

        // We pass all other font requests to the default handler.
        // When running on a web server without sufficient permission, you can return a default font at this stage.
        return PlatformFontResolver.ResolveTypeface(familyName, isBold, isItalic);
    }

    /// <summary>
    /// Return the font data for the fonts.
    /// </summary>
    public byte[] GetFont(string faceName)
    {
        switch (faceName)
        {
            case "Janitor#":
                return DemoFontHelper.Janitor;

            case "Ubuntu#":
                return DemoFontHelper.Ubuntu;

            case "Ubuntu#b":
                return DemoFontHelper.UbuntuBold;

            case "Ubuntu#i":
                return DemoFontHelper.UbuntuItalic;

            case "Ubuntu#bi":
                return DemoFontHelper.UbuntuBoldItalic;
        }

        return GetFont(faceName);
    }
}

/// <summary>
/// Helper class that reads font data from embedded resources.
/// </summary>
public static class DemoFontHelper
{
    public static byte[] Janitor
    {
        get { return LoadFontData("RealEstateDocumentGenerator.fonts.janitor.Janitor.ttf"); }
    }

    // Tip: I used JetBrains dotPeek to find the names of the resources (just look how dots in folder names are encoded).
    // Make sure the fonts have compile type "Embedded Resource". Names are case-sensitive.
    public static byte[] Ubuntu
    {
        get { return LoadFontData("RealEstateDocumentGenerator.fonts.ubuntufontfamily0._80.Ubuntu-B.ttf"); }
    }

    public static byte[] UbuntuBold
    {
        get { return LoadFontData("RealEstateDocumentGenerator.fonts.ubuntufontfamily0._80.Ubuntu-B.ttf"); }
    }

    public static byte[] UbuntuItalic
    {
        get { return LoadFontData("RealEstateDocumentGenerator.fonts.ubuntufontfamily0._80.Ubuntu-RI.ttf"); }
    }

    public static byte[] UbuntuBoldItalic
    {
        get { return LoadFontData("RealEstateDocumentGenerator.fonts.ubuntufontfamily0._80.Ubuntu-BI.ttf"); }
    }

    /// <summary>
    /// Returns the specified font from an embedded resource.
    /// </summary>
    static byte[] LoadFontData(string name)
    {
        var assembly = Assembly.GetExecutingAssembly();

        using (Stream stream = assembly.GetManifestResourceStream(name))
        {
            if (stream == null)
                throw new ArgumentException("No resource with name " + name);

            int count = (int)stream.Length;
            byte[] data = new byte[count];
            stream.Read(data, 0, count);
            return data;
        }
    }
}

Home controller:

public class HomeController : Controller
{
    [HttpPost]
    public ActionResult CreateDocument()
    {
        DemoProjectMain();
        return View();
    }

    public void DemoProjectMain()
    {
        // That's all it takes to register your own fontresolver
        GlobalFontSettings.FontResolver = new DemoFontResolver();

        // And now the slightly modified MigraDoc Hello World sample.

        // Create a MigraDoc document
        Document document = DemoCreateDocument();
        document.UseCmykColor = true;

        // Create a renderer for the MigraDoc document.
        PdfDocumentRenderer pdfRenderer = new PdfDocumentRenderer(unicode);

        WriteDocument(document, pdfRenderer);
    }

    public void WriteDocument(Document document, PdfDocumentRenderer renderer)
    {

        renderer.Document = document;
        renderer.RenderDocument();

        // Send PDF to browser
        MemoryStream stream = new MemoryStream();
        renderer.PdfDocument.Save(stream, false);
        Response.Clear();
        Response.ContentType = "application/pdf";
        Response.AddHeader("content-length", stream.Length.ToString());
        Response.BinaryWrite(stream.ToArray());
        Response.Flush();
        stream.Close();
        Response.End();
    }

    /// <summary>
    /// Creates an absolutely minimalistic document.
    /// </summary>
    static Document DemoCreateDocument()
    {
        // Create a new MigraDoc document
        Document document = new Document();

        DemoSetupStyles(document);

        // Add a section to the document
        Section section = document.AddSection();

        // Add a paragraph to the section
        Paragraph paragraph = section.AddParagraph();

        paragraph.Format.Font.Color = Color.FromCmyk(100, 30, 20, 50);

        // Add some text to the paragraph
        paragraph.AddFormattedText("Hello, World!", TextFormat.Bold);

        section.AddParagraph("Hello, World!");

        // Demonstration for Heading styles.
        paragraph = section.AddParagraph("Hello, World! (Heading 1)");
        paragraph.Style = StyleNames.Heading1;

        paragraph = section.AddParagraph("Hello, World! (Heading 2)");
        paragraph.Style = StyleNames.Heading2;

        paragraph = section.AddParagraph("Hello, World! (Heading 3)");
        paragraph.Style = StyleNames.Heading3;

        paragraph = section.AddParagraph("Hello, World! (Heading 4)");
        paragraph.Style = StyleNames.Heading4;

        paragraph = section.AddParagraph();

        paragraph.Format.Font.Color = Color.FromCmyk(100, 30, 20, 50);

        // Add some text to the paragraph
        paragraph.AddFormattedText("Hello, World!", TextFormat.Bold);

        section.AddParagraph("Hello, World!");

        return document;
    }

    private static void DemoSetupStyles(Document document)
    {
        // Default font for all styles.
        var style = document.Styles[StyleNames.Normal];
        style.Font.Name = "Ubuntu";

        // Overwrite font for headings 1 & 2.
        style = document.Styles[StyleNames.Heading1];
        style.Font.Name = "Janitor";
        style.Font.Size = 32;

        // Heading 2 inherits font from Heading 1.
        style = document.Styles[StyleNames.Heading2];
        style.Font.Size = 28;

        // Set normal font for Heading 3.
        style = document.Styles[StyleNames.Heading3];
        style.Font.Name = "Ubuntu";
        style.Font.Size = 24;

        style = document.Styles[StyleNames.Heading4];
        style.Font.Size = 20;
    }
}

When I run the application and click the button triggering the demo code, I get an error "Font 'Ubuntu' cannot be found" that occurs at renderer.RenderDocument(). How can I get the font resolver to find/recognise the font so I can use MigraDoc to generate PDFs on my ASP.NET MVC application?

The full error message and stack trace is as follows:

Server Error in '/' Application.

Font 'Ubuntu' cannot be found.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.ArgumentException: Font 'Ubuntu' cannot be found.

Source Error:

Line 305:
Line 306:            renderer.Document = document;
Line 307:            renderer.RenderDocument();
Line 308:
Line 309:            // Send PDF to browser

Source File: C:\Users\User\Documents\Visual Studio 2015\Projects\DocumentGenerator\DocumentGenerator\Controllers\HomeController.cs Line: 307

Stack Trace:

[ArgumentException: Font 'Ubuntu' cannot be found.]

System.Drawing.FontFamily.CreateFontFamily(String name, FontCollection fontCollection) +1123173
System.Drawing.FontFamily..ctor(String name) +11
PdfSharp.Drawing.XFontFamily..ctor(String name) +92
MigraDoc.Rendering.FontHandler.GetDescent(XFont font) +129
MigraDoc.Rendering.ParagraphRenderer.CalcVerticalInfo(XFont font) +154
MigraDoc.Rendering.ParagraphRenderer.InitFormat(Area area, FormatInfo previousFormatInfo) +392
MigraDoc.Rendering.ParagraphRenderer.Format(Area area, FormatInfo previousFormatInfo) +62
MigraDoc.Rendering.TopDownFormatter.FormatOnAreas(XGraphics gfx, Boolean topLevel) +738
MigraDoc.Rendering.FormattedDocument.Format(XGraphics gfx) +647
MigraDoc.Rendering.DocumentRenderer.PrepareDocument() +269
MigraDoc.Rendering.PdfDocumentRenderer.PrepareDocumentRenderer(Boolean prepareCompletely) +119
MigraDoc.Rendering.PdfDocumentRenderer.PrepareRenderPages() +19
MigraDoc.Rendering.PdfDocumentRenderer.RenderDocument() +13
DocumentGenerator.Controllers.HomeController.WriteDocument(Document document, PdfDocumentRenderer renderer) in C:\Users\User\Documents\Visual Studio 2015\Projects\DocumentGenerator\DocumentGenerator\Controllers\HomeController.cs:307
DocumentGenerator.Controllers.HomeController.DemoProjectMain() in C:\Users\User\Documents\Visual Studio 2015\Projects\DocumentGenerator\DocumentGenerator\Controllers\HomeController.cs:165
DocumentGenerator.Controllers.HomeController.CreateDocument(CreateDocumentViewModel model, String command) in C:\Users\User\Documents\Visual Studio 015\Projects\DocumentGenerator\DocumentGenerator\Controllers\HomeController.cs:56
lambda_method(Closure , ControllerBase , Object[] ) +146
System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +14
System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +157
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +27
System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState) +22
System.Web.Mvc.Async.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult) +29
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +32
System.Web.Mvc.Async.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3d() +50
System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +225
System.Web.Mvc.Async.<>c__DisplayClass33.<BeginInvokeActionMethodWithFilters>b__32(IAsyncResult asyncResult) +10
System.Web.Mvc.Async.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult) +10
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +34
System.Web.Mvc.Async.<>c__DisplayClass2b.<BeginInvokeAction>b__1c() +26
System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__1e(IAsyncResult asyncResult) +100
System.Web.Mvc.Async.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult) +10
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +27
System.Web.Mvc.Controller.<BeginExecuteCore>b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) +13
System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +29
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +36
System.Web.Mvc.Controller.<BeginExecute>b__15(IAsyncResult asyncResult, Controller controller) +12
System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +22
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +26
System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +10
System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +21
System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +29
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +28  
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9723757
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.6.79.0

Railroad answered 22/9, 2015 at 20:26 Comment(2)
The error occurs in line 307 renderer.RenderDocument();. The HomeController source you are showing contains the line pdfRenderer.RenderDocument();. Maybe the code you are running is not the code you want to run - at least it's not the code you are showing here. Makes it difficult to locate the error.Easel
Hi @ThomasH - you are right, I'm sorry about that. I actually moved the bit of code I added to the example to a separate WriteDocument method to isolate it more clearly. I have updated the code showing this, you can hopefully see where Line 307: renderer.RenderDocument(); is now in that method.Railroad
E
4

You are using the GDI build of MigraDoc. The error message comes from GDI+.

My sample code was tested with the WPF build. Please try the WPF build on your server.

AFAIK you still have to use the XPrivateFontCollection with the GDI build. If you want to stick to the GDI build on your server, remove the IFontResolver and use the XPrivateFontCollection.

Easel answered 22/9, 2015 at 21:4 Comment(4)
Thanks Thomas! I will try the WPF build, although I chose GDI because according to pdfsharp.net/NuGetPackage_PDFsharp-MigraDoc-wpf.ashx GDI should be used in ASP.NET applications. If I can't get it working I will look into using XPrivateFontCollection. I will let you know how this goes tonight.Railroad
We will check why the Font Resolver does not work with the GDI build. But switching to WPF is usually "painless" and worth a try. If it works, that will be a quick and easy solution.Easel
PDFsharpTeam and @ThomasH, thank you for your direct support, you were right it was a quick and easy solution! I swapped the GDI build package for the WPF build package and the FontResolver works perfectly! You might want to change the documentation I linked to, to recommend the WPF build for ASP.NET instead of the GDI build. Thanks again!!Railroad
I spent many, many hours trying to get this working and changing from GDI to WPF sorted me straight away...thank youWoodbine

© 2022 - 2024 — McMap. All rights reserved.