Typeface Serialisation


#1

Hi,

I have a custom .ttf font I’d like to embed in my application, so that I don’t have to install it on destination computers…

I’ve looked a bit at the Typeface class for doing this.

Is the idea to write a chunk of code that loads the typeface on my machine, serialise it out, then include that serialised file as binary in my app, then in the future reload it from that binary?

Or am I confused…?

thanks


#2

Ok, so I think I’ve figured that out (sorry!)…

On to my next question… I’m a bit confused as to how to use my freshly loaded typeface.

So, let’s say i have a Typeface loaded from someFont.ttf’s mob stream.

If i wanted to set that typeface as the default sansSerif font, how would I go about that?
The Font::setDefaultSansSerifFontName() method takes a name… so i tried setting my typeface’s name to “Test123” and then calling Font::setDefaultSansSerifFontName(“Test123”, false, false) …

The application is using a different font now, but not my specified typeface…?

Thanks


#3

what i usually do is:

[code]int main (int argc, char* argv[])
{
printf ("\n\n--------------------------------\n Font Serialiser by Niall Moody\n--------------------------------\n\n");

if (argc != 3)
{
    printf (" Usage: FontSerialiser <filename> <fontname>\n\n");
    printf (" FontSerialiser will turn a font into a compressed binary file.\n\n\n");

    return 0;
}

// because we're not using the proper application startup procedure, we need to call
// this explicitly here to initialise some of the time-related stuff..
SystemStats::initialiseStats();

const File destDirectory (File::getCurrentWorkingDirectory().getChildFile (argv[1]));

String FontName(argv[2]);

OutputStream *fontfile = destDirectory.createOutputStream();

if (fontfile == 0)
{
    String error ("\nError : Couldn't open ");
    error << destDirectory.getFullPathName() << " for writing.\n\n";
    printf ((const char*) error);
    return 0;
}

Typeface font = 0;
font = new Typeface(FontName, false, false);
if(!font)
{
String error ("\nError : Where’s the font?\n\n");
printf ((const char
) error);
return 0;
}

//Here's the important part.

for(int i=0;i<256;++i)
font->getGlyph(i);

String op("\nWrote font “);
op << FontName << " to file " << destDirectory.getFullPathName() << " successfully.\n\n”;
printf((const char *)op);

font->serialise(*fontfile);

delete font;
delete fontfile;

printf("\n(You might want to use Binary Builder to turn this file into a c++ file now)\n\n ");

return 0;

}
[/code]

then embed your file with the BinaryBuilder and reload it back with:

Font* myNewFont = 0; MemoryInputStream fontStream (Resource::serialized_font, Resource::serialized_font_size, false); Typeface* typeFace = new Typeface (fontStream); myNewFont = new Font (*typeFace); myNewFont->setHeight (10.0f); delete typeFace;

then you can just use your font as you like :slight_smile:


#4

Ok, thanks for the help… But i’m still having troubles.

It seems my problems aren’t with the BinaryBuilder end of things (I’m pretty well-versed with that), but more with the actual serialisation of fonts.

I’m working on a Mac, so it’s quite possible this is a Mac problem.

Ok, I have font (“7 Segment”) sitting in my /Library/Fonts folder. I can use it, no problem, as long as it sits in there.

Surely I’m missing something obvious… ?

Here’s some simplified code that hopefully explains my problem:


// this code sits inside my application's initialise() method... 

File fontFile(File("~/SomeFont.mob"));
	
#ifdef SAVE_EM
	// Save the font as a mob...
	// at this point, the font (7 Segment) exists in /Library/Fonts
	Typeface someTypeface(T("7 Segment"), false, false);
	FileOutputStream outStream(fontFile);
	for(int i = 0; i < 256; i++)
		someTypeface.getGlyph(i);
	someTypeface.serialise(outStream);
	outStream.flush();
	
	// at this point, setting the default font name to someTypeface actually WORKS.
	// (probably because the file exists in /Library/Fonts!)
	Font::setDefaultSansSerifFontName(someTypeface.getName());
	
#else
	// Load the font from a mob...
	// At this point, the font (7 Segment) is no longer in /Library/Fonts
	jassert(fontFile.exists());
	FileInputStream inStream(fontFile);
	Typeface someTypeface(inStream);
	myCustomFonts.add(new Font(someTypeface));
	
	// this correctly prints out "7 Segment"
	DBG_PRINTF((T("Font name: %s"), (const char *)myCustomFonts[0]->getTypefaceName()));
		
	// If we leave the file in /Library/Fonts, this works... but, since that's not the 
	// goal here, we remove it and this doesn't seem to use our newly loaded font.
	// Further, if I refer directly to myCustomFont in my drawing code, it still falls back on some other system font...
	// (not even the default JUCE system font)
	Font::setDefaultSansSerifFontName(myCustomFonts[0]->getTypefaceName());
#endif

#5

if you use Font::setDefaultSansSerifFontName you are actually referring to a font already present in your system font folder.
If you want to use your font you should write a new look and feel class and use your custom font where you need it.
Eventually you could ask jules of making a new static function Font::setDefaultSansSerifFont


#6

[quote=“kraken”]if you use Font::setDefaultSansSerifFontName you are actually referring to a font already present in your system font folder.
If you want to use your font you should write a new look and feel class and use your custom font where you need it./quote]

