Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 165 additions & 34 deletions src/freetype/freetype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
#include <vsg/nodes/Group.h>
#include <vsg/state/ShaderStage.h>
#include <vsg/text/Font.h>
#include <vsg/threading/OperationThreads.h>
#include <vsg/utils/CommandLine.h>

#include <ft2build.h>
Expand All @@ -27,6 +28,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
#include <chrono>
#include <iostream>
#include <set>
#include <thread>
#include <unordered_map>
#include <utility>

namespace vsgXchange
{
Expand Down Expand Up @@ -547,12 +551,50 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
init();
if (!_library) return {};

FT_Face face;
FT_Long face_index = 0;
FT_UInt pixel_size = 48;
FT_UInt freetype_pixel_size = pixel_size;
float freetype_pixel_size_scale = float(pixel_size) / (64.0f * float(freetype_pixel_size));

// Windows workaround for no wchar_t support in Freetype, convert vsg::Path's std::wstring to UTF8 std::string
std::string filenameToUse_string = filenameToUse.string();
int error = FT_New_Face(_library, filenameToUse_string.c_str(), face_index, &face);
// create and initialize a new freetype face
auto const createNewFace = [&]{
FT_Face face;
FT_Long face_index = 0;
// Windows workaround for no wchar_t support in Freetype, convert vsg::Path's std::wstring to UTF8 std::string
std::string filenameToUse_string = filenameToUse.string();
if (int error = FT_New_Face(_library, filenameToUse_string.c_str(), face_index, &face); error)
return std::make_pair(error,FT_Face{nullptr});
if (!face->charmap)
{
// A charmap wasn't selected; select one preferring Unicode encoding first, otherwise
// if not found use the first encoding. Note: some fonts like Microsoft's symbol.ttf
// font only contain Apple Roman and Microsoft Symbol encodings (in that order) however
// for some reason the Microsoft Symbol encodings appear to be missing a lot of glyphs
// so selecting the first charmap with Apple Roman encoding, is preferred.
FT_CharMap preferredCharmap = face->charmaps[0];
for (FT_Long c = 0; c < face->num_charmaps; ++c)
{
if (face->charmaps[c]->encoding == FT_ENCODING_UNICODE)
{
preferredCharmap = face->charmaps[c];
break;
}
}
if (int error = FT_Set_Charmap(face, preferredCharmap); error)
{
FT_Done_Face(face);
return std::make_pair(error,FT_Face{nullptr});
}
}
if (int error = FT_Set_Pixel_Sizes(face, freetype_pixel_size, freetype_pixel_size); error)
{
FT_Done_Face(face);
return std::make_pair(error,FT_Face{nullptr});
}

return std::make_pair(FT_Error{FT_Err_Ok},face);
};

auto[error,face] = createNewFace();
if (error == FT_Err_Unknown_File_Format)
{
std::cout << "Warning: FreeType unable to read font file : " << filenameToUse << ", error = " << FT_Err_Unknown_File_Format << std::endl;
Expand All @@ -564,14 +606,6 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
return {};
}

FT_UInt pixel_size = 48;
FT_UInt freetype_pixel_size = pixel_size;
float freetype_pixel_size_scale = float(pixel_size) / (64.0f * float(freetype_pixel_size));

{
error = FT_Set_Pixel_Sizes(face, freetype_pixel_size, freetype_pixel_size);
}

FT_Int32 load_flags = FT_LOAD_NO_BITMAP;
FT_Render_Mode render_mode = FT_RENDER_MODE_NORMAL;

Expand Down Expand Up @@ -737,26 +771,18 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
// initialize charmap to zeros.
for (auto& c : *charmap) c = 0;

for (auto& glyphQuad : sortedGlyphQuads)
{
error = FT_Load_Glyph(face, glyphQuad.glyph_index, load_flags);
if (error) continue;
auto const generateContours = [&](FT_Face faceData, GlyphQuad const& glyphQuad, unsigned int glyphXpos, unsigned int glyphYpos) {
if (FT_Load_Glyph(faceData, glyphQuad.glyph_index, load_flags) != 0)
return;

unsigned int width = glyphQuad.width;
unsigned int height = glyphQuad.height;
auto metrics = face->glyph->metrics;

if ((xpos + width + texel_margin) > atlas->width())
{
// glyph doesn't fit in present row so shift to next row.
xpos = texel_margin;
ypos = ytop;
}
auto metrics = faceData->glyph->metrics;

if (useOutline)
{
Contours contours;
generateOutlines(face->glyph->outline, contours);
generateOutlines(faceData->glyph->outline, contours);

// scale and offset the outline geometry
vsg::vec2 offset(float(metrics.horiBearingX) * freetype_pixel_size_scale, float(metrics.horiBearingY) * freetype_pixel_size_scale);
Expand Down Expand Up @@ -823,7 +849,7 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
int delta = quad_margin - 2;
for (int r = -delta; r < static_cast<int>(height + delta); ++r)
{
std::size_t index = atlas->index(xpos - delta, ypos + r);
std::size_t index = atlas->index(glyphXpos - delta, glyphYpos + r);
for (int c = -delta; c < static_cast<int>(width + delta); ++c)
{
vsg::vec2 v;
Expand Down Expand Up @@ -858,23 +884,23 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
}
else
{
if (face->glyph->format != FT_GLYPH_FORMAT_BITMAP)
if (faceData->glyph->format != FT_GLYPH_FORMAT_BITMAP)
{
error = FT_Render_Glyph(face->glyph, render_mode);
if (error) continue;
if (FT_Render_Glyph(faceData->glyph, render_mode) != 0)
return;
}

if (face->glyph->format != FT_GLYPH_FORMAT_BITMAP) continue;
if (faceData->glyph->format != FT_GLYPH_FORMAT_BITMAP) return;

const FT_Bitmap& bitmap = face->glyph->bitmap;
const FT_Bitmap& bitmap = faceData->glyph->bitmap;

// copy pixels
if (computeSDF)
{
int delta = quad_margin - 2;
for (int r = -delta; r < static_cast<int>(bitmap.rows + delta); ++r)
{
std::size_t index = atlas->index(xpos - delta, ypos + r);
std::size_t index = atlas->index(glyphXpos - delta, glyphYpos + r);
for (int c = -delta; c < static_cast<int>(bitmap.width + delta); ++c)
{
atlas->at(index++) = nearest_edge(bitmap, c, r, quad_margin);
Expand All @@ -886,14 +912,104 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
const unsigned char* ptr = bitmap.buffer;
for (unsigned int r = 0; r < bitmap.rows; ++r)
{
std::size_t index = atlas->index(xpos, ypos + r);
std::size_t index = atlas->index(glyphXpos, glyphYpos + r);
for (unsigned int c = 0; c < bitmap.width; ++c)
{
atlas->at(index++) = *ptr++;
}
}
}
}
};

// setup a thread storage if generating contour generation tasks concurrently
auto operationThreads{options
? options->operationThreads
: vsg::ref_ptr<vsg::OperationThreads>{}};

using ThreadFaceData = std::unordered_map<std::thread::id,FT_Face>;
ThreadFaceData threadFaceData;

if (operationThreads)
{
for (auto const& thread : operationThreads->threads)
{
auto [freetype_error,freetype_face]{createNewFace()};
threadFaceData.emplace(thread.get_id(), freetype_face);
}
}

struct ContourOperation : public vsg::Inherit<vsg::Operation, ContourOperation>
{
using GenerateContours = std::function<void(
FT_Face faceData,
GlyphQuad const& glyphQuad,
unsigned int glyphXpos,
unsigned int glyphYpos)>;

GlyphQuad const& glyphQuad;
unsigned int const glyphXpos;
unsigned int const glyphYpos;
GenerateContours const generateContours;
ThreadFaceData const& threadFaceData;
vsg::ref_ptr<vsg::Latch> const latch;

ContourOperation(
GlyphQuad const& in_glyphQuad,
unsigned int in_glyphXpos,
unsigned int in_glyphYpos,
GenerateContours const& in_generateContours,
ThreadFaceData const& in_threadFaceData,
vsg::ref_ptr<vsg::Latch> in_latch)
: glyphQuad(in_glyphQuad)
, glyphXpos(in_glyphXpos)
, glyphYpos(in_glyphYpos)
, generateContours(in_generateContours)
, threadFaceData(in_threadFaceData)
, latch(in_latch)
{
}

void run() override
{
auto const faceData = threadFaceData.at(std::this_thread::get_id());
generateContours(faceData, glyphQuad, glyphXpos, glyphYpos);
if (latch)
latch->count_down();
}
};

auto latch = operationThreads
? vsg::Latch::create(static_cast<int>(sortedGlyphQuads.size()))
: vsg::ref_ptr<vsg::Latch>{};

for (auto& glyphQuad : sortedGlyphQuads)
{
error = FT_Load_Glyph(face, glyphQuad.glyph_index, load_flags);
if (error) continue;

unsigned int width = glyphQuad.width;
unsigned int height = glyphQuad.height;
auto metrics = face->glyph->metrics;

if ((xpos + width + texel_margin) > atlas->width())
{
// glyph doesn't fit in present row so shift to next row.
xpos = texel_margin;
ypos = ytop;
}

if (operationThreads)
{
// submit the task to generate contours concurrently
auto operation = ContourOperation::create(glyphQuad, xpos, ypos, generateContours, threadFaceData, latch);
operationThreads->add(operation);
}
else
{
// generate contours serially
generateContours(face, glyphQuad, xpos, ypos);
}

vsg::vec4 uvrect(
(float(xpos - quad_margin) - 1.0f) / float(atlas->width() - 1), float(ypos + height + quad_margin) / float(atlas->height() - 1),
Expand Down Expand Up @@ -929,5 +1045,20 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
font->glyphMetrics = glyphMetrics;
font->charmap = charmap;

FT_Done_Face(face);

if (operationThreads)
{
// wait for all tasks to finish
latch->wait();

// cleanup thread storage
for (auto& [_,faceData] : threadFaceData)
{
if (faceData)
FT_Done_Face(faceData);
}
}

return font;
}