4

I've load the MemoryStream to a PrivateFontCollection and print the Font-Family count.

I've done these process for 10 times and I want the same output for every iteration. I want correct output for two iterations and sometimes the first iteration is also going wrong. I can't have a consistent output.

Provide me a solution to have a consistent output using PrivateFontCollection.
Note: Fonts folder contains 5 different fonts.

private static void Work()
{
    string fontPath = @"D:\fonts";
    PrivateFontCollection fontCollection = null;
    for (int i = 1; i < 11; i++)
    {
        var fileList = Directory.GetFiles(fontPath, "*.ttf", SearchOption.TopDirectoryOnly);
        fontCollection = SafeLoadFontFamily(fileList);
        Console.WriteLine(i+" Iteration and families count:"+fontCollection.Families.Length);
        fontCollection.Dispose();
    }
    Console.ReadKey();
}
private static PrivateFontCollection SafeLoadFontFamily(IEnumerable<string> fontList)
{
    if (fontList == null) return null;
    var fontCollection = new PrivateFontCollection();

    foreach (string fontFile in fontList)
    {
        if (!File.Exists(fontFile)) continue;
        byte[] fontBytes = File.ReadAllBytes(fontFile);
        var fontData = Marshal.AllocCoTaskMem(fontBytes.Length);
        Marshal.Copy(fontBytes, 0, fontData, fontBytes.Length);
        fontCollection.AddMemoryFont(fontData, fontBytes.Length);
    }
    return fontCollection;
}

Expected output for 10 times:
1 Iteration and families count:5
2 Iteration and families count:5
3 Iteration and families count:5
4 Iteration and families count:5
5 Iteration and families count:5
6 Iteration and families count:5
7 Iteration and families count:5
8 Iteration and families count:5
9 Iteration and families count:5
10 Iteration and families count:5

Actual output:[ inconsistent output]
1 Iteration and families count:5
2 Iteration and families count:5
3 Iteration and families count:5
4 Iteration and families count:5
5 Iteration and families count:4
6 Iteration and families count:3
7 Iteration and families count:3
8 Iteration and families count:4
9 Iteration and families count:4
10 Iteration and families count:4

Madhan
  • 41
  • 6

1 Answers1

2

If all you want to do is to print the name of the Font Family of each Font file stored in a directory, you can simplify your code with something like this.

It orders the Font files by name before printing the Family name.

string fontsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "fonts");
var fontFiles = Directory.GetFiles(fontsPath).OrderBy(s => s).ToList();

fontFiles.ForEach(f => {
    using (var fontCollection = new PrivateFontCollection())
    {
        fontCollection.AddFontFile(f);
        Console.WriteLine(fontCollection.Families[0].Name);
    };
});

If you instead want to preserve the list of the Fonts Family names (for other uses), add each Family name to a List<string> (as a Field, here):

//  As a field
List<string> fontNames = new List<string>();


 // Inside a method
var fontFiles = Directory.GetFiles(fontsPath).ToList();
fontFiles.ForEach(f => {
    using (var fontCollection = new PrivateFontCollection())
    {
        fontCollection.AddFontFile(f);
        fontNames.Add(fontCollection.Families[0].Name);
    };
});
fontNames = fontNames.OrderBy(s => s).ToList();
fontNames.ForEach(familyName => Console.WriteLine(familyName));

