Merge pull request #37 from jeremieroy/master

Refactor of fontsdf sample to show a big scrollable, scalable, rotable text
This commit is contained in:
Branimir Karadžić 2013-05-22 19:24:03 -07:00
commit 0d76b652a9
10 changed files with 1752 additions and 102 deletions

View file

@ -340,6 +340,12 @@ http://fontfabric.com/signika-font/
Visitor Font
http://www.dafont.com/visitor.font
Special-Elite Font
http://www.fontspace.com/astigmatic-one-eye-typographic-institute/special-elite
Sherlock Holmes text
http://www.gutenberg.org/ebooks/1661
Tree Pack 1
http://www.turbosquid.com/3d-models/free-obj-mode-tree-pack/506851

View file

@ -4,7 +4,6 @@
*/
#include "../common/common.h"
#include <bgfx.h>
#include <bx/timer.h>
#include "../common/entry.h"
@ -13,7 +12,12 @@
#include "../common/processevents.h"
#include "../common/font/font_manager.h"
#include "../common/font/text_metrics.h"
#include "../common/font/text_buffer_manager.h"
#include "../common/imgui/imgui.h"
#include <stdio.h>
#include <string.h>
inline void mtxTranslate(float* _result, float x, float y, float z)
{
@ -33,6 +37,56 @@ inline void mtxScale(float* _result, float x, float y, float z)
_result[15] = 1.0f;
}
long int fsize(FILE* _file)
{
long int pos = ftell(_file);
fseek(_file, 0L, SEEK_END);
long int size = ftell(_file);
fseek(_file, pos, SEEK_SET);
return size;
}
char* loadText(const char* _textFile)
{
FILE* pFile;
pFile = fopen(_textFile, "rb");
if (pFile == NULL)
{
return NULL;
}
// Go to the end of the file.
if (fseek(pFile, 0L, SEEK_END) == 0)
{
// Get the size of the file.
long bufsize = ftell(pFile);
if (bufsize == -1)
{
fclose(pFile);
return NULL;
}
char* buffer = new char[bufsize];
// Go back to the start of the file.
fseek(pFile, 0L, SEEK_SET);
// Read the entire file into memory.
uint32_t newLen = fread( (void*)buffer, sizeof(char), bufsize, pFile);
if (newLen == 0)
{
fclose(pFile);
delete[] buffer;
return NULL;
}
fclose(pFile);
return buffer;
}
return NULL;
}
int _main_(int /*_argc*/, char** /*_argv*/)
{
uint32_t width = 1280;
@ -55,35 +109,94 @@ int _main_(int /*_argc*/, char** /*_argv*/)
, 0
);
FILE* file = fopen("font/droidsans.ttf", "rb");
uint32_t size = (uint32_t)fsize(file);
void* data = malloc(size);
size_t ignore = fread(data, 1, size, file);
BX_UNUSED(ignore);
fclose(file);
imguiCreate(data, size);
free(data);
char* bigText = loadText( "text/sherlock_holmes_a_scandal_in_bohemia_arthur_conan_doyle.txt");
// Init the text rendering system.
FontManager* fontManager = new FontManager(512);
TextBufferManager* textBufferManager = new TextBufferManager(fontManager);
TrueTypeHandle times_tt = fontManager->loadTrueTypeFromFile("font/bleeding_cowboys.ttf");
TrueTypeHandle font_tt = fontManager->loadTrueTypeFromFile("font/special_elite.ttf");//bleeding_cowboys.ttf");
// Create a distance field font.
FontHandle distance_font = fontManager->createFontByPixelSize(times_tt, 0, 48, FONT_TYPE_DISTANCE);
FontHandle base_distance_font = fontManager->createFontByPixelSize(font_tt, 0, 48, FONT_TYPE_DISTANCE);
// Create a scaled down version of the same font (without adding anything to the atlas).
FontHandle scaled_font = fontManager->createScaledFontToPixelSize(base_distance_font, 14);
TextLineMetrics metrics(fontManager, scaled_font);
uint32_t lineCount = metrics.getLineCount(bigText);
float visibleLineCount = 20.0f;
// Create a scalled down version of the same font (without adding
// anything to the atlas).
FontHandle smaller_font = fontManager->createScaledFontToPixelSize(distance_font, 32);
const char* textBegin = 0;
const char* textEnd = 0;
metrics.getSubText(bigText, 0, (uint32_t)visibleLineCount, textBegin, textEnd);
// Preload glyph and generate (generate bitmap's).
fontManager->preloadGlyph(distance_font, L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.,\" \n");
TextBufferHandle scrollableBuffer = textBufferManager->createTextBuffer(FONT_TYPE_DISTANCE, TRANSIENT);
textBufferManager->setTextColor(scrollableBuffer, 0xFFFFFFFF);
// You can unload the TTF files at this stage, but in that case, the
// set of glyph's will be limited to the set of preloaded glyph.
fontManager->unloadTrueType(times_tt);
TextBufferHandle staticText = textBufferManager->createTextBuffer(FONT_TYPE_DISTANCE, STATIC);
textBufferManager->setTextColor(staticText, 0xDD0000FF);
textBufferManager->appendText(staticText, distance_font, L"BGFX ");
textBufferManager->appendText(staticText, smaller_font, L"bgfx");
int64_t timeOffset = bx::getHPCounter();
while (!processEvents(width, height, debug, reset) )
textBufferManager->appendText(scrollableBuffer, scaled_font, textBegin, textEnd);
MouseState mouseState;
int32_t scrollArea = 0;
while (!processEvents(width, height, debug, reset, &mouseState) )
{
imguiBeginFrame(mouseState.m_mx
, mouseState.m_my
, (mouseState.m_buttons[entry::MouseButton::Left ] ? IMGUI_MBUT_LEFT : 0)
| (mouseState.m_buttons[entry::MouseButton::Right ] ? IMGUI_MBUT_RIGHT : 0)
, 0
, width
, height
);
const int guiPanelWidth = 250;
const int guiPanelHeight = 200;
imguiBeginScrollArea("Text Area", width - guiPanelWidth - 10, 10, guiPanelWidth, guiPanelHeight, &scrollArea);
imguiSeparatorLine();
static float textScroll = 0.0f;
static float textRotation = 0.0f;
static float textScale = 1.0f;
static float textSize = 14.0f;
bool recomputeVisibleText = false;
recomputeVisibleText |= imguiSlider("Number of lines", &visibleLineCount, 1.0f, 177.0f , 1.0f);
if(imguiSlider("Font size", &textSize, 6.0f, 64.0f , 1.0f))
{
fontManager->destroyFont(scaled_font);
scaled_font = fontManager->createScaledFontToPixelSize(base_distance_font, (uint32_t) textSize);
metrics = TextLineMetrics (fontManager, scaled_font);
recomputeVisibleText = true;
}
recomputeVisibleText |= imguiSlider("Scroll", &textScroll, 0.0f, (lineCount-visibleLineCount) , 1.0f);
imguiSlider("Rotate", &textRotation, 0.0f, (float) M_PI *2.0f , 0.1f);
recomputeVisibleText |= imguiSlider("Scale", &textScale, 0.1f, 10.0f , 0.1f);
if( recomputeVisibleText)
{
textBufferManager->clearTextBuffer(scrollableBuffer);
metrics.getSubText(bigText,(uint32_t)textScroll, (uint32_t)(textScroll+visibleLineCount), textBegin, textEnd);
textBufferManager->appendText(scrollableBuffer, scaled_font, textBegin, textEnd);
}
imguiEndScrollArea();
imguiEndFrame();
// Set view 0 default viewport.
bgfx::setViewRect(0, 0, 0, width, height);
@ -97,7 +210,6 @@ int _main_(int /*_argc*/, char** /*_argv*/)
last = now;
const double freq = double(bx::getHPFrequency() );
const double toMs = 1000.0 / freq;
float time = (float)( (now - timeOffset) / double(bx::getHPFrequency() ) );
// Use debug font to print information about this example.
bgfx::dbgTextClear();
@ -115,42 +227,50 @@ int _main_(int /*_argc*/, char** /*_argv*/)
// Setup a top-left ortho matrix for screen space drawing.
mtxOrtho(proj, centering, width + centering, height + centering, centering, -1.0f, 1.0f);
// Set view and projection matrix for view 0.
bgfx::setViewTransform(0, view, proj);
TextRectangle rect = textBufferManager->getRectangle(staticText);
//very crude approximation :(
float textAreaWidth = 0.5f * 66.0f * fontManager->getFontInfo(scaled_font).maxAdvanceWidth;
float mtxA[16];
float mtxB[16];
float mtxC[16];
mtxRotateZ(mtxA, time * 0.37f);
mtxTranslate(mtxB, -(rect.width * 0.5f), -(rect.height * 0.5f), 0);
float textRotMat[16];
float textCenterMat[16];
float textScaleMat[16];
float screenCenterMat[16];
mtxRotateZ(textRotMat, textRotation);
mtxTranslate(textCenterMat, -(textAreaWidth * 0.5f), (-visibleLineCount)*metrics.getLineHeight()*0.5f, 0);
mtxScale(textScaleMat, textScale, textScale, 1.0f);
mtxTranslate(screenCenterMat, ( (width) * 0.5f), ( (height) * 0.5f), 0);
mtxMul(mtxC, mtxB, mtxA);
float scale = 4.1f + 4.0f * sinf(time);
mtxScale(mtxA, scale, scale, 1.0f);
mtxMul(mtxB, mtxC, mtxA);
mtxTranslate(mtxC, ( (width) * 0.5f), ( (height) * 0.5f), 0);
mtxMul(mtxA, mtxB, mtxC);
//first translate to text center, then scale, then rotate
float tmpMat[16];
mtxMul(tmpMat, textCenterMat, textRotMat);
float tmpMat2[16];
mtxMul(tmpMat2, tmpMat, textScaleMat);
float tmpMat3[16];
mtxMul(tmpMat3, tmpMat2, screenCenterMat);
// Set model matrix for rendering.
bgfx::setTransform(mtxA);
bgfx::setTransform(tmpMat3);
// Draw your text.
textBufferManager->submitTextBuffer(staticText, 0);
textBufferManager->submitTextBuffer(scrollableBuffer, 0);
// Advance to next frame. Rendering thread will be kicked to
// process submitted rendering primitives.
bgfx::frame();
}
fontManager->unloadTrueType(font_tt);
// Destroy the fonts.
fontManager->destroyFont(distance_font);
fontManager->destroyFont(smaller_font);
fontManager->destroyFont(base_distance_font);
fontManager->destroyFont(scaled_font);
textBufferManager->destroyTextBuffer(staticText);
textBufferManager->destroyTextBuffer(scrollableBuffer);
delete textBufferManager;
delete fontManager;

