Ver código fonte

Merge branch 'master' into deploy-ng

Mitchell Stokes 8 anos atrás
pai
commit
0ee6a46a62

+ 21 - 1
README.md

@@ -18,6 +18,23 @@ resources. If you get stuck, ask for help from our active
 Panda3D is licensed under the Modified BSD License.  See the LICENSE file for
 Panda3D is licensed under the Modified BSD License.  See the LICENSE file for
 more details.
 more details.
 
 
+Installing Panda3D
+==================
+
+By far, the easiest way to install the latest development build of Panda3D
+into an existing Python installation is using the following command:
+
+```bash
+pip install --pre --extra-index-url https://archive.panda3d.org/ panda3d
+```
+
+If you prefer to install the full SDK with all tools, the latest development
+builds can be obtained from this page:
+
+https://www.panda3d.org/download.php?sdk&version=devel
+
+These are automatically kept up-to-date with the latest GitHub version of Panda.
+
 Building Panda3D
 Building Panda3D
 ================
 ================
 
 
@@ -64,7 +81,7 @@ for you to install, depending on your distribution).
 The following command illustrates how to build Panda3D with some common
 The following command illustrates how to build Panda3D with some common
 options:
 options:
 ```bash
 ```bash
-python2.7 makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2
+python makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2 --no-opencv
 ```
 ```
 
 
 You will probably see some warnings saying that it's unable to find several
 You will probably see some warnings saying that it's unable to find several
@@ -93,6 +110,9 @@ may have to use the installpanda.py script instead, which will directly copy the
 files into the appropriate locations on your computer.  You may have to run the
 files into the appropriate locations on your computer.  You may have to run the
 `ldconfig` tool in order to update your library cache after installing Panda3D.
 `ldconfig` tool in order to update your library cache after installing Panda3D.
 
 
+Alternatively, you can add the `--wheel` option, which will produce a .whl
+file that can be installed into a Python installation using `pip`.
+
 macOS
 macOS
 -----
 -----
 
 

+ 16 - 1
direct/src/gui/OnscreenText.py

@@ -35,7 +35,8 @@ class OnscreenText(NodePath):
                  font = None,
                  font = None,
                  parent = None,
                  parent = None,
                  sort = 0,
                  sort = 0,
-                 mayChange = True):
+                 mayChange = True,
+                 direction = None):
         """
         """
         Make a text node from string, put it into the 2d sg and set it
         Make a text node from string, put it into the 2d sg and set it
         up with all the indicated parameters.
         up with all the indicated parameters.
@@ -95,6 +96,9 @@ class OnscreenText(NodePath):
           mayChange: pass true if the text or its properties may need
           mayChange: pass true if the text or its properties may need
               to be changed at runtime, false if it is static once
               to be changed at runtime, false if it is static once
               created (which leads to better memory optimization).
               created (which leads to better memory optimization).
+
+          direction: this can be set to 'ltr' or 'rtl' to override the
+              direction of the text.
         """
         """
         if parent == None:
         if parent == None:
             parent = aspect2d
             parent = aspect2d
@@ -192,6 +196,17 @@ class OnscreenText(NodePath):
             textNode.setFrameColor(frame[0], frame[1], frame[2], frame[3])
             textNode.setFrameColor(frame[0], frame[1], frame[2], frame[3])
             textNode.setFrameAsMargin(0.1, 0.1, 0.1, 0.1)
             textNode.setFrameAsMargin(0.1, 0.1, 0.1, 0.1)
 
 
+        if direction is not None:
+            if isinstance(direction, str):
+                direction = direction.lower()
+                if direction == 'rtl':
+                    direction = TextProperties.D_rtl
+                elif direction == 'ltr':
+                    direction = TextProperties.D_ltr
+                else:
+                    raise ValueError('invalid direction')
+            textNode.setDirection(direction)
+
         # Create a transform for the text for our scale and position.
         # Create a transform for the text for our scale and position.
         # We'd rather do it here, on the text itself, rather than on
         # We'd rather do it here, on the text itself, rather than on
         # our NodePath, so we have one fewer transforms in the scene
         # our NodePath, so we have one fewer transforms in the scene

+ 0 - 2
direct/src/showbase/Messenger.py

@@ -531,7 +531,6 @@ class Messenger:
         keys.sort()
         keys.sort()
         for event in keys:
         for event in keys:
             if repr(event).find(needle) >= 0:
             if repr(event).find(needle) >= 0:
-                print(self.__eventRepr(event))
                 return {event: self.__callbacks[event]}
                 return {event: self.__callbacks[event]}
 
 
     def findAll(self, needle, limit=None):
     def findAll(self, needle, limit=None):
@@ -545,7 +544,6 @@ class Messenger:
         keys.sort()
         keys.sort()
         for event in keys:
         for event in keys:
             if repr(event).find(needle) >= 0:
             if repr(event).find(needle) >= 0:
-                print(self.__eventRepr(event))
                 matches[event] = self.__callbacks[event]
                 matches[event] = self.__callbacks[event]
                 # if the limit is not None, decrement and
                 # if the limit is not None, decrement and
                 # check for break:
                 # check for break:

+ 6 - 1
dtool/src/cppparser/cppExpression.cxx

