Skip to content

Commit

Permalink
Added: support for word wrapping and text creation using a pre-calcul…
Browse files Browse the repository at this point in the history
…ated text metrics
  • Loading branch information
TheNicker committed Aug 23, 2023
1 parent 3ca5a8e commit f8d92bc
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 50 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ project(FreeTypeWrapper)
option(FREETYPE_WRAPPER_BUILD_SAMPLES "build freetype wrapper sample" ON)
option(FREETYPE_WRAPPER_BUILD_FRIBIDI "build fribidi" ON)

if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")

if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
add_compile_definitions(NOMINMAX _CRT_SECURE_NO_WARNINGS)
endif()

Expand Down
36 changes: 18 additions & 18 deletions FreeTypeWrapper/Include/FreeTypeWrapper/FreeTypeConnector.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ namespace FreeType
LLUtils::Color backgroundColor;
LLUtils::Color outlineColor;
uint32_t outlineWidth;
uint32_t maxWidthPx;
RenderMode renderMode;
uint16_t DPIx;
uint16_t DPIy;
Expand All @@ -58,12 +59,26 @@ namespace FreeType
};


struct TextMesureParams
{
TextCreateParams createParams;
};

struct TextMetrics
{
LLUtils::RectI32 rect;
uint32_t rowHeight;
uint32_t totalRows;
};


class FreeTypeConnector : public LLUtils::Singleton<FreeTypeConnector>
{
friend class LLUtils::Singleton<FreeTypeConnector>;
public:
~FreeTypeConnector();


struct Bitmap
{
uint32_t width;
Expand All @@ -78,23 +93,8 @@ namespace FreeType
using GlyphMappings = std::vector< LLUtils::RectI32>;





struct TextMesureParams
{
TextCreateParams createParams;
};

struct TextMesureResult
{
LLUtils::RectI32 rect;
uint32_t rowHeight;
};



void CreateBitmap(const TextCreateParams& textCreateParams, Bitmap& out_bitmap, GlyphMappings* out_glyphMapping = nullptr);
void CreateBitmap(const TextCreateParams& textCreateParams, Bitmap& out_bitmap, TextMetrics* metrics, GlyphMappings* out_glyphMapping = nullptr);
void MeasureText(const TextMesureParams& measureParams, TextMetrics& out_metrics);

private:

Expand All @@ -104,7 +104,7 @@ namespace FreeType
FreeTypeFont* GetOrCreateFont(const std::wstring& fontPath);
FT_Stroker GetStroker();
static std::string GenerateFreeTypeErrorString(std::string userMessage, FT_Error error);
void MeasureText(const TextMesureParams& measureParams, TextMesureResult& out_result);



private:
Expand Down
11 changes: 6 additions & 5 deletions FreeTypeWrapper/Source/BlitBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ namespace FreeType
std::byte* dstPos = dst.buffer + dst.GetStartOffset();

//Perform range check on target.
if (dst.left + src.width > dst.width ||
dst.top + src.height > dst.height)
{
LL_EXCEPTION(LLUtils::Exception::ErrorCode::LogicError, "Target Out of bounds ");
}
if (dst.left + src.width > dst.width)
LL_EXCEPTION(LLUtils::Exception::ErrorCode::LogicError, "Width overflow");

if (dst.top + src.height > dst.height)
LL_EXCEPTION(LLUtils::Exception::ErrorCode::LogicError, "Height overflow ");


const uint32_t bytesPerLine = src.pixelSizeInbytes * src.width;

Expand Down
77 changes: 52 additions & 25 deletions FreeTypeWrapper/Source/FreeTypeConnector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ namespace FreeType



void FreeTypeConnector::MeasureText(const TextMesureParams& measureParams, TextMesureResult& mesureResult)
void FreeTypeConnector::MeasureText(const TextMesureParams& measureParams, TextMetrics& mesureResult)
{
using namespace std;
FreeTypeFont* font = GetOrCreateFont(measureParams.createParams.fontPath);
Expand Down Expand Up @@ -122,29 +122,49 @@ namespace FreeType
continue;
}


const FT_UInt glyph_index = FT_Get_Char_Index(face, codepoint);

if (FT_Error error = FT_Load_Glyph(
face, /* handle to face object */
glyph_index, /* glyph index */
FT_LOAD_DEFAULT); error != FT_Err_Ok) /* load flags, see below */
FT_LOAD_BITMAP_METRICS_ONLY); error != FT_Err_Ok) /* load flags, see below */
{
LL_EXCEPTION(LLUtils::Exception::ErrorCode::RuntimeError, GenerateFreeTypeErrorString("can not load glyph", error));
}
penX += face->glyph->advance.x >> 6;

const auto advance = face->glyph->advance.x >> 6;

if (measureParams.createParams.maxWidthPx > 0 && penX + advance > static_cast<int>(measureParams.createParams.maxWidthPx))
{
numberOfLines++;
penX = 0;
}

penX += advance;
// When subpixel antialiasing is enabled, bitmap might be renderd at negative coordinates relative to origin.
mesureResult.rect.LeftTop().x = std::min<int>(mesureResult.rect.LeftTop().x, face->glyph->bitmap_left);
mesureResult.rect.RightBottom().x = std::max<int>(mesureResult.rect.RightBottom().x, penX);

}

mesureResult.rect.RightBottom().y = static_cast<int32_t>(rowHeight * numberOfLines);
}

//Add horizontal outline width to the final width, vertical outline width is already factored into rowHeight
mesureResult.rect = mesureResult.rect.Infalte(static_cast<int32_t>(measureParams.createParams.outlineWidth * 2), 0);