View file

@ -165,6 +165,7 @@ FontInfo TrueTypeFont::getFontInfo()
outFontInfo.ascender = metrics.ascender / 64.0f;
outFontInfo.descender = metrics.descender / 64.0f;
outFontInfo.lineGap = (metrics.height - metrics.ascender + metrics.descender) / 64.0f;
outFontInfo.maxAdvanceWidth = metrics.max_advance/ 64.0f;
outFontInfo.underlinePosition = FT_MulFix(holder->face->underline_position, metrics.y_scale) / 64.0f;
outFontInfo.underlineThickness = FT_MulFix(holder->face->underline_thickness, metrics.y_scale) / 64.0f;
@ -647,6 +648,7 @@ FontHandle FontManager::createScaledFontToPixelSize(FontHandle _baseFontHandle,
newFontInfo.ascender = (newFontInfo.ascender * newFontInfo.scale);
newFontInfo.descender = (newFontInfo.descender * newFontInfo.scale);
newFontInfo.lineGap = (newFontInfo.lineGap * newFontInfo.scale);
newFontInfo.maxAdvanceWidth = (newFontInfo.maxAdvanceWidth * newFontInfo.scale);
newFontInfo.underlineThickness = (newFontInfo.underlineThickness * newFontInfo.scale);
newFontInfo.underlinePosition = (newFontInfo.underlinePosition * newFontInfo.scale);
@ -782,7 +784,7 @@ bool FontManager::preloadGlyph(FontHandle _handle, CodePoint _codePoint)
return false;
}
const FontInfo& FontManager::getFontInfo(FontHandle _handle)
const FontInfo& FontManager::getFontInfo(FontHandle _handle) const
{
BX_CHECK(bgfx::invalidHandle != _handle.idx, "Invalid handle used");
return m_cachedFonts[_handle.idx].fontInfo;

View file

@ -7,6 +7,7 @@
#define __FONT_MANAGER_H__
#include <bx/handlealloc.h>
#include <bgfx.h>
class Atlas;
@ -29,6 +30,8 @@ struct FontInfo
float descender;
/// The spacing in pixels between one row's descent and the next row's ascent.
float lineGap;
/// This field gives the maximum horizontal cursor advance for all glyphs in the font.
float maxAdvanceWidth;
/// The thickness of the under/hover/strike-trough line in pixels.
float underlineThickness;
/// The position of the underline relatively to the baseline.
@ -169,7 +172,7 @@ public:
/// Return the font descriptor of a font.
///
/// @remark the handle is required to be valid
const FontInfo& getFontInfo(FontHandle _handle);
const FontInfo& getFontInfo(FontHandle _handle) const;
/// Return the rendering informations about the glyph region. Load the
/// glyph from a TrueType font if possible

View file

@ -22,7 +22,7 @@
#include "fs_font_distance_field_subpixel.bin.h"
#define MAX_TEXT_BUFFER_COUNT 64
#define MAX_BUFFERED_CHARACTERS 8192
#define MAX_BUFFERED_CHARACTERS (8192 - 5)
class TextBuffer
{
@ -64,15 +64,11 @@ public:
m_penX = _x; m_penY = _y;
}
/// return the size of the text
//Rectangle measureText(FontHandle _fontHandle, const char * _string);
//Rectangle measureText(FontHandle _fontHandle, const wchar_t * _string);
/// append an ASCII/utf-8 string to the buffer using current pen position and color
void appendText(FontHandle _fontHandle, const char* _string);
void appendText(FontHandle _fontHandle, const char* _string, const char* _end = NULL);
/// append a wide char unicode string to the buffer using current pen position and color
void appendText(FontHandle _fontHandle, const wchar_t* _string);
void appendText(FontHandle _fontHandle, const wchar_t* _string, const wchar_t* _end = NULL);
/// append a whole face of the atlas cube, mostly used for debugging and visualizing atlas
void appendAtlasFace(uint16_t _faceIndex);
@ -215,7 +211,7 @@ TextBuffer::~TextBuffer()
delete[] m_indexBuffer;
}
void TextBuffer::appendText(FontHandle _fontHandle, const char* _string)
void TextBuffer::appendText(FontHandle _fontHandle, const char* _string, const char* _end)
{
GlyphInfo glyph;
const FontInfo& font = m_fontManager->getFontInfo(_fontHandle);
@ -224,16 +220,17 @@ void TextBuffer::appendText(FontHandle _fontHandle, const char* _string)
{
m_originX = m_penX;
m_originY = m_penY;
m_lineDescender = 0; // font.m_descender;
m_lineAscender = 0; //font.m_ascender;
m_lineDescender = 0;
m_lineAscender = 0;
m_lineGap = 0;
}
CodePoint codepoint = 0;
uint32_t state = 0;
for (; *_string; ++_string)
for (; *_string && _string<_end ; ++_string)
{
if (!utf8_decode(&state, (uint32_t*)&codepoint, *_string) )
if (utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT )
{
if (m_fontManager->getGlyphInfo(_fontHandle, codepoint, glyph) )
{
@ -246,14 +243,10 @@ void TextBuffer::appendText(FontHandle _fontHandle, const char* _string)
}
}
if (state != UTF8_ACCEPT)
{
// assert(false && "The string is not well-formed");
return; //"The string is not well-formed\n"
}
BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
}
void TextBuffer::appendText(FontHandle _fontHandle, const wchar_t* _string)
void TextBuffer::appendText(FontHandle _fontHandle, const wchar_t* _string, const wchar_t* _end)
{
GlyphInfo glyph;
const FontInfo& font = m_fontManager->getFontInfo(_fontHandle);
@ -262,14 +255,19 @@ void TextBuffer::appendText(FontHandle _fontHandle, const wchar_t* _string)
{
m_originX = m_penX;
m_originY = m_penY;
m_lineDescender = 0; // font.m_descender;
m_lineAscender = 0; //font.m_ascender;
m_lineDescender = 0;
m_lineAscender = 0;
m_lineGap = 0;
}
for (uint32_t ii = 0, end = (uint32_t)wcslen(_string); ii < end; ++ii)
if( _end == NULL)
{
uint32_t _codePoint = _string[ii];
_end = _string + (uint32_t) wcslen(_string);
}
BX_CHECK(_end >= _string);
for (const wchar_t* _current = _string; _current < _end; ++_current)
{
uint32_t _codePoint = *_current;
if (m_fontManager->getGlyphInfo(_fontHandle, _codePoint, glyph) )
{
appendGlyph(_codePoint, font, glyph);
@ -280,8 +278,14 @@ void TextBuffer::appendText(FontHandle _fontHandle, const wchar_t* _string)
}
}
}
void TextBuffer::appendAtlasFace(uint16_t _faceIndex)
{
if( m_vertexCount/4 >= MAX_BUFFERED_CHARACTERS)
{
return;
}
float x0 = m_penX;
float y0 = m_penY;
float x1 = x0 + (float)m_fontManager->getAtlas()->getTextureSize();
@ -306,29 +310,40 @@ void TextBuffer::appendAtlasFace(uint16_t _faceIndex)
void TextBuffer::clearTextBuffer()
{
m_penX = 0;
m_penY = 0;
m_originX = 0;
m_originY = 0;
m_vertexCount = 0;
m_indexCount = 0;
m_lineStartIndex = 0;
m_lineAscender = 0;
m_lineDescender = 0;
m_lineGap = 0;
m_rectangle.width = 0;
m_rectangle.height = 0;
}
void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const GlyphInfo& _glyphInfo)
{
if (_codePoint == L'\n')
{
if( m_vertexCount/4 >= MAX_BUFFERED_CHARACTERS)
{
m_penX = m_originX;
m_penY -= m_lineDescender;
m_penY += m_lineGap;
m_lineDescender = 0;
m_lineAscender = 0;
m_lineStartIndex = m_vertexCount;
return;
}
if (_codePoint == L'\n')
{
m_penX = m_originX;
m_penY += m_lineGap + m_lineAscender -m_lineDescender;
m_lineGap = _font.lineGap;
m_lineDescender = _font.descender;
m_lineAscender = _font.ascender;
m_lineStartIndex = m_vertexCount;
return;
}
//is there a change of font size that require the text on the left to be centered again ?
if (_font.ascender > m_lineAscender
|| (_font.descender < m_lineDescender) )
{
@ -340,14 +355,12 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
float txtDecals = (_font.ascender - m_lineAscender);
m_lineAscender = _font.ascender;
m_lineGap = _font.lineGap;
m_penY += txtDecals;
verticalCenterLastLine( (txtDecals), (m_penY - m_lineAscender), (m_penY - m_lineDescender + m_lineGap) );
m_lineGap = _font.lineGap;
verticalCenterLastLine( (txtDecals), (m_penY - m_lineAscender), (m_penY + m_lineAscender - m_lineDescender + m_lineGap) );
}
float kerning = 0;
m_penX += kerning * _font.scale;
float kerning = 0 * _font.scale;
m_penX += kerning;
GlyphInfo& blackGlyph = m_fontManager->getBlackGlyph();
@ -355,9 +368,9 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
&& m_backgroundColor & 0xFF000000)
{
float x0 = (m_penX - kerning);
float y0 = (m_penY - m_lineAscender);
float y0 = (m_penY);
float x1 = ( (float)x0 + (_glyphInfo.advance_x) );
float y1 = (m_penY - m_lineDescender + m_lineGap);
float y1 = (m_penY + m_lineAscender - m_lineDescender + m_lineGap);
m_fontManager->getAtlas()->packUV(blackGlyph.regionIndex, (uint8_t*)m_vertexBuffer, sizeof(TextVertex) * m_vertexCount + offsetof(TextVertex, u), sizeof(TextVertex) );
@ -380,7 +393,7 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
&& m_underlineColor & 0xFF000000)
{
float x0 = (m_penX - kerning);
float y0 = (m_penY - m_lineDescender / 2);
float y0 = (m_penY + m_lineAscender - m_lineDescender * 0.5f);
float x1 = ( (float)x0 + (_glyphInfo.advance_x) );
float y1 = y0 + _font.underlineThickness;
@ -405,7 +418,7 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
&& m_overlineColor & 0xFF000000)
{
float x0 = (m_penX - kerning);
float y0 = (m_penY - _font.ascender);
float y0 = (m_penY);
float x1 = ( (float)x0 + (_glyphInfo.advance_x) );
float y1 = y0 + _font.underlineThickness;
@ -430,7 +443,7 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
&& m_strikeThroughColor & 0xFF000000)
{
float x0 = (m_penX - kerning);
float y0 = (m_penY - _font.ascender / 3);
float y0 = (m_penY + 0.666667f * _font.ascender);
float x1 = ( (float)x0 + (_glyphInfo.advance_x) );
float y1 = y0 + _font.underlineThickness;
@ -451,9 +464,8 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
m_indexCount += 6;
}
float x0_precise = m_penX + (_glyphInfo.offset_x);
float x0 = (x0_precise);
float y0 = (m_penY + (_glyphInfo.offset_y) );
float x0 = m_penX + (_glyphInfo.offset_x);
float y0 = (m_penY + m_lineAscender + (_glyphInfo.offset_y) );
float x1 = (x0 + _glyphInfo.width);
float y1 = (y0 + _glyphInfo.height);
@ -479,9 +491,9 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
m_rectangle.width = m_penX;
}
if ( (m_penY - m_lineDescender) > m_rectangle.height)
if ( (m_penY +m_lineAscender - m_lineDescender+m_lineGap) > m_rectangle.height)
{
m_rectangle.height = (m_penY - m_lineDescender);
m_rectangle.height = (m_penY +m_lineAscender - m_lineDescender+m_lineGap);
}
}
@ -813,18 +825,18 @@ void TextBufferManager::setPenPosition(TextBufferHandle _handle, float _x, float
bc.textBuffer->setPenPosition(_x, _y);
}
void TextBufferManager::appendText(TextBufferHandle _handle, FontHandle _fontHandle, const char* _string)
void TextBufferManager::appendText(TextBufferHandle _handle, FontHandle _fontHandle, const char* _string, const char* _end)
{
BX_CHECK(bgfx::invalidHandle != _handle.idx, "Invalid handle used");
BufferCache& bc = m_textBuffers[_handle.idx];
bc.textBuffer->appendText(_fontHandle, _string);
bc.textBuffer->appendText(_fontHandle, _string, _end);
}
void TextBufferManager::appendText(TextBufferHandle _handle, FontHandle _fontHandle, const wchar_t* _string)
void TextBufferManager::appendText(TextBufferHandle _handle, FontHandle _fontHandle, const wchar_t* _string, const wchar_t* _end)
{
BX_CHECK(bgfx::invalidHandle != _handle.idx, "Invalid handle used");
BufferCache& bc = m_textBuffers[_handle.idx];
bc.textBuffer->appendText(_fontHandle, _string);
bc.textBuffer->appendText(_fontHandle, _string, _end);
}
void TextBufferManager::appendAtlasFace(TextBufferHandle _handle, uint16_t _faceIndex)

