diff options
Diffstat (limited to 'graphics/font.cpp')
-rw-r--r-- | graphics/font.cpp | 305 |
1 files changed, 212 insertions, 93 deletions
diff --git a/graphics/font.cpp b/graphics/font.cpp index 3b00cd8568..dba48249bc 100644 --- a/graphics/font.cpp +++ b/graphics/font.cpp @@ -17,6 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * */ #include "graphics/font.h" @@ -26,123 +27,109 @@ namespace Graphics { -int Font::getKerningOffset(byte left, byte right) const { +int Font::getKerningOffset(uint32 left, uint32 right) const { return 0; } -int Font::getStringWidth(const Common::String &str) const { - int space = 0; - uint last = 0; - - for (uint i = 0; i < str.size(); ++i) { - const uint cur = str[i]; - space += getCharWidth(cur) + getKerningOffset(last, cur); - last = cur; - } - - return space; +Common::Rect Font::getBoundingBox(uint32 chr) const { + return Common::Rect(getCharWidth(chr), getFontHeight()); } -void Font::drawString(Surface *dst, const Common::String &sOld, int x, int y, int w, uint32 color, TextAlign align, int deltax, bool useEllipsis) const { - assert(dst != 0); - const int leftX = x, rightX = x + w; - uint i; - Common::String s = sOld; - int width = getStringWidth(s); - Common::String str; +namespace { - if (useEllipsis && width > w && s.hasSuffix("...")) { - // String is too wide. Check whether it ends in an ellipsis - // ("..."). If so, remove that and try again! - s.deleteLastChar(); - s.deleteLastChar(); - s.deleteLastChar(); - width = getStringWidth(s); - } +template<class StringType> +Common::Rect getBoundingBoxImpl(const Font &font, const StringType &str, int x, int y, int w, TextAlign align, int deltax) { + // We follow the logic of drawStringImpl here. The only exception is + // that we do allow an empty width to be specified here. This allows us + // to obtain the complete bounding box of a string. + const int leftX = x, rightX = w ? (x + w) : 0x7FFFFFFF; + int width = font.getStringWidth(str); - if (useEllipsis && width > w) { - // String is too wide. So we shorten it "intelligently" by - // replacing parts of the string by an ellipsis. There are - // three possibilities for this: replace the start, the end, or - // the middle of the string. What is best really depends on the - // context; but unless we want to make this configurable, - // replacing the middle seems to be a good compromise. + if (align == kTextAlignCenter) + x = x + (w - width)/2; + else if (align == kTextAlignRight) + x = x + w - width; + x += deltax; - const int ellipsisWidth = getStringWidth("..."); + bool first = true; + Common::Rect bbox; - // SLOW algorithm to remove enough of the middle. But it is good enough - // for now. - const int halfWidth = (w - ellipsisWidth) / 2; - int w2 = 0; - uint last = 0; - - for (i = 0; i < s.size(); ++i) { - const uint cur = s[i]; - int charWidth = getCharWidth(cur) + getKerningOffset(last, cur); - if (w2 + charWidth > halfWidth) - break; - last = cur; - w2 += charWidth; - str += cur; + typename StringType::unsigned_type last = 0; + for (typename StringType::const_iterator i = str.begin(), end = str.end(); i != end; ++i) { + const typename StringType::unsigned_type cur = *i; + x += font.getKerningOffset(last, cur); + last = cur; + w = font.getCharWidth(cur); + if (x+w > rightX) + break; + if (x+w >= leftX) { + Common::Rect charBox = font.getBoundingBox(cur); + charBox.translate(x, y); + if (first) { + bbox = charBox; + first = false; + } else { + bbox.extend(charBox); + } } + x += w; + } - // At this point we know that the first 'i' chars are together 'w2' - // pixels wide. We took the first i-1, and add "..." to them. - str += "..."; - last = '.'; - - // The original string is width wide. Of those we already skipped past - // w2 pixels, which means (width - w2) remain. - // The new str is (w2+ellipsisWidth) wide, so we can accommodate about - // (w - (w2+ellipsisWidth)) more pixels. - // Thus we skip ((width - w2) - (w - (w2+ellipsisWidth))) = - // (width + ellipsisWidth - w) - int skip = width + ellipsisWidth - w; - for (; i < s.size() && skip > 0; ++i) { - const uint cur = s[i]; - skip -= getCharWidth(cur) + getKerningOffset(last, cur); - last = cur; - } + return bbox; +} - // Append the remaining chars, if any - for (; i < s.size(); ++i) { - str += s[i]; - } +template<class StringType> +int getStringWidthImpl(const Font &font, const StringType &str) { + int space = 0; + typename StringType::unsigned_type last = 0; - width = getStringWidth(str); - } else { - str = s; + for (uint i = 0; i < str.size(); ++i) { + const typename StringType::unsigned_type cur = str[i]; + space += font.getCharWidth(cur) + font.getKerningOffset(last, cur); + last = cur; } + return space; +} + +template<class StringType> +void drawStringImpl(const Font &font, Surface *dst, const StringType &str, int x, int y, int w, uint32 color, TextAlign align, int deltax) { + // The logic in getBoundingImpl is the same as we use here. In case we + // ever change something here we will need to change it there too. + assert(dst != 0); + + const int leftX = x, rightX = x + w; + int width = font.getStringWidth(str); + if (align == kTextAlignCenter) x = x + (w - width)/2; else if (align == kTextAlignRight) x = x + w - width; x += deltax; - uint last = 0; - for (i = 0; i < str.size(); ++i) { - const uint cur = str[i]; - x += getKerningOffset(last, cur); + typename StringType::unsigned_type last = 0; + for (typename StringType::const_iterator i = str.begin(), end = str.end(); i != end; ++i) { + const typename StringType::unsigned_type cur = *i; + x += font.getKerningOffset(last, cur); last = cur; - w = getCharWidth(cur); + w = font.getCharWidth(cur); if (x+w > rightX) break; - if (x >= leftX) - drawChar(dst, str[i], x, y, color); + if (x+w >= leftX) + font.drawChar(dst, cur, x, y, color); x += w; } } - +template<class StringType> struct WordWrapper { - Common::Array<Common::String> &lines; + Common::Array<StringType> &lines; int actualMaxLineWidth; - WordWrapper(Common::Array<Common::String> &l) : lines(l), actualMaxLineWidth(0) { + WordWrapper(Common::Array<StringType> &l) : lines(l), actualMaxLineWidth(0) { } - void add(Common::String &line, int &w) { + void add(StringType &line, int &w) { if (actualMaxLineWidth < w) actualMaxLineWidth = w; @@ -153,10 +140,11 @@ struct WordWrapper { } }; -int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines) const { - WordWrapper wrapper(lines); - Common::String line; - Common::String tmpStr; +template<class StringType> +int wordWrapTextImpl(const Font &font, const StringType &str, int maxWidth, Common::Array<StringType> &lines) { + WordWrapper<StringType> wrapper(lines); + StringType line; + StringType tmpStr; int lineWidth = 0; int tmpWidth = 0; @@ -173,10 +161,10 @@ int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Co // of a line. If we encounter such a word, we have to wrap it over multiple // lines. - uint last = 0; - for (Common::String::const_iterator x = str.begin(); x != str.end(); ++x) { - const byte c = *x; - const int w = getCharWidth(c) + getKerningOffset(last, c); + typename StringType::unsigned_type last = 0; + for (typename StringType::const_iterator x = str.begin(); x != str.end(); ++x) { + const typename StringType::unsigned_type c = *x; + const int w = font.getCharWidth(c) + font.getKerningOffset(last, c); last = c; const bool wouldExceedWidth = (lineWidth + tmpWidth + w > maxWidth); @@ -212,7 +200,7 @@ int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Co tmpStr.deleteChar(0); // This is not very fast, but it is the simplest way to // assure we do not mess something up because of kerning. - tmpWidth = getStringWidth(tmpStr); + tmpWidth = font.getStringWidth(tmpStr); } } else { wrapper.add(tmpStr, tmpWidth); @@ -232,5 +220,136 @@ int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Co return wrapper.actualMaxLineWidth; } +} // End of anonymous namespace + +Common::Rect Font::getBoundingBox(const Common::String &input, int x, int y, const int w, TextAlign align, int deltax, bool useEllipsis) const { + // In case no width was given we cannot use ellipsis or any alignment + // apart from left alignment. + if (w == 0) { + if (useEllipsis) { + warning("Font::getBoundingBox: Requested ellipsis when no width was specified"); + } + + if (align != kTextAlignLeft) { + warning("Font::getBoundingBox: Requested text alignment when no width was specified"); + } + + useEllipsis = false; + align = kTextAlignLeft; + } + + const Common::String str = useEllipsis ? handleEllipsis(input, w) : input; + return getBoundingBoxImpl(*this, str, x, y, w, align, deltax); +} + +Common::Rect Font::getBoundingBox(const Common::U32String &str, int x, int y, const int w, TextAlign align) const { + // In case no width was given we cannot any alignment apart from left + // alignment. + if (w == 0) { + if (align != kTextAlignLeft) { + warning("Font::getBoundingBox: Requested text alignment when no width was specified"); + } + + align = kTextAlignLeft; + } + + return getBoundingBoxImpl(*this, str, x, y, w, align, 0); +} + +int Font::getStringWidth(const Common::String &str) const { + return getStringWidthImpl(*this, str); +} + +int Font::getStringWidth(const Common::U32String &str) const { + return getStringWidthImpl(*this, str); +} + +void Font::drawString(Surface *dst, const Common::String &str, int x, int y, int w, uint32 color, TextAlign align, int deltax, bool useEllipsis) const { + Common::String renderStr = useEllipsis ? handleEllipsis(str, w) : str; + drawStringImpl(*this, dst, renderStr, x, y, w, color, align, deltax); +} + +void Font::drawString(Surface *dst, const Common::U32String &str, int x, int y, int w, uint32 color, TextAlign align) const { + drawStringImpl(*this, dst, str, x, y, w, color, align, 0); +} + +int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines) const { + return wordWrapTextImpl(*this, str, maxWidth, lines); +} + +int Font::wordWrapText(const Common::U32String &str, int maxWidth, Common::Array<Common::U32String> &lines) const { + return wordWrapTextImpl(*this, str, maxWidth, lines); +} + +Common::String Font::handleEllipsis(const Common::String &input, int w) const { + Common::String s = input; + int width = getStringWidth(s); + + if (width > w && s.hasSuffix("...")) { + // String is too wide. Check whether it ends in an ellipsis + // ("..."). If so, remove that and try again! + s.deleteLastChar(); + s.deleteLastChar(); + s.deleteLastChar(); + width = getStringWidth(s); + } + + if (width > w) { + Common::String str; + + // String is too wide. So we shorten it "intelligently" by + // replacing parts of the string by an ellipsis. There are + // three possibilities for this: replace the start, the end, or + // the middle of the string. What is best really depends on the + // context; but unless we want to make this configurable, + // replacing the middle seems to be a good compromise. + + const int ellipsisWidth = getStringWidth("..."); + + // SLOW algorithm to remove enough of the middle. But it is good enough + // for now. + const int halfWidth = (w - ellipsisWidth) / 2; + int w2 = 0; + Common::String::unsigned_type last = 0; + uint i = 0; + + for (; i < s.size(); ++i) { + const Common::String::unsigned_type cur = s[i]; + int charWidth = getCharWidth(cur) + getKerningOffset(last, cur); + if (w2 + charWidth > halfWidth) + break; + last = cur; + w2 += charWidth; + str += cur; + } + + // At this point we know that the first 'i' chars are together 'w2' + // pixels wide. We took the first i-1, and add "..." to them. + str += "..."; + last = '.'; + + // The original string is width wide. Of those we already skipped past + // w2 pixels, which means (width - w2) remain. + // The new str is (w2+ellipsisWidth) wide, so we can accommodate about + // (w - (w2+ellipsisWidth)) more pixels. + // Thus we skip ((width - w2) - (w - (w2+ellipsisWidth))) = + // (width + ellipsisWidth - w) + int skip = width + ellipsisWidth - w; + for (; i < s.size() && skip > 0; ++i) { + const Common::String::unsigned_type cur = s[i]; + skip -= getCharWidth(cur) + getKerningOffset(last, cur); + last = cur; + } + + // Append the remaining chars, if any + for (; i < s.size(); ++i) { + str += s[i]; + } + + return str; + } else { + return s; + } +} } // End of namespace Graphics |