378 lines
9.4 KiB
C++
378 lines
9.4 KiB
C++
/* -----------------------------------------------------------------------------
|
|
GSFramework
|
|
Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
|
|
----------------------------------------------------------------------------- */
|
|
|
|
|
|
#include <cstring>
|
|
#include "font/font_renderer.h"
|
|
#include "picture/pict.h"
|
|
#include "ascii/parser.h"
|
|
#include "log/log.h"
|
|
|
|
using namespace GS;
|
|
using namespace GS::AsciiParser;
|
|
|
|
float FontRenderer::default_text_tracking = 0;
|
|
float FontRenderer::default_text_heading = 0;
|
|
|
|
//
|
|
struct GS::SubString
|
|
{
|
|
const char *entry;
|
|
int char_count;
|
|
int space_count;
|
|
int width; // 26.6
|
|
int height;
|
|
|
|
void Reset(const char *string)
|
|
{
|
|
entry = string;
|
|
char_count = 0;
|
|
space_count = 0;
|
|
width = 0;
|
|
height = 0;
|
|
}
|
|
};
|
|
|
|
int substring_count = 0;
|
|
SubString substring_array[1024];
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
const char *FontRenderer::CheckCommand(const char *string, Command &command)
|
|
{
|
|
command.code = CommandNone;
|
|
if (!string[0] || !string[1])
|
|
return string;
|
|
|
|
if ((string[0] == '~') && (string[1] == '~'))
|
|
{
|
|
//----------------------------------------------------------------------
|
|
#define PARSE_COMPONENT(_C_, _M_)\
|
|
{\
|
|
string = SkipSpace(string + 1, eos);\
|
|
command.vector._C_ = (float)String::atoi(string);\
|
|
string = NextEntry(string, eos);\
|
|
if (string[0] != (_M_))\
|
|
{\
|
|
command.code = CommandParseError;\
|
|
__LOG_E__ << "Error parsing text command components.\n";\
|
|
return NULL;\
|
|
}\
|
|
}
|
|
//----------------------------------------------------------------------
|
|
|
|
const char *eos = string + std::strlen(string);
|
|
|
|
if (!strncmp("Color(", string + 2, 6) || !strncmp("COLOR(", string + 2, 6)) // [EJ] 1st may: range is [0;255]
|
|
{
|
|
command.code = CommandColor;
|
|
string += 7;
|
|
|
|
const char *eop = Find(string, eos, ')');
|
|
if (!eop)
|
|
{
|
|
command.code = CommandParseError;
|
|
return NULL;
|
|
}
|
|
|
|
PARSE_COMPONENT(x, ',');
|
|
PARSE_COMPONENT(y, ',');
|
|
PARSE_COMPONENT(z, ',');
|
|
PARSE_COMPONENT(w, ')');
|
|
string = eop + 1;
|
|
}
|
|
else if (!strncmp("Size(", string + 2, 5) || !strncmp("SIZE(", string + 2, 5))
|
|
{
|
|
command.code = CommandSize;
|
|
|
|
string += 6;
|
|
|
|
const char *eop = Find(string, eos, ')');
|
|
if (!eop)
|
|
{
|
|
command.code = CommandParseError;
|
|
return NULL;
|
|
}
|
|
|
|
PARSE_COMPONENT(x, ')');
|
|
string = eop + 1;
|
|
}
|
|
}
|
|
return string;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
const char *FontRenderer::FetchSubString(const char *string, SubString &substring, TextState &state, int max_width, int max_char)
|
|
{
|
|
if (!string)
|
|
return NULL;
|
|
|
|
// Jump over leading spaces.
|
|
forever
|
|
{
|
|
if (string[0] != ' ')
|
|
break;
|
|
|
|
if (!string[0] || (string[0] == '\n'))
|
|
{
|
|
substring.Reset(NULL);
|
|
return string;
|
|
}
|
|
string++;
|
|
}
|
|
|
|
// Start sub-string.
|
|
substring.Reset(string);
|
|
int previous_codepoint = 0;
|
|
|
|
state.font->SetPixelSize(state.GetSize());
|
|
max_width <<= 6;
|
|
|
|
// Word cut point.
|
|
bool rollback_available = false;
|
|
SubString rollback_substring;
|
|
const char *rollback_string = NULL;
|
|
|
|
int tracking = int(state.GetTracking() * 64.f);
|
|
|
|
forever
|
|
{
|
|
Command command;
|
|
if ((string = CheckCommand(string, command)) == 0)
|
|
break;
|
|
|
|
if (command.code == CommandNone)
|
|
{
|
|
if (string[0] == '\n')
|
|
{
|
|
string++;
|
|
break;
|
|
}
|
|
if ((string[0] == '\\') && (string[1] == 'n'))
|
|
{
|
|
string += 2;
|
|
break;
|
|
}
|
|
if (string[0] == 0)
|
|
break;
|
|
if ((max_char > 0) && (substring.char_count == max_char))
|
|
break;
|
|
|
|
// Get glyph.
|
|
uint codepoint;
|
|
int codelength = String::Utf8toUtf32((const uchar *)string, &codepoint);
|
|
|
|
state.font->LoadGlyph(codepoint, false);
|
|
|
|
// Retrieve glyph formatting informations.
|
|
int advance = state.font->GetAdvance();
|
|
int kerning = (state.font->HasKerning() && (previous_codepoint != 0)) ? state.font->GetKerning(previous_codepoint, codepoint) : 0;
|
|
|
|
// Width constraint.
|
|
if ((max_width > 0) && ((substring.width + advance) >= max_width))
|
|
{
|
|
rollback_available = true;
|
|
break;
|
|
}
|
|
|
|
substring.width += advance + kerning + tracking;
|
|
substring.height = Types::Max(state.font->GetHeight(), substring.height);
|
|
|
|
// Count space.
|
|
if (string[0] == ' ')
|
|
{
|
|
substring.space_count++;
|
|
|
|
// Store the word rollback position.
|
|
Command dummy_command;
|
|
CheckCommand(string, dummy_command);
|
|
|
|
if ((dummy_command.code == CommandNone) && (string[1] != ' '))
|
|
{
|
|
rollback_substring = substring;
|
|
rollback_string = string;
|
|
}
|
|
}
|
|
substring.char_count++;
|
|
|
|
// Next glyph.
|
|
string += codelength ? codelength : 1;
|
|
previous_codepoint = codepoint;
|
|
}
|
|
else
|
|
switch (command.code)
|
|
{
|
|
case CommandColor: // Irrelevant when not composing.
|
|
break;
|
|
|
|
case CommandSize:
|
|
state.SetSize((int)command.vector.x);
|
|
state.font->SetPixelSize(state.GetSize());
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
// Rollback to the last word position.
|
|
if (rollback_available && rollback_string)
|
|
{
|
|
substring = rollback_substring;
|
|
string = rollback_string;
|
|
}
|
|
return string;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
void FontRenderer::DrawSubString(SubString &line, Picture &output, TextState &state, const iRect &_out_rect, const iRect &clip_rect, int justification)
|
|
{
|
|
const char *string = line.entry;
|
|
state.font->SetPixelSize(state.GetSize());
|
|
|
|
iRect out_rect(_out_rect);
|
|
uint previous_codepoint = 0;
|
|
|
|
int tracking = int(state.GetTracking() * 64.f);
|
|
|
|
for (int n = 0; n < line.char_count; ++n)
|
|
{
|
|
Command command;
|
|
string = CheckCommand(string, command);
|
|
|
|
if (command.code == CommandNone)
|
|
{
|
|
// Get glyph.
|
|
uint codepoint;
|
|
int codelength = String::Utf8toUtf32((const uchar *)string, &codepoint);
|
|
|
|
state.font->LoadGlyph(codepoint, true);
|
|
|
|
// Retrieve glyph formatting informations.
|
|
int advance = state.font->GetAdvance();
|
|
int kerning = (state.font->HasKerning() && (previous_codepoint != 0)) ? state.font->GetKerning(previous_codepoint, codepoint) : 0;
|
|
|
|
// Render.
|
|
int px = out_rect.sx >> 6;
|
|
int py = (out_rect.sy + (line.height * 3) / 4) >> 6; // FIXME smells the hack... at best!
|
|
|
|
state.font->RenderCurrentGlyph(output, iPoint(px, py), clip_rect, state.color);
|
|
|
|
out_rect.sx += advance + kerning + tracking;
|
|
|
|
// Next glyph.
|
|
string += codelength;
|
|
previous_codepoint = codepoint;
|
|
}
|
|
else
|
|
{
|
|
switch (command.code)
|
|
{
|
|
case CommandColor:
|
|
state.color = command.vector ;/// 255.f;
|
|
break;
|
|
|
|
case CommandSize:
|
|
state.SetSize((int)command.vector.x);
|
|
state.font->SetPixelSize(state.GetSize());
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
--n;
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
iRect FontRenderer::Format(const char *txt, const TextState &in_state, const iRect &out_rect)
|
|
{
|
|
if (!in_state.font)
|
|
return iRect (0, 0, 0, 0);
|
|
|
|
// Create working state.
|
|
TextState state = in_state;
|
|
|
|
// Setup formatting rules.
|
|
int max_width = (state.format == TextState::Line) ? -1 : out_rect.GetWidth(),
|
|
max_char = (state.format != TextState::Column) ? -1 : state.column_width;
|
|
|
|
// Fetch all substrings.
|
|
substring_count = 0;
|
|
while (txt && txt[0])
|
|
txt = FetchSubString(txt, substring_array[substring_count++], state, max_width, max_char);
|
|
|
|
// Create full text rectangle.
|
|
iRect text_rect;
|
|
text_rect.Set(0, 0, 0, 0);
|
|
|
|
int leading = int(state.GetLeading() * 64);
|
|
|
|
for (int n = 0; n < substring_count; ++n)
|
|
{
|
|
if (text_rect.ex < substring_array[n].width)
|
|
text_rect.ex = substring_array[n].width;
|
|
text_rect.ey += substring_array[n].height + leading;
|
|
}
|
|
if (substring_count > 0)
|
|
text_rect.ey -= leading;
|
|
|
|
text_rect.ex = text_rect.ex / 64;
|
|
text_rect.ey = text_rect.ey / 64;
|
|
return text_rect;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
iRect FontRenderer::Compose(Picture &output, const char *txt, const TextState &in_state, const iRect &out_rect, const iRect &clip_rect)
|
|
{
|
|
if (in_state.font.IsNull() || (output.GetPixelFormat().GetBpp() != 32))
|
|
return iRect(0, 0, 0, 0);
|
|
|
|
// Create working state.
|
|
TextState state = in_state;
|
|
int leading = int(state.GetLeading() * 64);
|
|
|
|
// Draw substrings.
|
|
iRect work_out_rect(out_rect * 64);
|
|
|
|
for (int n = 0; n < substring_count; ++n)
|
|
{
|
|
iRect line_rect(work_out_rect);
|
|
line_rect.ex = line_rect.sx + substring_array[n].width;
|
|
|
|
int offset = 0, justification = 0;
|
|
|
|
switch (state.alignment)
|
|
{
|
|
case TextState::Left:
|
|
offset = out_rect.sx * 64 - line_rect.sx;
|
|
break;
|
|
case TextState::Center:
|
|
offset = (out_rect.GetWidth() * 64 - line_rect.GetWidth()) / 2;
|
|
break;
|
|
case TextState::Right:
|
|
offset = out_rect.ex * 64 - line_rect.ex;
|
|
break;
|
|
|
|
case TextState::Justify:
|
|
if (n < (substring_count - 1))
|
|
justification = (out_rect.GetWidth() * 64 - line_rect.GetWidth()) / substring_array[n].space_count;
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
line_rect.sx += offset;
|
|
|
|
DrawSubString(substring_array[n], output, state, line_rect, clip_rect, justification);
|
|
work_out_rect.sy += substring_array[n].height + leading;
|
|
}
|
|
return out_rect;
|
|
}
|
|
//------------------------------------------------------------------------------
|