Cannot load TTF file directly from a ZIP using libzip and FreeType
Asked Answered
F

1

7

I'm trying to load a TTF file directly from a ZIP archive, using libzip and FreeType.

In particular, I'm using the FT_Open_Face function which can read from custom read/close functions (ft_zip_read and ft_zip_close). But although the file is apparently fully read, FT_Open_Face returns FT_Err_Unknown_File_Format. Opening the same file directly from the disk works fine.

I really don't know how to debug this, can anybody help?

The only thing I can imagine to be the issue right now is that my ft_zip_read function does not support seeking, the documentation says:

This function might be called to perform a seek or skip operation with a ‘count’ of 0. A non-zero return value then indicates an error.

And it is indeed called with count 0 a couple of times, but I can't see any way to do a seek with libzip.

unsigned long ft_zip_read(FT_Stream stream, unsigned long offset,
                          unsigned char* buffer, unsigned long count)
{
    zip_file* file = static_cast<zip_file*>(stream->descriptor.pointer);
    return zip_fread(file, buffer + offset, count);
}

void ft_zip_close(FT_Stream stream)
{
    zip_file* file = static_cast<zip_file*>(stream->descriptor.pointer);
    zip_fclose(file);
}

FT_Face load_zipped_face(const std::string& name, unsigned int size,
                         const std::string& zip_path)
{
    FT_Library library;
    FT_Error error = FT_Init_FreeType(&library);
    if (error)
        throw freetype_error_string("Failed to initialise FreeType", error);

    int zip_error;
    zip* zip = zip_open(zip_path.c_str(), 0, &zip_error);
    if (!zip) {
        std::ostringstream message_stream;
        message_stream << "Error loading ZIP (" << zip_path <<  "): "
                       << zip_error;
        throw message_stream.str();
    }

    std::string face_path = name + ".ttf";

    struct zip_stat stat;
    if (zip_stat(zip, face_path.c_str(), 0, &stat))
        throw std::string("zip_stat failed");

    zip_file* file = zip_fopen(zip, face_path.c_str(), 0);
    if (file == 0)
        throw face_path + ": " + strerror(errno);

    FT_StreamDesc descriptor;
    descriptor.pointer = file;

    FT_StreamRec* stream = new FT_StreamRec;
    stream->base = 0;
    stream->size = stat.size;
    stream->descriptor = descriptor;
    stream->read = &ft_zip_read;
    stream->close = &ft_zip_close;

    FT_Open_Args open_args;
    open_args.flags = FT_OPEN_STREAM;
    open_args.stream = stream;

    FT_Face face;
    error = FT_Open_Face(library, &open_args, 0, &face);

    zip_close(zip);

    if (error == FT_Err_Unknown_File_Format)
        throw std::string("Unsupported format");
    else if (error)
        throw freetype_error_string("Unknown error loading font", error);

    error = FT_Set_Pixel_Sizes(face, 0, size);
    if (error)
        throw freetype_error_string("Unable to set pixel sizes", error);

    return face;
}
Fenestella answered 14/1, 2013 at 8:38 Comment(0)
T
0

Seeking the truth

To be able to seek in a compressed datastream you need to decompress the stream up until the point you wish to seek to (some exceptions exist like streams that have reset markers and indexes only have to decompress since the previous marker). This is just plain inefficient if done often (not to mention that you need to write code for it yourself).

Now thinking about it, the only reason you'd want to not load the entire face into memory and have a custom IO for a font file is if it is too big to keep in memory; so that makes seeking mandatory for the stream IO interface of FT.

What can you do?

If the file is small enough: Read it all into memory and use FT_New_Memory_Face to load the face from memory.

If the file is too big so that you don't want the entire face in memory at once, extract the font file to a temporary file and read from that. (Use windows/unix/cstdio temp file API to have a well behaved temporary file)

If neither of the above suits you then you can implement your own caching and seekable zip stream on top of libzip and pass that to FT. This is probably cumbersome and involves some work so I'd go with one of the other two personally :)

Tiu answered 12/8, 2013 at 13:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.