Skip to content

Commit

Permalink
Fix opening sf::Font from non-ASCII path
Browse files Browse the repository at this point in the history
  • Loading branch information
FRex authored and ChrisThrasher committed Feb 7, 2025
1 parent 0eb92fc commit 73aa7a6
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 150 deletions.
18 changes: 8 additions & 10 deletions include/SFML/Graphics/Font.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,14 @@
#include <filesystem>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

#include <cstddef>
#include <cstdint>


#ifdef SFML_SYSTEM_ANDROID
namespace sf::priv
{
class ResourceStream;
}
#endif

namespace sf
{
class InputStream;
Expand Down Expand Up @@ -399,6 +393,12 @@ class SFML_GRAPHICS_API Font
////////////////////////////////////////////////////////////
void cleanup();

////////////////////////////////////////////////////////////
/// \brief Load from stream and print errors with custom message
///
////////////////////////////////////////////////////////////
[[nodiscard]] bool openFromStreamImpl(InputStream& stream, std::string_view type);

////////////////////////////////////////////////////////////
/// \brief Find or create the glyphs page corresponding to the given character size
///
Expand Down Expand Up @@ -457,9 +457,7 @@ class SFML_GRAPHICS_API Font
Info m_info; //!< Information about the font
mutable PageTable m_pages; //!< Table containing the glyphs pages by character size
mutable std::vector<std::uint8_t> m_pixelBuffer; //!< Pixel buffer holding a glyph's pixels before being written to the texture
#ifdef SFML_SYSTEM_ANDROID
std::shared_ptr<priv::ResourceStream> m_stream; //!< Asset file streamer (if loaded from file)
#endif
std::shared_ptr<InputStream> m_stream; //!< Stream for openFromFile and openFromMemory
};

} // namespace sf
Expand Down
241 changes: 102 additions & 139 deletions src/SFML/Graphics/Font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
#endif
#include <SFML/System/Err.hpp>
#include <SFML/System/Exception.hpp>
#include <SFML/System/InputStream.hpp>
#include <SFML/System/FileInputStream.hpp>
#include <SFML/System/MemoryInputStream.hpp>
#include <SFML/System/Utils.hpp>

#include <ft2build.h>
Expand Down Expand Up @@ -125,83 +126,63 @@ struct Font::FontHandles
Font::Font(const std::filesystem::path& filename)
{
if (!openFromFile(filename))
throw sf::Exception("Failed to open font from file");
throw Exception("Failed to open font from file");
}


////////////////////////////////////////////////////////////
Font::Font(const void* data, std::size_t sizeInBytes)
{
if (!openFromMemory(data, sizeInBytes))
throw sf::Exception("Failed to open font from memory");
throw Exception("Failed to open font from memory");
}


////////////////////////////////////////////////////////////
Font::Font(InputStream& stream)
{
if (!openFromStream(stream))
throw sf::Exception("Failed to open font from stream");
throw Exception("Failed to open font from stream");
}


////////////////////////////////////////////////////////////
bool Font::openFromFile(const std::filesystem::path& filename)
{
#ifndef SFML_SYSTEM_ANDROID
using namespace std::string_view_literals;

// Cleanup the previous resources
cleanup();

auto fontHandles = std::make_shared<FontHandles>();

// Initialize FreeType
// Note: we initialize FreeType for every font instance in order to avoid having a single
// global manager that would create a lot of issues regarding creation and destruction order.
if (FT_Init_FreeType(&fontHandles->library) != 0)
{
err() << "Failed to load font (failed to initialize FreeType)\n" << formatDebugPathInfo(filename) << std::endl;
return false;
}

// Load the new font face from the specified file
FT_Face face = nullptr;
if (FT_New_Face(fontHandles->library, filename.string().c_str(), 0, &face) != 0)
{
err() << "Failed to load font (failed to create the font face)\n" << formatDebugPathInfo(filename) << std::endl;
return false;
}
fontHandles->face = face;

// Load the stroker that will be used to outline the font
if (FT_Stroker_New(fontHandles->library, &fontHandles->stroker) != 0)
{
err() << "Failed to load font (failed to create the stroker)\n" << formatDebugPathInfo(filename) << std::endl;
return false;
}
#ifndef SFML_SYSTEM_ANDROID

// Select the unicode character map
if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0)
// Create the input stream and open the file
const auto stream = std::make_shared<FileInputStream>();
const auto type = "file"sv;
if (!stream->open(filename))
{
err() << "Failed to load font (failed to set the Unicode character set)\n"
err() << "Failed to load font (failed to open file): " << std::strerror(errno) << '\n'
<< formatDebugPathInfo(filename) << std::endl;
return false;
}

