/* ** ClanLib SDK ** Copyright (c) 1997-2005 The ClanLib Team ** ** This software is provided 'as-is', without any express or implied ** warranty. In no event will the authors be held liable for any damages ** arising from the use of this software. ** ** Permission is granted to anyone to use this software for any purpose, ** including commercial applications, and to alter it and redistribute it ** freely, subject to the following restrictions: ** ** 1. The origin of this software must not be misrepresented; you must not ** claim that you wrote the original software. If you use this software ** in a product, an acknowledgment in the product documentation would be ** appreciated but is not required. ** 2. Altered source versions must be plainly marked as such, and must not be ** misrepresented as being the original software. ** 3. This notice may not be removed or altered from any source distribution. ** ** Note: Some of the libraries ClanLib may link to may have additional ** requirements or restrictions. ** ** File Author(s): ** ** Magnus Norddahl ** (if your name is missing here, please add it) */ #include "Display/display_precomp.h" #include "API/Display/font.h" #include "API/Display/display.h" #include "API/Display/display_window.h" #include "API/Display/graphic_context.h" #include "API/Display/glyph_buffer.h" #include "API/Display/color.h" #include "API/Core/Resources/resource_manager.h" #include "API/Core/System/error.h" #include "API/Core/System/clanstring.h" #include "font_generic.h" #include "font_target.h" #include "font_target_sprite.h" #include "resourcedata_font.h" #include "display_target.h" #include #include #include #include #define cl_min(a,b) ((a < b) ? a : b) #define cl_max(a,b) ((a > b) ? a : b) ///////////////////////////////////////////////////////////////////////////// // CL_Font construction: CL_Font::CL_Font() { } CL_Font::CL_Font(const CL_Font ©) : impl(copy.impl) { resource = copy.resource; resource.load(); } CL_Font::CL_Font( const std::string &resource_id, CL_ResourceManager *manager) { resource = manager->get_resource(resource_id); resource.load(); CL_ResourceData_Font *data = (CL_ResourceData_Font *) resource.get_data("font"); if (!data) throw CL_Error("Resource '" + resource_id + "' is not of type 'font'"); operator=(data->get_font()); } CL_Font::CL_Font( const CL_Sprite &letters, const std::string &letter_chars, int space_width, bool monospace) : impl(new CL_Font_Generic) { impl->target = CL_SharedPtr( new CL_Font_Target_Sprite( impl.get(), letters, letter_chars, space_width, monospace)); } CL_Font::CL_Font( const std::string &font_name, int height, int width, bool bold, bool italic, bool underline, bool strikeout) : impl(new CL_Font_Generic) { //First thirty two characters are undrawable in ASCII std::string letters; letters.reserve(256-32); for (int x = 32; x < 256; ++x) letters.append(1, (unsigned int)(x)); impl->target = CL_SharedPtr( CL_DisplayTarget::current()->create_system_font_target( impl.get(), font_name, letters, height, width, bold, italic, underline, strikeout)); } CL_Font::CL_Font( const std::string &font_name, const std::string &letter_chars, int height, int width, bool bold, bool italic, bool underline, bool strikeout) : impl(new CL_Font_Generic) { impl->target = CL_SharedPtr( CL_DisplayTarget::current()->create_system_font_target( impl.get(), font_name, letter_chars, height, width, bold, italic, underline, strikeout)); } CL_Font::~CL_Font() { resource.unload(); } ///////////////////////////////////////////////////////////////////////////// // CL_Font attributes: std::string CL_Font::get_delims() const { return impl->delims; } int CL_Font::get_width_offset() const { return impl->width_offset; } int CL_Font::get_height_offset() const { return impl->height_offset; } void CL_Font::get_scale(float &x, float &y) const { impl->target->get_scale(x, y); } float CL_Font::get_alpha() const { return impl->target->get_alpha(); } void CL_Font::get_color(float &red, float &green, float &blue, float &alpha) const { impl->target->get_color(red, green, blue, alpha); } CL_Color CL_Font::get_color() const { float r,g,b,a; get_color(r, g, b, a); return CL_Color(int(r*255.0f), int(g*255.0f), int(b*255.0f), int(a*255.0f)); } void CL_Font::get_blend_func(CL_BlendFunc &src, CL_BlendFunc &dest) const { impl->target->get_blend_func(src, dest); } void CL_Font::get_glyph_rot_hotspot(CL_Origin &origin, int &x, int &y) const { impl->target->get_rot_hotspot(origin, x, y); } void CL_Font::get_alignment(CL_Origin &origin, int &x, int &y) const { origin = impl->trans_origin; x = impl->trans_x; y = impl->trans_y; } int CL_Font::get_height() const { return impl->target->get_height(); } int CL_Font::get_height(const std::string &str, CL_Size max_size) const { return get_size(str, max_size).height; } int CL_Font::get_height( std::string::const_iterator start, std::string::const_iterator end, CL_Size max_size) const { return get_size(start, end, max_size).height; } int CL_Font::get_width(unsigned int letter) const { return impl->target->get_width(letter); } int CL_Font::get_width( const std::string &str, CL_Size max_size) const { return get_size(str, max_size).width; } int CL_Font::get_width( std::string::const_iterator start, std::string::const_iterator end, CL_Size max_size) const { return get_size(start, end, max_size).width; } CL_Size CL_Font::get_size( std::string::const_iterator start, std::string::const_iterator end, CL_Size max_size) const { CL_GlyphBuffer gb; draw_to_gb(start, end, gb, max_size); return gb.get_size(); } CL_Size CL_Font::get_size(unsigned int letter) const { return CL_Size(get_width(letter), get_height()); } CL_Size CL_Font::get_size( const std::string &str, CL_Size max_size) const { return get_size(str.begin(), str.end(), max_size); } CL_Rect CL_Font::bounding_rect( CL_Rect dest, std::string::const_iterator start, std::string::const_iterator end) const { CL_GlyphBuffer gb; draw_to_gb(start, end, gb, dest.get_size()); gb.set_alignment(impl->trans_origin, impl->trans_x, impl->trans_y); return gb.bounding_rect(dest); } CL_Rect CL_Font::bounding_rect(int x, int y, const std::string &str) const { return bounding_rect(CL_Rect(x, y, x, y), str.begin(), str.end()); } CL_Rect CL_Font::bounding_rect(CL_Rect dest, const std::string &str) const { return bounding_rect(dest, str.begin(), str.end()); } CL_Rect CL_Font::bounding_rect( int x, int y, std::string::const_iterator start, std::string::const_iterator end) const { return bounding_rect(CL_Rect(x, y, x, y), start, end); } bool CL_Font::is_glyph(unsigned int chr) const { return impl->target->is_glyph(chr); } ///////////////////////////////////////////////////////////////////////////// // CL_Font operations: CL_Font &CL_Font::operator =(const CL_Font ©) { impl = copy.impl; resource.unload(); resource = copy.resource; resource.load(); return *this; } CL_Font::operator bool() const { return (impl.get() != 0); } int CL_Font::draw( CL_Rect dest, std::string::const_iterator start, std::string::const_iterator end, CL_GraphicContext *context) const { if (context == 0) context = CL_Display::get_current_window()->get_gc(); CL_GlyphBuffer gb; int dist = draw_to_gb(start, end, gb, dest.get_size()); gb.set_alignment(impl->trans_origin, impl->trans_x, impl->trans_y); if (impl->trans_origin == origin_top_left) gb.fixed_draw(dest.left - impl->trans_x, dest.top - impl->trans_y, context); else gb.draw(dest, context); return dist; } int CL_Font::draw( int x, int y, const std::string &str, CL_GraphicContext *context) const { return draw(CL_Rect(x, y, x, y), str.begin(), str.end(), context); } int CL_Font::draw( CL_Rect dest, const std::string &str, CL_GraphicContext *context) const { return draw(dest, str.begin(), str.end(), context); } int CL_Font::draw( int x, int y, std::string::const_iterator start, std::string::const_iterator end, CL_GraphicContext *context) const { return draw(CL_Rect(x, y, x, y), start, end, context); } //Given a CL_GlyphBuffer, ensures that a given line is flush with the line above it //Returns the offset that was applied int squish_line(CL_GlyphBuffer& gb, CL_GlyphBuffer::Range line) { std::vector& glyphs = gb.get_glyphs(); if (line.start <= line.end) return 0; if (glyphs.size() != 0 && line.start != 0) { int dist; //The offset we'll apply to y ord of each glyph in the line int curline_top = gb.internal_rect(line).top; if (curline_top == 0) return 0; if (line.start != 0) { int prevline_bottom = gb.internal_rect(gb.get_line(line.start-1)).bottom; if (prevline_bottom == 0) return 0; else dist = prevline_bottom - curline_top; } else { dist = -curline_top; //Move to 0 } for (int x = line.start; x != line.end; ++x) glyphs[x].pos.y += dist; return dist; } return 0; } int CL_Font::draw_to_gb( std::string::const_iterator start, std::string::const_iterator end, CL_GlyphBuffer &gb, CL_Size max_size) const { std::vector& glyphs = gb.get_glyphs(); //Shortcut to the glyphs vector in the gb const int font_height = impl->target->get_height(); //Distance to move at carriage feed const int orig_size = glyphs.size(); //Size of the glyphs vector before we start messing with it //Barf if things look evil if ( start >= end || font_height <= 0 || max_size.height < 0 || max_size.width < 0) { return 0; } CL_Point pos(0, 0); //Drawing position, at the top of the current line int line_height = font_height;//Height of the current line int line_start = 0; //Index of the character beginning the current line bool squishing = false; //True if we need to do squishing (if there's fonts of differing height on this line) //If we aren't starting at the beginning of a line, then we need to compensate //Either the line is taller than this font, which means any new glyphs we add on this line need to be pushed down //Or this font is taller than previous glyphs on the line, which means we need to move down the previous glyphs if (glyphs.size() != 0) { const CL_GlyphBuffer::Range eline(gb.get_line(glyphs.size()-1)); //Glyphs in the final existing GB line const CL_Rect eline_rect(gb.internal_rect(eline, false)); //Rectangle of eline, incl. invis glyphs //Place pos directly after the last glyph in the buffer pos.x = eline_rect.right; pos.y = eline_rect.top; //If glyphs already on this line are taller than this font, then adjust height for this line if (eline_rect.get_height() > font_height) line_height = eline_rect.get_height(); //If the last character of that line is a newline, do a carriage return //If it isn't, and our font is taller than previous glyphs on the line, then move those glyphs down to match if (glyphs[eline.end-1].character == '\n') { //Carriage return pos.x = 0; pos.y += line_height; } else if (eline_rect.get_height() < font_height) { //If we have enough room, move the existing glyphs down to meet our baseline if (max_size.height == 0 || pos.y + font_height <= max_size.height) for (int x = eline.start; x < eline.end; ++x) glyphs[x].pos.y += font_height - eline_rect.get_height(); } line_start = gb.get_line(glyphs.size()-1).start; if (line_height != font_height) squishing = true; } //If inserting the first glyph would go over the height limit, end the draw now if (max_size.height != 0 && pos.y + font_height > max_size.height) { if (squishing) squish_line(gb, CL_GlyphBuffer::Range(line_start, glyphs.size())); return 0; } //Put a marker for this font into the GB, and reserve a bit of space in the GB's glyph vector gb.change_font(*this); glyphs.reserve(cl_min(end - start, 1000)); //For every character in the input range (or as far through that range as we can get) for (std::string::const_iterator it = start; it != end; ++it) { const unsigned int chr = ((unsigned char) *it); // The character this glyph represents const int gwidth = get_width(chr); //Width of the glyph //If a character is wider than max width, stop the presses if (max_size.width != 0 && gwidth > max_size.width) { if (squishing) squish_line(gb, CL_GlyphBuffer::Range(line_start, glyphs.size())); return glyphs.size() - orig_size; } //If this character extends over the wrapping border, and word wrapping is on, and it has a glyph... if (max_size.width != 0 && pos.x + gwidth > max_size.width && is_glyph(chr)) { //Carriage return pos.x = 0; pos.y += line_height; line_height = font_height; //Try to find a delimiter on the last line int delim_idx = -1; for (int glyph_idx = glyphs.size()-1; glyph_idx >= line_start; --glyph_idx) { if ( impl->delims.find(glyphs[glyph_idx].character) != std::string::npos && (!is_glyph(glyph_idx) || gb.internal_rect(glyph_idx).right <= max_size.width) ) { delim_idx = glyph_idx; break; } } //If this line has a delimiter... if (delim_idx != -1) { //Height of glyphs after the delimiter int wrapline_height = gb.get_height(CL_GlyphBuffer::Range(delim_idx+1, glyphs.size())); //Height of the line we're wrapping the text onto, which becomes the new last line line_height = cl_max(wrapline_height, font_height); //If we don't have enough vertical room to wrap glyphs here, end the draw if (max_size.height != 0 && pos.y + line_height > max_size.height) { //Clip out that final partial word gb.remove(CL_GlyphBuffer::Range(delim_idx + 1, glyphs.size())); if (squishing) squish_line(gb, CL_GlyphBuffer::Range(line_start, glyphs.size())); return glyphs.size() - orig_size; } else { //Move glyphs after the delimiter onto the next line, move pos right after them std::vector::size_type x; for (x = delim_idx + 1; x != glyphs.size(); ++x) { glyphs[x].pos = CL_Point(pos.x, glyphs[x].pos.y + line_height); pos.x += gb.get_width(static_cast(x)); } //Remove excess vertical space from the previous-to-last-line //Move the cursor and the line segment we just wrapped up to compensate if (squishing) { int squishdist = squish_line(gb, CL_GlyphBuffer::Range(line_start, delim_idx+1)); pos.y += squishdist; for (x = delim_idx + 1; x != glyphs.size(); ++x) glyphs[x].pos.y += squishdist; } line_start = delim_idx + 1; squishing = false; } } //This line doesn't have a delimiter, so we have to break in the middle of a word else { //Remove any excess vertical space on the last line, move cursor to compensate if (squishing) pos.y += squish_line(gb, CL_GlyphBuffer::Range(line_start, glyphs.size())); line_start = glyphs.size(); squishing = false; //If we don't have enough vertical room to put any glyphs here, end the draw if (max_size.height != 0 && pos.y + line_height > max_size.height) return glyphs.size() - orig_size; } } //Insert the glyph glyphs.push_back(CL_GlyphBuffer::Glyph( CL_Point(pos.x, pos.y + (line_height - font_height)), chr)); //Advance the cursor pos.x += gwidth; //If this character is a newline if (chr == '\n') { //Carriage return pos.x = 0; pos.y += line_height; line_height = font_height; //Remove excess vertical space, adjust cursor to compensate if (squishing) pos.y += squish_line(gb, CL_GlyphBuffer::Range(line_start, glyphs.size())); line_start = glyphs.size(); squishing = false; //If we've exceeded height, then end the draw if (max_size.height != 0 && pos.y + line_height > max_size.height) return glyphs.size() - orig_size; } } //We made it through the entire input string, huzzah if (squishing) squish_line(gb, CL_GlyphBuffer::Range(line_start, glyphs.size())); return glyphs.size() - orig_size; } int CL_Font::draw_to_gb( const std::string &str, CL_GlyphBuffer &gb, CL_Size max_size) const { return draw_to_gb(str.begin(), str.end(), gb, max_size); } void CL_Font::draw_glyph(int x, int y, unsigned int glyph, float ang, CL_GraphicContext *gc) { if (gc == 0) gc = CL_Display::get_current_window()->get_gc(); impl->target->draw_glyph(x, y, glyph, ang, gc); } void CL_Font::draw_character(int x, int y, unsigned int chr, float ang, CL_GraphicContext *gc) { if (gc == 0) gc = CL_Display::get_current_window()->get_gc(); impl->target->draw_character(x, y, chr, ang, gc); } void CL_Font::set_delims(const std::string& delims) { impl->delims = delims; } void CL_Font::set_width_offset(int offset) { impl->width_offset = offset; } void CL_Font::set_height_offset(int offset) { impl->height_offset = offset; } void CL_Font::set_scale(float x, float y) { impl->target->set_scale(x, y); } void CL_Font::set_alpha(float alpha) { impl->target->set_alpha(alpha); } void CL_Font::set_color(float r, float g, float b, float a) { impl->target->set_color(r, g, b, a); } void CL_Font::set_color(const CL_Color& c) { set_color(float(c.get_red())/255.0f, float(c.get_green())/255.0f, float(c.get_blue())/255.0f, float(c.get_alpha())/255.0f); } void CL_Font::set_blend_func(CL_BlendFunc src, CL_BlendFunc dest) { impl->target->set_blend_func(src, dest); } void CL_Font::set_glyph_rot_hotspot(CL_Origin origin, int x, int y) { impl->target->set_rot_hotspot(origin, x, y); } void CL_Font::set_alignment(CL_Origin origin, int x, int y) { impl->trans_origin = origin; impl->trans_x = x; impl->trans_y = y; } CL_Sprite& CL_Font::get_glyphs() { CL_Font_Target* target = impl->target; CL_Font_Target_Sprite* sprite_target = static_cast(target); return sprite_target->get_glyphs(); }