//TODO: is it the right way to calculate the extra width created by the anti-aliasing ?
const FT_Render_Mode textRenderMOde = (measureParams.createParams.renderMode == RenderMode::SubpixelAntiAliased ? FT_RENDER_MODE_LCD : FT_RENDER_MODE_NORMAL);
if (textRenderMOde == FT_RENDER_MODE_LCD || textRenderMOde == FT_RENDER_MODE_NORMAL)
{
mesureResult.rect.RightBottom().x += 1;
mesureResult.rect.LeftTop().x -= 1;
}

mesureResult.totalRows = numberOfLines;
mesureResult.rect = mesureResult.rect.Infalte(measureParams.createParams.padding * 2, measureParams.createParams.padding * 2);
mesureResult.rowHeight = static_cast<uint32_t>(rowHeight);


}

FreeTypeFont* FreeTypeConnector::GetOrCreateFont(const std::wstring& fontPath)
Expand Down Expand Up @@ -177,7 +197,11 @@ namespace FreeType



void FreeTypeConnector::CreateBitmap(const TextCreateParams& textCreateParams, Bitmap& out_bitmap,GlyphMappings* out_glyphMapping /*= nullptr*/)
void FreeTypeConnector::CreateBitmap(const TextCreateParams& textCreateParams
, Bitmap& out_bitmap
, TextMetrics* in_metrics//optional
, GlyphMappings* out_glyphMapping /*= nullptr*/
)
{
using namespace std;

Expand All @@ -201,19 +225,15 @@ namespace FreeType

const FT_Render_Mode textRenderMOde = (renderMode == RenderMode::SubpixelAntiAliased ? FT_RENDER_MODE_LCD : FT_RENDER_MODE_NORMAL);

TextMesureResult mesaureResult;

MeasureText(params, mesaureResult);

//TODO: is it the right way to calculate the extra width created by the anti-aliasing ?

if (textRenderMOde == FT_RENDER_MODE_LCD || textRenderMOde == FT_RENDER_MODE_NORMAL)
{
mesaureResult.rect.RightBottom().x += 1;
mesaureResult.rect.LeftTop().x -= 1;
}
TextMetrics metrics;
if (in_metrics == nullptr)
MeasureText(params, metrics);
else
metrics = *in_metrics;


mesaureResult.rect = mesaureResult.rect.Infalte(params.createParams.padding * 2, params.createParams.padding * 2);
auto& mesaureResult = metrics;

const uint32_t destPixelSize = 4;
const uint32_t destRowPitch = mesaureResult.rect.GetWidth() * destPixelSize;
Expand Down Expand Up @@ -264,8 +284,8 @@ namespace FreeType


FT_Face face = font->GetFace();
auto descender = face->size->metrics.descender >> 6;
uint32_t rowHeight = mesaureResult.rowHeight;
const auto descender = face->size->metrics.descender >> 6;
const uint32_t rowHeight = mesaureResult.rowHeight;

vector<FormattedTextEntry> formattedText;
if ((textCreateParams.flags & TextCreateFlags::UseMetaText) == TextCreateFlags::UseMetaText)
Expand Down Expand Up @@ -298,9 +318,18 @@ namespace FreeType
LL_EXCEPTION(LLUtils::Exception::ErrorCode::RuntimeError, GenerateFreeTypeErrorString("can not Load glyph", error));
}

auto baseVerticalPos = rowHeight + penY + descender - OutlineWidth;

//Render outline
const auto advance = face->glyph->advance.x >> 6;

if (textCreateParams.maxWidthPx > 0 && penX + advance + mesaureResult.rect.LeftTop().x > static_cast<int>(textCreateParams.maxWidthPx))
{
penY += rowHeight;
penX = static_cast<int>(-mesaureResult.rect.LeftTop().x);

}

const auto baseVerticalPos = rowHeight + penY + descender - OutlineWidth;

if (renderOutline) // render outline
{
Expand All @@ -309,7 +338,6 @@ namespace FreeType

// 2 * 64 result in 2px outline
FT_Stroker_Set(stroker, static_cast<FT_Fixed>(OutlineWidth * 64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_BEVEL, 0);
FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
FT_Glyph glyph;
FT_Get_Glyph(face->glyph, &glyph);
FT_Glyph_StrokeBorder(&glyph, stroker, false, true);
Expand Down Expand Up @@ -360,16 +388,15 @@ namespace FreeType

dest.left = static_cast<uint32_t>(penX + bitmapGlyph->left);
dest.top = baseVerticalPos - bitmapGlyph->top;
FT_GlyphSlot slot = face->glyph;

const auto advance = slot->advance.x >> 6;
if (out_glyphMapping != nullptr)
{
out_glyphMapping->push_back(LLUtils::RectI32{ { penX, penY } ,
{penX + static_cast<int32_t>(advance), penY + static_cast<int32_t>(rowHeight)} });
}

penX += slot->advance.x >> 6;
penX += advance;

FT_Done_Glyph(glyph);
BlitBox::Blit(dest, source);

Expand Down
2 changes: 1 addition & 1 deletion Test/Test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ void runTest(FreeType::TextCreateParams freetypeParams, TestParams testParams)
using namespace FreeType;
using namespace LLUtils;
FreeTypeConnector::Bitmap textBitmap;
FreeTypeConnector::GetSingleton().CreateBitmap(freetypeParams, textBitmap);
FreeTypeConnector::GetSingleton().CreateBitmap(freetypeParams, textBitmap, nullptr);

auto hash = XXH3_64bits(static_cast<const void*>(textBitmap.buffer.data()), textBitmap.height * textBitmap.rowPitch);

Expand Down

0 comments on commit f8d92bc

Please sign in to comment.