View file

@ -55,19 +55,20 @@ public:
void setPenPosition(TextBufferHandle _handle, float _x, float _y);
/// append an ASCII/utf-8 string to the buffer using current pen position and color
void appendText(TextBufferHandle _handle, FontHandle _fontHandle, const char* _string);
void appendText(TextBufferHandle _handle, FontHandle _fontHandle, const char* _string, const char* _end = NULL);
/// append a wide char unicode string to the buffer using current pen position and color
void appendText(TextBufferHandle _handle, FontHandle _fontHandle, const wchar_t* _string);
void appendText(TextBufferHandle _handle, FontHandle _fontHandle, const wchar_t* _string, const wchar_t* _end = NULL);
/// append a whole face of the atlas cube, mostly used for debugging and visualizing atlas
void appendAtlasFace(TextBufferHandle _handle, uint16_t _faceIndex);
/// Clear the text buffer and reset its state (pen/color)
void clearTextBuffer(TextBufferHandle _handle);
TextRectangle getRectangle(TextBufferHandle _handle) const;
/// Return the rectangular size of the current text buffer (including all its content)
TextRectangle getRectangle(TextBufferHandle _handle) const;
private:
struct BufferCache
{

View file

@ -0,0 +1,303 @@
/*
* Copyright 2013 Jeremie Roy. All rights reserved.
* License: http://www.opensource.org/licenses/BSD-2-Clause
*/
#include "text_metrics.h"
#include <wchar.h> // wcslen
#include "utf8.h"
TextMetrics::TextMetrics(FontManager* _fontManager): m_fontManager(_fontManager), m_width(0), m_height(0), m_x(0), m_lineHeight(0), m_lineGap(0)
{
}
void TextMetrics::appendText(FontHandle _fontHandle, const char* _string)
{
GlyphInfo glyph;
const FontInfo& font = m_fontManager->getFontInfo(_fontHandle);
if(font.lineGap > m_lineGap)
{
m_lineGap = font.lineGap;
}
if( (font.ascender - font.descender) > m_lineHeight)
{
m_height -= m_lineHeight;
m_lineHeight = font.ascender - font.descender;
m_height += m_lineHeight;
}
CodePoint codepoint = 0;
uint32_t state = 0;
for (; *_string; ++_string)
{
if (!utf8_decode(&state, (uint32_t*)&codepoint, *_string) )
{
if (m_fontManager->getGlyphInfo(_fontHandle, codepoint, glyph) )
{
if (codepoint == L'\n')
{
m_height += m_lineGap + font.ascender - font.descender;
m_lineGap = font.lineGap;
m_lineHeight = font.ascender - font.descender;
m_x = 0;
break;
}
//TODO handle kerning
m_x += glyph.advance_x;
if(m_x > m_width)
{
m_width = m_x;
}
}
else
{
BX_CHECK(false, "Glyph not found");
}
}
}
BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
}
void TextMetrics::appendText(FontHandle _fontHandle, const wchar_t* _string)
{
GlyphInfo glyph;
const FontInfo& font = m_fontManager->getFontInfo(_fontHandle);
if(font.lineGap > m_lineGap)
{
m_lineGap = font.lineGap;
}
if( (font.ascender - font.descender) > m_lineHeight)
{
m_height -= m_lineHeight;
m_lineHeight = font.ascender - font.descender;
m_height += m_lineHeight;
}
for (uint32_t ii = 0, end = wcslen(_string); ii < end; ++ii)
{
uint32_t codepoint = _string[ii];
if (m_fontManager->getGlyphInfo(_fontHandle, codepoint, glyph) )
{
if (codepoint == L'\n')
{
m_height += m_lineGap + font.ascender - font.descender;
m_lineGap = font.lineGap;
m_lineHeight = font.ascender - font.descender;
m_x = 0;
break;
}
//TODO handle kerning
m_x += glyph.advance_x;
if(m_x > m_width)
{
m_width = m_x;
}
}
else
{
BX_CHECK(false, "Glyph not found");
}
}
}
TextLineMetrics::TextLineMetrics(FontManager* _fontManager, FontHandle _fontHandle )
{
const FontInfo& font = _fontManager->getFontInfo(_fontHandle);
m_lineHeight = font.ascender - font.descender + font.lineGap;
}
uint32_t TextLineMetrics::getLineCount(const char* _string) const
{
CodePoint codepoint = 0;
uint32_t state = 0;
uint32_t lineCount = 1;
for (; *_string; ++_string)
{
if(utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT)
{
if(codepoint == L'\n')
{
++lineCount;
}
}
}
BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
return lineCount;
}
uint32_t TextLineMetrics::getLineCount(const wchar_t* _string) const
{
uint32_t lineCount = 1;
for ( ;*_string != L'\0'; ++_string)
{
if(*_string == L'\n')
{
++lineCount;
}
}
return lineCount;
}
void TextLineMetrics::getSubText(const char* _string, uint32_t _firstLine, uint32_t _lastLine, const char*& _begin, const char*& _end)
{
CodePoint codepoint = 0;
uint32_t state = 0;
// y is bottom of a text line
uint32_t currentLine = 0;
while(*_string && (currentLine < _firstLine))
{
for (; *_string; ++_string)
{
if(utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT)
{
if(codepoint == L'\n')
{
++currentLine;
++_string;
break;
}
}
}
}
BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
_begin = _string;
while((*_string) && (currentLine < _lastLine) )
{
for (; *_string; ++_string)
{
if(utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT)
{
if(codepoint == L'\n')
{
++currentLine;
++_string;
break;
}
}
}
}
BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
_end = _string;
}
void TextLineMetrics::getSubText(const wchar_t* _string, uint32_t _firstLine, uint32_t _lastLine, const wchar_t*& _begin, const wchar_t*& _end)
{
uint32_t currentLine = 0;
while((*_string != L'\0') && (currentLine < _firstLine))
{
for ( ;*_string != L'\0'; ++_string)
{
if(*_string == L'\n')
{
++currentLine;
++_string;
break;
}
}
}
_begin = _string;
while((*_string != L'\0') && (currentLine < _lastLine) )
{
for ( ;*_string != L'\0'; ++_string)
{
if(*_string == L'\n')
{
++currentLine;
++_string;
break;
}
}
}
_end = _string;
}
void TextLineMetrics::getVisibleText(const char* _string, float _top, float _bottom, const char*& _begin, const char*& _end)
{
CodePoint codepoint = 0;
uint32_t state = 0;
// y is bottom of a text line
float y = m_lineHeight;
while(*_string && (y < _top))
{
for (; *_string; ++_string)
{
if(utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT)
{
if(codepoint == L'\n')
{
y += m_lineHeight;
++_string;
break;
}
}
}
}
BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
_begin = _string;
// y is now top of a text line
y -= m_lineHeight;
while((*_string) && (y < _bottom) )
{
for (; *_string; ++_string)
{
if(utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT)
{
if(codepoint == L'\n')
{
y += m_lineHeight;
++_string;
break;
}
}
}
}
BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
_end = _string;
}
void TextLineMetrics::getVisibleText(const wchar_t* _string, float _top, float _bottom, const wchar_t*& _begin, const wchar_t*& _end)
{
// y is bottom of a text line
float y = m_lineHeight;
const wchar_t* _textEnd = _string + wcslen(_string);
while(y < _top)
{
for (const wchar_t* _current = _string; _current < _textEnd; ++_current)
{
if(*_current == L'\n')
{
y += m_lineHeight;
++_string;
break;
}
}
}
_begin = _string;
// y is now top of a text line
y -= m_lineHeight;
while(y < _bottom )
{
for (const wchar_t* _current = _string; _current < _textEnd; ++_current)
{
if(*_current == L'\n')
{
y += m_lineHeight;
++_string;
break;
}
}
}
_end = _string;
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2013 Jeremie Roy. All rights reserved.
* License: http://www.opensource.org/licenses/BSD-2-Clause
*/
#ifndef __TEXT_METRICS_H__
#define __TEXT_METRICS_H__
#include "font_manager.h"
class TextMetrics
{
public:
TextMetrics(FontManager* _fontManager);
/// Append an ASCII/utf-8 string to the metrics helper
void appendText(FontHandle _fontHandle, const char* _string);
/// Append a wide char string to the metrics helper
void appendText(FontHandle _fontHandle, const wchar_t* _string);
/// return the width of the measured text
float getWidth() const { return m_width; }
/// return the height of the measured text
float getHeight() const { return m_height; }
private:
FontManager* m_fontManager;
float m_width;
float m_height;
float m_x;
float m_lineHeight;
float m_lineGap;
};
/// Compute text crop area for text using a single font
class TextLineMetrics
{
public:
TextLineMetrics(FontManager* _fontManager, FontHandle _fontHandle);
/// Return the height of a line of text using the given font
float getLineHeight() const { return m_lineHeight; }
/// Return the number of text line in the given text
uint32_t getLineCount(const char* _string) const;
/// Return the number of text line in the given text
uint32_t getLineCount(const wchar_t* _string) const;
/// Return the first and last character visible in the [_firstLine, _lastLine[ range
void getSubText(const char* _string, uint32_t _firstLine, uint32_t _lastLine, const char*& _begin, const char*& _end);
/// Return the first and last character visible in the [_firstLine, _lastLine[ range
void getSubText(const wchar_t* _string, uint32_t _firstLine, uint32_t _lastLine, const wchar_t*& _begin, const wchar_t*& _end);
/// Return the first and last character visible in the [_top, _bottom] range,
void getVisibleText(const char* _string, float _top, float _bottom, const char*& _begin, const char*& _end);
/// Return the first and last character visible in the [_top, _bottom] range,
void getVisibleText(const wchar_t* _string, float _top, float _bottom, const wchar_t*& _begin, const wchar_t*& _end);
private:
FontManager* m_fontManager;
float m_lineHeight;
};
#endif // __TEXT_METRICS_H__

Binary file not shown.

File diff suppressed because it is too large Load diff