/* ** 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/Core/Math/point.h" #include "API/Core/Math/rect.h" #include "API/Core/System/error.h" #include "API/Display/glyph_buffer.h" #include "API/Display/graphic_context.h" #include "API/Display/display.h" #include "API/Display/display_window.h" #include #include #include #define cl_min(a,b) ((a < b) ? a : b) #define cl_max(a,b) ((a > b) ? a : b) CL_GlyphBuffer::CL_GlyphBuffer() : scale_x(1.0f), scale_y(1.0f), rot_angle(0.0f), rot_origin(origin_center), rot_x(0), rot_y(0), trans_origin(origin_top_left), trans_x(0), trans_y(0) {} void CL_GlyphBuffer::Range::verify(const CL_GlyphBuffer& gb) { if (start < 0) start = 0; if (end > static_cast(gb.get_glyphs().size())) end = gb.get_glyphs().size(); if (end < 0) end = 0; if (start > end) start = end; } void CL_GlyphBuffer::Range::remove_invis_left(const CL_GlyphBuffer& gb) { verify(gb); while ( start < end && gb.get_font_for(start)->second.is_glyph(gb.get_glyphs()[start].character) == 0) ++start; } void CL_GlyphBuffer::Range::remove_invis_right(const CL_GlyphBuffer& gb) { verify(gb); while ( end > start && gb.get_font_for(end-1)->second.is_glyph(gb.get_glyphs()[end-1].character) == 0) --end; } std::map::iterator CL_GlyphBuffer::get_font_for(int glyph_index) { if (font_markers.size() == 0) return font_markers.end(); if (glyph_index < 0) glyph_index = 0; if (glyph_index > static_cast(glyphs.size() - 1)) glyph_index = glyphs.size() - 1; //Find a font_marker associated with an index above the given one, or the end std::map::iterator it = font_markers.begin(); for (; it != font_markers.end(); ++it) if (it->first > glyph_index) break; if (it == font_markers.begin()) return font_markers.end(); return --it; } std::map::const_iterator CL_GlyphBuffer::get_font_for(int glyph_index) const { if (glyph_index < 0) glyph_index = 0; if (glyph_index > static_cast(glyphs.size() - 1)) glyph_index = glyphs.size() - 1; //Upper bound finds the first element whose key is greater than the given key //So after decrementing it, then it will be the font marker which covers the given glyph //Remember that font markers affect glyphs with indices >= their key number, up to the next marker's key std::map::const_iterator it = font_markers.upper_bound(glyph_index); if (it == font_markers.begin()) return font_markers.end(); return --it; } CL_Rect CL_GlyphBuffer::bounding_rect(Range range, CL_Rect dest, bool ignore_invis) const { CL_Rect retVal = internal_rect(range, ignore_invis); CL_Point offset = i2e_offset(retVal, dest); retVal.left += offset.x; retVal.top += offset.y; retVal.right += offset.x; retVal.bottom += offset.y; return retVal; } CL_Rect CL_GlyphBuffer::internal_rect(Range range, bool ignore_invis) const { range.verify(*this); //Use a weird inverse rectangle to start out with //Since we get the bounding rect by just comparing each side of each glyph with respective sides of the rect-in-progress... //Using this rect means the first glyph we compare against determines the initial rectangle CL_Rect retVal(INT_MAX, INT_MAX, INT_MIN, INT_MIN); //For every glyph in the buffer... for (Iter it(*this, range); !it.at_end(range); ++it) { std::vector::const_iterator glyph = glyphs.begin() + it.get_glyph_num(); //Glyph vector element const CL_Font& fnt = it.get_font(); //If ignore_invis is turned on, check to see if this glyph is supplied by the font if (!ignore_invis || (ignore_invis && fnt.is_glyph(glyph->character))) { float scale_eff_x, scale_eff_y; it.get_scale_eff(scale_eff_x, scale_eff_y); //Rectangle containing the glyph CL_Rect gr( glyph->pos.x, glyph->pos.y, glyph->pos.x + (int)(fnt.get_width(glyph->character) * scale_eff_x), glyph->pos.y + (int)(fnt.get_height() * scale_eff_y)); //Compensate for rotation of the glyph and font rotation float angle_eff = it.get_angle_eff(); if (angle_eff != 0.0) { CL_Origin grot_hotspot; int grot_x, grot_y; fnt.get_glyph_rot_hotspot(grot_hotspot, grot_x, grot_y); gr = gr.get_rot_bounds(grot_hotspot, grot_x, grot_y, angle_eff); } //Push on the sides of the returned rect so it contains this glyph retVal.left = cl_min(gr.left, retVal.left); retVal.right = cl_max(gr.right, retVal.right); retVal.top = cl_min(gr.top, retVal.top); retVal.bottom = cl_max(gr.bottom, retVal.bottom); } } //If there weren't any real glyphs, then return a rectangle with zero size if (retVal == CL_Rect(INT_MAX, INT_MAX, INT_MIN, INT_MIN)) retVal = CL_Rect(0,0,0,0); return retVal; } std::vector CL_GlyphBuffer::get_lines(CL_GlyphBuffer::Range range) const { range.verify(*this); std::vector ret; std::map::const_iterator curfnt; if (range.start > 0) curfnt = get_font_for(range.start-1); else curfnt = font_markers.begin(); //Move the beginning of the input range to the beginning of the line while (range.start > 0) { //If curfnt isnt the font of range.start-1, make it that font if (curfnt->first > range.start-1) --curfnt; if (is_linebreak(range.start-1, curfnt->second)) break; --range.start; } //Advance until we've passed the end of the range and hit the linebreak at or after that std::map::const_iterator nextfnt = curfnt; ++nextfnt; for (int idx = range.start;; ++idx) { //If curfnt isnt the font of idx, make it that font if (nextfnt != font_markers.end() && idx >= nextfnt->first) { curfnt = nextfnt; ++nextfnt; } //If this glyph is the last glyph on a line... if (is_linebreak(idx, curfnt->second)) { //Add a line range to the return value if (ret.size() == 0) ret.push_back(CL_GlyphBuffer::Range(range.start, idx+1)); else ret.push_back(CL_GlyphBuffer::Range((ret.end()-1)->end, idx+1)); if (idx >= range.end) break; } } return ret; } CL_GlyphBuffer::Range CL_GlyphBuffer::get_line(int idx) const { CL_GlyphBuffer::Range ret(idx, idx); std::map::const_iterator idxfnt; if (idx > 0) idxfnt = get_font_for(idx-1); else idxfnt = font_markers.begin(); std::map::const_iterator curfnt = idxfnt; //Move the range start back to the beginning of the line while (ret.start > 0) { //If curfnt isnt the font of range.start-1, make it that font if (curfnt->first > ret.start-1) --curfnt; if (is_linebreak(ret.start-1, curfnt->second)) break; --ret.start; } //Move the range end to the end of the line curfnt = idxfnt; std::map::const_iterator nextfnt = curfnt; ++nextfnt; while (1) { //If curfnt isnt the font of idx, make it that font if (nextfnt != font_markers.end() && idx >= nextfnt->first) { curfnt = nextfnt; ++nextfnt; } //If ret.end is the last glyph on the line, then we're done if (is_linebreak(ret.end, curfnt->second)) { ++ret.end; break; } ++ret.end; } return ret; } //Utility function for add(), puts a map into another map with a key offset template void add_map_range( const std::map& src, std::map& dest, CL_GlyphBuffer::Range range, int offset) { const typename std::map::const_iterator end = src.upper_bound(range.end); for (typename std::map::const_iterator it = src.lower_bound(range.start); it != end; ++it) dest[it->first + offset] = it->second; } CL_GlyphBuffer &CL_GlyphBuffer::add( const CL_GlyphBuffer &other, Range range, int x, int y) { //Offset to make copied font markers/effects apply to after the end of the new buffer const int offset = glyphs.size(); glyphs.reserve(glyphs.size() + other.glyphs.size()); if ((x == 0) && (y == 0)) { for ( std::vector::const_iterator it = other.glyphs.begin() + range.start; it != other.glyphs.begin() + range.end; ++it) { glyphs.push_back(*it); } } else { for ( std::vector::const_iterator it = other.glyphs.begin() + range.start; it != other.glyphs.begin() + range.end; ++it) { glyphs.push_back(Glyph(CL_Point(it->pos.x + x, it->pos.y + y), it->character)); } } add_map_range(other.font_markers, font_markers, range, offset); add_map_range(other.color_effects, color_effects, range, offset); add_map_range(other.scale_x_effects, scale_x_effects, range, offset); add_map_range(other.scale_y_effects, scale_y_effects, range, offset); add_map_range(other.angle_effects, angle_effects, range, offset); return *this; } CL_GlyphBuffer &CL_GlyphBuffer::remove(Range range) { range.verify(*this); //Remove the elements themselves glyphs.erase(glyphs.begin() + range.start, glyphs.begin() + range.end); //Remove any map entries associated with the range, slide back any map entries after the range cut_range(font_markers, range.start, range.end); cut_range(color_effects, range.start, range.end); cut_range(scale_x_effects, range.start, range.end); cut_range(scale_y_effects, range.start, range.end); cut_range(angle_effects, range.start, range.end); return *this; } CL_GlyphBuffer &CL_GlyphBuffer::change_font(const CL_Font &font) { font_markers[glyphs.size()] = font; return *this; } void CL_GlyphBuffer::clear() { glyphs.clear(); font_markers.clear(); angle_effects.clear(); scale_x_effects.clear(); scale_y_effects.clear(); color_effects.clear(); } void CL_GlyphBuffer::justify_left(CL_GlyphBuffer::Range range, int left) { range.verify(*this); std::vector ranges = get_lines(range); for (std::vector::iterator it = ranges.begin(); it != ranges.end(); ++it) { CL_GlyphBuffer::Range vis_range = *it; CL_Rect line_rect = internal_rect(vis_range); int offset = left - line_rect.left; for (int glyph = it->start; glyph != it->end; ++glyph) glyphs[glyph].pos.x += offset; } } void CL_GlyphBuffer::justify_center(CL_GlyphBuffer::Range range, int center) { range.verify(*this); std::vector ranges = get_lines(range); for (std::vector::iterator it = ranges.begin(); it != ranges.end(); ++it) { CL_GlyphBuffer::Range vis_range = *it; vis_range.remove_invis_both(*this); CL_Rect line_rect = internal_rect(vis_range); int offset = center - ((line_rect.left + line_rect.right)/2); for (int glyph = it->start; glyph != it->end; ++glyph) glyphs[glyph].pos.x += offset; } } void CL_GlyphBuffer::justify_right(CL_GlyphBuffer::Range range, int right) { range.verify(*this); std::vector ranges = get_lines(range); for (std::vector::iterator it = ranges.begin(); it != ranges.end(); ++it) { CL_GlyphBuffer::Range vis_range = *it; vis_range.remove_invis_right(*this); CL_Rect line_rect = internal_rect(vis_range); int offset = right - line_rect.right; for (int glyph = it->start; glyph != it->end; ++glyph) glyphs[glyph].pos.x += offset; } } void CL_GlyphBuffer::set_angle(float angle) { while(angle >= 360.0f) angle -= 360.0f; while(angle < 0.0f) angle += 360.0f; rot_angle = angle; } void CL_GlyphBuffer::Iter::get_scale_eff(float& x, float& y) const { const std::map& scale_x_effects = pgb->get_scale_x_effects(); std::map::const_iterator x_eff = scale_x_effects.find(glyph_num); if (x_eff != scale_x_effects.end()) x = x_eff->second; else x = 1.0; const std::map& scale_y_effects = pgb->get_scale_y_effects(); std::map::const_iterator y_eff = scale_y_effects.find(glyph_num); if (y_eff != scale_y_effects.end()) y = y_eff->second; else y = 1.0; } float CL_GlyphBuffer::Iter::get_angle_eff() const { const std::map& angle_effects = pgb->get_angle_effects(); std::map::const_iterator ang_eff = angle_effects.find(glyph_num); if (ang_eff != angle_effects.end()) return ang_eff->second; else return 0.0; } CL_Color CL_GlyphBuffer::Iter::get_color_eff(const CL_Font& fnt) const { const std::map& color_effects = pgb->get_color_effects(); std::map::const_iterator col_eff = color_effects.find(glyph_num); if (col_eff != color_effects.end()) return col_eff->second; else return fnt.get_color(); } const CL_Font& CL_GlyphBuffer::Iter::get_font() const { std::map::const_iterator it = pgb->get_font_for(glyph_num); if (it == pgb->get_font_markers().end()) throw CL_Error("No font in the font markers map to draw the glyph(s) with"); return it->second; } bool CL_GlyphBuffer::is_linebreak(int idx, const CL_Font& fnt) const { const unsigned int chr = glyphs[idx].character; //This glyph and the glyph after it are on different lines if: if ( //...this is the last glyph of the buffer, which makes it the last glyph of the last line idx == static_cast(glyphs.size()-1) || //...this glyph is a manual newline character ('\n') chr == '\n' || //...the next glyph's left side is to the left of this glyph's right side glyphs[idx+1].pos.x < glyphs[idx].pos.x + fnt.get_width(chr) ) return true; return false; } CL_Point CL_GlyphBuffer::i2e_offset(CL_Rect src, CL_Rect dest) const { //Align the source rectangle to the origin CL_Point retVal(-src.left, -src.top); retVal -= calc_origin(trans_origin, CL_Size(src.get_width(), src.get_height())); //Align the origin to the dest rectangle retVal.x += dest.left; retVal.y += dest.top; retVal += calc_origin(trans_origin, CL_Size(dest.get_width(), dest.get_height())); //Apply the translation offsets retVal.x -= trans_x; retVal.y -= trans_y; return retVal; } CL_Point CL_GlyphBuffer::get_scale_hotspot(CL_Rect irect) const { CL_Point scale_hotspot(irect.left, irect.top); scale_hotspot += calc_origin(trans_origin, irect.get_size()); scale_hotspot.x += trans_x; scale_hotspot.y += trans_y; return scale_hotspot; } CL_Point CL_GlyphBuffer::get_rot_hotspot(CL_Rect irect) const { CL_Point rot_hotspot(irect.left, irect.top); rot_hotspot += calc_origin(rot_origin, irect.get_size()); rot_hotspot.x += rot_x; rot_hotspot.y += rot_y; return rot_hotspot; } void CL_GlyphBuffer::draw_glyphs( Range range, CL_Point offset, CL_Rect irect, CL_GraphicContext* gc) const { range.verify(*this); if (gc == 0) gc = CL_Display::get_current_window()->get_gc(); CL_Point rot_hotspot; CL_Point scale_hotspot(0,0); //True if we're doing total GlyphBuffer rotation bool rotate = false; if (rot_angle != 0.0) { rotate = true; if (irect == CL_Rect(0,0,0,0)) irect = internal_rect(); rot_hotspot = get_rot_hotspot(irect); } //True if we're doing total GlyphBuffer scaling bool scale = false; if (scale_x != 1.0 || scale_y != 1.0) { scale = true; if (irect == CL_Rect(0,0,0,0)) irect = internal_rect(); scale_hotspot = get_scale_hotspot(irect); } //For every glyph in the buffer... for (Iter it(*this, range); !it.at_end(range); ++it) { std::vector::const_iterator glyph = glyphs.begin() + it.get_glyph_num(); //Reference to the glyph vector element CL_Point draw_pos(glyph->pos); //Apply GB scaling if enabled if (scale) { //The offset from scale_hotspot to the new drawing position CL_Point scale_offset = draw_pos - scale_hotspot; scale_offset.x = (int)(scale_offset.x*scale_x); scale_offset.y = (int)(scale_offset.y*scale_y); draw_pos = scale_hotspot + scale_offset; } //Apply GB rotation if enabled if (rotate) draw_pos = draw_pos.rotate(rot_hotspot, rot_angle); //Move the glyph into the dest rectangle draw_pos += offset; //We reimplement get_font_for() here so that we can get a mutable iterator //That's because there's no such thing as a mutable method std::map::iterator fntiter = font_markers.upper_bound(it.get_glyph_num()); if (fntiter == font_markers.begin()) throw CL_Error("No font in the font markers map to draw the glyph(s) with"); CL_Font& font = (--fntiter)->second; if (font.is_glyph(glyph->character)) { //Record the font's original color and scale CL_Color orig_color = font.get_color(); float orig_scale_x, orig_scale_y; font.get_scale(orig_scale_x, orig_scale_y); //Temporarily set the Font's color and scale to the current glyph's properties CL_Color eff_color = it.get_color_eff(font); float scale_eff_x, scale_eff_y; it.get_scale_eff(scale_eff_x, scale_eff_y); //If there's any scale effect or scale property, apply it if (eff_color != orig_color) font.set_color(eff_color); if (scale_x != 1.0 || scale_eff_x != 1.0 || scale_y != 1.0 || scale_eff_y != 1.0) font.set_scale(scale_eff_x * scale_x * orig_scale_x, scale_eff_y * scale_y * orig_scale_y); //Draw the glyph, using the current angle font.draw_character(draw_pos.x, draw_pos.y, glyph->character, it.get_angle_eff() + rot_angle, gc); //Restore the original values, if they were changed if (eff_color != orig_color) font.set_color(orig_color); if (scale_x != 1.0 || scale_eff_x != 1.0 || scale_y != 1.0 || scale_eff_y != 1.0) font.set_scale(orig_scale_x, orig_scale_y); } } }