COPASI API  4.16.103
CQFontRenderer.cpp
Go to the documentation of this file.
1 // Copyright (C) 2010 - 2015 by Pedro Mendes, Virginia Tech Intellectual
2 // Properties, Inc., University of Heidelberg, and The University
3 // of Manchester.
4 // All rights reserved.
5 
6 #include "CQFontRenderer.h"
7 
8 #include <QtCore/QStringList>
9 #include <QtGui/QFontMetrics>
10 #include <QtGui/QImage>
11 #include <QtGui/QPaintEngine>
12 #include <QtOpenGL/QGLWidget>
13 #include <QtGui/QPainterPath>
14 
15 #include <string.h>
16 #include <cmath>
17 #include <assert.h>
18 
19 // opengl includes
20 #ifdef WIN32
21 # define WIN32_LEAN_AND_MEAN 1
22 # include <windows.h>
23 #endif // WIN32
24 
25 #ifdef __APPLE__
26 #include <OpenGL/gl.h>
27 #include <OpenGL/glu.h>
28 #else
29 #include <GL/gl.h>
30 #include <GL/glu.h>
31 #include <GL/glext.h>
32 #endif // __APPLE__
33 
35 
36 /**
37  * Static instance of CQFontRenderer for use in a static function that can be
38  * used as a callback.
39  */
41 
42 /**
43  * Constructor.
44  */
46 {
47 }
48 
49 /**
50  * Destructor
51  */
53 {
54  if (this->mpFontDatabase != NULL)
55  {
56  delete this->mpFontDatabase;
57  }
58 }
59 
60 /**
61  * Functor that returns a TextureSpec object with a texture
62  * for the given font family, font size font weight, font style and text.
63  * The caller is responsible to free the memory of the TextureSpec object
64  * and of the pData in the TextureSpec.
65  */
66 std::pair<CLTextTextureSpec*, GLubyte*> CQFontRenderer::operator()(const std::string& family, double fontSize, const std::string& text, CLText::FONT_WEIGHT weight, CLText::FONT_STYLE style, double zoomFactor)
67 {
68  CLFontSpec spec;
69  spec.mFamily = family;
70  spec.mSize = fontSize * zoomFactor;
71  spec.mWeight = weight;
72  spec.mStyle = style;
73  QFont font = this->getFont(spec);
74  return getTexture(font, text, zoomFactor);
75 }
76 
77 /**
78  * Returns the font that matches the given FontSpec.
79  */
81 {
82  // we only initialize the font database when it is needed for the first time,
83  // hopefully this will resolve the crash under linux if it is instanciated to early.
84  if (this->mpFontDatabase == NULL)
85  {
86  this->mpFontDatabase = new QFontDatabase();
87  }
88 
89  // check if it is a generic font name (sand, serif or monospaced)
90  // and try to find a fitting font
91  // sans: helvetica or arial
92  // serif: times, times new roman or garamond
93  // monospaced: courier, courier new or monaco
94  //std::cout << "Searching font for family \"" << spec.mFamily << "\" with size " << spec.mSize << " weight " << (spec.mWeight==CLText::WEIGHT_BOLD?(const char*)"bold":(const char*)"normal") << " and style " << (spec.mStyle==CLText::STYLE_ITALIC?(const char*)"italic":(const char*)"normal") << "." << std::endl;
95  std::map<CLFontSpec, QFont>::iterator pos;
96  pos = this->mFontMap.find(spec);
97  QFont font;
98  QString styleString;
99 
100  if (spec.mWeight == CLText::WEIGHT_BOLD)
101  {
102  styleString = "Bold";
103  }
104 
105  if (spec.mStyle == CLText::STYLE_ITALIC)
106  {
107  styleString += " Italic";
108  }
109 
110  styleString = styleString.trimmed();
111 
112  if (pos == this->mFontMap.end())
113  {
114  QString family(spec.mFamily.c_str());
115  family = family.toLower();
116 
117  if (family == "sans")
118  {
119  // try helvetica and arial in this order
120  QFont defaultFont;
121  QString defaultFamily = defaultFont.defaultFamily();
122  defaultFont = this->mpFontDatabase->font(defaultFamily, styleString, spec.mSize);
123  CLFontSpec tempSpec = spec;
124  tempSpec.mFamily = "helvetica";
125 
126  if (defaultFamily.contains(tempSpec.mFamily.c_str()))
127  {
128  font = defaultFont;
129  }
130  else
131  {
132  font = this->getFont(tempSpec);
133 
134  if (font == defaultFont)
135  {
136  // if the font is the default font, we try the next one
137  tempSpec.mFamily = "arial";
138 
139  if (defaultFamily.contains(tempSpec.mFamily.c_str()))
140  {
141  font = defaultFont;
142  }
143  else
144  {
145  font = this->getFont(tempSpec);
146  }
147  }
148  }
149  }
150  else if (family == "serif")
151  {
152  // try times, times new roman and garamond in this order
153  QFont defaultFont;
154  QString defaultFamily = defaultFont.defaultFamily();
155  defaultFont = this->mpFontDatabase->font(defaultFamily, styleString, spec.mSize);
156  CLFontSpec tempSpec = spec;
157  tempSpec.mFamily = "times";
158 
159  if (defaultFamily.contains(tempSpec.mFamily.c_str()))
160  {
161  font = defaultFont;
162  }
163  else
164  {
165  font = this->getFont(tempSpec);
166 
167  if (font == defaultFont)
168  {
169  // if the font is the default font, we try the next one
170  tempSpec.mFamily = "times new roman";
171 
172  if (defaultFamily.contains(tempSpec.mFamily.c_str()))
173  {
174  font = defaultFont;
175  }
176  else
177  {
178  font = this->getFont(tempSpec);
179 
180  if (font == defaultFont)
181  {
182  // if the font is the default font, we try the next one
183  tempSpec.mFamily = "garamond";
184 
185  if (defaultFamily.contains(tempSpec.mFamily.c_str()))
186  {
187  font = defaultFont;
188  }
189  else
190  {
191  font = this->getFont(tempSpec);
192  }
193  }
194  }
195  }
196  }
197  }
198  else if (family == "monospaced")
199  {
200  // try courier, courier new and monaco in this order
201  QFont defaultFont;
202  QString defaultFamily = defaultFont.defaultFamily();
203  defaultFont = this->mpFontDatabase->font(defaultFamily, styleString, spec.mSize);
204  CLFontSpec tempSpec = spec;
205  tempSpec.mFamily = "courier";
206 
207  if (defaultFamily.contains(tempSpec.mFamily.c_str()))
208  {
209  font = defaultFont;
210  }
211  else
212  {
213  font = this->getFont(tempSpec);
214 
215  if (font == defaultFont)
216  {
217  // if the font is the default font, we try the next one
218  tempSpec.mFamily = "courier new";
219 
220  if (defaultFamily.contains(tempSpec.mFamily.c_str()))
221  {
222  font = defaultFont;
223  }
224  else
225  {
226  font = getFont(tempSpec);
227 
228  if (font == defaultFont)
229  {
230  // if the font is the default font, we try the next one
231  tempSpec.mFamily = "monaco";
232 
233  if (defaultFamily.contains(tempSpec.mFamily.c_str()))
234  {
235  font = defaultFont;
236  }
237  else
238  {
239  font = this->getFont(tempSpec);
240  }
241  }
242  }
243  }
244  }
245  }
246  else
247  {
248  std::list<std::string> familyList;
249  this->getFamilyList(spec.mFamily, familyList);
250  // now create the font for one of the families in the list
251  QFont defaultFont;
252  QString defaultFamily = defaultFont.defaultFamily();
253  defaultFont = this->mpFontDatabase->font(defaultFamily, styleString, spec.mSize);
254 
255  if (!familyList.empty())
256  {
257  // go through the list and check if there is a font in the that
258  // does not return the default font for the given size and
259  // style
260  std::list<std::string>::const_iterator familyIt = familyList.begin(), familyEndit = familyList.end();
261 
262  while (familyIt != familyEndit)
263  {
264  font = QFont(familyIt->c_str(), spec.mSize, (spec.mWeight == CLText::WEIGHT_BOLD) ? QFont::Bold : QFont::Normal, (spec.mStyle == CLText::STYLE_NORMAL) ? false : true);
265 
266  if (font != defaultFont)
267  {
268  break;
269  }
270 
271  ++familyIt;
272  }
273  }
274  else
275  {
276  // return the default font
277  font = defaultFont;
278  }
279  }
280 
281  this->mFontMap.insert(std::pair<CLFontSpec, QFont>(spec, font));
282  }
283  else
284  {
285  font = pos->second;
286  }
287 
288  //std::cout << "Using font family \"" << font.mFamily().toLatin1().data() << "\" with size " << font.pointSize() << " weight " << (font.bold()?(const char*)"bold":(const char*)"normal") << " and style " << (font.italic()?(const char*)"italic":(const char*)"normal") << "." << std::endl;
289  return font;
290 }
291 
292 /**
293  * Returns the size for a font given a font specification, a text and a zoom factor.
294  */
295 std::pair<double, double> CQFontRenderer::getTextureSize(const QFont& font, const std::string& text)
296 {
297  QFontMetrics fontMetrics(font);
298  int textWidthInPixels = fontMetrics.width(text.c_str()) + 2;
299  int textHeightInPixels = fontMetrics.height() + 2;
300  return std::pair<double, double>(textWidthInPixels, textHeightInPixels);
301 }
302 
303 /**
304  * Returns the size for a font given a font, a text and a zoom factor.
305  */
306 std::pair<double, double> CQFontRenderer::getTextureSize(const CLFontSpec& spec, const std::string& text)
307 {
308  QFont font = this->getFont(spec);
309  return this->getTextureSize(font, text);
310 }
311 
312 /**
313  * Creates a texture for the given text using the given font object.
314  * The caller has to free the memory for the TextureSpec object and the
315  * pData in the TextureSpec object.
316  */
317 std::pair<CLTextTextureSpec*, GLubyte*> CQFontRenderer::getTexture(QFont& font, const std::string& text, double zoomFactor)
318 {
319  CLTextTextureSpec* pSpec = NULL;
320  // find out what size the text will have
321  font.setStyleStrategy(QFont::ForceOutline);
322  QFontMetrics fontMetrics(font);
323  std::pair<double, double> size = this->getTextureSize(font, text);
324  //std::cout << "texture size: " << size.first << "," << size.second << std::endl;
325  // make the size a power of 2
326  unsigned int exponentWidth = (unsigned int)ceil(log(size.first /** zoomFactor*/ + 2) / log(2.0));
327  unsigned int exponentHeight = (unsigned int)ceil(log(size.second /** zoomFactor*/ + 2) / log(2.0));
328  unsigned int width = 1 << exponentWidth;
329  unsigned int height = 1 << exponentHeight;
330  // draw the text somewhere with white stroke on black background
331  QImage image(width, height, QImage::Format_RGB32);
332  QPainter painter(&image);
333  painter.setBackground(Qt::black);
334  painter.setPen(QPen(QColor(255, 255, 255, 255), 0.25, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin));
335  painter.setBrush(Qt::NoBrush);
336  painter.setFont(font);
337  painter.eraseRect(0, 0, width, height);
338  // move over by 1,1 to get a small edge around the text
339  painter.translate(1.0, 1.0);
340  // we scale after the erase so that we don't have to divide the width and
341  // the height
342  //painter.scale(zoomFactor, zoomFactor);
343  painter.drawText(0.0, fontMetrics.ascent() + 1, text.c_str());
344  painter.end();
345  // convert the image to an OpenGL texture
346  image = QGLWidget::convertToGLFormat(image);
347  // create the texture spec
348  pSpec = new CLTextTextureSpec();
349  pSpec->mTextureWidth = width;
350  pSpec->mTextureHeight = height;
351  pSpec->mTextWidth = size.first;
352  pSpec->mTextHeight = size.second;
353  pSpec->mScale = zoomFactor;
354  pSpec->mMaxScale = -1.0;
355  pSpec->mNumComponents = 1;
356  pSpec->mTextureName = 0;
357  GLint w = 0;
358  GLenum format = GL_ALPHA;
359  glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
360  glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
361 
362  while (w == 0 && width > 1 && height > 1)
363  {
364  // divide the size by two in each direction
365  width = width >> 1;
366  height = height >> 1;
367  glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
368  glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
369  }
370 
371  GLubyte* textureData = NULL;
372 
373  if (w != 0)
374  {
375  if (w != pSpec->mTextureWidth)
376  {
377  pSpec->mTextureWidth = width;
378  pSpec->mTextureHeight = height;
379  }
380 
381  textureData = new GLubyte[width * height];
382  pSpec->mAscent = (double)fontMetrics.ascent();
383  unsigned int i, iMax = width * height;
384 
385  for (i = 0; i < iMax; ++i)
386  {
387  textureData[i] = image.bits()[4 * i];
388  }
389  }
390 
391  return std::pair<CLTextTextureSpec*, GLubyte*>(pSpec, textureData);
392 }
393 
394 /**
395  * Finds the font families that fit the given family name.
396  * A family fits if the name is exactly the same or if the name is
397  * contained in the family name.
398  * For generic names like sans, serif or monospaced the algorithm tries
399  * to find a suitable font family.
400  */
401 void CQFontRenderer::findSimilarFamily(const std::string& name, std::set<std::string>& families) const
402 {
403  QString qname(name.c_str());
404  QStringList familyList = this->mpFontDatabase->families();
405  QStringList::const_iterator constIterator;
406 
407  for (constIterator = familyList.constBegin(); constIterator != familyList.constEnd(); ++constIterator)
408  {
409  if ((*constIterator).contains(name.c_str(), Qt::CaseInsensitive))
410  {
411  families.insert((*constIterator).toLatin1().data());
412  }
413  }
414 }
415 
416 /**
417  * Given a certain name and a set of font family names, the algorithm
418  * tries to put them in an order from best match to worst match.
419  */
420 void CQFontRenderer::orderFamilies(const std::string& /*name*/, const std::set<std::string>& familySet, std::list<std::string>& familyList) const
421 {
422  std::set<std::string>::const_iterator it = familySet.begin();
423  std::set<std::string>::const_iterator endit = familySet.end();
424 
425  // simple version only copies the set
426  while (it != endit)
427  {
428  // TODO If the name is equal to the current iterator, we insert it before
429  // TODO the first item that is not equal
430  // TODO If the current item starts with the name, we add it before the
431  // TODO first one that starts with the name and has a longer string.
432  // TODO If the name is somewhere in the items string, we add it before
433  // TODO the first entry that does contain the name, but is longer.
434  // TODO For this comparison, we should take thefoundary into account
435  // TODO and maybe remove it befoe comparing
436  familyList.push_back(*it);
437  ++it;
438  }
439 }
440 
441 void CQFontRenderer::getFamilyList(const std::string& family, std::list<std::string>& list) const
442 {
443  QString name(family.c_str());
444  name = name.toLower();
445  name = name.trimmed();
446  // find a new font
447  std::set<std::string> familySet;
448  this->findSimilarFamily(family, familySet);
449 
450  if (!familySet.empty())
451  {
452  this->orderFamilies(family, familySet, list);
453  }
454 }
455 
456 std::pair<CLTextTextureSpec*, GLubyte*> CQFontRenderer::createTexture(const std::string& family, double fontSize, const std::string& text, CLText::FONT_WEIGHT weight, CLText::FONT_STYLE style, double zoomFactor)
457 {
458  return FONT_RENDERER(family, fontSize, text, weight, style, zoomFactor);
459 }
QFont getFont(const CLFontSpec &spec)
static std::pair< CLTextTextureSpec *, GLubyte * > createTexture(const std::string &family, double fontSize, const std::string &text, CLText::FONT_WEIGHT weight=CLText::WEIGHT_NORMAL, CLText::FONT_STYLE style=CLText::STYLE_NORMAL, double zoomFactor=1.0)
void findSimilarFamily(const std::string &name, std::set< std::string > &families) const
std::pair< CLTextTextureSpec *, GLubyte * > getTexture(QFont &font, const std::string &text, double zoomFactor)
FONT_STYLE
Definition: CLText.h:41
void getFamilyList(const std::string &family, std::list< std::string > &list) const
virtual std::pair< CLTextTextureSpec *, GLubyte * > operator()(const std::string &family, double fontSize, const std::string &text, CLText::FONT_WEIGHT weight=CLText::WEIGHT_NORMAL, CLText::FONT_STYLE style=CLText::STYLE_NORMAL, double zoomFactor=1.0)
double mTextureWidth
double mTextureHeight
virtual std::pair< double, double > getTextureSize(const CLFontSpec &spec, const std::string &text)
CLText::FONT_WEIGHT mWeight
std::map< CLFontSpec, QFont > mFontMap
unsigned int mNumComponents
QFontDatabase * mpFontDatabase
FONT_WEIGHT
Definition: CLText.h:34
std::string mFamily
CLText::FONT_STYLE mStyle
virtual ~CQFontRenderer()
static CQFontRenderer FONT_RENDERER
void orderFamilies(const std::string &name, const std::set< std::string > &familySet, std::list< std::string > &familyList) const