@@ -1658,7 +1658,12 @@ output(ostream &out, int indent_level, CPPScope *scope, bool) const {
     break;
     break;
 
 
   case T_function:
   case T_function:
-    out << _u._fgroup->_name;
+    // Pick any instance; they all have the same name anyway.
+    if (!_u._fgroup->_instances.empty() && _u._fgroup->_instances[0]->_ident != NULL) {
+      _u._fgroup->_instances[0]->_ident->output(out, scope);
+    } else {
+      out << _u._fgroup->_name;
+    }
     break;
     break;
 
 
   case T_unknown_ident:
   case T_unknown_ident:

+ 0 - 12
dtool/src/dtoolbase/dtoolbase.h

@@ -58,18 +58,6 @@
 #pragma warning (disable : 4267)
 #pragma warning (disable : 4267)
 /* C4577: 'noexcept' used with no exception handling mode specified */
 /* C4577: 'noexcept' used with no exception handling mode specified */
 #pragma warning (disable : 4577)
 #pragma warning (disable : 4577)
-
-#if _MSC_VER >= 1300
- #if _MSC_VER >= 1310
-   #define USING_MSVC7_1
-// #pragma message("VC 7.1")
- #else
-// #pragma message("VC 7.0")
- #endif
-#define USING_MSVC7
-#else
-// #pragma message("VC 6.0")
-#endif
 #endif  /* WIN32_VC */
 #endif  /* WIN32_VC */
 
 
 #ifndef __has_builtin
 #ifndef __has_builtin

+ 1 - 0
dtool/src/parser-inc/ft2build.h

@@ -29,6 +29,7 @@ class FT_Library;
 class FT_Bitmap;
 class FT_Bitmap;
 class FT_Vector;
 class FT_Vector;
 class FT_Span;
 class FT_Span;
+class FT_Outline;
 
 
 #endif
 #endif
 
 

+ 0 - 9
dtool/src/prc/pnotify.h

@@ -152,8 +152,6 @@ private:
 
 
 #define nassert_raise(message) Notify::write_string(message)
 #define nassert_raise(message) Notify::write_string(message)
 
 
-#define enter_debugger_if(condition) ((void)0)
-
 #else   // NDEBUG
 #else   // NDEBUG
 
 
 #define nassertr(condition, return_value) \
 #define nassertr(condition, return_value) \
@@ -183,13 +181,6 @@ private:
 
 
 #define nassert_raise(message) Notify::ptr()->assert_failure(message, __LINE__, __FILE__)
 #define nassert_raise(message) Notify::ptr()->assert_failure(message, __LINE__, __FILE__)
 
 
-#define enter_debugger_if(condition) \
-  if (_nassert_check(condition)) { \
-    Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__); \
-    __asm { int 3 } \
-  }
-
-
 #endif  // NDEBUG
 #endif  // NDEBUG
 
 
 #if __cplusplus >= 201103
 #if __cplusplus >= 201103

+ 9 - 3
makepanda/makepanda.py

@@ -80,8 +80,9 @@ PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "VORBIS", "FFMPEG", "SWSCALE", "SWRESAMPLE",         # Audio decoding
   "VORBIS", "FFMPEG", "SWSCALE", "SWRESAMPLE",         # Audio decoding
   "ODE", "PHYSX", "BULLET", "PANDAPHYSICS",            # Physics
   "ODE", "PHYSX", "BULLET", "PANDAPHYSICS",            # Physics
   "SPEEDTREE",                                         # SpeedTree
   "SPEEDTREE",                                         # SpeedTree
