]> git.eshelyaron.com Git - emacs.git/commitdiff
Add HarfBuzz font backend for MS-Windows
authorEli Zaretskii <eliz@gnu.org>
Fri, 31 May 2019 08:30:36 +0000 (11:30 +0300)
committerEli Zaretskii <eliz@gnu.org>
Fri, 31 May 2019 08:30:36 +0000 (11:30 +0300)
* src/w32uniscribe.c [HAVE_HARFBUZZ]: Include math.h and
hb.h.
(bswap_32): Define for GCC 4.3.0 and later; else include
<byteswap.h> from Gnulib.
(struct uniscribe_font_info): Extend for HarfBuzz; 'cache' is
now a 'void *' (all users changed).
[HAVE_HARFBUZZ]: Define typedefs for HarfBuzz functions to be
loaded dynamically from the HarfBuzz DLL.  Define macros to
call those functions via function pointers.
(uniscribe_open) [HAVE_HARFBUZZ]: Use the HarfBuzz font driver
if the type of the font entity is 'harfbuzz'.
(uniscribe_close) [HAVE_HARFBUZZ]: For fonts using the
HarfBuzz backend, call hb_font_destroy to free memory used for
the cached hb_font data.
(uniscribe_shape): Fix assignment of character codepoints to
glyphs from a single cluster.
(w32hb_list, w32hb_match, free_cb, w32hb_get_font_table)
(w32hb_get_font, w32hb_encode_char, w32hb_begin_font)
(w32uni_combining, w32uni_general, w32uni_mirroring)
(get_hb_unicode_funcs, w32hb_shape)
(w32hb_combining_capability, load_harfbuzz_funcs)
[HAVE_HARFBUZZ]: New functions.
(syms_of_w32uniscribe_for_pdumper) [HAVE_HARFBUZZ]: Load the
HarfBuzz DLL and register the HarfBuzz backend with its
functions.
* src/w32font.c (syms_of_w32font) <Qharfbuzz>: New DEFSYM.
* src/w32fns.c (Fx_create_frame, w32_create_tip_frame)
[HAVE_HARFBUZZ]: Register the harfbuzz font backend.
* src/lisp.h (get_unicode_property): Declare prototype.
* src/font.h (harfbuzz_font_driver) [HAVE_NTGUI]: Declare.
* src/chartab.c (get_unicode_property): New function, body
taken from get-unicode-property-internal.
(Fget_unicode_property_internal): Call get_unicode_property
after validating input.

* doc/lispref/frames.texi (Font and Color Parameters):
* doc/emacs/msdos.texi (Windows Fonts): Document support for
HarfBuzz text shaping on MS-Windows.

* configure.ac (HAVE_HARFBUZZ): Move out of the X-specific
part, and consider HarfBuzz also for HAVE_W32 systems.
Require HarfBuzz v1.2.3 for w32.

configure.ac
doc/emacs/msdos.texi
doc/lispref/frames.texi
src/chartab.c
src/font.h
src/lisp.h
src/w32fns.c
src/w32font.c
src/w32uniscribe.c

index e9001fd1b7ada1242c3f154b14c724e9d395cdb7..ab6b03f5462e2e9e580a8fa5879740bac0ea07a3 100644 (file)
@@ -3409,56 +3409,70 @@ if test "${HAVE_X11}" = "yes"; then
     fi
   fi                             # $HAVE_CAIRO != yes
 
-    HAVE_HARFBUZZ=no
-    HAVE_LIBOTF=no
-    if test "${HAVE_FREETYPE}" = "yes"; then
-      AC_DEFINE(HAVE_FREETYPE, 1,
-               [Define to 1 if using the freetype and fontconfig libraries.])
-      if test "${with_harfbuzz}" != "no"; then
-       EMACS_CHECK_MODULES([HARFBUZZ], [harfbuzz >= 0.9.42])
-       if test "$HAVE_HARFBUZZ" = "yes"; then
-         AC_DEFINE(HAVE_HARFBUZZ, 1, [Define to 1 if using HarfBuzz.])
+  HAVE_LIBOTF=no
+  if test "${HAVE_FREETYPE}" = "yes"; then
+    AC_DEFINE(HAVE_FREETYPE, 1,
+             [Define to 1 if using the freetype and fontconfig libraries.])
+    if test "${with_libotf}" != "no"; then
+      EMACS_CHECK_MODULES([LIBOTF], [libotf])
+      if test "$HAVE_LIBOTF" = "yes"; then
+       AC_DEFINE(HAVE_LIBOTF, 1, [Define to 1 if using libotf.])
+       AC_CHECK_LIB(otf, OTF_get_variation_glyphs,
+                    HAVE_OTF_GET_VARIATION_GLYPHS=yes,
+                    HAVE_OTF_GET_VARIATION_GLYPHS=no)
+       if test "${HAVE_OTF_GET_VARIATION_GLYPHS}" = "yes"; then
+         AC_DEFINE(HAVE_OTF_GET_VARIATION_GLYPHS, 1,
+                   [Define to 1 if libotf has OTF_get_variation_glyphs.])
        fi