Using PrivateFontCollection.AddMemoryFont(). This method can be used for both Font Files and font data from a Font added as a Project Resource (it's just a byte array).

Important: The PrivateFontCollection that these methods return MUST be disposed of in the Form.FormClosing or Form.FormClosed (or where the application's termination is handled).

Call these methods passing a collection of file paths:

// Field/Class object
PrivateFontCollection fontCollection = null;
// (...)
// Somewhere else
string fontsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "fonts");
var fontFiles = Directory.GetFiles(fontsPath);

fontCollection = UnsafeLoadFontFamily(fontFiles);
// Or...
fontCollection = SafeLoadFontFamily(fontFiles);

fontCollection.Families.OrderBy(f => f.Name).ToList().ForEach(font =>
{
    Console.WriteLine(font.GetName(0));
});

Using unsafe mode and a byte* pointer:
(Unsafe code must be enabled in the Project's Properties -> Build panel)

private unsafe PrivateFontCollection UnsafeLoadFontFamily(IEnumerable<string> fontList)
{
    if (fontList.Length == 0) return null;
    var fontCollection = new PrivateFontCollection();

    foreach (string fontFile in fontList)
    {
        if (!File.Exists(fontFile)) continue;
        byte[] fontData = File.ReadAllBytes(fontFile);
        fixed (byte* fontPtr = fontData)
        {
            fontCollection.AddMemoryFont(new IntPtr(fontPtr), fontData.Length);
        }
    }
    return fontCollection;
}

Using Marshal.AllocCoTaskMem() and Marshal.Copy().
Do not call Marshal.FreeCoTaskMem() here. Font curruption may occur. Call PrivateFontCollection.Dispose() instead, as already mentioned.

private PrivateFontCollection SafeLoadFontFamily(IEnumerable<string> fontList)
{
    if (fontList == null) return null;
    var fontCollection = new PrivateFontCollection();

    foreach (string fontFile in fontList)
    {
        if (!File.Exists(fontFile)) continue;
        byte[] fontBytes = File.ReadAllBytes(fontFile);
        var fontData = Marshal.AllocCoTaskMem(fontBytes.Length);
        Marshal.Copy(fontBytes, 0, fontData, fontBytes.Length);
        fontCollection.AddMemoryFont(fontData, fontBytes.Length);
    }
    return fontCollection;
}

Print the Font Collection content:

string fontPath = [The Fonts Path];
PrivateFontCollection fontCollection = null;
for (int i = 0; i < 5; i++) {
    var fileList = Directory.GetFiles(fontPath, "*.ttf", SearchOption.TopDirectoryOnly);
    fontCollection = SafeLoadFontFamily(fileList);
    fontCollection.Families.ToList().ForEach(ff => Console.WriteLine(ff.Name));
    fontCollection.Dispose();
}

System.Windows.Media provides the Fonts.GetFontFamilies() method, just in case.

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Thanks for your suggestions but issue occurs when i add the memory stream to the private font collection using addmemoryfont api. I have use addfontfile api and it's working fine but I need to use addmemoryfont api. – Madhan Jul 28 '19 at 02:50
  • Why would you need `AddMemoryFont`? In your sample code, you're just reading the FontFamily name and then you destroy the PrivateFontCollection you just created. Using `AddMemoryFont`for this is just a waste. Can you explain what you're actually trying to achieve with this code? – Jimi Jul 28 '19 at 03:38
  • I have word document with some fonts are embedded in it. So, I read embedded font value and convert to the memory stream. So, that I am using AddMemoryFont api and using font famiy font to render the text. – Madhan Jul 28 '19 at 16:01
  • But, you're using `string[] files = Directory.GetFiles("fonts");`. What are you reading here? In your question Word Documents are never mentioned. You said: *I've converted the Font to a MemoryStream and load the MemoryStream to a PrivateFontCollection and print the Font-Family name of the Font*. If you have a Word Document, you have to decompress the Word File, extract the Font files. Then you can use the procedure I've shown here. Please, update your question adding all the details needed to understand what files you're reading and what you're trying to accomplish, because it's very unclear – Jimi Jul 28 '19 at 16:08
  • I have posted simple sample to reproduce the issue and my requirement is to process the memory stream and find out the font family name. So, that i am using the `AddMemoryFont` api and Please provide the solution for this type of issue. – Madhan Jul 29 '19 at 05:33
  • You haven't provided any of the informations requested. Your edit just adds a list of names. I need to know 1) what come out of this: `string[] files = Directory.GetFiles("fonts");`. Font file names (e.g., `TimesNewRoman.ttf`) or something else? 2) You need to explain how a Word Document is related with all this. 3) Why do you think you need to use `AddMemoryFont()`, when it appears you're dealing with font files. Without these informations, I cannot give you a solution, because I don't know what objects I'm dealing with, thus how the output should be generated. – Jimi Jul 29 '19 at 11:06
  • Well, I've added a couple of methods that use `PrivateFontCollection.AddMemoryFont()`. Adapt them to whatever use you have in mind. – Jimi Jul 29 '19 at 21:10
  • Sorry for the late reply. I have requirement to add multiple memorystream to the `PrivateFontCollection` and so that i am using `AddMemoryFont()` methods, then create font using `privateFontCollection.Families`. Whole process is repeated more number time, we have unreliable `privateFontCollection.Families` count. The same issue is reported in the MSDN forum and query is still unresolved in this [MSDN link](https://social.msdn.microsoft.com/Forums/vstudio/en-US/97ef0e66-2cfc-4d17-b7f7-ac0a0b3970b6/adding-in-memory-fonts-to-a-private-font-collection-doesnt-work-reliably?forum=netfxbcl). – Madhan Jan 24 '20 at 05:44
  • A MemoryStream is an array of bytes. There's no difference (at all) from `byte[] fontBytes = [MemoryStream].ToArray()` and `byte[] fontBytes = File.ReadAllBytes(fontFile);`. If the MemoryStream contains the Font file data. Thus, mentioning a MemoryStream doesn't add anything to the question if you don't specify what the MemoryStream, or any other structure/class/object contains (as alerady asked). – Jimi Jan 24 '20 at 05:49
  • There is no problem with `byte[]` or `MemoryStream`. i am having 5 different `byte[]` and add to the `PrivateFontCollection` using `AddMemoryFont` method. whole process is repeated for 5 iteration. For 1st iteration i have `privateFontCollection.Families.count` is 5 and 3rd or 4th iteration its font family count is different and i don't get reliable font families count. – Madhan Jan 24 '20 at 06:20
  • You didn't use the `SafeLoadFontFamily` method I posted as it is (don't change aanything). Iterate that method call any time you want, you'll always get the same Font Family names in the same order all the Time. **Do not** call `Marshal.FreeCoTaskMem()` here (as mentioned) and **do not** dispose of the PrivateFontCollection for each Font. Call `SafeLoadFontFamily()`, print to the Console what's inside, dispose of it and call `SafeLoadFontFamily()` again to refill the collection. I've added this procedure to a new edit. You can also use `UnsafeLoadFontFamily` without marshaling. – Jimi Jan 24 '20 at 07:19
  • Thanks for your suggestion but i am still facing the issue and i have just changed the number of iteration `for (int i = 1; i < 11; i++)` and the issue reproduced, please print the font families count `Console.WriteLine(i+" Iteration and families count:"+fontCollection.Families.Length);`. Note: Result is inconsistent. – Madhan Jan 24 '20 at 07:43
  • I don't know what you're doing wrong now, but this is the [Proof of Concept](https://imgur.com/D9X4Rqr), using the same exact method I've posted here. Update your question, showing the code you're using now (including how you fill the source file list). – Jimi Jan 24 '20 at 07:55
  • I have updated my code and please use that code to reproduce the issue. – Madhan Jan 24 '20 at 08:54
  • That's my code and, as you could see in the animation, I cannot reproduce your problem. So, what Framework, C# and Visual Studio versions are you using? Is this a stand-alone Console application or is it part of a Solution (with different Projects and Framework versions, maybe)? You're not calling this code from a secondary thread, right? Do you have the `[STAThread]` attribute added to your Main? Can you post somewhere the list of Fonts you're enumerating (and the Project, maybe)? – Jimi Jan 24 '20 at 09:08
  • Try also the `unsafe` method. You need to enable unsafe code in the `Project->Properties->Build` panel. – Jimi Jan 24 '20 at 09:14
  • let discuss in [chat room](https://chat.stackoverflow.com/rooms/206567/discussion-between-madhan-and-jimi) – Madhan Jan 24 '20 at 09:27