-  "ZLIB", "PNG", "JPEG", "TIFF", "OPENEXR", "SQUISH", "FREETYPE", # 2D Formats support
+  "ZLIB", "PNG", "JPEG", "TIFF", "OPENEXR", "SQUISH",  # 2D Formats support
   ] + MAYAVERSIONS + MAXVERSIONS + [ "FCOLLADA", "ASSIMP", "EGG", # 3D Formats support
   ] + MAYAVERSIONS + MAXVERSIONS + [ "FCOLLADA", "ASSIMP", "EGG", # 3D Formats support
+  "FREETYPE", "HARFBUZZ",                              # Text rendering
   "VRPN", "OPENSSL",                                   # Transport
   "VRPN", "OPENSSL",                                   # Transport
   "FFTW",                                              # Algorithm helpers
   "FFTW",                                              # Algorithm helpers
   "ARTOOLKIT", "OPENCV", "DIRECTCAM", "VISION",        # Augmented Reality
   "ARTOOLKIT", "OPENCV", "DIRECTCAM", "VISION",        # Augmented Reality
@@ -638,6 +639,7 @@ if (COMPILER == "MSVC"):
     if (PkgSkip("NVIDIACG")==0): LibName("CGDX9",    GetThirdpartyDir() + "nvidiacg/lib/cgD3D9.lib")
     if (PkgSkip("NVIDIACG")==0): LibName("CGDX9",    GetThirdpartyDir() + "nvidiacg/lib/cgD3D9.lib")
     if (PkgSkip("NVIDIACG")==0): LibName("NVIDIACG", GetThirdpartyDir() + "nvidiacg/lib/cg.lib")
     if (PkgSkip("NVIDIACG")==0): LibName("NVIDIACG", GetThirdpartyDir() + "nvidiacg/lib/cg.lib")
     if (PkgSkip("FREETYPE")==0): LibName("FREETYPE", GetThirdpartyDir() + "freetype/lib/freetype.lib")
     if (PkgSkip("FREETYPE")==0): LibName("FREETYPE", GetThirdpartyDir() + "freetype/lib/freetype.lib")
+    if (PkgSkip("HARFBUZZ")==0): LibName("HARFBUZZ", GetThirdpartyDir() + "harfbuzz/lib/harfbuzz.lib")
     if (PkgSkip("FFTW")==0):     LibName("FFTW",     GetThirdpartyDir() + "fftw/lib/rfftw.lib")
     if (PkgSkip("FFTW")==0):     LibName("FFTW",     GetThirdpartyDir() + "fftw/lib/rfftw.lib")
     if (PkgSkip("FFTW")==0):     LibName("FFTW",     GetThirdpartyDir() + "fftw/lib/fftw.lib")
     if (PkgSkip("FFTW")==0):     LibName("FFTW",     GetThirdpartyDir() + "fftw/lib/fftw.lib")
     if (PkgSkip("ARTOOLKIT")==0):LibName("ARTOOLKIT",GetThirdpartyDir() + "artoolkit/lib/libAR.lib")
     if (PkgSkip("ARTOOLKIT")==0):LibName("ARTOOLKIT",GetThirdpartyDir() + "artoolkit/lib/libAR.lib")
@@ -803,6 +805,7 @@ if (COMPILER=="GCC"):
         SmartPkgEnable("FFTW",      "",          ("rfftw", "fftw"), ("fftw.h", "rfftw.h"))
         SmartPkgEnable("FFTW",      "",          ("rfftw", "fftw"), ("fftw.h", "rfftw.h"))
         SmartPkgEnable("FMODEX",    "",          ("fmodex"), ("fmodex", "fmodex/fmod.h"))
         SmartPkgEnable("FMODEX",    "",          ("fmodex"), ("fmodex", "fmodex/fmod.h"))
         SmartPkgEnable("FREETYPE",  "freetype2", ("freetype"), ("freetype2", "freetype2/freetype/freetype.h"))
         SmartPkgEnable("FREETYPE",  "freetype2", ("freetype"), ("freetype2", "freetype2/freetype/freetype.h"))
+        SmartPkgEnable("HARFBUZZ",  "harfbuzz",  ("harfbuzz"), ("harfbuzz", "harfbuzz/hb-ft.h"))
         SmartPkgEnable("GL",        "gl",        ("GL"), ("GL/gl.h"), framework = "OpenGL")
         SmartPkgEnable("GL",        "gl",        ("GL"), ("GL/gl.h"), framework = "OpenGL")
         SmartPkgEnable("GLES",      "glesv1_cm", ("GLESv1_CM"), ("GLES/gl.h"), framework = "OpenGLES")
         SmartPkgEnable("GLES",      "glesv1_cm", ("GLESv1_CM"), ("GLES/gl.h"), framework = "OpenGLES")
         SmartPkgEnable("GLES2",     "glesv2",    ("GLESv2"), ("GLES2/gl2.h")) #framework = "OpenGLES"?
         SmartPkgEnable("GLES2",     "glesv2",    ("GLESv2"), ("GLES2/gl2.h")) #framework = "OpenGLES"?
@@ -3807,7 +3810,10 @@ if (PkgSkip("FREETYPE")==0 and not RUNTIME):
 #
 #
 
 
 if (not RUNTIME):
 if (not RUNTIME):
-  OPTS=['DIR:panda/src/text', 'BUILDING:PANDA', 'ZLIB',  'FREETYPE']
+  if not PkgSkip("HARFBUZZ"):
+    DefSymbol("HARFBUZZ", "HAVE_HARFBUZZ")
+
+  OPTS=['DIR:panda/src/text', 'BUILDING:PANDA', 'ZLIB',  'FREETYPE', 'HARFBUZZ']
   TargetAdd('p3text_composite1.obj', opts=OPTS, input='p3text_composite1.cxx')
   TargetAdd('p3text_composite1.obj', opts=OPTS, input='p3text_composite1.cxx')
   TargetAdd('p3text_composite2.obj', opts=OPTS, input='p3text_composite2.cxx')
   TargetAdd('p3text_composite2.obj', opts=OPTS, input='p3text_composite2.cxx')
 
 
@@ -3957,7 +3963,7 @@ if (not RUNTIME):
 #
 #
 
 
 if (not RUNTIME):
 if (not RUNTIME):
-  OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG',
+  OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ',
       'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
       'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
       'SQUISH', 'NVIDIACG', 'VORBIS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI']
       'SQUISH', 'NVIDIACG', 'VORBIS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI']
 
 

+ 10 - 0
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -1501,6 +1501,16 @@ open_buffer() {
   return true;
   return true;
 }
 }
 
 
+/**
+ * This is normally called only from within make_texture_buffer().  When
+ * called on a ParasiteBuffer, it returns the host of that buffer; but when
+ * called on some other buffer, it returns the buffer itself.
+ */
+GraphicsOutput *CLP(GraphicsBuffer)::
+get_host() {
+  return _host;
+}
+
 /**
 /**
  * Closes the buffer right now.  Called from the window thread.
  * Closes the buffer right now.  Called from the window thread.
  */
  */

+ 2 - 0
panda/src/glstuff/glGraphicsBuffer_src.h

@@ -77,6 +77,8 @@ public:
   void unregister_shared_depth_buffer(GraphicsOutput *graphics_output);
   void unregister_shared_depth_buffer(GraphicsOutput *graphics_output);
 
 
 protected:
 protected:
+  virtual GraphicsOutput *get_host();
+
   virtual void close_buffer();
   virtual void close_buffer();
   virtual bool open_buffer();
   virtual bool open_buffer();
 
 

+ 9 - 4
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -1139,7 +1139,7 @@ reset() {
   _supports_multisample = false;
   _supports_multisample = false;
 #else
 #else
   _supports_multisample =
   _supports_multisample =
-    has_extension("GL_ARB_multisample") || is_at_least_gl_version(1, 3);
+    is_at_least_gl_version(1, 3) || has_extension("GL_ARB_multisample");
 #endif
 #endif
 
 
 #ifdef OPENGLES_1
 #ifdef OPENGLES_1
@@ -1305,7 +1305,7 @@ reset() {
   if (gl_support_shadow_filter &&
   if (gl_support_shadow_filter &&
       _supports_depth_texture &&
       _supports_depth_texture &&
       (is_at_least_gl_version(1, 4) || has_extension("GL_ARB_shadow")) &&
       (is_at_least_gl_version(1, 4) || has_extension("GL_ARB_shadow")) &&
-      has_extension("GL_ARB_fragment_program_shadow")) {
+      (is_at_least_gl_version(2, 0) || has_extension("GL_ARB_fragment_program_shadow"))) {
     _supports_shadow_filter = true;
     _supports_shadow_filter = true;
   }
   }
 #endif
 #endif
@@ -2550,8 +2550,8 @@ reset() {
   _border_clamp = _edge_clamp;
   _border_clamp = _edge_clamp;
 #ifndef OPENGLES
 #ifndef OPENGLES
   if (gl_support_clamp_to_border &&
   if (gl_support_clamp_to_border &&
-      (has_extension("GL_ARB_texture_border_clamp") ||
-       is_at_least_gl_version(1, 3))) {
+      (is_at_least_gl_version(1, 3) ||
+       has_extension("GL_ARB_texture_border_clamp"))) {
     _border_clamp = GL_CLAMP_TO_BORDER;
     _border_clamp = GL_CLAMP_TO_BORDER;
   }
   }
 #endif
 #endif
@@ -2580,6 +2580,11 @@ reset() {
     _mirror_clamp = GL_MIRROR_CLAMP_EXT;
     _mirror_clamp = GL_MIRROR_CLAMP_EXT;
     _mirror_edge_clamp = GL_MIRROR_CLAMP_TO_EDGE_EXT;
     _mirror_edge_clamp = GL_MIRROR_CLAMP_TO_EDGE_EXT;
     _mirror_border_clamp = GL_MIRROR_CLAMP_TO_BORDER_EXT;
     _mirror_border_clamp = GL_MIRROR_CLAMP_TO_BORDER_EXT;
+
+  } else if (is_at_least_gl_version(4, 4) ||
+             has_extension("GL_ARB_texture_mirror_clamp_to_edge")) {
+    _mirror_clamp = GL_MIRROR_CLAMP_TO_EDGE;
+    _mirror_edge_clamp = GL_MIRROR_CLAMP_TO_EDGE;
   }
   }
 #endif
 #endif
 
 

+ 7 - 1
panda/src/text/config_text.cxx

@@ -52,7 +52,13 @@ ConfigVariableBool text_kerning
 ("text-kerning", false,
 ("text-kerning", false,
  PRC_DESC("Set this true to enable kerning when the font provides kerning "
  PRC_DESC("Set this true to enable kerning when the font provides kerning "
           "tables.  This can result in more aesthetically pleasing spacing "
           "tables.  This can result in more aesthetically pleasing spacing "
-          "between individual glyphs."));
+          "between individual glyphs.  Has no effect when text-use-harfbuzz "
+          "is true, since HarfBuzz offers superior kerning support."));
+
+ConfigVariableBool text_use_harfbuzz
+("text-use-harfbuzz", false,
+ PRC_DESC("Set this true to enable HarfBuzz support, which offers superior "
+          "text shaping and better support for non-Latin text."));
 
 
 ConfigVariableInt text_anisotropic_degree
 ConfigVariableInt text_anisotropic_degree
 ("text-anisotropic-degree", 1,
 ("text-anisotropic-degree", 1,

+ 1 - 0
panda/src/text/config_text.h

@@ -31,6 +31,7 @@ NotifyCategoryDecl(text, EXPCL_PANDA_TEXT, EXPTP_PANDA_TEXT);
 extern ConfigVariableBool text_flatten;
 extern ConfigVariableBool text_flatten;
 extern ConfigVariableBool text_dynamic_merge;
 extern ConfigVariableBool text_dynamic_merge;
 extern ConfigVariableBool text_kerning;
 extern ConfigVariableBool text_kerning;
+extern ConfigVariableBool text_use_harfbuzz;
 extern ConfigVariableInt text_anisotropic_degree;
 extern ConfigVariableInt text_anisotropic_degree;
 extern ConfigVariableInt text_texture_margin;
 extern ConfigVariableInt text_texture_margin;
 extern ConfigVariableDouble text_poly_margin;
 extern ConfigVariableDouble text_poly_margin;

+ 69 - 1
panda/src/text/dynamicTextFont.cxx

@@ -44,6 +44,10 @@
 #include "textureAttrib.h"
 #include "textureAttrib.h"
 #include "transparencyAttrib.h"
 #include "transparencyAttrib.h"
 
 
+#ifdef HAVE_HARFBUZZ
+#include <hb-ft.h>
+#endif
+
 TypeHandle DynamicTextFont::_type_handle;
 TypeHandle DynamicTextFont::_type_handle;
 
 
 
 
@@ -114,7 +118,8 @@ DynamicTextFont(const DynamicTextFont &copy) :
   _has_outline(copy._has_outline),
   _has_outline(copy._has_outline),
   _tex_format(copy._tex_format),
   _tex_format(copy._tex_format),
   _needs_image_processing(copy._needs_image_processing),
   _needs_image_processing(copy._needs_image_processing),
-  _preferred_page(0)
+  _preferred_page(0),
+  _hb_font(nullptr)
 {
 {
 }
 }
 
 
@@ -123,6 +128,11 @@ DynamicTextFont(const DynamicTextFont &copy) :
  */
  */
 DynamicTextFont::
 DynamicTextFont::
 ~DynamicTextFont() {
 ~DynamicTextFont() {
+#ifdef HAVE_HARFBUZZ
+  if (_hb_font != nullptr) {
+    hb_font_destroy(_hb_font);
+  }
+#endif
 }
 }
 
 
 /**
 /**
@@ -203,6 +213,13 @@ clear() {
   _cache.clear();
   _cache.clear();
   _pages.clear();
   _pages.clear();
   _empty_glyphs.clear();
   _empty_glyphs.clear();
+
+#ifdef HAVE_HARFBUZZ
+  if (_hb_font != nullptr) {
+    hb_font_destroy(_hb_font);
+    _hb_font = nullptr;
+  }
+#endif
 }
 }
 
 
 /**
 /**
@@ -305,6 +322,55 @@ get_kerning(int first, int second) const {
   return delta.x / (_font_pixels_per_unit * 64);
   return delta.x / (_font_pixels_per_unit * 64);
 }
 }
 
 
+/**
+ * Like get_glyph, but uses a glyph index.
+ */
+bool DynamicTextFont::
+get_glyph_by_index(int character, int glyph_index, CPT(TextGlyph) &glyph) {
+  if (!_is_valid) {
+    glyph = nullptr;
+    return false;
+  }
+
+  Cache::iterator ci = _cache.find(glyph_index);
+  if (ci != _cache.end()) {
+    glyph = (*ci).second;
+  } else {
+    FT_Face face = acquire_face();
+    glyph = make_glyph(character, face, glyph_index);
+    _cache.insert(Cache::value_type(glyph_index, glyph.p()));
+    release_face(face);
+  }
+
+  if (glyph.is_null()) {
+    glyph = get_invalid_glyph();
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * If Panda was compiled with HarfBuzz enabled, returns a HarfBuzz font for
+ * this font.
+ */
+hb_font_t *DynamicTextFont::
+get_hb_font() const {
+#ifdef HAVE_HARFBUZZ
+  if (_hb_font != nullptr) {
+    return _hb_font;
+  }
+
+  FT_Face face = acquire_face();
+  _hb_font = hb_ft_font_create(face, nullptr);
+  release_face(face);
+
+  return _hb_font;
+#else
+  return nullptr;
+#endif
+}
+
 /**
 /**
  * Called from both constructors to set up some initial values.
  * Called from both constructors to set up some initial values.
  */
  */
@@ -328,6 +394,8 @@ initialize() {
   _winding_order = WO_default;
   _winding_order = WO_default;
 
 
   _preferred_page = 0;
   _preferred_page = 0;
+
+  _hb_font = nullptr;
 }
 }
 
 
 /**
 /**

+ 7 - 0
panda/src/text/dynamicTextFont.h

@@ -31,6 +31,8 @@
 
 
 class NurbsCurveResult;
 class NurbsCurveResult;
 
 
+typedef struct hb_font_t hb_font_t;
+
 /**
 /**
  * A DynamicTextFont is a special TextFont object that rasterizes its glyphs
  * A DynamicTextFont is a special TextFont object that rasterizes its glyphs
  * from a standard font file (e.g.  a TTF file) on the fly.  It requires the
  * from a standard font file (e.g.  a TTF file) on the fly.  It requires the
@@ -125,6 +127,9 @@ public:
   virtual bool get_glyph(int character, CPT(TextGlyph) &glyph);
   virtual bool get_glyph(int character, CPT(TextGlyph) &glyph);
   virtual PN_stdfloat get_kerning(int first, int second) const;
   virtual PN_stdfloat get_kerning(int first, int second) const;
 
 
+  bool get_glyph_by_index(int character, int glyph_index, CPT(TextGlyph) &glyph);
+  hb_font_t *get_hb_font() const;
+
 private:
 private:
   void initialize();
   void initialize();
   void update_filters();
   void update_filters();
@@ -171,6 +176,8 @@ private:
   typedef pvector< PT(TextGlyph) > EmptyGlyphs;
   typedef pvector< PT(TextGlyph) > EmptyGlyphs;
   EmptyGlyphs _empty_glyphs;
   EmptyGlyphs _empty_glyphs;
 
 
+  mutable hb_font_t *_hb_font;
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;

+ 121 - 3
panda/src/text/textAssembler.cxx

@@ -31,10 +31,15 @@
 #include "geomVertexData.h"
 #include "geomVertexData.h"
 #include "geom.h"
 #include "geom.h"
 #include "modelNode.h"
 #include "modelNode.h"
+#include "dynamicTextFont.h"
 
 
 #include <ctype.h>
 #include <ctype.h>
 #include <stdio.h>  // for sprintf
 #include <stdio.h>  // for sprintf
 
 
+#ifdef HAVE_HARFBUZZ
+#include <hb.h>
+#endif
+
 // This is the factor by which CT_small scales the character down.
 // This is the factor by which CT_small scales the character down.
 static const PN_stdfloat small_accent_scale = 0.6f;
 static const PN_stdfloat small_accent_scale = 0.6f;
 
 
@@ -1408,6 +1413,12 @@ assemble_row(TextAssembler::TextRow &row,
   PN_stdfloat underscore_start = 0.0f;
   PN_stdfloat underscore_start = 0.0f;
   const TextProperties *underscore_properties = NULL;
   const TextProperties *underscore_properties = NULL;
 
 
+  const ComputedProperties *prev_cprops;
+
+#ifdef HAVE_HARFBUZZ
+  hb_buffer_t *harfbuff = nullptr;
+#endif
+
   TextString::const_iterator si;
   TextString::const_iterator si;
   for (si = row._string.begin(); si != row._string.end(); ++si) {
   for (si = row._string.begin(); si != row._string.end(); ++si) {
     const TextCharacter &tch = (*si);
     const TextCharacter &tch = (*si);
@@ -1448,10 +1459,29 @@ assemble_row(TextAssembler::TextRow &row,
       LVecBase4 frame = graphic->get_frame();
       LVecBase4 frame = graphic->get_frame();
       line_height = max(line_height, frame[3] - frame[2]);
       line_height = max(line_height, frame[3] - frame[2]);
     } else {
     } else {
-      // [fabius] this is not the right place to calc line height (see below)
-      // line_height = max(line_height, font->get_line_height());
+      line_height = max(line_height, font->get_line_height() * properties->get_glyph_scale() * properties->get_text_scale());
+    }
+
+#ifdef HAVE_HARFBUZZ
+    if (tch._cprops != prev_cprops || graphic != nullptr) {
+      if (harfbuff != nullptr && hb_buffer_get_length(harfbuff) > 0) {
+        // Shape the buffer accumulated so far.
+        shape_buffer(harfbuff, placed_glyphs, xpos, prev_cprops->_properties);
+        hb_buffer_reset(harfbuff);
+
+      } else if (harfbuff == nullptr && text_use_harfbuzz &&
+                 font->is_of_type(DynamicTextFont::get_class_type())) {
+        harfbuff = hb_buffer_create();
+      }
+      prev_cprops = tch._cprops;
     }
     }
 
 
+    if (graphic == nullptr && harfbuff != nullptr) {
+      hb_buffer_add(harfbuff, character, character);
+      continue;
+    }
+#endif
+
     if (character == ' ') {
     if (character == ' ') {
       // A space is a special case.
       // A space is a special case.
       xpos += properties->get_glyph_scale() * properties->get_text_scale() * font->get_space_advance();
       xpos += properties->get_glyph_scale() * properties->get_text_scale() * font->get_space_advance();
@@ -1613,10 +1643,16 @@ assemble_row(TextAssembler::TextRow &row,
       }
       }
 
 
       xpos += advance * glyph_scale;
       xpos += advance * glyph_scale;
-      line_height = max(line_height, font->get_line_height() * glyph_scale);
     }
     }
   }
   }
 
 
+#ifdef HAVE_HARFBUZZ
+  if (harfbuff != nullptr && hb_buffer_get_length(harfbuff) > 0) {
+    shape_buffer(harfbuff, placed_glyphs, xpos, prev_cprops->_properties);
+  }
+  hb_buffer_destroy(harfbuff);
+#endif
+
   if (underscore && underscore_start != xpos) {
   if (underscore && underscore_start != xpos) {
     draw_underscore(placed_glyphs, underscore_start, xpos,
     draw_underscore(placed_glyphs, underscore_start, xpos,
                     underscore_properties);
                     underscore_properties);
@@ -1640,6 +1676,88 @@ assemble_row(TextAssembler::TextRow &row,
   }
   }
 }
 }
 
 
+/**
+ * Places the glyphs collected from a HarfBuzz buffer.
+ */
+void TextAssembler::
+shape_buffer(hb_buffer_t *buf, PlacedGlyphs &placed_glyphs, PN_stdfloat &xpos,
+             const TextProperties &properties) {
+
+#ifdef HAVE_HARFBUZZ
+  // If we did not specify a text direction, harfbuzz will guess it based on
+  // the script we are using.
+  hb_direction_t direction = HB_DIRECTION_INVALID;
+  if (properties.has_direction()) {
+    switch (properties.get_direction()) {
+    case TextProperties::D_ltr:
+      direction = HB_DIRECTION_LTR;
+      break;
+    case TextProperties::D_rtl:
+      direction = HB_DIRECTION_RTL;
+      break;
+    }
+  }
+  hb_buffer_set_direction(buf, direction);
+  hb_buffer_guess_segment_properties(buf);
+
+  DynamicTextFont *font = DCAST(DynamicTextFont, properties.get_font());
+  hb_font_t *hb_font = font->get_hb_font();
+  hb_shape(hb_font, buf, NULL, 0);
+
+  PN_stdfloat glyph_scale = properties.get_glyph_scale() * properties.get_text_scale();
+  PN_stdfloat scale = glyph_scale / (font->get_pixels_per_unit() * font->get_scale_factor() * 64.0);
+
+  unsigned int glyph_count;
+  hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count);
+  hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count);
+
+  for (unsigned int i = 0; i < glyph_count; ++i) {
+    int character = glyph_info[i].cluster;
+    int glyph_index = glyph_info[i].codepoint;
+
+    CPT(TextGlyph) glyph;
+    if (!font->get_glyph_by_index(character, glyph_index, glyph)) {
+      char buffer[512];
+      sprintf(buffer, "U+%04x", character);
+      text_cat.warning()
+        << "No definition in " << font->get_name()
+        << " for character " << buffer;
+      if (character < 128 && isprint((unsigned int)character)) {
+        text_cat.warning(false)
+          << " ('" << (char)character << "')";
+      }
+      text_cat.warning(false)
+        << "\n";
+    }
+
+    PN_stdfloat advance = glyph_pos[i].x_advance * scale;
+    if (glyph->is_whitespace()) {
+      // A space is a special case.
+      xpos += advance;
+      continue;
+    }
+
+    PN_stdfloat x_offset = glyph_pos[i].x_offset * scale;
+    PN_stdfloat y_offset = glyph_pos[i].y_offset * scale;
+
+    // Build up a GlyphPlacement, indicating all of the Geoms that go into
+    // this character.  Normally, there is only one Geom per character, but
+    // it may involve multiple Geoms if we need to add cheesy accents or
+    // ligatures.
+    GlyphPlacement placement;
+    placement._glyph = move(glyph);
+    placement._scale = glyph_scale;
+    placement._xpos = xpos + x_offset;
+    placement._ypos = properties.get_glyph_shift() + y_offset;
+    placement._slant = properties.get_slant();
+    placement._properties = &properties;
+    placed_glyphs.push_back(placement);
+
+    xpos += advance;
+  }
+#endif
+}
+
 /**
 /**
  * Creates the geometry to render the underscore line for the indicated range
  * Creates the geometry to render the underscore line for the indicated range
  * of glyphs in this row.
  * of glyphs in this row.

+ 4 - 0
panda/src/text/textAssembler.h

@@ -28,6 +28,7 @@
 
 
 #include "pmap.h"
 #include "pmap.h"
 
 
+typedef struct hb_buffer_t hb_buffer_t;
 
 
 class TextEncoder;
 class TextEncoder;
 class TextGraphic;
 class TextGraphic;
@@ -247,6 +248,9 @@ private:
                     PN_stdfloat &row_width, PN_stdfloat &line_height,
                     PN_stdfloat &row_width, PN_stdfloat &line_height,
                     TextProperties::Alignment &align, PN_stdfloat &wordwrap);
                     TextProperties::Alignment &align, PN_stdfloat &wordwrap);
 
 
+  void shape_buffer(hb_buffer_t *buf, PlacedGlyphs &glyphs, PN_stdfloat &xpos,
+                    const TextProperties &properties);
+
   // These interfaces are for implementing cheesy accent marks and ligatures
   // These interfaces are for implementing cheesy accent marks and ligatures
   // when the font doesn't support them.
   // when the font doesn't support them.
   enum CheesyPosition {
   enum CheesyPosition {

+ 36 - 0
panda/src/text/textProperties.I

@@ -797,3 +797,39 @@ INLINE PN_stdfloat TextProperties::
 get_text_scale() const {
 get_text_scale() const {
   return _text_scale;
   return _text_scale;
 }
 }
+
+/**
+ * Specifies the text direction.  If none is specified, it will be guessed
+ * based on the contents of the string.
+ */
+INLINE void TextProperties::
+set_direction(Direction direction) {
+  _direction = direction;
+  _specified |= F_has_direction;
+}
+
+/**
+ * Clears the text direction setting.  If no text direction is specified, it
+ * will be guessed based on the contents of the string.
+ */
+INLINE void TextProperties::
+clear_direction() {
+  _specified &= ~F_has_direction;
+  _direction = D_ltr;
+}
+
+/**
+ *
+ */
+INLINE bool TextProperties::
+has_direction() const {
+  return (_specified & F_has_direction) != 0;
+}
+
+/**
+ * Returns the direction of the text as specified by set_direction().
+ */
+INLINE TextProperties::Direction TextProperties::
+get_direction() const {
+  return _direction;
+}

+ 42 - 20
panda/src/text/textProperties.cxx

@@ -31,26 +31,27 @@ TypeHandle TextProperties::_type_handle;
  *
  *
  */
  */
 TextProperties::
 TextProperties::
-TextProperties() {
-  _specified = 0;
-
-  _small_caps = text_small_caps;
-  _small_caps_scale = text_small_caps_scale;
-  _slant = 0.0f;
-  _underscore = false;
-  _underscore_height = 0.0f;
-  _align = A_left;
-  _indent_width = 0.0f;
-  _wordwrap_width = 0.0f;
-  _preserve_trailing_whitespace = false;
-  _text_color.set(1.0f, 1.0f, 1.0f, 1.0f);
-  _shadow_color.set(0.0f, 0.0f, 0.0f, 1.0f);
-  _shadow_offset.set(0.0f, 0.0f);
-  _draw_order = 1;
-  _tab_width = text_tab_width;
-  _glyph_scale = 1.0f;
-  _glyph_shift = 0.0f;
-  _text_scale = 1.0f;
+TextProperties() :
+  _specified(0),
+
+  _small_caps(text_small_caps),
+  _small_caps_scale(text_small_caps_scale),
+  _slant(0.0f),
+  _underscore(false),
+  _underscore_height(0.0f),
+  _align(A_left),
+  _indent_width(0.0f),
+  _wordwrap_width(0.0f),
+  _preserve_trailing_whitespace(false),
+  _text_color(1.0f, 1.0f, 1.0f, 1.0f),
+  _shadow_color(0.0f, 0.0f, 0.0f, 1.0f),
+  _shadow_offset(0.0f, 0.0f),
+  _draw_order(1),
+  _tab_width(text_tab_width),
+  _glyph_scale(1.0f),
+  _glyph_shift(0.0f),
+  _text_scale(1.0f),
+  _direction(D_rtl) {
 }
 }
 
 
 /**
 /**
@@ -89,6 +90,7 @@ operator = (const TextProperties &copy) {
   _glyph_scale = copy._glyph_scale;
   _glyph_scale = copy._glyph_scale;
   _glyph_shift = copy._glyph_shift;
   _glyph_shift = copy._glyph_shift;
   _text_scale = copy._text_scale;
   _text_scale = copy._text_scale;
+  _direction = copy._direction;
 
 
   _text_state.clear();
   _text_state.clear();
   _shadow_state.clear();
   _shadow_state.clear();
@@ -163,6 +165,9 @@ operator == (const TextProperties &other) const {
   if ((_specified & F_has_text_scale) && _text_scale != other._text_scale) {
   if ((_specified & F_has_text_scale) && _text_scale != other._text_scale) {
     return false;
     return false;
   }
   }
+  if ((_specified & F_has_direction) && _direction != other._direction) {
+    return false;
+  }
   return true;
   return true;
 }
 }
 
 
@@ -238,6 +243,9 @@ add_properties(const TextProperties &other) {
   if (other.has_text_scale()) {
   if (other.has_text_scale()) {
     set_text_scale(other.get_text_scale());
     set_text_scale(other.get_text_scale());
   }
   }
+  if (other.has_direction()) {
+    set_direction(other.get_direction());
+  }
 }
 }
 
 
 
 
@@ -361,6 +369,20 @@ write(ostream &out, int indent_level) const {
     indent(out, indent_level)
     indent(out, indent_level)
       << "text scale is " << get_text_scale() << "\n";
       << "text scale is " << get_text_scale() << "\n";
   }
   }
+
+  if (has_direction()) {
+    indent(out, indent_level)
+      << "direction is ";
+    switch (get_direction()) {
+    case D_ltr:
+      out << "D_ltr\n";
+      break;
+
+    case D_rtl:
+      out << "D_rtl\n";
+      break;
+    }
+  }
 }
 }
 
 
 /**
 /**

+ 14 - 0
panda/src/text/textProperties.h

@@ -49,6 +49,11 @@ PUBLISHED:
     A_boxed_center
     A_boxed_center
   };
   };
 
 
+  enum Direction {
+    D_ltr,
+    D_rtl,
+  };
+
   TextProperties();
   TextProperties();
   TextProperties(const TextProperties &copy);
   TextProperties(const TextProperties &copy);
   void operator = (const TextProperties &copy);
   void operator = (const TextProperties &copy);
@@ -160,6 +165,11 @@ PUBLISHED:
   INLINE bool has_text_scale() const;
   INLINE bool has_text_scale() const;
   INLINE PN_stdfloat get_text_scale() const;
   INLINE PN_stdfloat get_text_scale() const;
 
 
+  INLINE void set_direction(Direction direction);
+  INLINE void clear_direction();
+  INLINE bool has_direction() const;
+  INLINE Direction get_direction() const;
+
   void add_properties(const TextProperties &other);
   void add_properties(const TextProperties &other);
 
 
   void write(ostream &out, int indent_level = 0) const;
   void write(ostream &out, int indent_level = 0) const;
@@ -197,6 +207,8 @@ PUBLISHED:
                               set_glyph_shift, clear_glyph_shift);
                               set_glyph_shift, clear_glyph_shift);
   MAKE_PROPERTY2(text_scale, has_text_scale, get_text_scale,
   MAKE_PROPERTY2(text_scale, has_text_scale, get_text_scale,
                              set_text_scale, clear_text_scale);
                              set_text_scale, clear_text_scale);
+  MAKE_PROPERTY2(direction, has_direction, get_direction,
+                            set_direction, clear_direction);
 
 
 public:
 public:
   const RenderState *get_text_state() const;
   const RenderState *get_text_state() const;
@@ -225,6 +237,7 @@ private:
     F_has_underscore                   = 0x00010000,
     F_has_underscore                   = 0x00010000,
     F_has_underscore_height            = 0x00020000,
     F_has_underscore_height            = 0x00020000,
     F_has_text_scale                   = 0x00040000,
     F_has_text_scale                   = 0x00040000,
+    F_has_direction                    = 0x00080000,
   };
   };
 
 
   int _specified;
   int _specified;
@@ -248,6 +261,7 @@ private:
   PN_stdfloat _glyph_scale;
   PN_stdfloat _glyph_scale;
   PN_stdfloat _glyph_shift;
   PN_stdfloat _glyph_shift;
   PN_stdfloat _text_scale;
   PN_stdfloat _text_scale;
+  Direction _direction;
 
 
   mutable CPT(RenderState) _text_state;
   mutable CPT(RenderState) _text_state;
   mutable CPT(RenderState) _shadow_state;
   mutable CPT(RenderState) _shadow_state;