// Store the loaded font handles
m_fontHandles = std::move(fontHandles);

// Store the font information
m_info.family = face->family_name ? face->family_name : std::string();

return true;

#else

m_stream = std::make_shared<priv::ResourceStream>(filename);
return openFromStream(*m_stream);
const auto stream = std::make_shared<priv::ResourceStream>(filename);
const auto type = "Android resource stream"sv;

#endif

// Open the font, and if succesful save the stream to keep it alive
if (openFromStreamImpl(*stream, type))
{
m_stream = stream;
return true;
}

// If loading failed, print filename (after the error message already printed in openFromStreamImpl)
err() << formatDebugPathInfo(filename) << std::endl;
return false;
}


Expand All @@ -211,122 +192,38 @@ bool Font::openFromMemory(const void* data, std::size_t sizeInBytes)
// Cleanup the previous resources
cleanup();

auto fontHandles = std::make_shared<FontHandles>();

// Initialize FreeType
// Note: we initialize FreeType for every font instance in order to avoid having a single
// global manager that would create a lot of issues regarding creation and destruction order.
if (FT_Init_FreeType(&fontHandles->library) != 0)
{
err() << "Failed to load font from memory (failed to initialize FreeType)" << std::endl;
return false;
}

// Load the new font face from the specified file
FT_Face face = nullptr;
if (FT_New_Memory_Face(fontHandles->library,
reinterpret_cast<const FT_Byte*>(data),
static_cast<FT_Long>(sizeInBytes),
0,
&face) != 0)
if (!data)
{
err() << "Failed to load font from memory (failed to create the font face)" << std::endl;
err() << "Failed to load font from memory - data pointer is nullptr" << std::endl;
return false;
}
fontHandles->face = face;

// Load the stroker that will be used to outline the font
if (FT_Stroker_New(fontHandles->library, &fontHandles->stroker) != 0)
{
err() << "Failed to load font from memory (failed to create the stroker)" << std::endl;
return false;
}
// Create memroy stream, the memory is owned by the user
const auto memorystream = std::make_shared<MemoryInputStream>(data, sizeInBytes);

// Select the Unicode character map
if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0)
// Open the font, and if succesful save the stream to keep it alive
if (openFromStreamImpl(*memorystream, "memory"))
{
err() << "Failed to load font from memory (failed to set the Unicode character set)" << std::endl;
return false;
m_stream = memorystream;
return true;
}

// Store the loaded font handles
m_fontHandles = std::move(fontHandles);

// Store the font information
m_info.family = face->family_name ? face->family_name : std::string();

return true;
return false;
}


////////////////////////////////////////////////////////////
bool Font::openFromStream(InputStream& stream)
{
// Cleanup the previous resources
cleanup();

auto fontHandles = std::make_shared<FontHandles>();

// Initialize FreeType
// Note: we initialize FreeType for every font instance in order to avoid having a single
// global manager that would create a lot of issues regarding creation and destruction order.
if (FT_Init_FreeType(&fontHandles->library) != 0)
{
err() << "Failed to load font from stream (failed to initialize FreeType)" << std::endl;
return false;
}

// Make sure that the stream's reading position is at the beginning
if (!stream.seek(0).has_value())
{
err() << "Failed to seek font stream" << std::endl;
return false;
}

// Prepare a wrapper for our stream, that we'll pass to FreeType callbacks
fontHandles->streamRec.base = nullptr;
fontHandles->streamRec.size = static_cast<unsigned long>(stream.getSize().value());
fontHandles->streamRec.pos = 0;
fontHandles->streamRec.descriptor.pointer = &stream;
fontHandles->streamRec.read = &read;
fontHandles->streamRec.close = &close;

// Setup the FreeType callbacks that will read our stream
FT_Open_Args args;
args.flags = FT_OPEN_STREAM;
args.stream = &fontHandles->streamRec;
args.driver = nullptr;

// Load the new font face from the specified stream
FT_Face face = nullptr;
if (FT_Open_Face(fontHandles->library, &args, 0, &face) != 0)
{
err() << "Failed to load font from stream (failed to create the font face)" << std::endl;
return false;
}
fontHandles->face = face;

// Load the stroker that will be used to outline the font
if (FT_Stroker_New(fontHandles->library, &fontHandles->stroker) != 0)
{
err() << "Failed to load font from stream (failed to create the stroker)" << std::endl;
return false;
}

// Select the Unicode character map
if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0)
{
err() << "Failed to load font from stream (failed to set the Unicode character set)" << std::endl;
return false;
}