-      fi
-      if test "${with_libotf}" != "no"; then
-       EMACS_CHECK_MODULES([LIBOTF], [libotf])
-       if test "$HAVE_LIBOTF" = "yes"; then
-         AC_DEFINE(HAVE_LIBOTF, 1, [Define to 1 if using libotf.])
-         AC_CHECK_LIB(otf, OTF_get_variation_glyphs,
-                      HAVE_OTF_GET_VARIATION_GLYPHS=yes,
-                      HAVE_OTF_GET_VARIATION_GLYPHS=no)
-         if test "${HAVE_OTF_GET_VARIATION_GLYPHS}" = "yes"; then
-           AC_DEFINE(HAVE_OTF_GET_VARIATION_GLYPHS, 1,
-                     [Define to 1 if libotf has OTF_get_variation_glyphs.])
-         fi
-          if ! $PKG_CONFIG --atleast-version=0.9.16 libotf; then
-           AC_DEFINE(HAVE_OTF_KANNADA_BUG, 1,
+       if ! $PKG_CONFIG --atleast-version=0.9.16 libotf; then
+         AC_DEFINE(HAVE_OTF_KANNADA_BUG, 1,
 [Define to 1 if libotf is affected by https://debbugs.gnu.org/28110.])
-         fi
        fi
       fi
-    dnl FIXME should there be an error if HAVE_FREETYPE != yes?
-    dnl Does the new font backend require it, or can it work without it?
     fi
+  dnl FIXME should there be an error if HAVE_FREETYPE != yes?
+  dnl Does the new font backend require it, or can it work without it?
+  fi
 
-    HAVE_M17N_FLT=no
-    if test "${HAVE_LIBOTF}" = yes; then
-      if test "${with_m17n_flt}" != "no"; then
-       EMACS_CHECK_MODULES([M17N_FLT], [m17n-flt])
-       if test "$HAVE_M17N_FLT" = "yes"; then
-         AC_DEFINE(HAVE_M17N_FLT, 1, [Define to 1 if using libm17n-flt.])
-       fi
+  HAVE_M17N_FLT=no
+  if test "${HAVE_LIBOTF}" = yes; then
+    if test "${with_m17n_flt}" != "no"; then
+      EMACS_CHECK_MODULES([M17N_FLT], [m17n-flt])
+      if test "$HAVE_M17N_FLT" = "yes"; then
+       AC_DEFINE(HAVE_M17N_FLT, 1, [Define to 1 if using libm17n-flt.])
       fi
     fi
-else
-    HAVE_XFT=no
-    HAVE_FREETYPE=no
-    HAVE_HARFBUZZ=no
-    HAVE_LIBOTF=no
-    HAVE_M17N_FLT=no
+  fi
+else # "${HAVE_X11}" != "yes"
+  HAVE_XFT=no
+  HAVE_FREETYPE=no
+  HAVE_LIBOTF=no
+  HAVE_M17N_FLT=no
+fi   # "${HAVE_X11}" != "yes"
+
+HAVE_HARFBUZZ=no
+if test "${HAVE_X11}" = "yes" && test "${HAVE_FREETYPE}" = "yes" \
+        || test "${HAVE_W32}" = "yes"; then
+  if test "${with_harfbuzz}" != "no"; then
+    ### On MS-Windows we use hb_font_get_nominal_glyph, which appeared
+    ### in HarfBuzz version 1.2.3
+    if test "${HAVE_W32}" = "yes"; then
+      EMACS_CHECK_MODULES([HARFBUZZ], [harfbuzz >= 1.2.3])
+    else
+      EMACS_CHECK_MODULES([HARFBUZZ], [harfbuzz >= 0.9.42])
+    fi
+    if test "$HAVE_HARFBUZZ" = "yes"; then
+      AC_DEFINE(HAVE_HARFBUZZ, 1, [Define to 1 if using HarfBuzz.])
+      ### mingw32 and Cygwin-w32 don't use -lharfbuzz, since they load
+      ### the library dynamically.
+      if test "${HAVE_W32}" = "yes"; then
+        HARFBUZZ_LIBS=
+      fi
+    fi
+  fi
 fi
 
-### End of font-backend (under X11) section.
+### End of font-backend section.
 
 AC_SUBST(FREETYPE_CFLAGS)
 AC_SUBST(FREETYPE_LIBS)
index 9fc4b6262fb007e40273e8ad4a2e75c4cd17e2dc..27a7cd80968865cda36bec659f88768ac233dd01 100644 (file)
@@ -985,21 +985,28 @@ fontconfig library used in modern Free desktops:
   The old XLFD based format is also supported for backwards compatibility.
 
 @cindex font backend selection (MS-Windows)
-  Emacs 23 and later supports a number of font backends.  Currently,
-the @code{gdi} and @code{uniscribe} backends are supported on Windows.
-The @code{gdi} font backend is available on all versions of Windows,
-and supports all fonts that are natively supported by Windows.  The
-@code{uniscribe} font backend is available on Windows 2000 and later,
-and supports TrueType and OpenType fonts.  Some languages requiring
-complex layout can only be properly supported by the Uniscribe
-backend.  By default, both backends are enabled if supported, with
-@code{uniscribe} taking priority over @code{gdi}.  To override that
-and use the GDI backend even if Uniscribe is available, invoke Emacs
-with the @kbd{-xrm Emacs.fontBackend:gdi} command-line argument, or
-add a @code{Emacs.fontBackend} resource with the value @code{gdi} in
-the Registry under either the
-@samp{HKEY_CURRENT_USER\SOFTWARE\GNU\Emacs} or the
-@samp{HKEY_LOCAL_MACHINE\SOFTWARE\GNU\Emacs} key (@pxref{Resources}).
+  Emacs on MS-Windows supports a number of font backends.  Currently,
+the @code{gdi}, @code{uniscribe}, and @code{harfbuzz} backends are
+available.  The @code{gdi} font backend is available on all versions
+of Windows, and supports all fonts that are natively supported by
+Windows.  The @code{uniscribe} font backend is available on Windows
+2000 and later, and supports TrueType and OpenType fonts.  The
+@code{harfbuzz} font backend is available if Emacs was built with
+HarfBuzz support, and if the HarfBuzz DLL is installed on your system;
+like @code{uniscribe}, this backend supports only TrueType and
+OpenType fonts.  Some languages requiring complex layout can only be
+properly supported by the Uniscribe or HarfBuzz backends.  By default,
+all backends are enabled if supported, with @code{harfbuzz} taking
+priority over @code{uniscribe}, and @code{uniscribe} taking priority
+over @code{gdi}.  To override that and use the GDI backend even if
+Uniscribe is available, invoke Emacs with the @kbd{-xrm
+Emacs.fontBackend:gdi} command-line argument, or add a
+@code{Emacs.fontBackend} resource with the value @code{gdi} in the
+Registry under either the @samp{HKEY_CURRENT_USER\SOFTWARE\GNU\Emacs}
+or the @samp{HKEY_LOCAL_MACHINE\SOFTWARE\GNU\Emacs} key
+(@pxref{Resources}).  Similarly, to use the Uniscribe backend even if
+HarfBuzz is available, use @kbd{-xrm Emacs.fontBackend:uniscribe} on
+the command line that invokes Emacs.
 
 @cindex font properties (MS Windows)
 @noindent
index c94b4e67aed18ea88a196790999c3081e0237b5e..336075a1ca2d0b631a6ffa8447477444a15fb419 100644 (file)
@@ -2278,18 +2278,21 @@ variable do not take effect immediately, only when you specify the
 @vindex font-backend@r{, a frame parameter}
 @item font-backend
 A list of symbols, specifying the @dfn{font backends} to use for
-drawing fonts in the frame, in order of priority.  On X, there are
-currently three available font backends if Emacs was built without the
-Cairo drawing: @code{x} (the X core font driver), @code{xft} (the Xft
-font driver), and @code{xfthb} (the Xft font driver with HarfBuzz text
-shaping).  If built with the Cairo drawing, then there are two
-available font backends: @code{ftcr} (the FreeType font driver on
-Cairo) and @code{ftcrhb} (the FreeType font driver on Cairo with
-HarfBuzz text shaping).  On MS-Windows, there are currently two
-available font backends: @code{gdi} and @code{uniscribe}
-(@pxref{Windows Fonts,,, emacs, The GNU Emacs Manual}).  On other
-systems, there is only one available font backend, so it does not make
-sense to modify this frame parameter.
+drawing characters on the frame, in order of priority.  In Emacs built
+without Cairo drawing on X, there are currently three available font
+backends: @code{x} (the X core font driver), @code{xft} (the Xft font
+driver), and @code{xfthb} (the Xft font driver with HarfBuzz text
+shaping).  If built with the Cairo drawing, there are two available
+font backends on X: @code{ftcr} (the FreeType font driver on Cairo)
+and @code{ftcrhb} (the FreeType font driver on Cairo with HarfBuzz
+text shaping).  On MS-Windows, there are currently three available
+font backends: @code{gdi} (the core MS-Windows font driver),
+@code{uniscribe} (font driver for OTF and TTF fonts with text shaping
+by the Uniscribe engine), and @code{harfbuzz} (font driver for OTF and
+TTF fonts with HarfBuzz text shaping) (@pxref{Windows Fonts,,, emacs,
+The GNU Emacs Manual}).  On other systems, there is only one available
+font backend, so it does not make sense to modify this frame
+parameter.
 
 @vindex background-mode@r{, a frame parameter}
 @item background-mode
index 16017f4a49a3fbdc58b4e4d0df04efd858a58c8e..bf8e34b2529dd1883c73b12f5fe879228949ed39 100644 (file)
@@ -1321,22 +1321,25 @@ and put an element value.  */)
   return Fcdr (Fassq (prop, Vchar_code_property_alist));
 }
 
+Lisp_Object
+get_unicode_property (Lisp_Object char_table, int ch)
+{
+  Lisp_Object val = CHAR_TABLE_REF (char_table, ch);
+  uniprop_decoder_t decoder = uniprop_get_decoder (char_table);
+  return (decoder ? decoder (char_table, val) : val);
+}
+
 DEFUN ("get-unicode-property-internal", Fget_unicode_property_internal,
        Sget_unicode_property_internal, 2, 2, 0,
        doc: /* Return an element of CHAR-TABLE for character CH.
 CHAR-TABLE must be what returned by `unicode-property-table-internal'. */)
   (Lisp_Object char_table, Lisp_Object ch)
 {
-  Lisp_Object val;
-  uniprop_decoder_t decoder;
-
   CHECK_CHAR_TABLE (char_table);
   CHECK_CHARACTER (ch);
   if (! UNIPROP_TABLE_P (char_table))
     error ("Invalid Unicode property table");
-  val = CHAR_TABLE_REF (char_table, XFIXNUM (ch));
-  decoder = uniprop_get_decoder (char_table);
-  return (decoder ? decoder (char_table, val) : val);
+  return get_unicode_property (char_table, XFIXNUM (ch));
 }
 
 DEFUN ("put-unicode-property-internal", Fput_unicode_property_internal,
index a590bda3db4c5bb1b680be8b29d8ba4b3fac238c..4d1341a0db8a05cbf0086daab4cb2004e753a892 100644 (file)
@@ -951,6 +951,9 @@ extern void syms_of_bdffont (void);
 #ifdef HAVE_NTGUI
 extern struct font_driver w32font_driver;
 extern struct font_driver uniscribe_font_driver;
+#ifdef HAVE_HARFBUZZ
+extern struct font_driver harfbuzz_font_driver;
+#endif
 extern void syms_of_w32font (void);
 #endif /* HAVE_NTGUI */
 #ifdef HAVE_NS
index 6db905968995896e816b8ac637e396c8eb7236a4..5bd88f32e6ec3647c72beb6518dca729f2ffa32e 100644 (file)
@@ -3994,6 +3994,7 @@ extern void map_char_table_for_charset (void (*c_function) (Lisp_Object, Lisp_Ob
                                        Lisp_Object, struct charset *,
                                        unsigned, unsigned);
 extern Lisp_Object uniprop_table (Lisp_Object);
+extern Lisp_Object get_unicode_property (Lisp_Object, int);
 extern void syms_of_chartab (void);
 
 /* Defined in print.c.  */
index bb74fcc1640275d1dfc084ecc92f960b03a8978f..25fa1ac6ea0363c985f599771057485aef9f9e93 100644 (file)
@@ -221,6 +221,7 @@ int menubar_in_use = 0;
 /* From w32uniscribe.c  */
 extern void syms_of_w32uniscribe (void);
 extern int uniscribe_available;
+extern int harfbuzz_available;
 
 #ifdef WINDOWSNT
 /* From w32inevt.c */
@@ -5843,6 +5844,10 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
       specbind (Qx_resource_name, name);
     }
 
+#ifdef HAVE_HARFBUZZ
+  if (harfbuzz_available)
+    register_font_driver (&harfbuzz_font_driver, f);
+#endif
   if (uniscribe_available)
     register_font_driver (&uniscribe_font_driver, f);
   register_font_driver (&w32font_driver, f);
@@ -6896,6 +6901,10 @@ w32_create_tip_frame (struct w32_display_info *dpyinfo, Lisp_Object parms)
       specbind (Qx_resource_name, name);
     }
 
+#ifdef HAVE_HARFBUZZ
+  if (harfbuzz_available)
+    register_font_driver (&harfbuzz_font_driver, f);
+#endif
   if (uniscribe_available)
     register_font_driver (&uniscribe_font_driver, f);
   register_font_driver (&w32font_driver, f);
index bd68e22cc902ea6fd0c447828e56b6943b099bc8..2576df64b6fd6a45f815f5bfc2b8bf2ff4755303 100644 (file)
@@ -2634,6 +2634,7 @@ syms_of_w32font (void)
 {
   DEFSYM (Qgdi, "gdi");
   DEFSYM (Quniscribe, "uniscribe");
+  DEFSYM (Qharfbuzz, "harfbuzz");
   DEFSYM (QCformat, ":format");
 
   /* Generic font families.  */
index 693e4a4b1b330802f0f6fbedf931c7e6b92b60ae..f1e69452160c80787a6d87d884576257af5e4ce6 100644 (file)
@@ -29,6 +29,15 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #define _WIN32_WINNT 0x0600
 #include <windows.h>
 #include <usp10.h>
+#ifdef HAVE_HARFBUZZ
+# include <math.h>     /* for lround */
+# include <hb.h>
+# if GNUC_PREREQ (4, 3, 0)
+#  define bswap_32(v)  __builtin_bswap32(v)
+# else
+#  include <byteswap.h>
+# endif
+#endif
 
 #include "lisp.h"
 #include "w32term.h"
@@ -39,10 +48,16 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include "pdumper.h"
 #include "w32common.h"
 
+/* Extension of w32font_info used by Uniscribe and HarfBuzz backends.  */
 struct uniscribe_font_info
 {
   struct w32font_info w32_font;
-  SCRIPT_CACHE cache;
+  /* This is used by the Uniscribe backend as a pointer to the script
+     cache, and by the HarfBuzz backend as a pointer to a hb_font_t
+     object.  */
+  void *cache;
+  /* This is used by the HarfBuzz backend to store the font scale.  */
+  double scale;
 };
 
 int uniscribe_available = 0;
@@ -51,6 +66,94 @@ int uniscribe_available = 0;
 static int CALLBACK ALIGN_STACK add_opentype_font_name_to_list (ENUMLOGFONTEX *,
                                                                NEWTEXTMETRICEX *,
                                                                DWORD, LPARAM);
+#ifdef HAVE_HARFBUZZ
+
+struct font_driver harfbuzz_font_driver;
+int harfbuzz_available = 0;
+
+/* Typedefs for HarfBuzz functions which we call through function
+   pointers initialized after we load the HarfBuzz DLL.  */
+DEF_DLL_FN (hb_blob_t *, hb_blob_create,
+           (const char *, unsigned int, hb_memory_mode_t, void *,
+            hb_destroy_func_t));
+DEF_DLL_FN (hb_face_t *, hb_face_create_for_tables,
+           (hb_reference_table_func_t, void *, hb_destroy_func_t));
+DEF_DLL_FN (unsigned, hb_face_get_glyph_count, (const hb_face_t *));
+DEF_DLL_FN (hb_font_t *, hb_font_create, (hb_face_t *));
+DEF_DLL_FN (void, hb_font_destroy, (hb_font_t *));
+DEF_DLL_FN (void, hb_face_destroy, (hb_face_t *));
+DEF_DLL_FN (unsigned int, hb_face_get_upem, (hb_face_t *));
+DEF_DLL_FN (hb_bool_t, hb_font_get_nominal_glyph,
+           (hb_font_t *, hb_codepoint_t, hb_codepoint_t *));
+DEF_DLL_FN (hb_unicode_funcs_t *, hb_unicode_funcs_create,
+           (hb_unicode_funcs_t *));
+DEF_DLL_FN (hb_unicode_funcs_t *, hb_unicode_funcs_get_default, (void));
+DEF_DLL_FN (void, hb_unicode_funcs_set_combining_class_func,
+           (hb_unicode_funcs_t *, hb_unicode_combining_class_func_t,
+            void *, hb_destroy_func_t));
+DEF_DLL_FN (void, hb_unicode_funcs_set_general_category_func,
+           (hb_unicode_funcs_t *, hb_unicode_general_category_func_t,
+            void *, hb_destroy_func_t));
+DEF_DLL_FN (void, hb_unicode_funcs_set_mirroring_func,
+           (hb_unicode_funcs_t *, hb_unicode_mirroring_func_t,
+            void *, hb_destroy_func_t));
+DEF_DLL_FN (hb_buffer_t *, hb_buffer_create, (void));
+DEF_DLL_FN (void, hb_buffer_set_unicode_funcs,
+           (hb_buffer_t *, hb_unicode_funcs_t *));
+DEF_DLL_FN (void, hb_buffer_clear_contents, (hb_buffer_t *));
+DEF_DLL_FN (hb_bool_t, hb_buffer_pre_allocate, (hb_buffer_t *, unsigned int));
+DEF_DLL_FN (void, hb_buffer_add, (hb_buffer_t *, hb_codepoint_t, unsigned int));
+DEF_DLL_FN (void, hb_buffer_set_content_type,
+           (hb_buffer_t *, hb_buffer_content_type_t));
+DEF_DLL_FN (void, hb_buffer_set_cluster_level,
+           (hb_buffer_t *, hb_buffer_cluster_level_t));
+DEF_DLL_FN (void, hb_buffer_set_direction, (hb_buffer_t *, hb_direction_t));
+DEF_DLL_FN (void, hb_buffer_set_language, (hb_buffer_t *, hb_language_t));
+DEF_DLL_FN (hb_language_t, hb_language_from_string, (const char *, int));
+DEF_DLL_FN (void, hb_buffer_guess_segment_properties, (hb_buffer_t *));
+DEF_DLL_FN (hb_bool_t, hb_shape_full,
+           (hb_font_t *, hb_buffer_t *, const hb_feature_t *,
+            unsigned int, const char * const *));
+DEF_DLL_FN (unsigned int, hb_buffer_get_length, (hb_buffer_t *));
+DEF_DLL_FN (hb_direction_t, hb_buffer_get_direction, (hb_buffer_t *));
+DEF_DLL_FN (void, hb_buffer_reverse_clusters, (hb_buffer_t *));
+DEF_DLL_FN (hb_glyph_info_t *, hb_buffer_get_glyph_infos,
+           (hb_buffer_t *, unsigned int *));
+DEF_DLL_FN (hb_glyph_position_t *, hb_buffer_get_glyph_positions,
+           (hb_buffer_t *, unsigned int *));
+
+#define hb_blob_create fn_hb_blob_create
+#define hb_face_create_for_tables fn_hb_face_create_for_tables
+#define hb_face_get_glyph_count fn_hb_face_get_glyph_count
+#define hb_font_create fn_hb_font_create
+#define hb_font_destroy fn_hb_font_destroy
+#define hb_face_destroy fn_hb_face_destroy
+#define hb_face_get_upem fn_hb_face_get_upem
+#define hb_font_get_nominal_glyph fn_hb_font_get_nominal_glyph
+#define hb_unicode_funcs_create fn_hb_unicode_funcs_create
+#define hb_unicode_funcs_get_default fn_hb_unicode_funcs_get_default
+#define hb_unicode_funcs_set_combining_class_func fn_hb_unicode_funcs_set_combining_class_func
+#define hb_unicode_funcs_set_general_category_func fn_hb_unicode_funcs_set_general_category_func
+#define hb_unicode_funcs_set_mirroring_func fn_hb_unicode_funcs_set_mirroring_func
+#define hb_buffer_create fn_hb_buffer_create
+#define hb_buffer_set_unicode_funcs fn_hb_buffer_set_unicode_funcs
+#define hb_buffer_clear_contents fn_hb_buffer_clear_contents
+#define hb_buffer_pre_allocate fn_hb_buffer_pre_allocate
+#define hb_buffer_add fn_hb_buffer_add
+#define hb_buffer_set_content_type fn_hb_buffer_set_content_type
+#define hb_buffer_set_cluster_level fn_hb_buffer_set_cluster_level
+#define hb_buffer_set_direction fn_hb_buffer_set_direction
+#define hb_buffer_set_language fn_hb_buffer_set_language
+#define hb_language_from_string fn_hb_language_from_string
+#define hb_buffer_guess_segment_properties fn_hb_buffer_guess_segment_properties
+#define hb_shape_full fn_hb_shape_full
+#define hb_buffer_get_length fn_hb_buffer_get_length
+#define hb_buffer_get_direction fn_hb_buffer_get_direction
+#define hb_buffer_reverse_clusters fn_hb_buffer_reverse_clusters
+#define hb_buffer_get_glyph_infos fn_hb_buffer_get_glyph_infos
+#define hb_buffer_get_glyph_positions fn_hb_buffer_get_glyph_positions
+#endif
+
 /* Used by uniscribe_otf_capability.  */
 static Lisp_Object otf_features (HDC context, const char *table);
 
@@ -117,7 +220,10 @@ uniscribe_open (struct frame *f, Lisp_Object font_entity, int pixel_size)
   struct uniscribe_font_info *uniscribe_font
     = (struct uniscribe_font_info *) XFONT_OBJECT (font_object);
 
-  ASET (font_object, FONT_TYPE_INDEX, Quniscribe);
+  if (!NILP (AREF (font_entity, FONT_TYPE_INDEX)))
+    ASET (font_object, FONT_TYPE_INDEX, AREF (font_entity, FONT_TYPE_INDEX));
+  else /* paranoia: this should never happen */
+    ASET (font_object, FONT_TYPE_INDEX, Quniscribe);
 
   if (!w32font_open_internal (f, font_entity, pixel_size, font_object))
     {
@@ -127,10 +233,15 @@ uniscribe_open (struct frame *f, Lisp_Object font_entity, int pixel_size)
   /* Initialize the cache for this font.  */
   uniscribe_font->cache = NULL;
 
-  /* Uniscribe backend uses glyph indices.  */
+  /* Uniscribe and HarfBuzz backends use glyph indices.  */
   uniscribe_font->w32_font.glyph_idx = ETO_GLYPH_INDEX;
 
-  uniscribe_font->w32_font.font.driver = &uniscribe_font_driver;
+#ifdef HAVE_HARFBUZZ
+  if (EQ (AREF (font_object, FONT_TYPE_INDEX), Qharfbuzz))
+    uniscribe_font->w32_font.font.driver = &harfbuzz_font_driver;
+  else
+#endif  /* HAVE_HARFBUZZ */
+    uniscribe_font->w32_font.font.driver = &uniscribe_font_driver;
 
   return font_object;
 }
@@ -141,8 +252,16 @@ uniscribe_close (struct font *font)
   struct uniscribe_font_info *uniscribe_font
     = (struct uniscribe_font_info *) font;
 
+#ifdef HAVE_HARFBUZZ
+  if (uniscribe_font->w32_font.font.driver == &harfbuzz_font_driver
+      && uniscribe_font->cache)
+    hb_font_destroy ((hb_font_t *) uniscribe_font->cache);
+  else
+#endif
   if (uniscribe_font->cache)
-    ScriptFreeCache (&(uniscribe_font->cache));
+    ScriptFreeCache ((SCRIPT_CACHE) &(uniscribe_font->cache));
+
+  uniscribe_font->cache = NULL;
 
   w32font_close (font);
 }
@@ -290,7 +409,7 @@ uniscribe_shape (Lisp_Object lgstring, Lisp_Object direction)
 
       /* Context may be NULL here, in which case the cache should be
          used without needing to select the font.  */
-      result = ScriptShape (context, &(uniscribe_font->cache),
+      result = ScriptShape (context, (SCRIPT_CACHE) &(uniscribe_font->cache),
                            chars + items[i].iCharPos, nchars_in_run,
                            max_glyphs - done_glyphs, &(items[i].a),
                            glyphs, clusters, attributes, &nglyphs);
@@ -304,7 +423,7 @@ uniscribe_shape (Lisp_Object lgstring, Lisp_Object direction)
          context = get_frame_dc (f);
          old_font = SelectObject (context, FONT_HANDLE (font));
 
-         result = ScriptShape (context, &(uniscribe_font->cache),
+         result = ScriptShape (context, (SCRIPT_CACHE) &(uniscribe_font->cache),
                                chars + items[i].iCharPos, nchars_in_run,
                                max_glyphs - done_glyphs, &(items[i].a),
                                glyphs, clusters, attributes, &nglyphs);
@@ -329,7 +448,7 @@ uniscribe_shape (Lisp_Object lgstring, Lisp_Object direction)
        }
       else
        {
-         result = ScriptPlace (context, &(uniscribe_font->cache),
+         result = ScriptPlace (context, (SCRIPT_CACHE) &(uniscribe_font->cache),
                                glyphs, nglyphs, attributes, &(items[i].a),
                                advances, offsets, &overall_metrics);
          if (result == E_PENDING && !context)
@@ -339,13 +458,15 @@ uniscribe_shape (Lisp_Object lgstring, Lisp_Object direction)
              context = get_frame_dc (f);
              old_font = SelectObject (context, FONT_HANDLE (font));
 
-             result = ScriptPlace (context, &(uniscribe_font->cache),
+             result = ScriptPlace (context,
+                                   (SCRIPT_CACHE) &(uniscribe_font->cache),
                                    glyphs, nglyphs, attributes, &(items[i].a),
                                    advances, offsets, &overall_metrics);
            }
           if (SUCCEEDED (result))
            {
              int j, from, to, adj_offset = 0;
+             int cluster_offset = 0;
 
              from = 0;
              to = from;
@@ -389,6 +510,7 @@ uniscribe_shape (Lisp_Object lgstring, Lisp_Object direction)
                                }
                            }
                        }
+                     cluster_offset = 0;
 
                      /* For RTL text, the Uniscribe shaper prepares
                         the values in ADVANCES array for layout in
@@ -419,8 +541,11 @@ uniscribe_shape (Lisp_Object lgstring, Lisp_Object direction)
                        }
                    }
 
-                 LGLYPH_SET_CHAR (lglyph, chars[items[i].iCharPos
-                                                + from]);
+                 int char_idx = items[i].iCharPos + from + cluster_offset;
+                 if (from + cluster_offset > to)
+                   char_idx = items[i].iCharPos + to;
+                 cluster_offset++;
+                 LGLYPH_SET_CHAR (lglyph, chars[char_idx]);
                  LGLYPH_SET_FROM (lglyph, items[i].iCharPos + from);
                  LGLYPH_SET_TO (lglyph, items[i].iCharPos + to);
 
@@ -429,18 +554,18 @@ uniscribe_shape (Lisp_Object lgstring, Lisp_Object direction)
                  LGLYPH_SET_ASCENT (lglyph, font->ascent);
                  LGLYPH_SET_DESCENT (lglyph, font->descent);
 
-                 result = ScriptGetGlyphABCWidth (context,
-                                                  &(uniscribe_font->cache),
-                                                  glyphs[j], &char_metric);
+                 result = ScriptGetGlyphABCWidth
+                   (context, (SCRIPT_CACHE) &(uniscribe_font->cache),
+                    glyphs[j], &char_metric);
                  if (result == E_PENDING && !context)
                    {
                      /* Cache incomplete... */
                      f = XFRAME (selected_frame);
                      context = get_frame_dc (f);
                      old_font = SelectObject (context, FONT_HANDLE (font));
-                     result = ScriptGetGlyphABCWidth (context,
-                                                      &(uniscribe_font->cache),
-                                                      glyphs[j], &char_metric);
+                     result = ScriptGetGlyphABCWidth
+                       (context, (SCRIPT_CACHE) &(uniscribe_font->cache),
+                        glyphs[j], &char_metric);
                    }
 
                  if (SUCCEEDED (result))
@@ -572,7 +697,8 @@ uniscribe_encode_char (struct font *font, int c)
             order.  */
          items[0].a.fLogicalOrder = 1;
 
-          result = ScriptShape (context, &(uniscribe_font->cache),
+          result = ScriptShape (context,
+                               (SCRIPT_CACHE) &(uniscribe_font->cache),
                                 ch, len, 2, &(items[0].a),
                                 glyphs, clusters, attrs, &nglyphs);
 
@@ -583,7 +709,8 @@ uniscribe_encode_char (struct font *font, int c)
               f = XFRAME (selected_frame);
               context = get_frame_dc (f);
               old_font = SelectObject (context, FONT_HANDLE (font));
-              result = ScriptShape (context, &(uniscribe_font->cache),
+              result = ScriptShape (context,
+                                   (SCRIPT_CACHE) &(uniscribe_font->cache),
                                     ch, len, 2, &(items[0].a),
                                     glyphs, clusters, attrs, &nglyphs);
             }
@@ -601,7 +728,8 @@ uniscribe_encode_char (struct font *font, int c)
                  when shaped. But we still need the return from here
                  to be valid for the shaping engine to be invoked
                  later.  */
-              result = ScriptGetCMap (context, &(uniscribe_font->cache),
+              result = ScriptGetCMap (context,
+                                     (SCRIPT_CACHE) &(uniscribe_font->cache),
                                       ch, len, 0, glyphs);
               if (SUCCEEDED (result) && glyphs[0])
                 code = glyphs[0];
@@ -1148,6 +1276,508 @@ font_table_error:
   return Qnil;
 }
 
+#ifdef HAVE_HARFBUZZ
+
+/* W32 implementation of the 'list' method for HarfBuzz backend.  */
+static Lisp_Object
+w32hb_list (struct frame *f, Lisp_Object font_spec)
+{
+  Lisp_Object fonts = w32font_list_internal (f, font_spec, true);
+  FONT_ADD_LOG ("harfbuzz-list", font_spec, fonts);
+
+  for (Lisp_Object tail = fonts; CONSP (tail); tail = XCDR (tail))
+    ASET (XCAR (tail), FONT_TYPE_INDEX, Qharfbuzz);
+
+  return fonts;
+}
+
+/* W32 implementation of the 'match' method for HarfBuzz backend.  */
+static Lisp_Object
+w32hb_match (struct frame *f, Lisp_Object font_spec)
+{
+  Lisp_Object entity = w32font_match_internal (f, font_spec, true);
+  FONT_ADD_LOG ("harfbuzz-match", font_spec, entity);
+
+  if (! NILP (entity))
+    ASET (entity, FONT_TYPE_INDEX, Qharfbuzz);
+  return entity;
+}
+
+/* Callback function to free memory.  We need this so we could pass it
+   to HarfBuzz as the function to call to destroy objects for which we
+   allocated data by calling our 'malloc' (as opposed to 'malloc' from
+   the MS CRT, against which HarfBuzz was linked).  */
+static void
+free_cb (void *ptr)
+{
+  free (ptr);
+}
+
+/* A function used as reference_table_func for HarfBuzz.  It returns
+   the data of a specified table of a font as a blob.  */
+static hb_blob_t *
+w32hb_get_font_table (hb_face_t *face, hb_tag_t tag, void *data)
+{
+  struct frame *f = XFRAME (selected_frame);
+  HDC context = get_frame_dc (f);
+  HFONT old_font = SelectObject (context, (HFONT) data);
+  char *font_data = NULL;
+  DWORD font_data_size = 0, val;
+  DWORD table = bswap_32 (tag);
+  hb_blob_t *blob = NULL;
+
+  val = GetFontData (context, table, 0, font_data, font_data_size);
+  if (val != GDI_ERROR)
+    {
+      font_data_size = val;
+      /* Don't call xmalloc, because it can signal an error, while
+        we are inside a critical section established by get_frame_dc.  */
+      font_data = malloc (font_data_size);
+      if (font_data)
+       {
+         val = GetFontData (context, table, 0, font_data, font_data_size);
+         if (val != GDI_ERROR)
+           blob = hb_blob_create (font_data, font_data_size,
+                                  HB_MEMORY_MODE_READONLY, font_data, free_cb);
+       }
+    }
+
+  /* Restore graphics context.  */
+  SelectObject (context, old_font);
+  release_frame_dc (f, context);
+
+  return blob;
+}
+
+/* Helper function used by the HarfBuzz implementations of the
+   encode_char, has_char, and begin_hb_font methods.  It creates an
+   hb_font_t object for a given Emacs font.  */
+static hb_font_t *
+w32hb_get_font (struct font *font, double *scale)
+{
+  hb_font_t *hb_font = NULL;
+  HFONT font_handle = FONT_HANDLE (font);
+  hb_face_t *hb_face =
+    hb_face_create_for_tables (w32hb_get_font_table, font_handle, NULL);
+  if (hb_face_get_glyph_count (hb_face) > 0)
+    hb_font = hb_font_create (hb_face);
+
+  struct uniscribe_font_info *uniscribe_font =
+    (struct uniscribe_font_info *) font;
+  unsigned upem = hb_face_get_upem (hb_face);
+  eassert (upem > 0);
+  /* https://support.microsoft.com/en-sg/help/74299/info-calculating-the-logical-height-and-point-size-of-a-font.  */
+  LONG font_point_size =
+    uniscribe_font->w32_font.metrics.tmHeight
+    - uniscribe_font->w32_font.metrics.tmInternalLeading;
+  /* https://docs.microsoft.com/en-us/typography/opentype/spec/ttch01,
+     under "Converting FUnits to pixels".  */
+  *scale = font_point_size * 1.0 / upem;
+
+  hb_face_destroy (hb_face);
+
+  /* FIXME: Can hb_font be non-NULL and yet invalid?  Compare to get_empty?  */
+  return hb_font;
+}
+
+/* W32 implementation of encode_char method for HarfBuzz backend.  */
+static unsigned
+w32hb_encode_char (struct font *font, int c)
+{
+  struct uniscribe_font_info *uniscribe_font
+    = (struct uniscribe_font_info *) font;
+  eassert (uniscribe_font->w32_font.font.driver == &harfbuzz_font_driver);
+  hb_font_t *hb_font = uniscribe_font->cache;
+
+  /* First time we use this font with HarfBuzz, create the hb_font_t
+     object and cache it.  */
+  if (!hb_font)
+    {
+      double scale;
+      hb_font = w32hb_get_font (font, &scale);
+      if (!hb_font)
+       return FONT_INVALID_CODE;
+
+      uniscribe_font->cache = hb_font;
+      eassert (scale > 0.0);
+      uniscribe_font->scale = scale;
+    }
+  hb_codepoint_t glyph;
+  if (hb_font_get_nominal_glyph (hb_font, c, &glyph))
+    return glyph;
+  return FONT_INVALID_CODE;
+}
+
+/* W32 implementation of HarfBuzz begin_hb_font and end_hb_font
+   methods.  */
+
+/* Return a HarfBuzz font object for FONT and store in POSITION_UNIT
+   the scale factor to convert a hb_position_t value to the number of
+   pixels.  Return NULL if HarfBuzz font object is not available for
+   FONT.  */
+static hb_font_t *
+w32hb_begin_font (struct font *font, double *position_unit)
+{
+  struct uniscribe_font_info *uniscribe_font
+    = (struct uniscribe_font_info *) font;
+  eassert (uniscribe_font->w32_font.font.driver == &harfbuzz_font_driver);
+
+  /* First time we use this font with HarfBuzz, create the hb_font_t
+     object and cache it.  */
+  if (!uniscribe_font->cache)
+    {
+      double scale;
+      uniscribe_font->cache = w32hb_get_font (font, &scale);
+      eassert (scale > 0.0);
+      uniscribe_font->scale = scale;
+    }
+  *position_unit = uniscribe_font->scale;
+  return (hb_font_t *) uniscribe_font->cache;
+}
+
+static bool combining_class_loaded = false;
+static Lisp_Object canonical_combining_class_table;
+
+static hb_unicode_combining_class_t
+w32uni_combining (hb_unicode_funcs_t *funcs, hb_codepoint_t ch, void *user_data)
+{
+  /* Load the Unicode table first time it is needed.  */
+  if (!combining_class_loaded)
+    {
+      canonical_combining_class_table =
+       uniprop_table (intern ("canonical-combining-class"));
+      if (NILP (canonical_combining_class_table))
+       emacs_abort ();
+      staticpro (&canonical_combining_class_table);
+      combining_class_loaded = true;
+    }
+
+  Lisp_Object combining =
+    get_unicode_property (canonical_combining_class_table, ch);
+  if (FIXNUMP (combining))
+    return (hb_unicode_combining_class_t) XFIXNUM (combining);
+
+  return HB_UNICODE_COMBINING_CLASS_NOT_REORDERED;
+}
+
+static hb_unicode_general_category_t
+w32uni_general (hb_unicode_funcs_t *funcs, hb_codepoint_t ch, void *user_data)
+{
+  Lisp_Object category = CHAR_TABLE_REF (Vunicode_category_table, ch);
+
+  if (INTEGERP (category))
+    {
+    switch (XFIXNUM (category))
+      {
+      case UNICODE_CATEGORY_Cc:
+        return HB_UNICODE_GENERAL_CATEGORY_CONTROL;
+      case UNICODE_CATEGORY_Cf:
+        return HB_UNICODE_GENERAL_CATEGORY_FORMAT;
+      case UNICODE_CATEGORY_Cn:
+        return HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED;
+      case UNICODE_CATEGORY_Co:
+        return HB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE;
+      case UNICODE_CATEGORY_Cs:
+        return HB_UNICODE_GENERAL_CATEGORY_SURROGATE;
+      case UNICODE_CATEGORY_Ll:
+        return HB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER;
+      case UNICODE_CATEGORY_Lm:
+        return HB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER;
+      case UNICODE_CATEGORY_Lo:
+        return HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER;
+      case UNICODE_CATEGORY_Lt:
+        return HB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER;
+      case UNICODE_CATEGORY_Lu:
+        return HB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER;
+      case UNICODE_CATEGORY_Mc:
+        return HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK;
+      case UNICODE_CATEGORY_Me:
+        return HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK;
+      case UNICODE_CATEGORY_Mn:
+        return HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK;
+      case UNICODE_CATEGORY_Nd:
+        return HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER;
+      case UNICODE_CATEGORY_Nl:
+        return HB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER;
+      case UNICODE_CATEGORY_No:
+        return HB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER;
+      case UNICODE_CATEGORY_Pc:
+        return HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION;
+      case UNICODE_CATEGORY_Pd:
+        return HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION;
+      case UNICODE_CATEGORY_Pe:
+        return HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION;
+      case UNICODE_CATEGORY_Pf:
+        return HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION;
+      case UNICODE_CATEGORY_Pi:
+        return HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION;
+      case UNICODE_CATEGORY_Po:
+        return HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION;
+      case UNICODE_CATEGORY_Ps:
+        return HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION;
+      case UNICODE_CATEGORY_Sc:
+        return HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL;
+      case UNICODE_CATEGORY_Sk:
+        return HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL;
+      case UNICODE_CATEGORY_Sm:
+        return HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL;
+      case UNICODE_CATEGORY_So:
+        return HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL;
+      case UNICODE_CATEGORY_Zl:
+        return HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR;
+      case UNICODE_CATEGORY_Zp:
+        return HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR;
+      case UNICODE_CATEGORY_Zs:
+        return HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR;
+      case UNICODE_CATEGORY_UNKNOWN:
+        return HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED;
+      }
+    }
+
+  return HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED;
+}
+
+static hb_codepoint_t
+w32uni_mirroring (hb_unicode_funcs_t *funcs, hb_codepoint_t ch, void *user_data)
+{
+  return bidi_mirror_char (ch);
+}
+
+static hb_unicode_funcs_t *
+get_hb_unicode_funcs (void)
+{
+  /* Subclass HarfBuzz's default Unicode functions and override functions that
+   * use data Emacs can provide. This way changing Emacs data is reflected in
+   * the shaped output. */
+  hb_unicode_funcs_t *funcs = hb_unicode_funcs_create (hb_unicode_funcs_get_default ());
+
+  hb_unicode_funcs_set_combining_class_func (funcs, w32uni_combining, NULL, NULL);
+  hb_unicode_funcs_set_general_category_func (funcs, w32uni_general, NULL, NULL);
+  hb_unicode_funcs_set_mirroring_func (funcs, w32uni_mirroring, NULL, NULL);
+
+  /* Use default implmentation for Unicode composition/decomposition, we might
+   * want to revisit this later.
+  hb_unicode_funcs_set_compose_func (funcs, uni_compose, NULL, NULL);
+  hb_unicode_funcs_set_decompose_func (funcs, uni_decompose, NULL, NULL);
+  */
+
+  /* Emacs own script mapping for characters differs from Unicode, so we want
+   * to keep the default HarfBuzz's implementation here.
+  hb_unicode_funcs_set_script_func (funcs, uni_script, NULL, NULL);
+  */
+
+  return funcs;
+}
+
+/* HarfBuzz implementation of shape for font backend.  See the
+   commentary before uniscribe_shape for the meaning of the
+   arguments.  */
+static Lisp_Object
+w32hb_shape (Lisp_Object lgstring, Lisp_Object direction)
+{
+  struct font *font = CHECK_FONT_GET_OBJECT (LGSTRING_FONT (lgstring));
+  ptrdiff_t glyph_len = 0, text_len = LGSTRING_GLYPH_LEN (lgstring);
+  ptrdiff_t i;
+
+  hb_glyph_info_t *info;
+  hb_glyph_position_t *pos;
+
+  /* Cache the HarfBuzz buffer for better performance and less allocations.
+   * We intentionally never destroy the buffer. */
+  static hb_buffer_t *hb_buffer = NULL;
+  if (! hb_buffer)
+    {
+      hb_buffer = hb_buffer_create ();
+      hb_unicode_funcs_t* ufuncs = get_hb_unicode_funcs();
+      hb_buffer_set_unicode_funcs(hb_buffer, ufuncs);
+    }
+
+  hb_buffer_clear_contents (hb_buffer);
+  hb_buffer_pre_allocate (hb_buffer, text_len);
+
+  /* Copy the characters in their original logical order, so we can
+     assign them to glyphs correctly after shaping.  */
+  int *chars = alloca (text_len * sizeof (int));
+  for (i = 0; i < text_len; i++)
+    {
+      Lisp_Object g = LGSTRING_GLYPH (lgstring, i);
+      int c;
+
+      if (NILP (g))
+       break;
+      c = LGLYPH_CHAR (g);
+      hb_buffer_add (hb_buffer, c, i);
+      chars[i] = c;
+    }
+
+  text_len = i;
+  if (!text_len)
+    return Qnil;
+
+  hb_buffer_set_content_type (hb_buffer, HB_BUFFER_CONTENT_TYPE_UNICODE);
+  hb_buffer_set_cluster_level (hb_buffer,
+                              HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES);
+
+  /* If the caller didn't provide a meaningful DIRECTION, let HarfBuzz
+     guess it. */
+  if (!NILP (direction))
+    {
+      hb_direction_t dir = HB_DIRECTION_LTR;
+      if (EQ (direction, QL2R))
+       dir = HB_DIRECTION_LTR;
+      else if (EQ (direction, QR2L))
+       dir = HB_DIRECTION_RTL;
+      hb_buffer_set_direction (hb_buffer, dir);
+    }
+
+  /* Leave the script determination to HarfBuzz, until Emacs has a
+     better idea of the script of LGSTRING.  FIXME. */
+#if 0
+  hb_buffer_set_script (hb_buffer, XXX);
+#endif
+
+  /* FIXME: This can only handle the single global language, which
+     normally comes from the locale.  In addition, if
+     current-iso639-language is a list, we arbitrarily use the first
+     one.  We should instead have a notion of the language of the text
+     being shaped.  */
+  Lisp_Object lang = Vcurrent_iso639_language;
+  if (CONSP (Vcurrent_iso639_language))
+    lang = XCAR (Vcurrent_iso639_language);
+  if (SYMBOLP (lang))
+    {
+      Lisp_Object lang_str = SYMBOL_NAME (lang);
+      hb_buffer_set_language (hb_buffer,
+                             hb_language_from_string (SSDATA (lang_str),
+                                                      SBYTES (lang_str)));
+    }
+
+  /* Guess the default properties for when they cannot be determined above.
+
+     FIXME: maybe drop this guessing once script and language handling
+     is fixed above; but then will need to guess the direction by
+     ourselves, perhaps by looking at the the characters using
+     bidi_get_type or somesuch.  */
+  hb_buffer_guess_segment_properties (hb_buffer);
+
+  double position_unit;
+  hb_font_t *hb_font
+    = font->driver->begin_hb_font
+    ? font->driver->begin_hb_font (font, &position_unit)
+    : NULL;
+  if (!hb_font)
+    return make_fixnum (0);
+
+  hb_bool_t success = hb_shape_full (hb_font, hb_buffer, NULL, 0, NULL);
+  if (font->driver->end_hb_font)
+    font->driver->end_hb_font (font, hb_font);
+  if (!success)
+    return Qnil;
+
+  glyph_len = hb_buffer_get_length (hb_buffer);
+  /* FIXME: can't we just grow the lgstring in this case? Giving up is an
+   * overly heavy handed solution. */
+  if (glyph_len > LGSTRING_GLYPH_LEN (lgstring))
+    return Qnil;
+
+  /* We need the clusters in logical order.  */
+  bool buf_reversed = false;
+  if (HB_DIRECTION_IS_BACKWARD (hb_buffer_get_direction (hb_buffer)))
+    {
+      buf_reversed = true;
+      hb_buffer_reverse_clusters (hb_buffer);
+    }
+  info = hb_buffer_get_glyph_infos (hb_buffer, NULL);
+  pos = hb_buffer_get_glyph_positions (hb_buffer, NULL);
+  int from = -1, to, cluster_offset = 0;
+  int char_idx, incr = buf_reversed ? -1 : 1;
+  for (i = 0; i < glyph_len; i++)
+    {
+      Lisp_Object lglyph = LGSTRING_GLYPH (lgstring, i);
+      struct font_metrics metrics = {.width = 0};
+      int xoff, yoff, wadjust;
+
+      if (NILP (lglyph))
+       {
+         lglyph = LGLYPH_NEW ();
+         LGSTRING_SET_GLYPH (lgstring, i, lglyph);
+       }
+
+      if (info[i].cluster != from)
+       {
+         int j;
+         /* Found a new cluster.  Determine its FROM and TO, and the
+            offset to the first character of the cluster.  */
+         /* FROM is the index of the first character that contributed
+            to this cluster.  */
+         from = info[i].cluster;
+         /* TO is the index of the last character that contributed to
+            this cluster.  */
+         for (j = i; j < glyph_len && info[j].cluster == from; j++)
+           ;
+         to = (j == glyph_len) ? text_len - 1 : info[j].cluster - 1;
+         cluster_offset = 0;
+         /* For RTL buffers, HarfBuzz produces glyphs in a cluster in
+            reverse order, so we need to account for that to record
+            the correct character in each glyph.
+
+            Implementation note: the character codepoint recorded in
+            each glyph is not really used, except when we display the
+            glyphs in descr-text.el.  So this is just an aeasthetic
+            issue.  */
+         if (buf_reversed)
+           cluster_offset = to - from;
+       }
+
+      /* All the glyphs in a cluster have the same values of FROM and TO.  */
+      LGLYPH_SET_FROM (lglyph, from);
+      LGLYPH_SET_TO (lglyph, to);
+
+      /* Not every glyph in a cluster maps directly to a single
+        character; in general, N characters can yield M glyphs, where
+        M could be smaller or greater than N.  However, in many cases
+        there is a one-to-one correspondence, and it would be a pity
+        to lose that information, even if it's sometimes inaccurate.  */
+      char_idx = from + cluster_offset;
+      cluster_offset += incr;
+      if (char_idx > to)
+       char_idx = to;
+      if (char_idx < from)
+       char_idx = from;
+      LGLYPH_SET_CHAR (lglyph, chars[char_idx]);
+      LGLYPH_SET_CODE (lglyph, info[i].codepoint);
+
+      unsigned code = info[i].codepoint;
+      font->driver->text_extents (font, &code, 1, &metrics);
+      LGLYPH_SET_WIDTH (lglyph, metrics.width);
+      LGLYPH_SET_LBEARING (lglyph, metrics.lbearing);
+      LGLYPH_SET_RBEARING (lglyph, metrics.rbearing);
+      LGLYPH_SET_ASCENT (lglyph, metrics.ascent);
+      LGLYPH_SET_DESCENT (lglyph, metrics.descent);
+
+      xoff = lround (pos[i].x_offset * position_unit);
+      yoff = - lround (pos[i].y_offset * position_unit);
+      wadjust = lround (pos[i].x_advance * position_unit);
+      if (xoff || yoff || wadjust != metrics.width)
+       {
+         Lisp_Object vec = make_uninit_vector (3);
+         ASET (vec, 0, make_fixnum (xoff));
+         ASET (vec, 1, make_fixnum (yoff));
+         ASET (vec, 2, make_fixnum (wadjust));
+         LGLYPH_SET_ADJUSTMENT (lglyph, vec);
+       }
+    }
+
+  return make_fixnum (glyph_len);
+}
+
+static Lisp_Object
+w32hb_combining_capability (struct font *font)
+{
+  return Qt;
+}
+#endif /* HAVE_HARFBUZZ */
+
 #undef OTF_INT16_VAL
 #undef OTF_TAG_VAL
 #undef OTF_TAG
@@ -1196,17 +1826,53 @@ syms_of_w32uniscribe (void)
   pdumper_do_now_and_after_load (syms_of_w32uniscribe_for_pdumper);
 }
 
+#ifdef HAVE_HARFBUZZ
+static bool
+load_harfbuzz_funcs (HMODULE library)
+{
+  LOAD_DLL_FN (library, hb_blob_create);
+  LOAD_DLL_FN (library, hb_face_create_for_tables);
+  LOAD_DLL_FN (library, hb_face_get_glyph_count);
+  LOAD_DLL_FN (library, hb_font_create);
+  LOAD_DLL_FN (library, hb_font_destroy);
+  LOAD_DLL_FN (library, hb_face_get_upem);
+  LOAD_DLL_FN (library, hb_face_destroy);
+  LOAD_DLL_FN (library, hb_font_get_nominal_glyph);
+  LOAD_DLL_FN (library, hb_unicode_funcs_create);
+  LOAD_DLL_FN (library, hb_unicode_funcs_get_default);
+  LOAD_DLL_FN (library, hb_unicode_funcs_set_combining_class_func);
+  LOAD_DLL_FN (library, hb_unicode_funcs_set_general_category_func);
+  LOAD_DLL_FN (library, hb_unicode_funcs_set_mirroring_func);
+  LOAD_DLL_FN (library, hb_buffer_create);
+  LOAD_DLL_FN (library, hb_buffer_set_unicode_funcs);
+  LOAD_DLL_FN (library, hb_buffer_clear_contents);
+  LOAD_DLL_FN (library, hb_buffer_pre_allocate);
+  LOAD_DLL_FN (library, hb_buffer_add);
+  LOAD_DLL_FN (library, hb_buffer_set_content_type);
+  LOAD_DLL_FN (library, hb_buffer_set_cluster_level);
+  LOAD_DLL_FN (library, hb_buffer_set_direction);
+  LOAD_DLL_FN (library, hb_buffer_set_language);
+  LOAD_DLL_FN (library, hb_language_from_string);
+  LOAD_DLL_FN (library, hb_buffer_guess_segment_properties);
+  LOAD_DLL_FN (library, hb_shape_full);
+  LOAD_DLL_FN (library, hb_buffer_get_length);
+  LOAD_DLL_FN (library, hb_buffer_get_direction);
+  LOAD_DLL_FN (library, hb_buffer_reverse_clusters);
+  LOAD_DLL_FN (library, hb_buffer_get_glyph_infos);
+  LOAD_DLL_FN (library, hb_buffer_get_glyph_positions);
+  return true;
+}
+#endif /* HAVE_HARFBUZZ */
+
 static void
 syms_of_w32uniscribe_for_pdumper (void)
 {
-  HMODULE uniscribe;
-
-  /* Don't init uniscribe when dumping */
+  /* Don't init Uniscribe and HarfBuzz when dumping */
   if (!initialized)
     return;
 
-  /* Don't register if uniscribe is not available.  */
-  uniscribe = GetModuleHandle ("usp10");
+  /* Don't register if Uniscribe is not available.  */
+  HMODULE uniscribe = GetModuleHandle ("usp10");
   if (!uniscribe)
     return;
 
@@ -1226,4 +1892,30 @@ syms_of_w32uniscribe_for_pdumper (void)
     uniscribe_new_apis = true;
   else
     uniscribe_new_apis = false;
+
+#ifdef HAVE_HARFBUZZ
+  /* Currently, HarfBuzz DLLs are always named libharfbuzz-0.dll, as
+     the project keeps the ABI backeard-compatible.  So we can
+     hard-code the name of the library here, for now.  If they ever
+     break ABI compatibility, we may need to load the DLL that
+     corresponds to the HarfBuzz version for which Emacs was built.  */
+  HMODULE harfbuzz = LoadLibrary ("libharfbuzz-0.dll");
+  /* Don't register if HarfBuzz is not available.  */
+  if (!harfbuzz)
+    return;
+
+  if (!load_harfbuzz_funcs (harfbuzz))
+    return;
+
+  harfbuzz_available = 1;
+  harfbuzz_font_driver = uniscribe_font_driver;
+  harfbuzz_font_driver.type = Qharfbuzz;
+  harfbuzz_font_driver.list = w32hb_list;
+  harfbuzz_font_driver.match = w32hb_match;
+  harfbuzz_font_driver.encode_char = w32hb_encode_char;
+  harfbuzz_font_driver.shape = w32hb_shape;
+  harfbuzz_font_driver.combining_capability = w32hb_combining_capability;
+  harfbuzz_font_driver.begin_hb_font = w32hb_begin_font;
+  register_font_driver (&harfbuzz_font_driver, NULL);
+#endif /* HAVE_HARFBUZZ */
 }