/* ----------------------------------------------------------------------------- GSFramework Copyright 2001-2013 Emmanuel Julien. All Rights Reserved. ----------------------------------------------------------------------------- */ #include #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; } //------------------------------------------------------------------------------