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, _native_antialias ? ft_render_mode_normal : ft_render_mode_mono);
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 (glyph == nullptr) {
625 return nullptr;
626 }
627 if (!_needs_image_processing) {
628 copy_pnmimage_to_texture(image, glyph);
629 } else {
630 blend_pnmimage_to_texture(image, glyph, _fg);
631 }
632
633 } else if (_tex_pixels_per_unit == _font_pixels_per_unit &&
634 !_needs_image_processing) {
635 // If the bitmap produced from the font doesn't require scaling or any
636 // other processing before it goes to the texture, we can just copy it
637 // directly into the texture.
638 glyph = slot_glyph(character, bitmap.width, bitmap.rows, advance);
639 if (glyph == nullptr) {
640 return nullptr;
641 }
642 copy_bitmap_to_texture(bitmap, glyph);
643
644 } else {
645 // Otherwise, we need to copy to a PNMImage first, so we can scale it
646 // andor process it; and then copy it to the texture from there.
647 tex_x_size /= _scale_factor;
648 tex_y_size /= _scale_factor;
649 int int_x_size = (int)ceil(tex_x_size);
650 int int_y_size = (int)ceil(tex_y_size);
651 int bmp_x_size = (int)(int_x_size * _scale_factor + 0.5f);
652 int bmp_y_size = (int)(int_y_size * _scale_factor + 0.5f);
653
654 PNMImage image(bmp_x_size, bmp_y_size, PNMImage::CT_grayscale);
655 copy_bitmap_to_pnmimage(bitmap, image);
656
657 PNMImage reduced(int_x_size, int_y_size, PNMImage::CT_grayscale);
658 reduced.quick_filter_from(image);
659
660 // convert the outline width from points to tex_pixels.
661 PN_stdfloat outline_pixels = _outline_width / _points_per_unit * _tex_pixels_per_unit;
662 outline = (int)ceil(outline_pixels);
663
664 int_x_size += outline * 2;
665 int_y_size += outline * 2;
666 tex_x_size += outline * 2;
667 tex_y_size += outline * 2;
668 glyph = slot_glyph(character, int_x_size, int_y_size, advance);
669 if (glyph == nullptr) {
670 return nullptr;
671 }
672
673 if (outline != 0) {
674 // Pad the glyph image to make room for the outline.
675 PNMImage padded(int_x_size, int_y_size, PNMImage::CT_grayscale);
676 padded.copy_sub_image(reduced, outline, outline);
677 copy_pnmimage_to_texture(padded, glyph);
678
679 } else {
680 copy_pnmimage_to_texture(reduced, glyph);
681 }
682 }
683
684 DynamicTextPage *page = glyph->get_page();
685 if (page != nullptr) {
686 int bitmap_top = (int)floor(tex_y_orig + outline * _scale_factor + 0.5f);
687 int bitmap_left = (int)floor(tex_x_orig - outline * _scale_factor + 0.5f);
688
689 tex_x_size += glyph->_margin * 2;
690 tex_y_size += glyph->_margin * 2;
691
692 // Determine the corners of the rectangle in geometric units.
693 PN_stdfloat tex_poly_margin = _poly_margin / _tex_pixels_per_unit;
694 PN_stdfloat origin_y = bitmap_top / _font_pixels_per_unit;
695 PN_stdfloat origin_x = bitmap_left / _font_pixels_per_unit;
696
697 LVecBase4 dimensions(
698 origin_x - tex_poly_margin,
699 origin_y - tex_y_size / _tex_pixels_per_unit - tex_poly_margin,
700 origin_x + tex_x_size / _tex_pixels_per_unit + tex_poly_margin,
701 origin_y + tex_poly_margin);
702
703 // And the corresponding corners in UV units. We add 0.5f to center the
704 // UV in the middle of its texel, to minimize roundoff errors when we
705 // are close to 1-to-1 pixel size.
706 LVecBase2i page_size = page->get_size();
707 LVecBase4 texcoords(
708 ((PN_stdfloat)(glyph->_x - _poly_margin) + 0.5f) / page_size[0],
709 1.0f - ((PN_stdfloat)(glyph->_y + _poly_margin + tex_y_size) + 0.5f) / page_size[1],
710 ((PN_stdfloat)(glyph->_x + _poly_margin + tex_x_size) + 0.5f) / page_size[0],
711 1.0f - ((PN_stdfloat)(glyph->_y - _poly_margin) + 0.5f) / page_size[1]);
712
713 CPT(RenderState) state;
714 state = RenderState::make(TextureAttrib::make(page),
715 TransparencyAttrib::make(alpha_mode));
716 state = state->add_attrib(ColorAttrib::make_flat(LColor(1.0f, 1.0f, 1.0f, 1.0f)), -1);
717
718 glyph->set_quad(dimensions, texcoords, state);
719 }
720
721 return glyph;
722 }
723}
724
725/**
726 * Copies a bitmap as rendered by FreeType directly into the texture memory
727 * image for the indicated glyph, without any scaling of pixels.
728 */
729void DynamicTextFont::
730copy_bitmap_to_texture(const FT_Bitmap &bitmap, DynamicTextGlyph *glyph) {
731 if (bitmap.pixel_mode == ft_pixel_mode_grays && bitmap.num_grays == 256) {
732 // This is the easy case: we can memcpy the rendered glyph directly into
733 // our texture image, one row at a time.
734 unsigned char *buffer_row = bitmap.buffer;
735 for (int yi = 0; yi < (int)bitmap.rows; yi++) {
736
737 unsigned char *texture_row = glyph->get_row(yi);
738 nassertv(texture_row != nullptr);
739 memcpy(texture_row, buffer_row, bitmap.width);
740 buffer_row += bitmap.pitch;
741 }
742
743 } else if (bitmap.pixel_mode == ft_pixel_mode_mono) {
744 // This is a little bit more work: we have to expand the one-bit-per-pixel
745 // bitmap into a one-byte-per-pixel texture.
746 unsigned char *buffer_row = bitmap.buffer;
747 for (int yi = 0; yi < (int)bitmap.rows; yi++) {
748 unsigned char *texture_row = glyph->get_row(yi);
749 nassertv(texture_row != nullptr);
750
751 int bit = 0x80;
752 unsigned char *b = buffer_row;
753 for (int xi = 0; xi < (int)bitmap.width; xi++) {
754 if (*b & bit) {
755 texture_row[xi] = 0xff;
756 } else {
757 texture_row[xi] = 0x00;
758 }
759 bit >>= 1;
760 if (bit == 0) {
761 ++b;
762 bit = 0x80;
763 }
764 }
765
766 buffer_row += bitmap.pitch;
767 }
768
769
770 } else if (bitmap.pixel_mode == ft_pixel_mode_grays) {
771 // Here we must expand a grayscale pixmap with n levels of gray into our
772 // 256-level texture.
773 unsigned char *buffer_row = bitmap.buffer;
774 for (int yi = 0; yi < (int)bitmap.rows; yi++) {
775 unsigned char *texture_row = glyph->get_row(yi);
776 nassertv(texture_row != nullptr);
777 for (int xi = 0; xi < (int)bitmap.width; xi++) {
778 texture_row[xi] = (int)(buffer_row[xi] * 255) / (bitmap.num_grays - 1);
779 }
780 buffer_row += bitmap.pitch;
781 }
782
783 } else {
784 text_cat.error()
785 << "Unexpected pixel mode in bitmap: " << (int)bitmap.pixel_mode << "\n";
786 }
787}
788
789/**
790 * Copies a bitmap stored in a PNMImage into the texture memory image for the
791 * indicated glyph.
792 */
793void DynamicTextFont::
794copy_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph) {
795 if (!_needs_image_processing) {
796 // Copy the image directly into the alpha component of the texture.
797 nassertv(glyph->_page->get_num_components() == 1);
798 for (int yi = 0; yi < image.get_y_size(); yi++) {
799 unsigned char *texture_row = glyph->get_row(yi);
800 nassertv(texture_row != nullptr);
801 for (int xi = 0; xi < image.get_x_size(); xi++) {
802 texture_row[xi] = image.get_gray_val(xi, yi);
803 }
804 }
805
806 } else {
807 if (_has_outline) {
808 // Gaussian blur the glyph to generate an outline.
809 PNMImage outline(image.get_x_size(), image.get_y_size(), PNMImage::CT_grayscale);
810 PN_stdfloat outline_pixels = _outline_width / _points_per_unit * _tex_pixels_per_unit;
811 outline.gaussian_filter_from(outline_pixels * 0.707, image);
812
813 // Filter the resulting outline to make a harder edge. Square
814 // _outline_feather first to make the range more visually linear (this
815 // approximately compensates for the Gaussian falloff of the feathered
816 // edge).
817 PN_stdfloat f = _outline_feather * _outline_feather;
818
819 for (int yi = 0; yi < outline.get_y_size(); yi++) {
820 for (int xi = 0; xi < outline.get_x_size(); xi++) {
821 PN_stdfloat v = outline.get_gray(xi, yi);
822 if (v == 0.0f) {
823 // Do nothing.
824 } else if (v >= f) {
825 // Clamp to 1.
826 outline.set_gray(xi, yi, 1.0);
827 } else {
828 // Linearly scale the range 0 .. f onto 0 .. 1.
829 outline.set_gray(xi, yi, v / f);
830 }
831 }
832 }
833
834 // Now blend that into the texture.
835 blend_pnmimage_to_texture(outline, glyph, _outline_color);
836 }
837
838 // Colorize the image as we copy it in. This assumes the previous color
839 // at this part of the texture was already initialized to the background
840 // color.
841 blend_pnmimage_to_texture(image, glyph, _fg);
842 }
843}
844
845/**
846 * Blends the PNMImage into the appropriate part of the texture, where 0.0 in
847 * the image indicates the color remains the same, and 1.0 indicates the color
848 * is assigned the indicated foreground color.
849 */
850void DynamicTextFont::
851blend_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph,
852 const LColor &fg) {
853 LColor fgv = fg * 255.0f;
854
855 int num_components = glyph->_page->get_num_components();
856 if (num_components == 1) {
857 // Luminance or alpha.
858 int ci = 3;
859 if (glyph->_page->get_format() != Texture::F_alpha) {
860 ci = 0;
861 }
862
863 for (int yi = 0; yi < image.get_y_size(); yi++) {
864 unsigned char *texture_row = glyph->get_row(yi);
865 nassertv(texture_row != nullptr);
866 for (int xi = 0; xi < image.get_x_size(); xi++) {
867 unsigned char *tr = texture_row + xi;
868 PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
869 tr[0] = (unsigned char)(tr[0] + t * (fgv[ci] - tr[0]));
870 }
871 }
872
873 } else if (num_components == 2) {
874 // Luminance + alpha.
875
876 for (int yi = 0; yi < image.get_y_size(); yi++) {
877 unsigned char *texture_row = glyph->get_row(yi);
878 nassertv(texture_row != nullptr);
879 for (int xi = 0; xi < image.get_x_size(); xi++) {
880 unsigned char *tr = texture_row + xi * 2;
881 PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
882 tr[0] = (unsigned char)(tr[0] + t * (fgv[0] - tr[0]));
883 tr[1] = (unsigned char)(tr[1] + t * (fgv[3] - tr[1]));
884 }
885 }
886
887 } else if (num_components == 3) {
888 // RGB.
889
890 for (int yi = 0; yi < image.get_y_size(); yi++) {
891 unsigned char *texture_row = glyph->get_row(yi);
892 nassertv(texture_row != nullptr);
893 for (int xi = 0; xi < image.get_x_size(); xi++) {
894 unsigned char *tr = texture_row + xi * 3;
895 PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
896 tr[0] = (unsigned char)(tr[0] + t * (fgv[2] - tr[0]));
897 tr[1] = (unsigned char)(tr[1] + t * (fgv[1] - tr[1]));
898 tr[2] = (unsigned char)(tr[2] + t * (fgv[0] - tr[2]));
899 }
900 }
901
902 } else { // (num_components == 4)
903 // RGBA.
904
905 for (int yi = 0; yi < image.get_y_size(); yi++) {
906 unsigned char *texture_row = glyph->get_row(yi);
907 nassertv(texture_row != nullptr);
908 for (int xi = 0; xi < image.get_x_size(); xi++) {
909 unsigned char *tr = texture_row + xi * 4;
910 PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
911 tr[0] = (unsigned char)(tr[0] + t * (fgv[2] - tr[0]));
912 tr[1] = (unsigned char)(tr[1] + t * (fgv[1] - tr[1]));
913 tr[2] = (unsigned char)(tr[2] + t * (fgv[0] - tr[2]));
914 tr[3] = (unsigned char)(tr[3] + t * (fgv[3] - tr[3]));
915 }
916 }
917 }
918}
919
920/**
921 * Chooses a page that will have room for a glyph of the indicated size (after
922 * expanding the indicated size by the current margin). Returns the newly-
923 * allocated glyph on the chosen page; the glyph has not been filled in yet
924 * except with its size.
925 */
926DynamicTextGlyph *DynamicTextFont::
927slot_glyph(int character, int x_size, int y_size, PN_stdfloat advance) {
928 // Increase the indicated size by the current margin.
929 x_size += _texture_margin * 2;
930 y_size += _texture_margin * 2;
931
932 if (!_pages.empty()) {
933 // Start searching on the preferred page. That way, we'll fill up the
934 // preferred page first, and we can gradually rotate this page around; it
935 // keeps us from spending too much time checking already-filled pages for
936 // space.
937 _preferred_page = _preferred_page % _pages.size();
938 int pi = _preferred_page;
939
940 do {
941 DynamicTextPage *page = _pages[pi];
942 DynamicTextGlyph *glyph = page->slot_glyph(character, x_size, y_size, _texture_margin, advance);
943 if (glyph != nullptr) {
944 // Once we found a page to hold the glyph, that becomes our new
945 // preferred page.
946 _preferred_page = pi;
947 return glyph;
948 }
949
950 if (page->is_empty()) {
951 // If we couldn't even put it on an empty page, we're screwed.
952 goto does_not_fit;
953 }
954
955 pi = (pi + 1) % _pages.size();
956 } while (pi != _preferred_page);
957 }
958
959 // All pages are filled. Can we free up space by removing some old glyphs?
960 if (garbage_collect() != 0) {
961 // Yes, we just freed up some space. Try once more, recursively.
962 return slot_glyph(character, x_size, y_size, advance);
963 }
964
965 // No good; all recorded glyphs are actually in use. We need to make a new
966 // page.
967 {
968 _preferred_page = _pages.size();
969 PT(DynamicTextPage) page = new DynamicTextPage(this, _preferred_page);
970 _pages.push_back(page);
971
972 DynamicTextGlyph *glyph = page->slot_glyph(character, x_size, y_size, _texture_margin, advance);
973 if (glyph != nullptr) {
974 return glyph;
975 }
976 }
977
978does_not_fit:
979 // If you get this error, increase text-page-size in Config.prc.
980 text_cat.error()
981 << "Glyph of size " << x_size << " by " << y_size
982 << " pixels won't fit on an empty page.\n";
983 return nullptr;
984}
985
986/**
987 * Converts from the _contours list to an actual glyph geometry, as a
988 * wireframe render.
989 */
990void DynamicTextFont::
991render_wireframe_contours(TextGlyph *glyph) {
992 PT(GeomVertexData) vdata = new GeomVertexData
993 (std::string(), GeomVertexFormat::get_v3(),
994 Geom::UH_static);
995 GeomVertexWriter vertex(vdata, InternalName::get_vertex());
996
997 PT(GeomLinestrips) lines = new GeomLinestrips(Geom::UH_static);
998
999 Contours::const_iterator ci;
1000 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1001 const Contour &contour = (*ci);
1002 Points::const_iterator pi;
1003
1004 for (pi = contour._points.begin(); pi != contour._points.end(); ++pi) {
1005 const LPoint2 &p = (*pi)._p;
1006 vertex.add_data3(p[0], 0.0f, p[1]);
1007 }
1008
1009 lines->add_next_vertices(contour._points.size());
1010 lines->close_primitive();
1011 }
1012
1013 glyph->set_geom(vdata, lines, RenderState::make_empty());
1014 _contours.clear();
1015}
1016
1017/**
1018 * Converts from the _contours list to an actual glyph geometry, as a polygon
1019 * render.
1020 */
1021void DynamicTextFont::
1022render_polygon_contours(TextGlyph *glyph, bool face, bool extrude) {
1023 PT(GeomVertexData) vdata = new GeomVertexData
1024 (std::string(), GeomVertexFormat::get_v3n3(),
1025 Geom::UH_static);
1026 GeomVertexWriter vertex(vdata, InternalName::get_vertex());
1027 GeomVertexWriter normal(vdata, InternalName::get_normal());
1028
1029 PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static);
1030 Triangulator t;
1031
1032 Contours::iterator ci;
1033
1034 if (face) {
1035 // First, build up the list of vertices for the face, and determine which
1036 // contours are solid and which are holes.
1037 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1038 Contour &contour = (*ci);
1039
1040 t.clear_polygon();
1041 contour._start_vertex = t.get_num_vertices();
1042 for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1043 const LPoint2 &p = contour._points[i]._p;
1044 vertex.add_data3(p[0], 0.0f, p[1]);
1045 normal.add_data3(0.0f, -1.0f, 0.0f);
1046 int vi = t.add_vertex(p[0], p[1]);
1047 t.add_polygon_vertex(vi);
1048 }
1049
1050 contour._is_solid = t.is_left_winding();
1051 }
1052
1053 // Now go back and generate the actual triangles for the face.
1054 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1055 const Contour &contour = (*ci);
1056
1057 if (contour._is_solid && !contour._points.empty()) {
1058 t.clear_polygon();
1059 for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1060 t.add_polygon_vertex(contour._start_vertex + i);
1061 }
1062
1063 // Also add all the holes to each polygon.
1064 Contours::iterator cj;
1065 for (cj = _contours.begin(); cj != _contours.end(); ++cj) {
1066 Contour &hole = (*cj);
1067 if (!hole._is_solid && !hole._points.empty()) {
1068 t.begin_hole();
1069 for (size_t j = 0; j < hole._points.size() - 1; ++j) {
1070 t.add_hole_vertex(hole._start_vertex + j);
1071 }
1072 }
1073 }
1074
1075 t.triangulate();
1076 int num_triangles = t.get_num_triangles();
1077 for (int ti = 0; ti < num_triangles; ++ti) {
1078 tris->add_vertex(t.get_triangle_v0(ti));
1079 tris->add_vertex(t.get_triangle_v1(ti));
1080 tris->add_vertex(t.get_triangle_v2(ti));
1081 tris->close_primitive();
1082 }
1083 }
1084 }
1085 }
1086
1087 if (extrude) {
1088 // If we're generating extruded geometry (polygons along the edges, down
1089 // the y axis), generate them now. These are pretty easy, but we need to
1090 // create more vertices--they don't share the same normals.
1091 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1092 const Contour &contour = (*ci);
1093
1094 for (size_t i = 0; i < contour._points.size(); ++i) {
1095 const ContourPoint &cp = contour._points[i];
1096 const LPoint2 &p = cp._p;
1097 const LVector2 &t_in = cp._in;
1098 const LVector2 &t_out = cp._out;
1099
1100 LVector3 n_in(t_in[1], 0.0f, -t_in[0]);
1101 vertex.add_data3(p[0], 1.0f, p[1]);
1102 vertex.add_data3(p[0], 0.0f, p[1]);
1103 normal.add_data3(n_in);
1104 normal.add_data3(n_in);
1105
1106 if (i != 0) {
1107 int vi = vertex.get_write_row();
1108 tris->add_vertex(vi - 4);
1109 tris->add_vertex(vi - 2);
1110 tris->add_vertex(vi - 1);
1111 tris->close_primitive();
1112 tris->add_vertex(vi - 1);
1113 tris->add_vertex(vi - 3);
1114 tris->add_vertex(vi - 4);
1115 tris->close_primitive();
1116 }
1117
1118 if (i != contour._points.size() - 1 && !t_in.almost_equal(t_out)) {
1119 // If the out tangent is different from the in tangent, we need to
1120 // store new vertices for the next quad.
1121 LVector3 n_out(t_out[1], 0.0f, -t_out[0]);
1122 vertex.add_data3(p[0], 1.0f, p[1]);
1123 vertex.add_data3(p[0], 0.0f, p[1]);
1124 normal.add_data3(n_out);
1125 normal.add_data3(n_out);
1126 }
1127 }
1128 }
1129
1130 if (face) {
1131 // Render the back side of the face too.
1132 int back_start = vertex.get_write_row();
1133
1134 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1135 Contour &contour = (*ci);
1136 for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1137 const LPoint2 &p = contour._points[i]._p;
1138 vertex.add_data3(p[0], 1.0f, p[1]);
1139 normal.add_data3(0.0f, 1.0f, 0.0f);
1140 }
1141 }
1142
1143 // Now go back and generate the actual triangles for the face.
1144 for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1145 const Contour &contour = (*ci);
1146
1147 if (contour._is_solid && !contour._points.empty()) {
1148 t.clear_polygon();
1149 for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1150 t.add_polygon_vertex(contour._start_vertex + i);
1151 }
1152
1153 // Also add all the holes to each polygon.
1154 Contours::iterator cj;
1155 for (cj = _contours.begin(); cj != _contours.end(); ++cj) {
1156 Contour &hole = (*cj);
1157 if (!hole._is_solid && !hole._points.empty()) {
1158 t.begin_hole();
1159 for (size_t j = 0; j < hole._points.size() - 1; ++j) {
1160 t.add_hole_vertex(hole._start_vertex + j);
1161 }
1162 }
1163 }
1164
1165 t.triangulate();
1166 int num_triangles = t.get_num_triangles();
1167 for (int ti = 0; ti < num_triangles; ++ti) {
1168 tris->add_vertex(t.get_triangle_v2(ti) + back_start);
1169 tris->add_vertex(t.get_triangle_v1(ti) + back_start);
1170 tris->add_vertex(t.get_triangle_v0(ti) + back_start);
1171 tris->close_primitive();
1172 }
1173 }
1174 }
1175 }
1176 }
1177
1178 glyph->set_geom(vdata, tris, RenderState::make_empty());
1179 // glyph->set_geom(vdata, tris, RenderState::make(RenderModeAttrib::make(Ren
1180 // derModeAttrib::M_wireframe))); glyph->set_geom(vdata, tris,
1181 // RenderState::make(AntialiasAttrib::make(AntialiasAttrib::M_auto)));
1182 _contours.clear();
1183}
1184
1185#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.