LCOV - code coverage report
Current view: top level - ftmesh - font.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 143 211 67.8 %
Date: 2022-01-29 21:33:29 Functions: 19 27 70.4 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2021  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/ftmesh
       4             : // contact@m2osw.com
       5             : //
       6             : // This program is free software; you can redistribute it and/or modify
       7             : // it under the terms of the GNU General Public License as published by
       8             : // the Free Software Foundation; either version 2 of the License, or
       9             : // (at your option) any later version.
      10             : //
      11             : // This program is distributed in the hope that it will be useful,
      12             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             : // GNU General Public License for more details.
      15             : //
      16             : // You should have received a copy of the GNU General Public License along
      17             : // with this program; if not, write to the Free Software Foundation, Inc.,
      18             : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      19             : 
      20             : /** \file
      21             :  * \brief The main class to load fonts and build meshes.
      22             :  *
      23             :  * This file implements the ftmesh class which handles the loading of
      24             :  * fonts and builds meshes for specified glyphs.
      25             :  */
      26             : 
      27             : // self
      28             : //
      29             : #include    "ftmesh/font.h"
      30             : 
      31             : #include    "ftmesh/polygon.h"
      32             : 
      33             : 
      34             : // snapdev lib
      35             : //
      36             : #include    <snapdev/not_used.h>
      37             : 
      38             : 
      39             : // libutf8 lib
      40             : //
      41             : #include    <libutf8/libutf8.h>
      42             : 
      43             : 
      44             : // snaplogger lib
      45             : //
      46             : #include    <snaplogger/message.h>
      47             : 
      48             : 
      49             : // OpenGL/GLUT libs
      50             : //
      51             : #include    <GL/glu.h>
      52             : 
      53             : 
      54             : // FreeType lib
      55             : //
      56             : // ft2build.h must come first
      57             : #include    <ft2build.h>
      58             : 
      59             : #include    FT_FREETYPE_H
      60             : #include    FT_GLYPH_H
      61             : #include    FT_OUTLINE_H
      62             : 
      63             : 
      64             : // C++ lib
      65             : //
      66             : #include    <iostream>
      67             : 
      68             : 
      69             : // last include
      70             : //
      71             : #include    <snapdev/poison.h>
      72             : 
      73             : 
      74             : 
      75             : 
      76             : 
      77             : FT_Library  g_ft_library;
      78             : 
      79             : 
      80             : namespace ftmesh
      81             : {
      82             : 
      83             : 
      84             : namespace
      85             : {
      86             : 
      87             : 
      88             : constexpr FT_Long const DEFAULT_FACE_INDEX = 0;
      89             : 
      90             : 
      91             : 
      92             : class auto_init_freetype_library
      93             : {
      94             : public:
      95           2 :     auto_init_freetype_library()
      96             :     {
      97           2 :         int e(FT_Init_FreeType(&g_ft_library));
      98           2 :         if(e != 0)
      99             :         {
     100           0 :             SNAP_LOG_ERROR
     101           0 :                 << "An error occurred initializing the FreeType library ("
     102             :                 << e
     103             :                 << ")"
     104             :                 << SNAP_LOG_SEND;
     105             :         }
     106           2 :     }
     107             : };
     108             : 
     109           2 : auto_init_freetype_library       g_auto_init_freetype_library = auto_init_freetype_library();
     110             : 
     111             : 
     112             : 
     113             : 
     114             : } // no name namespace
     115             : 
     116             : 
     117             : namespace detail
     118             : {
     119             : 
     120             : 
     121             : //////////////
     122             : // font_impl
     123             : 
     124             : 
     125             : // in order to hide all the FreeType headers, we use an internal implementation
     126             : //
     127             : class font_impl
     128             : {
     129             : public:
     130             :     typedef std::shared_ptr<font_impl>
     131             :                             pointer_t;
     132             : 
     133             :                             font_impl(std::string const & filename);
     134             :                             font_impl(font_impl const &) = delete;
     135             :                             ~font_impl();
     136             :     font_impl &             operator = (font_impl const &) = delete;
     137             : 
     138             :     mesh::pointer_t         get_mesh(char32_t glyph);
     139             :     void                    set_precision(int precision);
     140             :     bool                    has_kerning_table() const;
     141             :     void                    set_size(int point_size, int x_resolution, int y_resolution);
     142             :     float                   get_kerning(char32_t current_char, char32_t next_char);
     143             : 
     144             : private:
     145             :     // WARNING: the callback parameters are not what is defined in the
     146             :     //          documentation because the function used to set them up
     147             :     //          requires a cast either way and so I just use our types
     148             :     //          directly as long as compatible with the docs.
     149             :     //
     150             :     static void             tess_callback_edge(GLboolean edge, font_impl * impl);
     151             :     static void             tess_callback_begin(GLenum type, font_impl * impl);
     152             :     static void             tess_callback_vertex(GLdouble const * vertex, font_impl * impl);
     153             :     static void             tess_callback_combine(
     154             :                                       GLdouble coords[3]
     155             :                                     , GLdouble * vertex_data[4]
     156             :                                     , GLfloat weight[4]
     157             :                                     , GLdouble ** out_data
     158             :                                     , font_impl * impl);
     159             :     static void             tess_callback_end(font_impl * impl);
     160             :     static void             tess_callback_error(GLenum errCode, font_impl * impl);
     161             : 
     162             :     void                    callback_begin();
     163             :     void                    callback_vertex(point const & p);
     164             :     point::pointer_t        callback_combine(point const & p);
     165             :     void                    callback_end();
     166             :     void                    callback_error(GLenum errCode);
     167             : 
     168             :     std::string const       f_filename = std::string();
     169             :     FT_Face                 f_face = FT_Face();
     170             :     mesh::pointer_t         f_current_mesh = mesh::pointer_t();
     171             :     int                     f_precision = DEFAULT_UPSCALE;
     172             :     std::size_t             f_temporary_vertex_pos = 0;
     173             :     point::safe_vector_t    f_temporary_vertex = point::safe_vector_t();       
     174             : };
     175             : 
     176             : 
     177           1 : font_impl::font_impl(std::string const & font)
     178           1 :     : f_filename(font)
     179             : {
     180           1 :     FT_Error e(FT_New_Face(
     181             :               g_ft_library
     182             :             , f_filename.c_str()
     183             :             , DEFAULT_FACE_INDEX
     184           1 :             , &f_face));
     185           1 :     if(e != FT_Err_Ok)
     186             :     {
     187             :         throw std::runtime_error(
     188             :                   "FT_New_Face() could not load \""
     189           0 :                 + f_filename
     190           0 :                 + "\" (FT_Error: "
     191           0 :                 + std::to_string(e)
     192           0 :                 + ")");
     193             :     }
     194             : 
     195           1 :     e = FT_Select_Charmap(f_face, FT_ENCODING_UNICODE);
     196           1 :     if(e != FT_Err_Ok)
     197             :     {
     198             :         throw std::runtime_error(
     199             :                   "FT_New_Face() could not set Unicode Charmap for \""
     200           0 :                 + f_filename
     201           0 :                 + "\" (FT_Error: "
     202           0 :                 + std::to_string(e)
     203           0 :                 + ")");
     204             :     }
     205             : 
     206             :     // make sure to define a default size which is always the same
     207             :     //
     208           1 :     set_size(DEFAULT_SIZE, DEFAULT_RESOLUTION, DEFAULT_RESOLUTION);
     209           1 : }
     210             : 
     211             : 
     212           2 : font_impl::~font_impl()
     213             : {
     214           1 :     FT_Done_Face(f_face);
     215           1 : }
     216             : 
     217             : 
     218           9 : mesh::pointer_t font_impl::get_mesh(char32_t glyph)
     219             : {
     220           9 :     FT_UInt const index(FT_Get_Char_Index(f_face, glyph));
     221           9 :     int const e(FT_Load_Glyph(f_face, index, FT_LOAD_DEFAULT));
     222           9 :     if(e != FT_Err_Ok
     223           9 :     || f_face->glyph == nullptr)     // the load failed
     224             :     {
     225           0 :         return mesh::pointer_t();
     226             :     }
     227             : 
     228           9 :     if(f_face->glyph->format != FT_GLYPH_FORMAT_OUTLINE)
     229             :     {
     230             :         // valid load, not a valid format, we expected vertices (an outline)
     231             :         //
     232           0 :         return mesh::pointer_t();
     233             :     }
     234             : 
     235           9 :     int start_index(0);
     236           9 :     int end_index(0);
     237             : 
     238          18 :     polygon::vector_t polygons(f_face->glyph->outline.n_contours);
     239             : 
     240          20 :     for(std::size_t i(0); i < polygons.size(); ++i)
     241             :     {
     242          11 :         end_index = f_face->glyph->outline.contours[i] + 1;
     243             : 
     244          44 :         polygons[i] = std::make_shared<polygon>(
     245          22 :                                   f_face->glyph->outline.points + start_index
     246          22 :                                 , f_face->glyph->outline.tags + start_index
     247          22 :                                 , end_index - start_index);
     248             : 
     249          11 :         start_index = end_index;
     250             :     }
     251             : 
     252             :     // compute the parity of each polygon
     253             :     //
     254             :     // FIXME: see whether FT_Outline_Get_Orientation can do it for us.
     255             :     //
     256          20 :     for(std::size_t i(0); i < polygons.size(); ++i)
     257             :     {
     258          22 :         polygon::pointer_t c1(polygons[i]);
     259          11 :         point const leftmost(c1->leftmost());
     260          11 :         int parity(0);
     261          28 :         for(std::size_t j(0); j < polygons.size(); ++j)
     262             :         {
     263          17 :             if(j == i)
     264             :             {
     265          11 :                 continue;
     266             :             }
     267             : 
     268          12 :             polygon::pointer_t c2(polygons[j]);
     269         128 :             for(size_t n(0); n < c2->size(); ++n)
     270             :             {
     271         122 :                 point const p1(c2->at(n));
     272         122 :                 point const p2(c2->at(n + 1));
     273             : 
     274             :                 /* FIXME: combinations of >= > <= and < do not seem stable */
     275         320 :                 if((p1.y() <  leftmost.y() && p2.y() <  leftmost.y())
     276          47 :                 || (p1.y() >= leftmost.y() && p2.y() >= leftmost.y())
     277         124 :                 || (p1.x() >  leftmost.x() && p2.x() >  leftmost.x()))
     278             :                 {
     279             :                     ;
     280             :                 }
     281           2 :                 else if(p1.x() < leftmost.x()
     282           1 :                      && p2.x() < leftmost.x())
     283             :                 {
     284           1 :                     parity++;
     285             :                 }
     286             :                 else
     287             :                 {
     288           0 :                     point const a(p1 - leftmost);
     289           0 :                     point const b(p2 - leftmost);
     290           0 :                     if(b.x() * a.y() > b.y() * a.x())
     291             :                     {
     292           0 :                         parity++;
     293             :                     }
     294             :                 }
     295             :             }
     296             :         }
     297             : 
     298          11 :         c1->apply_parity(parity);
     299             :     }
     300             : 
     301           9 :     f_current_mesh = std::make_shared<mesh>(static_cast<float>(f_face->glyph->advance.x) / static_cast<float>(f_precision));
     302           9 :     f_temporary_vertex_pos = 0;
     303             : 
     304           9 :     GLUtesselator * tobj(gluNewTess());
     305             : 
     306           9 :     gluTessCallback(tobj, GLU_TESS_EDGE_FLAG_DATA, reinterpret_cast<_GLUfuncptr>(&tess_callback_edge));
     307           9 :     gluTessCallback(tobj, GLU_TESS_BEGIN_DATA,     reinterpret_cast<_GLUfuncptr>(&tess_callback_begin));
     308           9 :     gluTessCallback(tobj, GLU_TESS_VERTEX_DATA,    reinterpret_cast<_GLUfuncptr>(&tess_callback_vertex));
     309           9 :     gluTessCallback(tobj, GLU_TESS_COMBINE_DATA,   reinterpret_cast<_GLUfuncptr>(&tess_callback_combine));
     310           9 :     gluTessCallback(tobj, GLU_TESS_END_DATA,       reinterpret_cast<_GLUfuncptr>(&tess_callback_end));
     311           9 :     gluTessCallback(tobj, GLU_TESS_ERROR_DATA,     reinterpret_cast<_GLUfuncptr>(&tess_callback_error));
     312             : 
     313           9 :     if((f_face->glyph->outline.flags & FT_OUTLINE_EVEN_ODD_FILL) != 0) // ft_outline_reverse_fill
     314             :     {
     315           0 :         gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);
     316             :     }
     317             :     else
     318             :     {
     319           9 :         gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
     320             :     }
     321             : 
     322           9 :     gluTessProperty(tobj, GLU_TESS_BOUNDARY_ONLY, GLU_FALSE);
     323           9 :     gluTessProperty(tobj, GLU_TESS_TOLERANCE, 0.0);
     324           9 :     gluTessNormal(tobj, 0.0, 0.0, 1.0);
     325             : 
     326           9 :     gluTessBeginPolygon(tobj, this);
     327             : 
     328          20 :         for(std::size_t c(0); c < polygons.size(); ++c)
     329             :         {
     330          11 :             gluTessBeginContour(tobj);
     331             : 
     332          22 :                 polygon::pointer_t polygon(polygons[c]);
     333         373 :                 for(std::size_t p(0); p < polygon->size(); ++p)
     334             :                 {
     335         724 :                     gluTessVertex(
     336             :                           tobj
     337         362 :                         , const_cast<double *>(polygon->at(p).f_coordinates)
     338         362 :                         , const_cast<double *>(polygon->at(p).f_coordinates));
     339             :                 }
     340             : 
     341          11 :             gluTessEndContour(tobj);
     342             :         }
     343             : 
     344           9 :     gluTessEndPolygon(tobj);
     345             : 
     346           9 :     gluDeleteTess(tobj);
     347             : 
     348          18 :     mesh::pointer_t result;
     349           9 :     f_current_mesh.swap(result);
     350           9 :     return result;
     351             : }
     352             : 
     353             : 
     354           0 : void font_impl::set_precision(int precision)
     355             : {
     356           0 :     if(precision <= 0)
     357             :     {
     358           0 :         throw std::runtime_error("the precision must be positive");
     359             :     }
     360             : 
     361           0 :     f_precision = precision;
     362             : 
     363             :     // TBD: should we call the set_size() funciton? if we want to be able to
     364             :     // do that, we need to save the point and resolution parameters...
     365           0 : }
     366             : 
     367             : 
     368           0 : bool font_impl::has_kerning_table() const
     369             : {
     370           0 :     return FT_HAS_KERNING(f_face) != 0; 
     371             : }
     372             : 
     373             : 
     374             : /** \brief Set the size of the font.
     375             :  *
     376             :  * This function sets the size of the font. You must have called the
     377             :  * set_precision() function first if you want your new precision to work.
     378             :  *
     379             :  * The \p point_size is the usual \em point definition. More or less, the
     380             :  * height, knowing that it is not going to match the pixel height.
     381             :  *
     382             :  * \note
     383             :  * The constructor calls this function once with:
     384             :  *
     385             :  * \code
     386             :  *     set_size(DEFAULT_SIZE, DEFAULT_RESOLUTION, DEFAULT_RESOLUTION);
     387             :  * \endcode
     388             :  *
     389             :  * \warning
     390             :  * It is important to call this function BEFORE you ever call the get_mesh()
     391             :  * or convert_string(). Also, calling this function AFTER will not work as
     392             :  * expected (i.e. it doesn't dynamically reset all the existing glyph and
     393             :  * restart with the new size). In a future version, we may fix this issue.
     394             :  *
     395             :  * \param[in] point_size  The font height to use.
     396             :  * \param[in] x_resolution  The horizontal resolution, 72 by default.
     397             :  * \param[in] x_resolution  The vertical resolution, 72 by default.
     398             :  */
     399           2 : void font_impl::set_size(int point_size, int x_resolution, int y_resolution)
     400             : {
     401           4 :     int const e(FT_Set_Char_Size(
     402             :               f_face
     403             :             , 0L
     404           2 :             , point_size * f_precision
     405             :             , x_resolution
     406           2 :             , y_resolution));
     407           2 :     if(e != FT_Err_Ok)
     408             :     {
     409           0 :         SNAP_LOG_ERROR
     410           0 :             << "FT_Set_Char_Size() failed with error #"
     411             :             << e
     412           0 :             << " for point: "
     413           0 :             << point_size * f_precision
     414           0 :             << " (including the upscaling), horizontal resolution: "
     415             :             << x_resolution
     416           0 :             << ", vertical resolution: "
     417             :             << y_resolution
     418             :             << SNAP_LOG_SEND;
     419             :     }
     420           2 : }
     421             : 
     422             : 
     423           8 : float font_impl::get_kerning(char32_t current_char, char32_t next_char)
     424             : {
     425           8 :     FT_Vector kern_advance = FT_Vector();
     426             : 
     427             :     //if(has_kerning_table()) -- TBD
     428             :     {
     429           8 :         FT_UInt const current_index(FT_Get_Char_Index(f_face, current_char));
     430           8 :         FT_UInt const next_index(FT_Get_Char_Index(f_face, next_char));
     431           8 :         int const e(FT_Get_Kerning(
     432             :                   f_face
     433             :                 , current_index
     434             :                 , next_index
     435             :                 , FT_KERNING_UNFITTED
     436           8 :                 , &kern_advance));
     437             :         if(e != FT_Err_Ok)
     438             :         {
     439             :             // this is probably common?
     440             :         }
     441             :     }
     442             : 
     443           8 :     return static_cast<float>(kern_advance.x) / static_cast<float>(f_precision);
     444             : }
     445             : 
     446             : 
     447         671 : void font_impl::tess_callback_edge(GLboolean edge, font_impl * impl)
     448             : {
     449             :     // we have this callback to force the GLU library to only create
     450             :     // triangles (GL_TRIANGLES); that way we avoid the GL_TRIANGLE_FAN
     451             :     // and GL_TRIANGLE_STRIP and reduce the amount of possible cases
     452             :     // outside of this library
     453             :     //
     454         671 :     snapdev::NOT_USED(edge, impl);
     455         671 : }
     456             : 
     457             : 
     458           8 : void font_impl::tess_callback_begin(GLenum type, font_impl * impl)
     459             : {
     460           8 :     if(type != GL_TRIANGLES)
     461             :     {
     462           0 :         throw std::runtime_error("we expected the type of the tessellation to be GL_TRIANGLES");
     463             :     }
     464             : 
     465           8 :     impl->callback_begin();
     466           8 : }
     467             : 
     468             : 
     469        1032 : void font_impl::tess_callback_vertex(GLdouble const * vertex, font_impl * impl)
     470             : {
     471        1032 :     impl->callback_vertex(point(vertex[0], vertex[1]));
     472        1032 : }
     473             : 
     474             : 
     475           0 : void font_impl::tess_callback_combine(
     476             :           GLdouble vertex[3]
     477             :         , GLdouble * vertex_data[4]
     478             :         , GLfloat weight[4]
     479             :         , GLdouble ** out_data
     480             :         , font_impl * impl)
     481             : {
     482           0 :     snapdev::NOT_USED(vertex_data, weight);
     483             : 
     484           0 :     point::pointer_t p(impl->callback_combine(point(vertex[0], vertex[1])));
     485           0 :     *out_data = p->f_coordinates;
     486             :     //const FTGL_DOUBLE* vertex = static_cast<const FTGL_DOUBLE*>(coords);
     487             :     //*outData = const_cast<FTGL_DOUBLE*>(mesh->Combine(vertex[0], vertex[1], vertex[2]));
     488           0 : }
     489             : 
     490             : 
     491           8 : void font_impl::tess_callback_end(font_impl * impl)
     492             : {
     493           8 :     impl->callback_end();
     494           8 : }
     495             : 
     496             : 
     497           0 : void font_impl::tess_callback_error(GLenum error_code, font_impl * impl)
     498             : {
     499           0 :     impl->callback_error(error_code);
     500           0 : }
     501             : 
     502             : 
     503           0 : void font_impl::callback_error(GLenum error_code)
     504             : {
     505           0 :     snapdev::NOT_USED(error_code);
     506           0 : }
     507             : 
     508             : 
     509           8 : void font_impl::callback_begin()
     510             : {
     511           8 :     f_current_mesh->begin();
     512           8 : }
     513             : 
     514             : 
     515           8 : void font_impl::callback_end()
     516             : {
     517           8 :     f_current_mesh->end();
     518           8 : }
     519             : 
     520             : 
     521        1032 : void font_impl::callback_vertex(point const & p)
     522             : {
     523        1032 :     f_current_mesh->add_point(point(p.x() / f_precision, p.y() / f_precision));
     524        1032 : }
     525             : 
     526             : 
     527           0 : point::pointer_t font_impl::callback_combine(point const & p)
     528             : {
     529             :     // the temporary vertex is used to have points that are allocated
     530             :     // and do not move in memory while building the mesh, once done
     531             :     // they get deleted
     532             :     //
     533             :     // WARNING: the same pointers are reused between glyphs, in other words
     534             :     //          the tessellation is not thread safe at all (but at least we
     535             :     //          avoid many alloc/free/re-alloc/re-free/...)
     536             :     //
     537           0 :     if(f_temporary_vertex_pos < f_temporary_vertex.size())
     538             :     {
     539           0 :         *f_temporary_vertex[f_temporary_vertex_pos] = p;
     540             :     }
     541             :     else
     542             :     {
     543           0 :         f_temporary_vertex.push_back(std::make_shared<point>(p));
     544             :     }
     545             : 
     546           0 :     point::pointer_t result(f_temporary_vertex[f_temporary_vertex_pos]);
     547             : 
     548           0 :     ++f_temporary_vertex_pos;
     549             : 
     550           0 :     return result;
     551             : }
     552             : 
     553             : 
     554             : 
     555             : 
     556             : }
     557             : 
     558             : 
     559             : 
     560             : 
     561             : ///////////
     562             : // ftfont
     563             : 
     564           1 : font::font(std::string const & filename)
     565           1 :     : f_impl(std::make_shared<detail::font_impl>(filename))
     566             : {
     567           1 : }
     568             : 
     569             : 
     570           0 : void font::set_precision(int precision)
     571             : {
     572           0 :     f_impl->set_precision(precision);
     573           0 : }
     574             : 
     575             : 
     576           1 : void font::set_size(int point, int x_resolution, int y_resolution)
     577             : {
     578           1 :     f_impl->set_size(point, x_resolution, y_resolution);
     579           1 : }
     580             : 
     581             : 
     582             : 
     583           9 : mesh::pointer_t font::get_mesh(char32_t glyph)
     584             : {
     585           9 :     auto it(f_map.find(glyph));
     586           9 :     if(it != f_map.end())
     587             :     {
     588           0 :         return it->second;
     589             :     }
     590             : 
     591             :     // not yet cached, build the mesh now
     592             :     //
     593          18 :     mesh::pointer_t result(f_impl->get_mesh(glyph));
     594           9 :     f_map[glyph] = result;
     595             : 
     596           9 :     return result;
     597             : }
     598             : 
     599             : 
     600           1 : mesh_string::pointer_t font::convert_string(std::string const & message)
     601             : {
     602           1 :     mesh_string::pointer_t result(std::make_shared<mesh_string>());
     603             : 
     604           2 :     std::u32string const s(libutf8::to_u32string(message));
     605           1 :     std::size_t const size(s.size());
     606           1 :     if(size > 0)
     607             :     {
     608           1 :         std::size_t const max(size - 1);
     609           9 :         for(std::size_t i(0); i < max; ++i)
     610             :         {
     611          16 :             mesh::pointer_t m(get_mesh(s[i]));
     612           8 :             if(m != nullptr)
     613             :             {
     614           8 :                 float advance(m->get_advance());
     615           8 :                 advance += f_impl->get_kerning(s[i], s[i + 1]);
     616           8 :                 result->add_glyph(m, advance);
     617             :             }
     618             :         }
     619           2 :         mesh::pointer_t m(get_mesh(s[max]));
     620           1 :         if(m != nullptr)
     621             :         {
     622           1 :             result->add_glyph(m, m->get_advance());
     623             :         }
     624             :     }
     625             : 
     626           2 :     return result;
     627             : }
     628             : 
     629             : 
     630           0 : float font::string_width(std::string const & message)
     631             : {
     632           0 :     float result(0.0f);
     633             : 
     634           0 :     std::u32string const s(libutf8::to_u32string(message));
     635           0 :     std::size_t const size(s.size());
     636           0 :     if(size > 0)
     637             :     {
     638           0 :         std::size_t const max(size - 1);
     639           0 :         for(std::size_t i(0); i < max; ++i)
     640             :         {
     641           0 :             mesh::pointer_t m(get_mesh(s[i]));
     642           0 :             if(m != nullptr)
     643             :             {
     644           0 :                 result += m->get_advance();
     645           0 :                 result += f_impl->get_kerning(s[i], s[i + 1]);
     646             :             }
     647             :         }
     648           0 :         mesh::pointer_t m(get_mesh(s[max]));
     649           0 :         if(m != nullptr)
     650             :         {
     651           0 :             result += m->get_advance();
     652             :         }
     653             :     }
     654             : 
     655           0 :     return result;
     656             : }
     657             : 
     658             : 
     659             : 
     660             : 
     661           6 : } // namespace ftmesh
     662             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13