Panda3D
Loading...
Searching...
No Matches
dynamicTextFont.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file dynamicTextFont.cxx
10 * @author drose
11 * @date 2002-02-08
12 */
13
14#include "dynamicTextFont.h"
15
16#ifdef HAVE_FREETYPE
17
18#undef interface // I don't know where this symbol is defined, but it interferes with FreeType.
19#include FT_OUTLINE_H
20#ifdef FT_BBOX_H
21#include FT_BBOX_H
22#endif
23#ifdef FT_BITMAP_H
24#include FT_BITMAP_H
25#endif
26#ifdef FT_STROKER_H
27#include FT_STROKER_H
28#endif
29
30#include "config_text.h"
31#include "config_putil.h"
32#include "config_express.h"
33#include "virtualFileSystem.h"
34#include "geomVertexData.h"
35#include "geomVertexFormat.h"
36#include "geomVertexWriter.h"
37#include "geomLinestrips.h"
38#include "geomTriangles.h"
39#include "renderState.h"
40#include "string_utils.h"
41#include "triangulator.h"
42// #include "renderModeAttrib.h" #include "antialiasAttrib.h"
43#include "colorAttrib.h"
44#include "textureAttrib.h"
45#include "transparencyAttrib.h"
46
47#ifdef HAVE_HARFBUZZ
48#include <hb-ft.h>
49#endif
50
51TypeHandle DynamicTextFont::_type_handle;
52
53
54/**
55 * The constructor expects the name of some font file that FreeType can read,
56 * along with face_index, indicating which font within the file to load
57 * (usually 0).
58 */
59DynamicTextFont::
60DynamicTextFont(const Filename &font_filename, int face_index) {
61 initialize();
62 _is_valid = load_font(font_filename, face_index);
63 TextFont::set_name(FreetypeFont::get_name());
64 TextFont::_line_height = FreetypeFont::get_line_height();
65 TextFont::_space_advance = FreetypeFont::get_space_advance();
66
67 _fg.set(1.0f, 1.0f, 1.0f, 1.0f);
68 _bg.set(1.0f, 1.0f, 1.0f, 0.0f);
69 _outline_color.set(1.0f, 1.0f, 1.0f, 0.0f);
70 _outline_width = 0.0f;
71 _outline_feather = 0.0f;
72 _has_outline = false;
73 _tex_format = Texture::F_alpha;
74 _needs_image_processing = false;
75}
76
77/**
78 * This constructor accepts a table of data representing the font file, loaded
79 * from some source other than a filename on disk.
80 */
81DynamicTextFont::
82DynamicTextFont(const char *font_data, int data_length, int face_index) {
83 initialize();
84 _is_valid = load_font(font_data, data_length, face_index);
85 TextFont::set_name(FreetypeFont::get_name());
86 TextFont::_line_height = FreetypeFont::_line_height;
87 TextFont::_space_advance = FreetypeFont::_space_advance;
88
89 _fg.set(1.0f, 1.0f, 1.0f, 1.0f);
90 _bg.set(1.0f, 1.0f, 1.0f, 0.0f);
91 _outline_color.set(1.0f, 1.0f, 1.0f, 0.0f);
92 _outline_width = 0.0f;
93 _outline_feather = 0.0f;
94 _has_outline = false;
95 _tex_format = Texture::F_alpha;
96 _needs_image_processing = false;
97}
98
99/**
100 *
101 */
102DynamicTextFont::
103DynamicTextFont(const DynamicTextFont &copy) :
104 TextFont(copy),
105 FreetypeFont(copy),
106 _texture_margin(copy._texture_margin),
107 _poly_margin(copy._poly_margin),
108 _page_size(copy._page_size),
109 _minfilter(copy._minfilter),
110 _magfilter(copy._magfilter),
111 _anisotropic_degree(copy._anisotropic_degree),
112 _render_mode(copy._render_mode),
113 _fg(copy._fg),
114 _bg(copy._bg),
115 _outline_color(copy._outline_color),
116 _outline_width(copy._outline_width),
117 _outline_feather(copy._outline_feather),
118 _has_outline(copy._has_outline),
119 _tex_format(copy._tex_format),
120 _needs_image_processing(copy._needs_image_processing),
121 _preferred_page(0),
122 _hb_font(nullptr)
123{
124}
125
126/**
127 *
128 */
129DynamicTextFont::
130~DynamicTextFont() {
131#ifdef HAVE_HARFBUZZ
132 if (_hb_font != nullptr) {
133 hb_font_destroy(_hb_font);
134 }
135#endif
136}
137
138/**
139 * Returns a new copy of the same font.
140 */
141PT(TextFont) DynamicTextFont::
142make_copy() const {
143 return new DynamicTextFont(*this);
144}
145
146/**
147 * Returns the number of pages associated with the font. Initially, the font
148 * has zero pages; when the first piece of text is rendered with the font, it
149 * will add additional pages as needed. Each page is a Texture object that
150 * contains the images for each of the glyphs currently in use somewhere.
151 */
152int DynamicTextFont::
153get_num_pages() const {
154 return _pages.size();
155}
156
157/**
158 * Returns the nth page associated with the font. Initially, the font has
159 * zero pages; when the first piece of text is rendered with the font, it will
160 * add additional pages as needed. Each page is a Texture object that
161 * contains the images for each of the glyphs currently in use somewhere.
162 */
163DynamicTextPage *DynamicTextFont::
164get_page(int n) const {
165 nassertr(n >= 0 && n < (int)_pages.size(), nullptr);
166 return _pages[n];
167}
168
169/**
170 * Removes all of the glyphs from the font that are no longer being used by
171 * any Geoms. Returns the number of glyphs removed.
172 */
173int DynamicTextFont::
174garbage_collect() {
175 int removed_count = 0;
176
177 // First, remove all the old entries from our cache index.
178 Cache new_cache;
179 Cache::iterator ci;
180 for (ci = _cache.begin(); ci != _cache.end(); ++ci) {
181 const TextGlyph *glyph = (*ci).second;
182 if (glyph == nullptr || glyph->get_ref_count() > 1) {
183 // Keep this one.
184 new_cache.insert(new_cache.end(), (*ci));
185 } else {
186 // Drop this one.
187 removed_count++;
188 }
189 }
190 _cache.swap(new_cache);
191
192 // Now, go through each page and do the same thing.
193 Pages::iterator pi;
194 for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
195 DynamicTextPage *page = (*pi);
196 page->garbage_collect(this);
197 }
198
199 return removed_count;
200}
201
202/**
203 * Drops all the glyphs out of the cache and frees any association with any
204 * previously-generated pages.
205 *
206 * Calling this frequently can result in wasted texture memory, as any
207 * previously rendered text will still keep a pointer to the old, previously-
208 * generated pages. As long as the previously rendered text remains around,
209 * the old pages will also remain around.
210 */
211void DynamicTextFont::
212clear() {
213 _cache.clear();
214 _pages.clear();
215 _empty_glyphs.clear();
216
217#ifdef HAVE_HARFBUZZ
218 if (_hb_font != nullptr) {
219 hb_font_destroy(_hb_font);
220 _hb_font = nullptr;
221 }
222#endif
223}
224
225/**
226 *
227 */
228void DynamicTextFont::
229write(std::ostream &out, int indent_level) const {
230 static const int max_glyph_name = 1024;
231 char glyph_name[max_glyph_name];
232
233 indent(out, indent_level)
234 << "DynamicTextFont " << get_name() << ", "
235 << get_num_pages() << " pages, "
236 << _cache.size() << " glyphs:\n";
237 Cache::const_iterator ci;
238 for (ci = _cache.begin(); ci != _cache.end(); ++ci) {
239 int glyph_index = (*ci).first;
240 indent(out, indent_level + 2)
241 << glyph_index;
242
243 FT_Face face = acquire_face();
244 if (FT_HAS_GLYPH_NAMES(face)) {
245 int error = FT_Get_Glyph_Name(face, glyph_index,
246 glyph_name, max_glyph_name);
247
248 // Some fonts, notably MS Mincho, claim to have glyph names but only
249 // report ".notdef" as the name of each glyph. Thanks.
250 if (!error && strcmp(glyph_name, ".notdef") != 0) {
251 out << " (" << glyph_name << ")";
252 }
253 }
254 release_face(face);
255
256 out << '\n';
257 }
258}
259
260/**
261 * Gets the glyph associated with the given character code, as well as an
262 * optional scaling parameter that should be applied to the glyph's geometry
263 * and advance parameters. Returns true if the glyph exists, false if it does
264 * not. Even if the return value is false, the value for glyph might be
265 * filled in with a printable glyph.
266 */
267bool DynamicTextFont::
268get_glyph(int character, CPT(TextGlyph) &glyph) {
269 if (!_is_valid) {
270 glyph = nullptr;
271 return false;
272 }
273
274 FT_Face face = acquire_face();
275 int glyph_index = FT_Get_Char_Index(face, character);
276 if (text_cat.is_spam()) {
277 text_cat.spam()
278 << *this << " maps " << character << " to glyph " << glyph_index << "\n";
279 }
280
281 Cache::iterator ci = _cache.find(glyph_index);
282 if (ci != _cache.end()) {
283 glyph = (*ci).second;
284 } else {
285 glyph = make_glyph(character, face, glyph_index);
286 _cache.insert(Cache::value_type(glyph_index, glyph.p()));
287 }
288
289 if (glyph.is_null()) {
290 glyph = get_invalid_glyph();
291 glyph_index = 0;
292 }
293
294 release_face(face);
295 return (glyph_index != 0);
296}
297
298/**
299 * Returns the amount by which to offset the second glyph when it directly
300 * follows the first glyph. This is an additional offset that is added on top
301 * of the advance.
302 */
303PN_stdfloat DynamicTextFont::
304get_kerning(int first, int second) const {
305 if (!_is_valid) {
306 return 0;
307 }
308
309 FT_Face face = acquire_face();
310 if (!FT_HAS_KERNING(face)) {
311 release_face(face);
312 return 0;
313 }
314
315 int first_index = FT_Get_Char_Index(face, first);
316 int second_index = FT_Get_Char_Index(face, second);
317
318 FT_Vector delta;
319 FT_Get_Kerning(face, first_index, second_index, FT_KERNING_DEFAULT, &delta);
320 release_face(face);
321
322 return delta.x / (_font_pixels_per_unit * 64);
323}
324
325/**
326 * Like get_glyph, but uses a glyph index.
327 */
328bool DynamicTextFont::
329get_glyph_by_index(int character, int glyph_index, CPT(TextGlyph) &glyph) {
330 if (!_is_valid) {
331 glyph = nullptr;
332 return false;
333 }
334
335 Cache::iterator ci = _cache.find(glyph_index);
336 if (ci != _cache.end()) {
337 glyph = (*ci).second;
338 } else {
339 FT_Face face = acquire_face();
340 glyph = make_glyph(character, face, glyph_index);
341 _cache.insert(Cache::value_type(glyph_index, glyph.p()));
342 release_face(face);
343 }
344
345 if (glyph.is_null()) {
346 glyph = get_invalid_glyph();
347 return false;
348 }
349
350 return true;
351}
352
353/**
354 * If Panda was compiled with HarfBuzz enabled, returns a HarfBuzz font for
355 * this font.
356 */
357hb_font_t *DynamicTextFont::
358get_hb_font() const {
359#ifdef HAVE_HARFBUZZ
360 if (_hb_font != nullptr) {
361 return _hb_font;
362 }
363
364 FT_Face face = acquire_face();
365 _hb_font = hb_ft_font_create(face, nullptr);
366 release_face(face);
367
368 return _hb_font;
369#else
370 return nullptr;
371#endif
372}
373
374/**
375 * Called from both constructors to set up some initial values.
376 */
377void DynamicTextFont::
378initialize() {
379 _texture_margin = text_texture_margin;
380 _poly_margin = text_poly_margin;
381 _page_size.set(text_page_size[0], text_page_size[1]);
382
383 // We don't necessarily want to use mipmaps, since we don't want to
384 // regenerate those every time the texture changes, but we probably do want
385 // at least linear filtering. Use whatever the Configrc file suggests.
386 _minfilter = text_minfilter;
387 _magfilter = text_magfilter;
388
389 // Anisotropic filtering can help the look of the text, and doesn't require
390 // generating mipmaps, but does require hardware support.
391 _anisotropic_degree = text_anisotropic_degree;
392
393 _render_mode = text_render_mode;
394 _winding_order = WO_default;
395
396 _preferred_page = 0;
397
398 _hb_font = nullptr;
399}
400
401/**
402 * Reapplies all current filter settings to all of the pages. This is
403 * normally called whenever the filter settings change.
404 */
405void DynamicTextFont::
406update_filters() {
407 Pages::iterator pi;
408 for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
409 DynamicTextPage *page = (*pi);
410 page->set_minfilter(_minfilter);
411 page->set_magfilter(_magfilter);
412 page->set_anisotropic_degree(_anisotropic_degree);
413 }
414}
415
416/**
417 * Examines the _fg, _bg, and _outline colors to determine the appropriate
418 * format for the font pages, including the outline properties.
419 */
420void DynamicTextFont::
421determine_tex_format() {
422 nassertv(get_num_pages() == 0);
423
424 _has_outline = (_outline_color != _bg && _outline_width > 0.0f);
425 _needs_image_processing = true;
426
427 bool needs_color = false;
428 bool needs_grayscale = false;
429 bool needs_alpha = false;
430
431 if (_fg[1] != _fg[0] || _fg[2] != _fg[0] ||
432 _bg[1] != _bg[0] || _bg[2] != _bg[0] ||
433 (_has_outline && (_outline_color[1] != _outline_color[0] || _outline_color[2] != _outline_color[0]))) {
434 // At least one of fg, bg, or outline contains a color, not just a
435 // grayscale value.
436 needs_color = true;
437
438 } else if (_fg[0] != 1.0f || _fg[1] != 1.0f || _fg[2] != 1.0f ||
439 _bg[0] != 1.0f || _bg[1] != 1.0f || _bg[2] != 1.0f ||
440 (_has_outline && (_outline_color[0] != 1.0f || _outline_color[1] != 1.0f || _outline_color[2] != 1.0f))) {
441 // fg, bg, and outline contain non-white grayscale values.
442 needs_grayscale = true;
443 }
444
445 if (_fg[3] != 1.0f || _bg[3] != 1.0f ||
446 (_has_outline && (_outline_color[3] != 1.0f))) {
447 // fg, bg, and outline contain non-opaque alpha values.
448 needs_alpha = true;
449 }
450
451 if (needs_color) {
452 if (needs_alpha) {
453 _tex_format = Texture::F_rgba;
454 } else {
455 _tex_format = Texture::F_rgb;
456 }
457 } else if (needs_grayscale) {
458 if (needs_alpha) {
459 _tex_format = Texture::F_luminance_alpha;
460 } else {
461 _tex_format = Texture::F_luminance;
462 }
463 } else {
464 if (needs_alpha) {
465 _tex_format = Texture::F_alpha;
466
467 if (!_has_outline &&
468 _fg == LColor(1.0f, 1.0f, 1.0f, 1.0f) &&
469 _bg == LColor(1.0f, 1.0f, 1.0f, 0.0f)) {
470 // This is the standard font color. It can be copied directly without
471 // any need for special processing.
472 _needs_image_processing = false;
473 }
474
475 } else {
476 // This won't be a very interesting font.
477 _tex_format = Texture::F_luminance;
478 }
479 }
480}
481
482/**
483 * Slots a space in the texture map for the new character and renders the
484 * glyph, returning the newly-created TextGlyph object, or NULL if the glyph
485 * cannot be created for some reason.
486 */
487CPT(TextGlyph) DynamicTextFont::
488make_glyph(int character, FT_Face face, int glyph_index) {
489 if (!load_glyph(face, glyph_index, false)) {
490 return nullptr;
491 }
492
493 FT_GlyphSlot slot = face->glyph;
494 FT_Bitmap &bitmap = slot->bitmap;
495
496 if ((bitmap.width == 0 || bitmap.rows == 0) && (glyph_index == 0)) {
497 // Here's a special case: a glyph_index of 0 means an invalid glyph. Some
498 // fonts define a symbol to represent an invalid glyph, but if that symbol
499 // is the empty bitmap, we return NULL, and use Panda's invalid glyph in
500 // its place. We do this to guarantee that every invalid glyph is visible
501 // as *something*.
502 return nullptr;
503 }
504
505 PN_stdfloat advance = slot->advance.x / 64.0;
506 advance /= _font_pixels_per_unit;
507
508 if (_render_mode != RM_texture &&
509 slot->format == ft_glyph_format_outline) {
510 // Re-stroke the glyph to make it an outline glyph.
511 /*
512 FT_Stroker stroker;
513 FT_Stroker_New(face->memory, &stroker);
514 FT_Stroker_Set(stroker, 16 * 16, FT_STROKER_LINECAP_BUTT,
515 FT_STROKER_LINEJOIN_ROUND, 0);
516
517 FT_Stroker_ParseOutline(stroker, &slot->outline, 0);
518
519 FT_UInt num_points, num_contours;
520 FT_Stroker_GetCounts(stroker, &num_points, &num_contours);
521
522 FT_Outline border;
523 FT_Outline_New(_ft_library, num_points, num_contours, &border);
524 border.n_points = 0;
525 border.n_contours = 0;
526 FT_Stroker_Export(stroker, &border);
527 FT_Stroker_Done(stroker);
528
529 FT_Outline_Done(_ft_library, &slot->outline);
530 memcpy(&slot->outline, &border, sizeof(border));
531 */
532
533 // Ask FreeType to extract the contours out of the outline description.
534 decompose_outline(slot->outline);
535
536 PT(TextGlyph) glyph =
537 new TextGlyph(character, advance);
538 switch (_render_mode) {
539 case RM_wireframe:
540 render_wireframe_contours(glyph);
541 return glyph;
542
543 case RM_polygon:
544 render_polygon_contours(glyph, true, false);
545 return glyph;
546
547 case RM_extruded:
548 render_polygon_contours(glyph, false, true);
549 return glyph;
550
551 case RM_solid:
552 render_polygon_contours(glyph, true, true);
553 return glyph;
554
555 case RM_texture:
556 case RM_distance_field:
557 default:
558 break;
559 }
560 }
561
562 PN_stdfloat tex_x_size, tex_y_size, tex_x_orig, tex_y_orig;
563 FT_BBox bounds;
564 TransparencyAttrib::Mode alpha_mode;
565
566 if (_render_mode == RM_texture) {
567 // Render the glyph if necessary.
568 if (slot->format != ft_glyph_format_bitmap) {
569 FT_Render_Glyph(slot, ft_render_mode_normal);
570 }
571
572 tex_x_size = bitmap.width;
573 tex_y_size = bitmap.rows;
574 tex_x_orig = slot->bitmap_left;
575 tex_y_orig = slot->bitmap_top;
576 alpha_mode = TransparencyAttrib::M_alpha;
577
578 } else {
579 // Calculate suitable texture dimensions for the signed distance field.
580 // This is the same calculation that Freetype uses in its bitmap renderer.
581 FT_Outline_Get_CBox(&slot->outline, &bounds);
582
583 bounds.xMin = bounds.xMin & ~63;
584 bounds.yMin = bounds.yMin & ~63;
585 bounds.xMax = (bounds.xMax + 63) & ~63;
586 bounds.yMax = (bounds.yMax + 63) & ~63;
587
588 tex_x_size = (bounds.xMax - bounds.xMin) >> 6;
589 tex_y_size = (bounds.yMax - bounds.yMin) >> 6;
590 tex_x_orig = (bounds.xMin >> 6);
591 tex_y_orig = (bounds.yMax >> 6);
592 alpha_mode = TransparencyAttrib::M_binary;
593 }
594
595 if (tex_x_size == 0 || tex_y_size == 0) {
596 // If we got an empty bitmap, it's a special case.
597
598 PT(TextGlyph) glyph =
599 new DynamicTextGlyph(character, advance);
600 _empty_glyphs.push_back(glyph);
601 return glyph;
602
603 } else {
604 DynamicTextGlyph *glyph;
605
606 int outline = 0;
607
608 if (_render_mode == RM_distance_field) {
609 tex_x_size /= _scale_factor;
610 tex_y_size /= _scale_factor;
611 int int_x_size = (int)ceil(tex_x_size);
612 int int_y_size = (int)ceil(tex_y_size);
613
614 outline = 4;
615 int_x_size += outline * 2;
616 int_y_size += outline * 2;
617 tex_x_size += outline * 2;
618 tex_y_size += outline * 2;
619
620 PNMImage image(int_x_size, int_y_size, PNMImage::CT_grayscale);
621 render_distance_field(image, outline, bounds.xMin, bounds.yMin);
622
623 glyph = slot_glyph(character, int_x_size, int_y_size, advance);
624 if (!_needs_image_processing) {
625 copy_pnmimage_to_texture(image, glyph);
626 } else {
627 blend_pnmimage_to_texture(image, glyph, _fg);
628 }
629
630 } else if (_tex_pixels_per_unit == _font_pixels_per_unit &&
631 !_needs_image_processing) {
632 // If the bitmap produced from the font doesn't require scaling or any
633 // other processing before it goes to the texture, we can just copy it
634 // directly into the texture.
635 glyph = slot_glyph(character, bitmap.width, bitmap.rows, advance);
636 copy_bitmap_to_texture(bitmap, glyph);
637
638 } else {
639 // Otherwise, we need to copy to a PNMImage first, so we can scale it
640 // andor process it; and then copy it to the texture from there.
641 tex_x_size /= _scale_factor;
642 tex_y_size /= _scale_factor;
643 int int_x_size = (int)ceil(tex_x_size);
644 int int_y_size = (int)ceil(tex_y_size);
645 int bmp_x_size = (int)(int_x_size * _scale_factor + 0.5f);
646 int bmp_y_size = (int)(int_y_size * _scale_factor + 0.5f);
647
648 PNMImage image(bmp_x_size, bmp_y_size, PNMImage::CT_grayscale);
649 copy_bitmap_to_pnmimage(bitmap, image);
650
651 PNMImage reduced(int_x_size, int_y_size, PNMImage::CT_grayscale);
652 reduced.quick_filter_from(image);
653
654 // convert the outline width from points to tex_pixels.
655 PN_stdfloat outline_pixels = _outline_width / _points_per_unit * _tex_pixels_per_unit;
656 outline = (int)ceil(outline_pixels);
657
658 int_x_size += outline * 2;
659 int_y_size += outline * 2;
660 tex_x_size += outline * 2;
661 tex_y_size += outline * 2;
662 glyph = slot_glyph(character, int_x_size, int_y_size, advance);
663
664 if (outline != 0) {
665 // Pad the glyph image to make room for the outline.
666 PNMImage padded(int_x_size, int_y_size, PNMImage::CT_grayscale);
667 padded.copy_sub_image(reduced, outline, outline);
668 copy_pnmimage_to_texture(padded, glyph);
669
670 } else {
671 copy_pnmimage_to_texture(reduced, glyph);
672 }
673 }
674
675 DynamicTextPage *page = glyph->get_page();
676 if (page != nullptr) {
677 int bitmap_top = (int)floor(tex_y_orig + outline * _scale_factor + 0.5f);
678 int bitmap_left = (int)floor(tex_x_orig - outline * _scale_factor + 0.5f);
679
680 tex_x_size += glyph->_margin * 2;
681 tex_y_size += glyph->_margin * 2;
682
683 // Determine the corners of the rectangle in geometric units.
684 PN_stdfloat tex_poly_margin = _poly_margin / _tex_pixels_per_unit;
685 PN_stdfloat origin_y = bitmap_top / _font_pixels_per_unit;
686 PN_stdfloat origin_x = bitmap_left / _font_pixels_per_unit;
687
688 LVecBase4 dimensions(
689 origin_x - tex_poly_margin,
690 origin_y - tex_y_size / _tex_pixels_per_unit - tex_poly_margin,
691 origin_x + tex_x_size / _tex_pixels_per_unit + tex_poly_margin,
692 origin_y + tex_poly_margin);
693
694 // And the corresponding corners in UV units. We add 0.5f to center the
695 // UV in the middle of its texel, to minimize roundoff errors when we
696 // are close to 1-to-1 pixel size.
697 LVecBase2i page_size = page->get_size();
698 LVecBase4 texcoords(
699 ((PN_stdfloat)(glyph->_x - _poly_margin) + 0.5f) / page_size[0],
700 1.0f - ((PN_stdfloat)(glyph->_y + _poly_margin + tex_y_size) + 0.5f) / page_size[1],
701 ((PN_stdfloat)(glyph->_x + _poly_margin + tex_x_size) + 0.5f) / page_size[0],
702 1.0f - ((PN_stdfloat)(glyph->_y - _poly_margin) + 0.5f) / page_size[1]);
703
704 CPT(RenderState) state;
705 state = RenderState::make(TextureAttrib::make(page),
706 TransparencyAttrib::make(alpha_mode));
707 state = state->add_attrib(ColorAttrib::make_flat(LColor(1.0f, 1.0f, 1.0f, 1.0f)), -1);
708
709 glyph->set_quad(dimensions, texcoords, state);
710 }
711
712 return glyph;
713 }
714}
715
716/**
717 * Copies a bitmap as rendered by FreeType directly into the texture memory
718 * image for the indicated glyph, without any scaling of pixels.
719 */
720void DynamicTextFont::
721copy_bitmap_to_texture(const FT_Bitmap &bitmap, DynamicTextGlyph *glyph) {
722 if (bitmap.pixel_mode == ft_pixel_mode_grays && bitmap.num_grays == 256) {
723 // This is the easy case: we can memcpy the rendered glyph directly into
724 // our texture image, one row at a time.
725 unsigned char *buffer_row = bitmap.buffer;
726 for (int yi = 0; yi < (int)bitmap.rows; yi++) {
727
728 unsigned char *texture_row = glyph->get_row(yi);
729 nassertv(texture_row != nullptr);
730 memcpy(texture_row, buffer_row, bitmap.width);
731 buffer_row += bitmap.pitch;
732 }
733
734 } else if (bitmap.pixel_mode == ft_pixel_mode_mono) {
735 // This is a little bit more work: we have to expand the one-bit-per-pixel
736 // bitmap into a one-byte-per-pixel texture.
737 unsigned char *buffer_row = bitmap.buffer;
738 for (int yi = 0; yi < (int)bitmap.rows; yi++) {
739 unsigned char *texture_row = glyph->get_row(yi);
740 nassertv(texture_row != nullptr);
741
742 int bit = 0x80;
743 unsigned char *b = buffer_row;
744 for (int xi = 0; xi < (int)bitmap.width; xi++) {
745 if (*b & bit) {
746 texture_row[xi] = 0xff;
747 } else {
748 texture_row[xi] = 0x00;
749 }
750 bit >>= 1;
751 if (bit == 0) {
752 ++b;
753 bit = 0x80;
754 }
755 }
756
757 buffer_row += bitmap.pitch;
758 }
759
760
761 } else if (bitmap.pixel_mode == ft_pixel_mode_grays) {
762 // Here we must expand a grayscale pixmap with n levels of gray into our
763 // 256-level texture.
764 unsigned char *buffer_row = bitmap.buffer;
765 for (int yi = 0; yi < (int)bitmap.rows; yi++) {
766 unsigned char *texture_row = glyph->get_row(yi);
767 nassertv(texture_row != nullptr);
768 for (int xi = 0; xi < (int)bitmap.width; xi++) {
769 texture_row[xi] = (int)(buffer_row[xi] * 255) / (bitmap.num_grays - 1);
770 }
771 buffer_row += bitmap.pitch;
772 }
773
774 } else {
775 text_cat.error()
776 << "Unexpected pixel mode in bitmap: " << (int)bitmap.pixel_mode << "\n";
777 }
778}
779
780/**
781 * Copies a bitmap stored in a PNMImage into the texture memory image for the
782 * indicated glyph.
783 */
784void DynamicTextFont::
785copy_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph) {
786 if (!_needs_image_processing) {
787 // Copy the image directly into the alpha component of the texture.
788 nassertv(glyph->_page->get_num_components() == 1);
789 for (int yi = 0; yi < image.get_y_size(); yi++) {
790 unsigned char *texture_row = glyph->get_row(yi);
791 nassertv(texture_row != nullptr);
792 for (int xi = 0; xi < image.get_x_size(); xi++) {
793 texture_row[xi] = image.get_gray_val(xi, yi);
794 }
795 }
796
797 } else {
798 if (_has_outline) {
799 // Gaussian blur the glyph to generate an outline.
800 PNMImage outline(image.get_x_size(), image.get_y_size(), PNMImage::CT_grayscale);
801 PN_stdfloat outline_pixels = _outline_width / _points_per_unit * _tex_pixels_per_unit;
802 outline.gaussian_filter_from(outline_pixels * 0.707, image);
803
804 // Filter the resulting outline to make a harder edge. Square
805 // _outline_feather first to make the range more visually linear (this
806 // approximately compensates for the Gaussian falloff of the feathered
807 // edge).
808 PN_stdfloat f = _outline_feather * _outline_feather;
809
810 for (int yi = 0; yi < outline.get_y_size(); yi++) {
811 for (int xi = 0; xi < outline.get_x_size(); xi++) {
812 PN_stdfloat v = outline.get_gray(xi, yi);
813 if (v == 0.0f) {
814 // Do nothing.
815 } else if (v >= f) {
816 // Clamp to 1.
817 outline.set_gray(xi, yi, 1.0);
818 } else {
819 // Linearly scale the range 0 .. f onto 0 .. 1.
820 outline.set_gray(xi, yi, v / f);
821 }
822 }
823 }
824
825 // Now blend that into the texture.
826 blend_pnmimage_to_texture(outline, glyph, _outline_color);
827 }
828
829 // Colorize the image as we copy it in. This assumes the previous color
830 // at this part of the texture was already initialized to the background
831 // color.
832 blend_pnmimage_to_texture(image, glyph, _fg);
833 }
834}
835
836/**
837 * Blends the PNMImage into the appropriate part of the texture, where 0.0 in
838 * the image indicates the color remains the same, and 1.0 indicates the color
839 * is assigned the indicated foreground color.
840 */
841void DynamicTextFont::
842blend_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph,
843 const LColor &fg) {
844 LColor fgv = fg * 255.0f;
845
846 int num_components = glyph->_page->get_num_components();
847 if (num_components == 1) {
848 // Luminance or alpha.
849 int ci = 3;
850 if (glyph->_page->get_format() != Texture::F_alpha) {
851 ci = 0;
852 }
853
854 for (int yi = 0; yi < image.get_y_size(); yi++) {
855 unsigned char *texture_row = glyph->get_row(yi);
856 nassertv(texture_row != nullptr);
857 for (int xi = 0; xi < image.get_x_size(); xi++) {
858 unsigned char *tr = texture_row + xi;
859 PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
860 tr[0] = (unsigned char)(tr[0] + t * (fgv[ci] - tr[0]));
861 }
862 }
863
864 } else if (num_components == 2) {
865 // Luminance + alpha.
866
867 for (int yi = 0; yi < image.get_y_size(); yi++) {
868 unsigned char *texture_row = glyph->get_row(yi);
869 nassertv(texture_row != nullptr);
870 for (int xi = 0; xi < image.get_x_size(); xi++) {
871 unsigned char *tr = texture_row + xi * 2;
872 PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
873 tr[0] = (unsigned char)(tr[0] + t * (fgv[0] - tr[0]));
874 tr[1] = (unsigned char)(tr[1] + t * (fgv[3] - tr[1]));
875 }
876 }
877
878 } else if (num_components == 3) {
879 // RGB.
880
881 for (int yi = 0; yi < image.get_y_size(); yi++) {
882 unsigned char *texture_row = glyph->get_row(yi);
883 nassertv(texture_row != nullptr);
884 for (int xi = 0; xi < image.get_x_size(); xi++) {
885 unsigned char *tr = texture_row + xi * 3;
886 PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
887 tr[0] = (unsigned char)(tr[0] + t * (fgv[2] - tr[0]));
888 tr[1] = (unsigned char)(tr[1] + t * (fgv[1] - tr[1]));
889 tr[2] = (unsigned char)(tr[2] + t * (fgv[0] - tr[2]));
890 }
891 }
892
893 } else { // (num_components == 4)
894 // RGBA.
895
896 for (int yi = 0; yi < image.get_y_size(); yi++) {
897 unsigned char *texture_row = glyph->get_row(yi);
898 nassertv(texture_row != nullptr);
899 for (int xi = 0; xi < image.get_x_size(); xi++) {
900 unsigned char *tr = texture_row + xi * 4;
901 PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
902 tr[0] = (unsigned char)(tr[0] + t * (fgv[2] - tr[0]));
903 tr[1] = (unsigned char)(tr[1] + t * (fgv[1] - tr[1]));
904 tr[2] = (unsigned char)(tr[2] + t * (fgv[0] - tr[2]));
905 tr[3] = (unsigned char)(tr[3] + t * (fgv[3] - tr[3]));
906 }
907 }
908 }
909}
910
911/**
912 * Chooses a page that will have room for a glyph of the indicated size (after
913 * expanding the indicated size by the current margin). Returns the newly-
914 * allocated glyph on the chosen page; the glyph has not been filled in yet
915 * except with its size.
916 */
917DynamicTextGlyph *DynamicTextFont::
918slot_glyph(int character, int x_size, int y_size, PN_stdfloat advance) {
919 // Increase the indicated size by the current margin.
920 x_size += _texture_margin * 2;
921 y_size += _texture_margin * 2;
922
923 if (!_pages.empty()) {
924 // Start searching on the preferred page. That way, we'll fill up the
925 // preferred page first, and we can gradually rotate this page around; it
926 // keeps us from spending too much time checking already-filled pages for
927 // space.
928 _preferred_page = _preferred_page % _pages.size();
929 int pi = _preferred_page;
930
931 do {
932 DynamicTextPage *page = _pages[pi];
933 DynamicTextGlyph *glyph = page->slot_glyph(character, x_size, y_size, _texture_margin, advance);
934 if (glyph != nullptr) {
935 // Once we found a page to hold the glyph, that becomes our new
936 // preferred page.
937 _preferred_page = pi;
938 return glyph;
939 }
940
941 if (page->is_empty()) {
942 // If we couldn't even put it on an empty page, we're screwed.
943 text_cat.error()
944 << "Glyph of size " << x_size << " by " << y_size
945 << " pixels won't fit on an empty page.\n";
946 return nullptr;
947 }
948
949 pi = (pi + 1) % _pages.size();
950 } while (pi != _preferred_page);
951 }
952
953 // All pages are filled. Can we free up space by removing some old glyphs?
954 if (garbage_collect() != 0) {
955 // Yes, we just freed up some space. Try once more, recursively.
956 return slot_glyph(character, x_size, y_size, advance);
957
958 } else {
959 // No good; all recorded glyphs are actually in use. We need to make a
960 // new page.
961 _preferred_page = _pages.size();
962 PT(DynamicTextPage) page = new DynamicTextPage(this, _preferred_page);
963 _pages.push_back(page);
964 return page->slot_glyph(character, x_size, y_size, _texture_margin, advance);
965 }
966}
967
968/**
969 * Converts from the _contours list to an actual glyph geometry, as a
970 * wireframe render.
971 */
972void DynamicTextFont::
973render_wireframe_contours(TextGlyph *glyph) {
974 PT(GeomVertexData) vdata = new GeomVertexData
975 (std::string(), GeomVertexFormat::get_v3(),
976 Geom::UH_static);
977 GeomVertexWriter vertex(vdata, InternalName::get_vertex());
978
979 PT(GeomLinestrips) lines = new GeomLinestrips(Geom::UH_static);
980
981 Contours::const_iterator ci;
982 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
983 const Contour &contour = (*ci);
984 Points::const_iterator pi;
985
986 for (pi = contour._points.begin(); pi != contour._points.end(); ++pi) {
987 const LPoint2 &p = (*pi)._p;
988 vertex.add_data3(p[0], 0.0f, p[1]);
989 }
990
991 lines->add_next_vertices(contour._points.size());
992 lines->close_primitive();
993 }
994
995 glyph->set_geom(vdata, lines, RenderState::make_empty());
996 _contours.clear();
997}
998
999/**
1000 * Converts from the _contours list to an actual glyph geometry, as a polygon
1001 * render.
1002 */
1003void DynamicTextFont::
1004render_polygon_contours(TextGlyph *glyph, bool face, bool extrude) {
1005 PT(GeomVertexData) vdata = new GeomVertexData
1006 (std::string(), GeomVertexFormat::get_v3n3(),
1007 Geom::UH_static);
1008 GeomVertexWriter vertex(vdata, InternalName::get_vertex());
1009 GeomVertexWriter normal(vdata, InternalName::get_normal());
1010
1011 PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static);
1012 Triangulator t;
1013
1014 Contours::iterator ci;
1015
1016 if (face) {
1017 // First, build up the list of vertices for the face, and determine which
1018 // contours are solid and which are holes.
1019 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1020 Contour &contour = (*ci);
1021
1022 t.clear_polygon();
1023 contour._start_vertex = t.get_num_vertices();
1024 for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1025 const LPoint2 &p = contour._points[i]._p;
1026 vertex.add_data3(p[0], 0.0f, p[1]);
1027 normal.add_data3(0.0f, -1.0f, 0.0f);
1028 int vi = t.add_vertex(p[0], p[1]);
1029 t.add_polygon_vertex(vi);
1030 }
1031
1032 contour._is_solid = t.is_left_winding();
1033 }
1034
1035 // Now go back and generate the actual triangles for the face.
1036 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1037 const Contour &contour = (*ci);
1038
1039 if (contour._is_solid && !contour._points.empty()) {
1040 t.clear_polygon();
1041 for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1042 t.add_polygon_vertex(contour._start_vertex + i);
1043 }
1044
1045 // Also add all the holes to each polygon.
1046 Contours::iterator cj;
1047 for (cj = _contours.begin(); cj != _contours.end(); ++cj) {
1048 Contour &hole = (*cj);
1049 if (!hole._is_solid && !hole._points.empty()) {
1050 t.begin_hole();
1051 for (size_t j = 0; j < hole._points.size() - 1; ++j) {
1052 t.add_hole_vertex(hole._start_vertex + j);
1053 }
1054 }
1055 }
1056
1057 t.triangulate();
1058 int num_triangles = t.get_num_triangles();
1059 for (int ti = 0; ti < num_triangles; ++ti) {
1060 tris->add_vertex(t.get_triangle_v0(ti));
1061 tris->add_vertex(t.get_triangle_v1(ti));
1062 tris->add_vertex(t.get_triangle_v2(ti));
1063 tris->close_primitive();
1064 }
1065 }
1066 }
1067 }
1068
1069 if (extrude) {
1070 // If we're generating extruded geometry (polygons along the edges, down
1071 // the y axis), generate them now. These are pretty easy, but we need to
1072 // create more vertices--they don't share the same normals.
1073 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1074 const Contour &contour = (*ci);
1075
1076 for (size_t i = 0; i < contour._points.size(); ++i) {
1077 const ContourPoint &cp = contour._points[i];
1078 const LPoint2 &p = cp._p;
1079 const LVector2 &t_in = cp._in;
1080 const LVector2 &t_out = cp._out;
1081
1082 LVector3 n_in(t_in[1], 0.0f, -t_in[0]);
1083 vertex.add_data3(p[0], 1.0f, p[1]);
1084 vertex.add_data3(p[0], 0.0f, p[1]);
1085 normal.add_data3(n_in);
1086 normal.add_data3(n_in);
1087
1088 if (i != 0) {
1089 int vi = vertex.get_write_row();
1090 tris->add_vertex(vi - 4);
1091 tris->add_vertex(vi - 2);
1092 tris->add_vertex(vi - 1);
1093 tris->close_primitive();
1094 tris->add_vertex(vi - 1);
1095 tris->add_vertex(vi - 3);
1096 tris->add_vertex(vi - 4);
1097 tris->close_primitive();
1098 }
1099
1100 if (i != contour._points.size() - 1 && !t_in.almost_equal(t_out)) {
1101 // If the out tangent is different from the in tangent, we need to
1102 // store new vertices for the next quad.
1103 LVector3 n_out(t_out[1], 0.0f, -t_out[0]);
1104 vertex.add_data3(p[0], 1.0f, p[1]);
1105 vertex.add_data3(p[0], 0.0f, p[1]);
1106 normal.add_data3(n_out);
1107 normal.add_data3(n_out);
1108 }
1109 }
1110 }
1111
1112 if (face) {
1113 // Render the back side of the face too.
1114 int back_start = vertex.get_write_row();
1115
1116 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1117 Contour &contour = (*ci);
1118 for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1119 const LPoint2 &p = contour._points[i]._p;
1120 vertex.add_data3(p[0], 1.0f, p[1]);
1121 normal.add_data3(0.0f, 1.0f, 0.0f);
1122 }
1123 }
1124
1125 // Now go back and generate the actual triangles for the face.
1126 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1127 const Contour &contour = (*ci);
1128
1129 if (contour._is_solid && !contour._points.empty()) {
1130 t.clear_polygon();
1131 for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1132 t.add_polygon_vertex(contour._start_vertex + i);
1133 }
1134
1135 // Also add all the holes to each polygon.
1136 Contours::iterator cj;
1137 for (cj = _contours.begin(); cj != _contours.end(); ++cj) {
1138 Contour &hole = (*cj);
1139 if (!hole._is_solid && !hole._points.empty()) {
1140 t.begin_hole();
1141 for (size_t j = 0; j < hole._points.size() - 1; ++j) {
1142 t.add_hole_vertex(hole._start_vertex + j);
1143 }
1144 }
1145 }
1146
1147 t.triangulate();
1148 int num_triangles = t.get_num_triangles();
1149 for (int ti = 0; ti < num_triangles; ++ti) {
1150 tris->add_vertex(t.get_triangle_v2(ti) + back_start);
1151 tris->add_vertex(t.get_triangle_v1(ti) + back_start);
1152 tris->add_vertex(t.get_triangle_v0(ti) + back_start);
1153 tris->close_primitive();
1154 }
1155 }
1156 }
1157 }
1158 }
1159
1160 glyph->set_geom(vdata, tris, RenderState::make_empty());
1161 // glyph->set_geom(vdata, tris, RenderState::make(RenderModeAttrib::make(Ren
1162 // derModeAttrib::M_wireframe))); glyph->set_geom(vdata, tris,
1163 // RenderState::make(AntialiasAttrib::make(AntialiasAttrib::M_auto)));
1164 _contours.clear();
1165}
1166
1167#endif // HAVE_FREETYPE
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
Defines a series of line strips.
Defines a series of disconnected triangles.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
static const GeomVertexFormat * get_v3n3()
Returns a standard vertex format with a 3-component normal and a 3-component vertex position.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
int get_x_size() const
Returns the number of pixels in the X direction.
int get_y_size() const
Returns the number of pixels in the Y direction.
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition pnmImage.h:58
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition pnmImage.I:799
xelval get_gray_val(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition pnmImage.I:484
get_ref_count
Returns the current reference count.
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition renderState.h:47
An encapsulation of a font; i.e.
Definition textFont.h:32
A representation of a single glyph (character) from a font.
Definition textGlyph.h:28
void set_geom(GeomVertexData *vdata, GeomPrimitive *prim, const RenderState *state)
Sets the geom from a pre-built Geom object.
This class can triangulate a convex or concave polygon, even one with holes.
int get_triangle_v0(int n) const
Returns vertex 0 of the nth triangle generated by the previous call to triangulate().
void add_polygon_vertex(int index)
Adds the next consecutive vertex of the polygon.
void clear_polygon()
Removes the current polygon definition (and its set of holes), but does not clear the vertex pool.
bool is_left_winding() const
Returns true if the polygon vertices are listed in counterclockwise order, or false if they appear to...
int get_triangle_v1(int n) const
Returns vertex 1 of the nth triangle generated by the previous call to triangulate().
int get_num_triangles() const
Returns the number of triangles generated by the previous call to triangulate().
int add_vertex(const LPoint2d &point)
Adds a new vertex to the vertex pool.
void add_hole_vertex(int index)
Adds the next consecutive vertex of the current hole.
void triangulate()
Does the work of triangulating the specified polygon.
void begin_hole()
Finishes the previous hole, if any, and prepares to add a new hole.
get_num_vertices
Returns the number of vertices in the pool.
int get_triangle_v2(int n) const
Returns vertex 2 of the nth triangle generated by the previous call to triangulate().
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.