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
|