Ok, while that isn’t optimal, i can live with it (can you imagine re-implementing all text-rendering in your look and feel just to use a font not found in the system folder?)

However, this doesn’t explain why when i set the font directly on the canvas, it doesn’t use it…?

Font *pFont = pApp->myCustomFonts[0];
pFont->setHeight(getHeight());
g.setFont(*pFont);

#7

it should. you probably doing something nasty


#8

Lol.
Well, maybe… but I can’t see what that would be.
Anyone else have a clue?

It’s pretty simple code, really (pasted above), followed with some simple drawing in my component’s paint routine:

// (yes, this is just a simple example... i'm not really going to be grabbing
// a pointer to the app to get a font!)

// this approach doesn't work:
jassert(pApp->myFont);
pApp->myFont->setHeight(getHeight());
g.setFont(*pApp->myFont); // this doesn't work
	
// but this does... (i thought maybe directly giving the Graphics a
// Font was the problem, but no...)
//g.setFont(Font(T("Times New Roman"), getHeight(), Font::plain));
	
g.drawText(getText(), 0, 0, getWidth(), getHeight(), Justification::centredLeft, false);

#9

Look to this line:

[quote=“bigjim”]

pApp->myFont->setHeight(getHeight()); [/quote]

You’re setting your font height to be the component’s height, not the previous font height.
I guess correct code would have been:

pApp->myFont->setHeight(g.getCurrentFont().getHeight());

#10

[quote=“X-Ryl669”]Look to this line:

[quote=“bigjim”]

pApp->myFont->setHeight(getHeight()); [/quote]

You’re setting your font height to be the component’s height, not the previous font height.
I guess correct code would have been:

pApp->myFont->setHeight(g.getCurrentFont().getHeight()); [/quote]

Nah…
In this unique example, I actually want the font to be the height of the component…
The problem is that it’s not drawing with my font…

Any other thoughts?

I’m about to prepare a win32 partition to give it a whirl there. ech.


#11

Ok…
Problem solved…

I made a Win32 version of my app, serialised the fonts, embedded them as binary in my app…
and all works perfectly now on both platforms (using the embedded binary versions of the fonts).

So, that’s all I need for now – I’m behind schedule, so I have no time to debug why this works on Win and not Mac…

But, Jules, you might want to be aware of this “issue”…

Cheerio!

james.


#12

So it was the mac stuff that didn’t serialise the font properly? (I can believe that, because I always use windows for stuff like that)

Good point about using an embedded font as the default, though. Probably the correct thing to do is to make the look and feel class supply the default fonts. I should look into that and do a bit of tidying up.


#13

[quote=“jules”]So it was the mac stuff that didn’t serialise the font properly? (I can believe that, because I always use windows for stuff like that)

Good point about using an embedded font as the default, though. Probably the correct thing to do is to make the look and feel class supply the default fonts. I should look into that and do a bit of tidying up.[/quote]

Yup – the Mac serialisation stuff seems to be the problem… the code looked fairly high-level though, so I’m a bit surprised (and I’m running Intel).

A default font would be cool, but in this case I think I can get away without.

Thanks!


#14

I found the code provided to serialize a font was easy to use as long as you are sure the font is currently installed in your system. If the typeface is not installed on your system, it still completes successfully but you don’t have a real serialized font in the output file. I added the following lines of code just before the Typeface constructor to verify that the requested font exists before proceeding.

StringArray FontNames = Font::findAllTypefaceNames();

if(!FontNames.contains(FontName))
{
String error ("\nError: The font " + FontName + " does not exist in the system\n");
printf ((const char*) error);
return 0;
}

Here’s the entire program:

[code]// FontBuilder.cpp : Defines the entry point for the console application.
//

#include “stdafx.h”
#include “juce.h”

