Search code examples
c#linuxwindows.net-core

Cross-platform method to determine text width


At the moment I've got the following method which I'm using to determine the text size in my WPF app, used when generating some Excel reports:

// see: https://stackoverflow.com/questions/18268620/openxml-auto-size-column-width-in-excel
private static double GetTextWidth(string text)
{
    var bmp = new Bitmap(1, 1);
    using var graphics = Graphics.FromImage(bmp);
    graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
    graphics.PageUnit = GraphicsUnit.Pixel;
    using var font = new System.Drawing.Font("Calibri", 10F, FontStyle.Regular, GraphicsUnit.Point);
    var pixelWidth = graphics.MeasureString(text, font).Width;
    //see: https://stackoverflow.com/questions/7716078/formula-to-convert-net-pixels-to-excel-width-in-openxml-format/7902415
    var openXmlWidth = (pixelWidth - 12 + 5) / 7d + 1;
    return openXmlWidth;
}

As you can see from the comments, I'm already taking some hints from other SO questions, namely this one and this one. But, really, the latter isn't as important as the former (the latter is just a bit of math, so not a problem for this case).

I'd like to move my Excel code so it's not "WPF, client-side" but rather server-side, and here I want the server to either be a Windows machine OR a Linux machine, i.e. I need the code to be cross-platform compatible.

Alas, I'm not sure what I can use to measure the pixel-width of text which isn't going to be Windows-dependant...

The problem here is that a lot of the classes from the code-snippet above seem to be Windows-dependant. I.e. the compiler throws a lot of warnings at me, such as:

warning CA1416: This call site is reachable on all platforms. 'Graphics.MeasureString(string?, Font)' is only supported on: 'windows' 6.1 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
warning CA1416: This call site is reachable on all platforms. 'Graphics.TextRenderingHint' is only supported on: 'windows' 6.1 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
warning CA1416: This call site is reachable on all platforms. 'FontStyle.Regular' is only supported on: 'windows' 6.1 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
warning CA1416: This call site is reachable on all platforms. 'GraphicsUnit.Pixel' is only supported on: 'windows' 6.1 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
warning CA1416: This call site is reachable on all platforms. 'GraphicsUnit.Point' is only supported on: 'windows' 6.1 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
warning CA1416: This call site is reachable on all platforms. 'Bitmap' is only supported on: 'windows' 6.1 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
warning CA1416: This call site is reachable on all platforms. 'Graphics.PageUnit' is only supported on: 'windows' 6.1 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
warning CA1416: This call site is reachable on all platforms. 'TextRenderingHint.AntiAlias' is only supported on: 'windows' 6.1 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
warning CA1416: This call site is reachable on all platforms. 'Font' is only supported on: 'windows' 6.1 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
warning CA1416: This call site is reachable on all platforms. 'Graphics.FromImage(Image)' is only supported on: 'windows' 6.1 and later. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

I've no reason not to trust the compiler warning in this case. Plus I've also found this MS document on stopping cross-compatibility of System.Drawing.

Are there any libraries or classes I can use for this? Or is the compiler warning "wrong", and this will work perfectly fine on a Linux server?

PS. BTW - the code is used to calculate if a given text "fits" into an Excel cell of specific width; if it does not then a different text is used. I.e. I don't want to use Excel's "native" truncate-text-so-it-fits logic.


Solution

  • You can measure text with Sixlabors.Fonts.TextMeasurer and it will be cross-platform. The basic implementation may be following (I have omitted the openXML-related corrections). The code tested on windows.

    private static double GetTextWidth(string text)
    {
        var font = GetFont("Calibri",10, FontStyle.Regular);
        var fontSize = TextMeasurer.MeasureSize("your Text", new TextOptions(font));
        return fontSize.Width;
    }
    
    private static Font GetFont(string fontName, float size, FontStyle style)
    {
        var fontPath = GetFontPath(fontName);
        var collection = new FontCollection();
        var family = collection.Add(fontPath);
        var font = family.CreateFont(size, style);
        return font;
    }
    
    
    private static string GetFontPath(string fontName)
    {
        //TODO: implement platform-specific font location code
        //or just bundle a font with your app
        var fontsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
        return Path.Combine(fontsFolder, $"{fontName}.ttf");
    }
    

    Notes:

    • that the measurement result will only match only the Sixlabors.Fonts text renderer and may not match with the system renderer (so may not be usable for your task)
    • the library does not have any built-in way to access system fonts. So, you will need to write the code to locate font file for the current system manually.