// Store the loaded font handles
m_fontHandles = std::move(fontHandles);

// Store the font information
m_info.family = face->family_name ? face->family_name : std::string();

return true;
// Open the font, do not save the stream in m_stream, its owned by the caller
return openFromStreamImpl(stream, "stream");
}


Expand Down Expand Up @@ -492,6 +389,72 @@ void Font::cleanup()
// Reset members
m_pages.clear();
std::vector<std::uint8_t>().swap(m_pixelBuffer);

// Drop the file stream if we held one due to openFromFile or openFromMemory
m_stream.reset();
}

////////////////////////////////////////////////////////////
bool Font::openFromStreamImpl(InputStream& stream, std::string_view type)
{
// Cleanup the previous resources
cleanup();

auto fontHandles = std::make_shared<FontHandles>();

// Initialize FreeType
// Note: we initialize FreeType for every font instance in order to avoid having a single
// global manager that would create a lot of issues regarding creation and destruction order.
if (FT_Init_FreeType(&fontHandles->library) != 0)
{
err() << "Failed to load font from " << type << " (failed to initialize FreeType)" << std::endl;
return false;
}

// Prepare a wrapper for our stream, that we'll pass to FreeType callbacks
fontHandles->streamRec.base = nullptr;
fontHandles->streamRec.size = static_cast<unsigned long>(stream.getSize().value());
fontHandles->streamRec.pos = 0;
fontHandles->streamRec.descriptor.pointer = &stream;
fontHandles->streamRec.read = &read;
fontHandles->streamRec.close = &close;

// Setup the FreeType callbacks that will read our stream
FT_Open_Args args;
args.flags = FT_OPEN_STREAM;
args.stream = &fontHandles->streamRec;
args.driver = nullptr;

// Load the new font face from the specified stream
FT_Face face = nullptr;
if (FT_Open_Face(fontHandles->library, &args, 0, &face) != 0)
{
err() << "Failed to load font from " << type << " (failed to create the font face)" << std::endl;
return false;
}
fontHandles->face = face;

// Load the stroker that will be used to outline the font
if (FT_Stroker_New(fontHandles->library, &fontHandles->stroker) != 0)
{
err() << "Failed to load font from " << type << " (failed to create the stroker)" << std::endl;
return false;
}

// Select the Unicode character map
if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0)
{
err() << "Failed to load font from " << type << " (failed to set the Unicode character set)" << std::endl;
return false;
}

// Store the loaded font handles
m_fontHandles = std::move(fontHandles);

// Store the font information
m_info.family = face->family_name ? face->family_name : std::string();

return true;
}


Expand Down
7 changes: 6 additions & 1 deletion test/Graphics/Font.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
#include <SFML/System/FileInputStream.hpp>

#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>

#include <GraphicsUtil.hpp>
#include <WindowUtil.hpp>
#include <fstream>
#include <type_traits>

TEST_CASE("[Graphics] sf::Font", runDisplayTests())
Expand Down Expand Up @@ -146,7 +146,12 @@ TEST_CASE("[Graphics] sf::Font", runDisplayTests())

SECTION("Successful load")
{
const std::u32string filenameSuffix = GENERATE(U"", U"", U"-🐌");
const std::filesystem::path filename = U"Graphics/tuffy" + filenameSuffix + U".ttf";
INFO("Filename: " << reinterpret_cast<const char*>(filename.u8string().c_str()));

REQUIRE(font.openFromFile("Graphics/tuffy.ttf"));

CHECK(font.getInfo().family == "Tuffy");
const auto& glyph = font.getGlyph(0x45, 16, false);
CHECK(glyph.advance == 9);
Expand Down
Binary file added test/Graphics/tuffy-ń.ttf
Binary file not shown.
Binary file added test/Graphics/tuffy-🐌.ttf
Binary file not shown.

0 comments on commit 73aa7a6

Please sign in to comment.