int main (int argc, char* argv[])
{
printf ("\n\n--------------------------------\n Font Serialiser by Niall Moody\n--------------------------------\n\n");

if (argc != 3)
{
    printf (" Usage: FontSerialiser <filename> <fontname>\n\n");
    printf (" FontSerialiser will turn a font into a compressed binary file.\n\n\n");

    return 0;
}

// because we're not using the proper application startup procedure, we need to call
// this explicitly here to initialise some of the time-related stuff..
SystemStats::initialiseStats();

const File destDirectory (File::getCurrentWorkingDirectory().getChildFile (argv[1]));

String FontName(argv[2]);

OutputStream *fontfile = destDirectory.createOutputStream();

if (fontfile == 0)
{
    String error ("\nError : Couldn't open ");
    error << destDirectory.getFullPathName() << " for writing.\n\n";
    printf ((const char*) error);
    return 0;
}

Typeface *font = 0;

StringArray FontNames = Font::findAllTypefaceNames();

if(!FontNames.contains(FontName))
{
String error ("\nError: The font " + FontName + " does not exist in the system\n");
printf ((const char*) error);
return 0;
}

font = new Typeface(FontName, false, false);

if(!font)
{
String error ("\nError : Where’s the font?\n\n");
printf ((const char*) error);
return 0;
}

//Here's the important part.

for(int i=0;i<256;++i)
font->getGlyph(i);

String op("\nWrote font “);
op << FontName << " to file " << destDirectory.getFullPathName() << " successfully.\n\n”;
printf((const char *)op);

font->serialise(*fontfile);

delete font;
delete fontfile;

printf("\n(You might want to use Binary Builder to turn this file into a c++ file now)\n\n ");

return 0;

}
[/code]


#15

I had to make a few changes to get the FontBuilder code posted above to build and work with the 1.51 release.

Here’s an updated version that works for me (up to the point of drawing a simple text with the embedded font - I have probably missed something for more advanced font-rendering). So anyone looking for a quick copy&pasteable solution for this task can try this, it might work for you as well.

// FontBuilder.cpp : Defines the entry point for the console application.
//

#include <iostream>
#include "juce.h"

int main (int argc, char* argv[])
{
    printf ("\n\n--------------------------------\n Font Serialiser by Niall Moody\n--------------------------------\n\n");

    if (argc != 3)
    {
        printf (" Usage: FontSerialiser <filename> <fontname>\n\n");
        printf (" FontSerialiser will turn a font into a compressed binary file.\n\n\n");

        return 1;
    }

    // because we're not using the proper application startup procedure, we need to call
    // this explicitly here to initialise some of the time-related stuff..
    initialiseJuce_GUI();

    // get file and font name from command line arguments
    const File destFile (File::getCurrentWorkingDirectory().getChildFile (argv[1]));
    String fontName(argv[2]);

    // make sure the destination file can be written to 
    OutputStream *destStream = destFile.createOutputStream();
    if (destStream == 0)
    {
        String error;
        error << "\nError : Couldn't open " << destFile.getFullPathName() << " for writing.\n\n";
        std::cout << error;
        return 2;
    }

    // make sure the font is installed on the current system
    StringArray fontNames = Font::findAllTypefaceNames();
    if(!fontNames.contains(fontName))
    {
        String error ("\nError: The font " + fontName + " does not exist in the system\n");
        std::cout << error;
        return 3;
    }

    // load the font as a system-Typeface
    Font font(fontName, 10, 0);
    if(!Typeface::createSystemTypefaceFor  (font))
    {
        String error ("\nError : Where's the font?\n\n");
        std::cout << error;
        return 4;
    }


    // copy the font-properties to a CustomTypeface 
    CustomTypeface customTypeface;
    customTypeface.setCharacteristics(font.getTypefaceName(), font.getAscent(), 
                                      font.isBold(), font.isItalic(), ' ');
    // Here's the important part: copy all glyphs to a new instance of CustomTypeface
    customTypeface.addGlyphsFromOtherTypeface( *font.getTypeface(), 0, 256);


    // finally write the typeface into the destination file
    customTypeface.writeToStream(*destStream);

    String op;
    op << "\nWrote font " << fontName << " to file " << destFile.getFullPathName() << " successfully.\n\n";
    std::cout << op;

    delete destStream;

    std::cout << "\n(You might want to use Binary Builder to turn this file into a c++ file now)\n\n ";

    // this avoids leaking the LookAndFeel instance
    shutdownJuce_GUI();

    return 0;
}

#16