Otclient  14/8/2020
bitmapfont.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2010-2020 OTClient <https://github.com/edubart/otclient>
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20  * THE SOFTWARE.
21  */
22 
23 #include "bitmapfont.h"
24 #include "texturemanager.h"
25 #include "graphics.h"
26 #include "image.h"
27 
28 #include <framework/otml/otml.h>
29 
30 void BitmapFont::load(const OTMLNodePtr& fontNode)
31 {
32  OTMLNodePtr textureNode = fontNode->at("texture");
33  std::string textureFile = stdext::resolve_path(textureNode->value(), textureNode->source());
34  Size glyphSize = fontNode->valueAt<Size>("glyph-size");
35  m_glyphHeight = fontNode->valueAt<int>("height");
36  m_yOffset = fontNode->valueAt("y-offset", 0);
37  m_firstGlyph = fontNode->valueAt("first-glyph", 32);
38  m_glyphSpacing = fontNode->valueAt("spacing", Size(0,0));
39  int spaceWidth = fontNode->valueAt("space-width", glyphSize.width());
40 
41  // load font texture
42  m_texture = g_textures.getTexture(textureFile);
43 
44  if(OTMLNodePtr node = fontNode->get("fixed-glyph-width")) {
45  for(int glyph = m_firstGlyph; glyph < 256; ++glyph)
46  m_glyphsSize[glyph] = Size(node->value<int>(), m_glyphHeight);
47  } else {
48  calculateGlyphsWidthsAutomatically(Image::load(textureFile), glyphSize);
49  }
50 
51  // 32 and 160 are spaces (&nbsp;)
52  m_glyphsSize[32].setWidth(spaceWidth);
53  m_glyphsSize[160].setWidth(spaceWidth);
54 
55  // use 127 as spacer [Width: 1], Important for the current NPC highlighting system
56  m_glyphsSize[127].setWidth(1);
57 
58  // new line actually has a size that will be useful in multiline algorithm
59  m_glyphsSize[(uchar)'\n'] = Size(1, m_glyphHeight);
60 
61  // read custom widths
62  /*
63  if(OTMLNodePtr node = fontNode->get("glyph-widths")) {
64  for(const OTMLNodePtr& child : node->children())
65  m_glyphsSize[stdext::safe_cast<int>(child->tag())].setWidth(child->value<int>());
66  }
67  */
68 
69 
70  // calculate glyphs texture coords
71  int numHorizontalGlyphs = m_texture->getSize().width() / glyphSize.width();
72  for(int glyph = m_firstGlyph; glyph < 256; ++glyph) {
73  m_glyphsTextureCoords[glyph].setRect(((glyph - m_firstGlyph) % numHorizontalGlyphs) * glyphSize.width(),
74  ((glyph - m_firstGlyph) / numHorizontalGlyphs) * glyphSize.height(),
75  m_glyphsSize[glyph].width(),
76  m_glyphHeight);
77  }
78 }
79 
80 void BitmapFont::drawText(const std::string& text, const Point& startPos)
81 {
82  Size boxSize = g_painter->getResolution() - startPos.toSize();
83  Rect screenCoords(startPos, boxSize);
84  drawText(text, screenCoords, Fw::AlignTopLeft);
85 }
86 
87 void BitmapFont::drawText(const std::string& text, const Rect& screenCoords, Fw::AlignmentFlag align)
88 {
89  static CoordsBuffer coordsBuffer;
90  coordsBuffer.clear();
91 
92  calculateDrawTextCoords(coordsBuffer, text, screenCoords, align);
93  g_painter->drawTextureCoords(coordsBuffer, m_texture);
94 }
95 
96 void BitmapFont::calculateDrawTextCoords(CoordsBuffer& coordsBuffer, const std::string& text, const Rect& screenCoords, Fw::AlignmentFlag align)
97 {
98  // prevent glitches from invalid rects
99  if(!screenCoords.isValid() || !m_texture)
100  return;
101 
102  int textLenght = text.length();
103 
104  // map glyphs positions
105  Size textBoxSize;
106  const std::vector<Point>& glyphsPositions = calculateGlyphsPositions(text, align, &textBoxSize);
107 
108  for(int i = 0; i < textLenght; ++i) {
109  int glyph = (uchar)text[i];
110 
111  // skip invalid glyphs
112  if(glyph < 32)
113  continue;
114 
115  // calculate initial glyph rect and texture coords
116  Rect glyphScreenCoords(glyphsPositions[i], m_glyphsSize[glyph]);
117  Rect glyphTextureCoords = m_glyphsTextureCoords[glyph];
118 
119  // first translate to align position
120  if(align & Fw::AlignBottom) {
121  glyphScreenCoords.translate(0, screenCoords.height() - textBoxSize.height());
122  } else if(align & Fw::AlignVerticalCenter) {
123  glyphScreenCoords.translate(0, (screenCoords.height() - textBoxSize.height()) / 2);
124  } else { // AlignTop
125  // nothing to do
126  }
127 
128  if(align & Fw::AlignRight) {
129  glyphScreenCoords.translate(screenCoords.width() - textBoxSize.width(), 0);
130  } else if(align & Fw::AlignHorizontalCenter) {
131  glyphScreenCoords.translate((screenCoords.width() - textBoxSize.width()) / 2, 0);
132  } else { // AlignLeft
133  // nothing to do
134  }
135 
136  // only render glyphs that are after 0, 0
137  if(glyphScreenCoords.bottom() < 0 || glyphScreenCoords.right() < 0)
138  continue;
139 
140  // bound glyph topLeft to 0,0 if needed
141  if(glyphScreenCoords.top() < 0) {
142  glyphTextureCoords.setTop(glyphTextureCoords.top() - glyphScreenCoords.top());
143  glyphScreenCoords.setTop(0);
144  }
145  if(glyphScreenCoords.left() < 0) {
146  glyphTextureCoords.setLeft(glyphTextureCoords.left() - glyphScreenCoords.left());
147  glyphScreenCoords.setLeft(0);
148  }
149 
150  // translate rect to screen coords
151  glyphScreenCoords.translate(screenCoords.topLeft());
152 
153  // only render if glyph rect is visible on screenCoords
154  if(!screenCoords.intersects(glyphScreenCoords))
155  continue;
156 
157  // bound glyph bottomRight to screenCoords bottomRight
158  if(glyphScreenCoords.bottom() > screenCoords.bottom()) {
159  glyphTextureCoords.setBottom(glyphTextureCoords.bottom() + (screenCoords.bottom() - glyphScreenCoords.bottom()));
160  glyphScreenCoords.setBottom(screenCoords.bottom());
161  }
162  if(glyphScreenCoords.right() > screenCoords.right()) {
163  glyphTextureCoords.setRight(glyphTextureCoords.right() + (screenCoords.right() - glyphScreenCoords.right()));
164  glyphScreenCoords.setRight(screenCoords.right());
165  }
166 
167  // render glyph
168  coordsBuffer.addRect(glyphScreenCoords, glyphTextureCoords);
169  }
170 }
171 
172 const std::vector<Point>& BitmapFont::calculateGlyphsPositions(const std::string& text,
173  Fw::AlignmentFlag align,
174  Size *textBoxSize)
175 {
176  // for performance reasons we use statics vectors that are allocated on demand
177  static std::vector<Point> glyphsPositions(1);
178  static std::vector<int> lineWidths(1);
179 
180  int textLength = text.length();
181  int maxLineWidth = 0;
182  int lines = 0;
183  int glyph;
184  int i;
185 
186  // return if there is no text
187  if(textLength == 0) {
188  if(textBoxSize)
189  textBoxSize->resize(0,m_glyphHeight);
190  return glyphsPositions;
191  }
192 
193  // resize glyphsPositions vector when needed
194  if(textLength > (int)glyphsPositions.size())
195  glyphsPositions.resize(textLength);
196 
197  // calculate lines width
198  if((align & Fw::AlignRight || align & Fw::AlignHorizontalCenter) || textBoxSize) {
199  lineWidths[0] = 0;
200  for(i = 0; i< textLength; ++i) {
201  glyph = (uchar)text[i];
202 
203  if(glyph == (uchar)'\n') {
204  lines++;
205  if(lines+1 > (int)lineWidths.size())
206  lineWidths.resize(lines+1);
207  lineWidths[lines] = 0;
208  } else if(glyph >= 32) {
209  lineWidths[lines] += m_glyphsSize[glyph].width() ;
210  if((i+1 != textLength && text[i+1] != '\n')) // only add space if letter is not the last or before a \n.
211  lineWidths[lines] += m_glyphSpacing.width();
212  maxLineWidth = std::max<int>(maxLineWidth, lineWidths[lines]);
213  }
214  }
215  }
216 
217  Point virtualPos(0, m_yOffset);
218  lines = 0;
219  for(i = 0; i < textLength; ++i) {
220  glyph = (uchar)text[i];
221 
222  // new line or first glyph
223  if(glyph == (uchar)'\n' || i == 0) {
224  if(glyph == (uchar)'\n') {
225  virtualPos.y += m_glyphHeight + m_glyphSpacing.height();
226  lines++;
227  }
228 
229  // calculate start x pos
230  if(align & Fw::AlignRight) {
231  virtualPos.x = (maxLineWidth - lineWidths[lines]);
232  } else if(align & Fw::AlignHorizontalCenter) {
233  virtualPos.x = (maxLineWidth - lineWidths[lines]) / 2;
234  } else { // AlignLeft
235  virtualPos.x = 0;
236  }
237  }
238 
239  // store current glyph topLeft
240  glyphsPositions[i] = virtualPos;
241 
242  // render only if the glyph is valid
243  if(glyph >= 32 && glyph != (uchar)'\n') {
244  virtualPos.x += m_glyphsSize[glyph].width() + m_glyphSpacing.width();
245  }
246  }
247 
248  if(textBoxSize) {
249  textBoxSize->setWidth(maxLineWidth);
250  textBoxSize->setHeight(virtualPos.y + m_glyphHeight);
251  }
252 
253  return glyphsPositions;
254 }
255 
256 Size BitmapFont::calculateTextRectSize(const std::string& text)
257 {
258  Size size;
260  return size;
261 }
262 
263 void BitmapFont::calculateGlyphsWidthsAutomatically(const ImagePtr& image, const Size& glyphSize)
264 {
265  if(!image)
266  return;
267 
268  int numHorizontalGlyphs = image->getSize().width() / glyphSize.width();
269  auto texturePixels = image->getPixels();
270 
271  // small AI to auto calculate pixels widths
272  for(int glyph = m_firstGlyph; glyph< 256; ++glyph) {
273  Rect glyphCoords(((glyph - m_firstGlyph) % numHorizontalGlyphs) * glyphSize.width(),
274  ((glyph - m_firstGlyph) / numHorizontalGlyphs) * glyphSize.height(),
275  glyphSize.width(),
276  m_glyphHeight);
277  int width = glyphSize.width();
278  for(int x = glyphCoords.left(); x <= glyphCoords.right(); ++x) {
279  int filledPixels = 0;
280  // check if all vertical pixels are alpha
281  for(int y = glyphCoords.top(); y <= glyphCoords.bottom(); ++y) {
282  if(texturePixels[(y * image->getSize().width() * 4) + (x*4) + 3] != 0)
283  filledPixels++;
284  }
285  if(filledPixels > 0)
286  width = x - glyphCoords.left() + 1;
287  }
288  // store glyph size
289  m_glyphsSize[glyph].resize(width, m_glyphHeight);
290  }
291 }
292 
293 std::string BitmapFont::wrapText(const std::string& text, int maxWidth)
294 {
295  std::string outText;
296  std::string line;
297  std::vector<std::string> words;
298  std::vector<std::string> wordsSplit = stdext::split(text);
299 
300  // break huge words into small ones
301  for(const auto &word: wordsSplit) {
302  int wordWidth = calculateTextRectSize(word).width();
303  if(wordWidth > maxWidth) {
304  std::string newWord;
305  for(uint j=0;j<word.length();++j) {
306  std::string candidate = newWord + word[j];
307  if(j != word.length() - 1)
308  candidate += "-";
309  int candidateWidth = calculateTextRectSize(candidate).width();
310 
311  if(candidateWidth > maxWidth) {
312  newWord += "-";
313  words.push_back(newWord);
314  newWord = "";
315  }
316 
317  newWord += word[j];
318  }
319 
320  words.push_back(newWord);
321  } else
322  words.push_back(word);
323  }
324 
325  // compose lines
326  for(const auto &word: words) {
327  std::string candidate = line + word;
328  int candidateWidth = calculateTextRectSize(candidate).width();
329 
330  if(candidateWidth > maxWidth) {
331  if(!line.empty())
332  outText += line.substr(0, line.length()-1) + "\n";
333  line = "";
334  }
335 
336  line += word + " ";
337  }
338 
339  outText += line;
340  outText = outText.substr(0, outText.length()-1);
341 
342  return outText;
343 }
BitmapFont::load
void load(const OTMLNodePtr &fontNode)
Load font from otml node.
Definition: bitmapfont.cpp:30
OTMLNode::source
std::string source()
Definition: otmlnode.h:38
BitmapFont::calculateTextRectSize
Size calculateTextRectSize(const std::string &text)
Simulate render and calculate text size.
Definition: bitmapfont.cpp:256
graphics.h
TPoint::y
T y
Definition: point.h:83
TSize::setWidth
void setWidth(T w)
Definition: size.h:47
otml.h
TRect< int >
Texture::getSize
const Size & getSize()
Definition: texture.h:50
Image::load
static ImagePtr load(std::string file)
Definition: image.cpp:39
Fw::AlignTopLeft
@ AlignTopLeft
Definition: const.h:200
TRect::left
T left() const
Definition: rect.h:52
texturemanager.h
TRect::setTop
void setTop(T pos)
Definition: rect.h:76
TPoint::toSize
TSize< T > toSize() const
Definition: point.h:42
TRect::bottom
T bottom() const
Definition: rect.h:55
TRect::setLeft
void setLeft(T pos)
Definition: rect.h:75
TSize::width
int width() const
Definition: size.h:43
Painter::getResolution
Size getResolution()
Definition: painter.h:94
TRect::setRight
void setRight(T pos)
Definition: rect.h:77
TRect::topLeft
TPoint< T > topLeft() const
Definition: rect.h:60
stdext::resolve_path
std::string resolve_path(const std::string &filePath, std::string sourcePath)
Resolve a file path by combining sourcePath with filePath.
Definition: string.cpp:35
OTMLNode::valueAt
T valueAt(const std::string &childTag)
Definition: otmlnode.h:130
BitmapFont::wrapText
std::string wrapText(const std::string &text, int maxWidth)
Definition: bitmapfont.cpp:293
uint
unsigned int uint
Definition: types.h:31
TSize::setHeight
void setHeight(T h)
Definition: size.h:48
Fw::AlignHorizontalCenter
@ AlignHorizontalCenter
Definition: const.h:198
OTMLNode::value
T value()
Definition: otmlnode.h:122
CoordsBuffer::clear
void clear()
Definition: coordsbuffer.h:38
BitmapFont::calculateDrawTextCoords
void calculateDrawTextCoords(CoordsBuffer &coordsBuffer, const std::string &text, const Rect &screenCoords, Fw::AlignmentFlag align=Fw::AlignTopLeft)
Definition: bitmapfont.cpp:96
TSize::resize
void resize(T w, T h)
Definition: size.h:46
image.h
Size
TSize< int > Size
Definition: size.h:107
TPoint::x
T x
Definition: point.h:83
OTMLNode::get
OTMLNodePtr get(const std::string &childTag)
Definition: otmlnode.cpp:54
TextureManager::getTexture
TexturePtr getTexture(const std::string &fileName)
Definition: texturemanager.cpp:90
Fw::AlignmentFlag
AlignmentFlag
Definition: const.h:192
BitmapFont::drawText
void drawText(const std::string &text, const Point &startPos)
Simple text render starting at startPos.
Definition: bitmapfont.cpp:80
stdext::split
std::vector< std::string > split(const std::string &str, const std::string &separators)
Definition: string.cpp:273
TSize::height
int height() const
Definition: size.h:44
uchar
unsigned char uchar
Definition: types.h:29
TRect::right
T right() const
Definition: rect.h:54
TRect::isValid
bool isValid() const
Definition: rect.h:50
stdext::shared_object_ptr< OTMLNode >
BitmapFont::calculateGlyphsPositions
const std::vector< Point > & calculateGlyphsPositions(const std::string &text, Fw::AlignmentFlag align=Fw::AlignTopLeft, Size *textBoxSize=nullptr)
Calculate glyphs positions to use on render, also calculates textBoxSize if wanted.
Definition: bitmapfont.cpp:172
CoordsBuffer
Definition: coordsbuffer.h:29
bitmapfont.h
TRect::intersects
bool intersects(const TRect< T > &r) const
Definition: rect.h:181
CoordsBuffer::addRect
void addRect(const Rect &dest)
Definition: coordsbuffer.h:48
g_painter
Painter * g_painter
Definition: painter.cpp:28
TRect::height
T height() const
Definition: rect.h:70
TRect::top
T top() const
Definition: rect.h:53
g_textures
TextureManager g_textures
Definition: texturemanager.cpp:33
Fw::AlignBottom
@ AlignBottom
Definition: const.h:197
TPoint< int >
TRect::setRect
void setRect(T x, T y, T width, T height)
Definition: rect.h:88
TRect::width
T width() const
Definition: rect.h:69
Fw::AlignVerticalCenter
@ AlignVerticalCenter
Definition: const.h:199
TSize< int >
Painter::drawTextureCoords
virtual void drawTextureCoords(CoordsBuffer &coordsBuffer, const TexturePtr &texture)=0
TRect::translate
void translate(T x, T y)
Definition: rect.h:98
Fw::AlignRight
@ AlignRight
Definition: const.h:195
OTMLNode::at
OTMLNodePtr at(const std::string &childTag)
Definition: otmlnode.cpp:70
TRect::setBottom
void setBottom(T pos)
Definition: rect.h:78