From aa8c7777ddc9ca9e8dc323c0cbd99636fc52f0ee Mon Sep 17 00:00:00 2001 From: Cecilio Pardo Date: Wed, 9 Oct 2024 11:40:28 +0200 Subject: [PATCH] Implement drawing text with DirectWrite on MS-Windows. This adds support for color fonts. * configure.ac: Add src/w32drite to W32_OBJ. * src/w32dwrite.c: This is a new file. (w32-initialize-direct-write): New function, initialize the DirectWrite library if it is available, and required global variables. (w32_use_direct_write): New function, check if DirectWrite is available and activated by the user. (w32_dwrite_encode_char): New function, replacement for HarfBuzz's encode_char. (w32_dwrite_text_extents): New function, replacement for w32font text_extents. (w32_dwrite_draw): New function, replacement for w32font draw. (w32_dwrite_free_cached_face): New function, used on the font deletion process to also delete DirectWrite data. (verify_hr): New function, verify COM method results. * src/w32font.c (w32font_text_extents): If DirectWrite is enabled, call w32_dwrite_text_extents. (w32font_draw): If DirectWrite is enabled, call w32_dwrite_draw. * src/w32uniscribe.c: (w32hb_encode_char): If DirectWrite is enabled, call w32_dwrite_encode_char. (syms_of_w32uniscribe_for_pdumper): Initialize DirectWrite. (uniscribe_close): Free DirectWrite data for the font. --- configure.ac | 2 +- etc/NEWS | 5 + src/w32dwrite.c | 997 +++++++++++++++++++++++++++++++++++++++++++++ src/w32font.c | 39 +- src/w32font.h | 30 ++ src/w32uniscribe.c | 33 +- 6 files changed, 1080 insertions(+), 26 deletions(-) create mode 100644 src/w32dwrite.c diff --git a/configure.ac b/configure.ac index 8a5ba7db3d1..ba9cd8f0cd5 100644 --- a/configure.ac +++ b/configure.ac @@ -3172,7 +3172,7 @@ AC_DEFUN AC_CHECK_TOOL([WINDRES], [windres], [AC_MSG_ERROR([No resource compiler found.])]) W32_OBJ="w32fns.o w32menu.o w32reg.o w32font.o w32term.o" - W32_OBJ="$W32_OBJ w32xfns.o w32select.o w32uniscribe.o w32cygwinx.o" + W32_OBJ="$W32_OBJ w32xfns.o w32select.o w32uniscribe.o w32dwrite.o w32cygwinx.o" EMACSRES="emacs.res" case "$canonical" in x86_64-*-*) EMACS_MANIFEST="emacs-x64.manifest" ;; diff --git a/etc/NEWS b/etc/NEWS index 63c294fac41..f65333a36c0 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -750,6 +750,11 @@ and later versions. ** Emacs on MS-Windows now supports drag-n-drop of text into a buffer. This is in addition to drag-n-drop of files, that was already supported. +** Emacs on MS-Windows now supports color fonts. +On Windows 8.1 and later versions Emacs now uses DirectWrite to draw +text, which supports color fonts. This can be disabled by setting the +variable w32-inhibit-dwrite to t. + ---------------------------------------------------------------------- This file is part of GNU Emacs. diff --git a/src/w32dwrite.c b/src/w32dwrite.c new file mode 100644 index 00000000000..47b93a5340f --- /dev/null +++ b/src/w32dwrite.c @@ -0,0 +1,997 @@ +/* Support for using DirectWrite on MS-Windows to draw text. This + allows for color fonts. + Copyright (C) 2024 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +/* This requires the HarfBuzz font backend to be available. + + It works by modifying the HarfBuzz backend to use DirectWrite at + some points, if it is available: + + - When encoding characters: w32hb_encode_char + - When measuring text: w32font_text_extents + - When drawing text: w32font_draw + + DirectWrite is setup by calling w32_initialize_direct_write. From + that point, the function w32_use_direct_write will return true if + DirectWrite is to be used. + + DirectWrite is available since Windows 7, but we don't activate it on + versions before 8.1, because color fonts are only available since that. */ + +#include +#include +#include + +#ifndef MINGW_W64 +# define INITGUID +#endif +#include +#include +#include + +#include "frame.h" +#include "w32font.h" +#include "w32common.h" +#include "w32term.h" + +#ifndef MINGW_W64 + +/* The following definitions would be included from dwrite_3.h, but it + is not available when building with mingw.org's MinGW. Methods that + we don't use are declared with the EMACS_DWRITE_UNUSED macro, to + avoid bringing in more types that would need to be declared. */ + +#define EMACS_DWRITE_UNUSED(name) void (STDMETHODCALLTYPE *name)(void) + +#define DWRITE_E_NOCOLOR _HRESULT_TYPEDEF_(0x8898500CL) + +typedef enum DWRITE_PIXEL_GEOMETRY { + DWRITE_PIXEL_GEOMETRY_FLAT = 0, + DWRITE_PIXEL_GEOMETRY_RGB = 1, + DWRITE_PIXEL_GEOMETRY_BGR = 2 +} DWRITE_PIXEL_GEOMETRY; + +typedef enum DWRITE_RENDERING_MODE { + DWRITE_RENDERING_MODE_DEFAULT = 0, + DWRITE_RENDERING_MODE_ALIASED = 1, + DWRITE_RENDERING_MODE_GDI_CLASSIC = 2, + DWRITE_RENDERING_MODE_GDI_NATURAL = 3, + DWRITE_RENDERING_MODE_NATURAL = 4, + DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC = 5, + DWRITE_RENDERING_MODE_OUTLINE = 6 +} DWRITE_RENDERING_MODE; + +typedef enum DWRITE_MEASURING_MODE { + DWRITE_MEASURING_MODE_NATURAL = 0, + DWRITE_MEASURING_MODE_GDI_CLASSIC = 1, + DWRITE_MEASURING_MODE_GDI_NATURAL = 2 +} DWRITE_MEASURING_MODE; + +typedef enum DWRITE_TEXT_ANTIALIAS_MODE { + DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE = 0, + DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE = 1 +} DWRITE_TEXT_ANTIALIAS_MODE; + +typedef enum DWRITE_FACTORY_TYPE { + DWRITE_FACTORY_TYPE_SHARED = 0, + DWRITE_FACTORY_TYPE_ISOLATED = 1 +} DWRITE_FACTORY_TYPE; + +typedef struct DWRITE_FONT_METRICS { + UINT16 designUnitsPerEm; + UINT16 ascent; + UINT16 descent; + INT16 lineGap; + UINT16 capHeight; + UINT16 xHeight; + INT16 underlinePosition; + UINT16 underlineThickness; + INT16 strikethroughPosition; + UINT16 strikethroughThickness; +} DWRITE_FONT_METRICS; + +typedef struct DWRITE_GLYPH_METRICS { + INT32 leftSideBearing; + UINT32 advanceWidth; + INT32 rightSideBearing; + INT32 topSideBearing; + UINT32 advanceHeight; + INT32 bottomSideBearing; + INT32 verticalOriginY; +} DWRITE_GLYPH_METRICS; + +typedef interface IDWriteRenderingParams IDWriteRenderingParams; +typedef interface IDWriteFont IDWriteFont; +typedef interface IDWriteGdiInterop IDWriteGdiInterop; +typedef interface IDWriteFactory IDWriteFactory; +typedef interface IDWriteFactory2 IDWriteFactory2; +typedef interface IDWriteFontFace IDWriteFontFace; +typedef interface IDWriteBitmapRenderTarget IDWriteBitmapRenderTarget; +typedef interface IDWriteBitmapRenderTarget1 IDWriteBitmapRenderTarget1; +typedef interface IDWriteColorGlyphRunEnumerator IDWriteColorGlyphRunEnumerator; + +DEFINE_GUID(IID_IDWriteBitmapRenderTarget1, 0x791e8298, 0x3ef3, 0x4230, 0x98,0x80, 0xc9,0xbd,0xec,0xc4,0x20,0x64); +DEFINE_GUID(IID_IDWriteFactory2, 0x0439fc60, 0xca44, 0x4994, 0x8d,0xee, 0x3a,0x9a,0xf7,0xb7,0x32,0xec); +DEFINE_GUID(IID_IDWriteFactory, 0xb859ee5a, 0xd838, 0x4b5b, 0xa2,0xe8, 0x1a,0xdc,0x7d,0x93,0xdb,0x48); + +typedef struct DWRITE_GLYPH_OFFSET { + FLOAT advanceOffset; + FLOAT ascenderOffset; +} DWRITE_GLYPH_OFFSET; + +typedef struct DWRITE_GLYPH_RUN { + IDWriteFontFace *fontFace; + FLOAT fontEmSize; + UINT32 glyphCount; + const UINT16 *glyphIndices; + const FLOAT *glyphAdvances; + const DWRITE_GLYPH_OFFSET *glyphOffsets; + WINBOOL isSideways; + UINT32 bidiLevel; +} DWRITE_GLYPH_RUN; + +typedef struct _D3DCOLORVALUE { + float r; + float g; + float b; + float a; +} D3DCOLORVALUE; + +typedef D3DCOLORVALUE DWRITE_COLOR_F; + +typedef struct DWRITE_COLOR_GLYPH_RUN { + DWRITE_GLYPH_RUN glyphRun; + void *glyphRunDescription; + FLOAT baselineOriginX; + FLOAT baselineOriginY; + DWRITE_COLOR_F runColor; + UINT16 paletteIndex; +} DWRITE_COLOR_GLYPH_RUN; + +typedef struct IDWriteFontFaceVtbl { + BEGIN_INTERFACE + + HRESULT (STDMETHODCALLTYPE *QueryInterface) + (IDWriteFontFace *This, REFIID riid, void **ppvObject); + ULONG (STDMETHODCALLTYPE *AddRef)(IDWriteFontFace *This); + ULONG (STDMETHODCALLTYPE *Release)(IDWriteFontFace *This); + + EMACS_DWRITE_UNUSED(GetType); + EMACS_DWRITE_UNUSED(GetFiles); + EMACS_DWRITE_UNUSED(GetIndex); + EMACS_DWRITE_UNUSED(GetSimulations); + EMACS_DWRITE_UNUSED(IsSymbolFont); + + void (STDMETHODCALLTYPE *GetMetrics) + (IDWriteFontFace *This, DWRITE_FONT_METRICS *metrics); + + EMACS_DWRITE_UNUSED (GetGlyphCount); + EMACS_DWRITE_UNUSED (GetDesignGlyphMetrics); + + HRESULT (STDMETHODCALLTYPE *GetGlyphIndices) + (IDWriteFontFace *This, const UINT32 *codepoints, UINT32 count, + UINT16 *glyph_indices); + + EMACS_DWRITE_UNUSED (TryGetFontTable); + EMACS_DWRITE_UNUSED (ReleaseFontTable); + EMACS_DWRITE_UNUSED (GetGlyphRunOutline); + EMACS_DWRITE_UNUSED (GetRecommendedRenderingMode); + EMACS_DWRITE_UNUSED (GetGdiCompatibleMetrics); + + HRESULT (STDMETHODCALLTYPE *GetGdiCompatibleGlyphMetrics) + (IDWriteFontFace *This, + FLOAT emSize, + FLOAT pixels_per_dip, + void *transform, + WINBOOL use_gdi_natural, + const UINT16 *glyph_indices, + UINT32 glyph_count, + DWRITE_GLYPH_METRICS *metrics, + WINBOOL is_sideways); + END_INTERFACE +} IDWriteFontFaceVtbl; + +interface IDWriteFontFace { + CONST_VTBL IDWriteFontFaceVtbl* lpVtbl; +}; + +typedef struct IDWriteRenderingParamsVtbl { + BEGIN_INTERFACE + + HRESULT (STDMETHODCALLTYPE *QueryInterface) + (IDWriteRenderingParams *This, REFIID riid, void **ppvObject); + ULONG (STDMETHODCALLTYPE *AddRef)(IDWriteRenderingParams *This); + ULONG (STDMETHODCALLTYPE *Release)(IDWriteRenderingParams *This); + + FLOAT (STDMETHODCALLTYPE *GetGamma) + (IDWriteRenderingParams *This); + FLOAT (STDMETHODCALLTYPE *GetEnhancedContrast) + (IDWriteRenderingParams *This); + FLOAT (STDMETHODCALLTYPE *GetClearTypeLevel) + (IDWriteRenderingParams *This); + int (STDMETHODCALLTYPE *GetPixelGeometry) + (IDWriteRenderingParams *This); + END_INTERFACE +} IDWriteRenderingParamsVtbl; + +interface IDWriteRenderingParams { + CONST_VTBL IDWriteRenderingParamsVtbl* lpVtbl; +}; + +typedef struct IDWriteFontVtbl { + BEGIN_INTERFACE + + HRESULT (STDMETHODCALLTYPE *QueryInterface) + (IDWriteFont *This, REFIID riid, void **ppvObject); + ULONG (STDMETHODCALLTYPE *AddRef)(IDWriteFont *This); + ULONG (STDMETHODCALLTYPE *Release)(IDWriteFont *This); + + EMACS_DWRITE_UNUSED (GetFontFamily); + EMACS_DWRITE_UNUSED (GetWeight); + EMACS_DWRITE_UNUSED (GetStretch); + EMACS_DWRITE_UNUSED (GetStyle); + EMACS_DWRITE_UNUSED (IsSymbolFont); + EMACS_DWRITE_UNUSED (GetFaceNames); + EMACS_DWRITE_UNUSED (GetInformationalStrings); + EMACS_DWRITE_UNUSED (GetSimulations); + + void (STDMETHODCALLTYPE *GetMetrics) + (IDWriteFont *This, DWRITE_FONT_METRICS *metrics); + + EMACS_DWRITE_UNUSED(HasCharacter); + + HRESULT (STDMETHODCALLTYPE *CreateFontFace) + (IDWriteFont *This, IDWriteFontFace **face); + + END_INTERFACE +} IDWriteFontVtbl; + +interface IDWriteFont { + CONST_VTBL IDWriteFontVtbl* lpVtbl; +}; + +typedef struct IDWriteBitmapRenderTargetVtbl { + BEGIN_INTERFACE + + HRESULT (STDMETHODCALLTYPE *QueryInterface) + (IDWriteBitmapRenderTarget *This, REFIID riid, void **ppvObject); + ULONG (STDMETHODCALLTYPE *AddRef)(IDWriteBitmapRenderTarget *This); + ULONG (STDMETHODCALLTYPE *Release)(IDWriteBitmapRenderTarget *This); + + HRESULT (STDMETHODCALLTYPE *DrawGlyphRun) + (IDWriteBitmapRenderTarget *This, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + DWRITE_MEASURING_MODE measuring_mode, + const DWRITE_GLYPH_RUN *glyph_run, + IDWriteRenderingParams *params, + COLORREF textColor, + RECT *blackbox_rect); + + HDC (STDMETHODCALLTYPE *GetMemoryDC)(IDWriteBitmapRenderTarget *This); + + EMACS_DWRITE_UNUSED (GetPixelsPerDip); + + HRESULT (STDMETHODCALLTYPE *SetPixelsPerDip) + (IDWriteBitmapRenderTarget *This, FLOAT pixels_per_dip); + + EMACS_DWRITE_UNUSED (GetCurrentTransform); + EMACS_DWRITE_UNUSED (SetCurrentTransform); + EMACS_DWRITE_UNUSED (GetSize); + EMACS_DWRITE_UNUSED (Resize); + END_INTERFACE +} IDWriteBitmapRenderTargetVtbl; + +interface IDWriteBitmapRenderTarget { + CONST_VTBL IDWriteBitmapRenderTargetVtbl* lpVtbl; +}; + +typedef struct IDWriteBitmapRenderTarget1Vtbl { + BEGIN_INTERFACE + + HRESULT (STDMETHODCALLTYPE *QueryInterface) + (IDWriteBitmapRenderTarget1 *This, REFIID riid, void **ppvObject); + ULONG (STDMETHODCALLTYPE *AddRef) (IDWriteBitmapRenderTarget1 *This); + ULONG (STDMETHODCALLTYPE *Release) (IDWriteBitmapRenderTarget1 *This); + + EMACS_DWRITE_UNUSED (DrawGlyphRun); + EMACS_DWRITE_UNUSED (GetMemoryDC); + EMACS_DWRITE_UNUSED (GetPixelsPerDip); + EMACS_DWRITE_UNUSED (SetPixelsPerDip); + EMACS_DWRITE_UNUSED (GetCurrentTransform); + EMACS_DWRITE_UNUSED (SetCurrentTransform); + EMACS_DWRITE_UNUSED (GetSize); + EMACS_DWRITE_UNUSED (Resize); + EMACS_DWRITE_UNUSED (GetTextAntialiasMode); + + HRESULT (STDMETHODCALLTYPE *SetTextAntialiasMode) + (IDWriteBitmapRenderTarget1 *This, DWRITE_TEXT_ANTIALIAS_MODE mode); + + END_INTERFACE +} IDWriteBitmapRenderTarget1Vtbl; + +interface IDWriteBitmapRenderTarget1 { + CONST_VTBL IDWriteBitmapRenderTarget1Vtbl* lpVtbl; +}; + +typedef struct IDWriteGdiInteropVtbl { + BEGIN_INTERFACE + + HRESULT (STDMETHODCALLTYPE *QueryInterface) + (IDWriteGdiInterop *This, REFIID riid, void **ppvObject); + ULONG (STDMETHODCALLTYPE *AddRef)(IDWriteGdiInterop *This); + ULONG (STDMETHODCALLTYPE *Release)(IDWriteGdiInterop *This); + + HRESULT (STDMETHODCALLTYPE *CreateFontFromLOGFONT) + (IDWriteGdiInterop *This, const LOGFONTW *logfont, + IDWriteFont **font); + + EMACS_DWRITE_UNUSED (ConvertFontToLOGFONT); + EMACS_DWRITE_UNUSED (ConvertFontFaceToLOGFONT); + EMACS_DWRITE_UNUSED (CreateFontFaceFromHdc); + + HRESULT (STDMETHODCALLTYPE *CreateBitmapRenderTarget) + (IDWriteGdiInterop *This, HDC hdc, UINT32 width, UINT32 height, + IDWriteBitmapRenderTarget **target); + END_INTERFACE +} IDWriteGdiInteropVtbl; + +interface IDWriteGdiInterop { + CONST_VTBL IDWriteGdiInteropVtbl* lpVtbl; +}; + +typedef struct IDWriteFactoryVtbl { + BEGIN_INTERFACE + + HRESULT (STDMETHODCALLTYPE *QueryInterface) + (IDWriteFactory *This, REFIID riid, void **ppvObject); + ULONG (STDMETHODCALLTYPE *AddRef)(IDWriteFactory *This); + ULONG (STDMETHODCALLTYPE *Release)(IDWriteFactory *This); + + EMACS_DWRITE_UNUSED (GetSystemFontCollection); + EMACS_DWRITE_UNUSED (CreateCustomFontCollection); + EMACS_DWRITE_UNUSED (RegisterFontCollectionLoader); + EMACS_DWRITE_UNUSED (UnregisterFontCollectionLoader); + EMACS_DWRITE_UNUSED (CreateFontFileReference); + EMACS_DWRITE_UNUSED (CreateCustomFontFileReference); + EMACS_DWRITE_UNUSED (CreateFontFace); + HRESULT (STDMETHODCALLTYPE *CreateRenderingParams) + (IDWriteFactory *This, IDWriteRenderingParams **params); + EMACS_DWRITE_UNUSED (CreateMonitorRenderingParams); + HRESULT (STDMETHODCALLTYPE *CreateCustomRenderingParams) + (IDWriteFactory *This, FLOAT gamma, FLOAT enhancedContrast, + FLOAT cleartype_level, DWRITE_PIXEL_GEOMETRY geometry, + DWRITE_RENDERING_MODE mode, IDWriteRenderingParams **params); + EMACS_DWRITE_UNUSED (RegisterFontFileLoader); + EMACS_DWRITE_UNUSED (UnregisterFontFileLoader); + EMACS_DWRITE_UNUSED (CreateTextFormat); + EMACS_DWRITE_UNUSED (CreateTypography); + HRESULT (STDMETHODCALLTYPE *GetGdiInterop) + (IDWriteFactory *This, IDWriteGdiInterop **gdi_interop); + EMACS_DWRITE_UNUSED (CreateTextLayout); + EMACS_DWRITE_UNUSED (CreateGdiCompatibleTextLayout); + EMACS_DWRITE_UNUSED (CreateEllipsisTrimmingSign); + EMACS_DWRITE_UNUSED (CreateTextAnalyzer); + EMACS_DWRITE_UNUSED (CreateNumberSubstitution); + EMACS_DWRITE_UNUSED (CreateGlyphRunAnalysis); + END_INTERFACE +} IDWriteFactoryVtbl; + +interface IDWriteFactory { CONST_VTBL IDWriteFactoryVtbl* lpVtbl; }; + +typedef struct IDWriteColorGlyphRunEnumeratorVtbl { + BEGIN_INTERFACE + + HRESULT (STDMETHODCALLTYPE *QueryInterface) + (IDWriteColorGlyphRunEnumerator *This, REFIID riid, void **ppvObject); + ULONG (STDMETHODCALLTYPE *AddRef)(IDWriteColorGlyphRunEnumerator *This); + ULONG (STDMETHODCALLTYPE *Release)(IDWriteColorGlyphRunEnumerator *This); + + HRESULT (STDMETHODCALLTYPE *MoveNext)( + IDWriteColorGlyphRunEnumerator *This, + WINBOOL *hasRun); + + HRESULT (STDMETHODCALLTYPE *GetCurrentRun)( + IDWriteColorGlyphRunEnumerator *This, + const DWRITE_COLOR_GLYPH_RUN **run); + + END_INTERFACE +} IDWriteColorGlyphRunEnumeratorVtbl; + +interface IDWriteColorGlyphRunEnumerator { + CONST_VTBL IDWriteColorGlyphRunEnumeratorVtbl* lpVtbl; +}; + +typedef struct IDWriteFactory2Vtbl { + BEGIN_INTERFACE + HRESULT (STDMETHODCALLTYPE *QueryInterface) + (IDWriteFactory2 *This, REFIID riid, void **ppvObject); + ULONG (STDMETHODCALLTYPE *AddRef)(IDWriteFactory2 *This); + ULONG (STDMETHODCALLTYPE *Release)(IDWriteFactory2 *This); + EMACS_DWRITE_UNUSED (GetSystemFontCollection); + EMACS_DWRITE_UNUSED (CreateCustomFontCollection); + EMACS_DWRITE_UNUSED (RegisterFontCollectionLoader); + EMACS_DWRITE_UNUSED (UnregisterFontCollectionLoader); + EMACS_DWRITE_UNUSED (CreateFontFileReference); + EMACS_DWRITE_UNUSED (CreateCustomFontFileReference); + EMACS_DWRITE_UNUSED (CreateFontFace); + EMACS_DWRITE_UNUSED (CreateRenderingParams); + EMACS_DWRITE_UNUSED (CreateMonitorRenderingParams); + EMACS_DWRITE_UNUSED (CreateCustomRenderingParams); + EMACS_DWRITE_UNUSED (RegisterFontFileLoader); + EMACS_DWRITE_UNUSED (UnregisterFontFileLoader); + EMACS_DWRITE_UNUSED (CreateTextFormat); + EMACS_DWRITE_UNUSED (CreateTypography); + EMACS_DWRITE_UNUSED (GetGdiInterop); + EMACS_DWRITE_UNUSED (CreateTextLayout); + EMACS_DWRITE_UNUSED (CreateGdiCompatibleTextLayout); + EMACS_DWRITE_UNUSED (CreateEllipsisTrimmingSign); + EMACS_DWRITE_UNUSED (CreateTextAnalyzer); + EMACS_DWRITE_UNUSED (CreateNumberSubstitution); + EMACS_DWRITE_UNUSED (CreateGlyphRunAnalysis); + + EMACS_DWRITE_UNUSED (GetEudcFontCollection); + EMACS_DWRITE_UNUSED (IDWriteFactory1_CreateCustomRenderingParams); + + EMACS_DWRITE_UNUSED (GetSystemFontFallback); + EMACS_DWRITE_UNUSED (CreateFontFallbackBuilder); + HRESULT (STDMETHODCALLTYPE *TranslateColorGlyphRun) + (IDWriteFactory2 *This, + FLOAT originX, + FLOAT originY, + const DWRITE_GLYPH_RUN *run, + void *rundescr, + DWRITE_MEASURING_MODE mode, + void *transform, + UINT32 palette_index, + IDWriteColorGlyphRunEnumerator **colorlayers); + + EMACS_DWRITE_UNUSED (IDWriteFactory2_CreateCustomRenderingParams); + EMACS_DWRITE_UNUSED (IDWriteFactory2_CreateGlyphRunAnalysis); + END_INTERFACE +} IDWriteFactory2Vtbl; + +interface IDWriteFactory2 { + CONST_VTBL IDWriteFactory2Vtbl* lpVtbl; +}; +#else /* ifndef MINGW_W64 */ +# include +#endif + +/* Values to use for DirectWrite rendering. */ +#define MEASURING_MODE DWRITE_MEASURING_MODE_NATURAL +#define RENDERING_MODE DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC +#define ANTIALIAS_MODE DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE + +#define RELEASE_COM(i) (i)->lpVtbl->Release (i) + +/* Global variables for DirectWrite. */ +static bool direct_write_available = false; +static IDWriteFactory *dwrite_factory; +static IDWriteFactory2 *dwrite_factory2; +static IDWriteGdiInterop *gdi_interop; +static IDWriteRenderingParams *rendering_params; + +static bool +verify_hr (HRESULT hr, const char *msg) +{ + if (FAILED (hr)) + { + DebPrint (("DirectWrite HRESULT failed: (%d) %s\n", hr, msg)); + eassert (SUCCEEDED (hr)); + return false; + } + return true; +} + +/* Gets a IDWriteFontFace from a struct font (its HFONT). Returns the + font size in points. It may fail to get a DirectWrite font, and face + will be NULL on return. This happens for some fonts like Courier. + + Never call Release on the result, as it is cached for reuse on the + struct font. */ +static float +get_font_face (struct font *infont, IDWriteFontFace **face) +{ + HRESULT hr; + LOGFONTW logfont; + IDWriteFont *font; + + struct uniscribe_font_info *uniscribe_font = + (struct uniscribe_font_info *) infont; + + /* Check the cache. */ + *face = uniscribe_font->dwrite_cache; + if (*face) + return uniscribe_font->dwrite_font_size; + + GetObjectW (FONT_HANDLE(infont), sizeof (LOGFONTW), &logfont); + + hr = gdi_interop->lpVtbl->CreateFontFromLOGFONT (gdi_interop, + (const LOGFONTW *) &logfont, + &font); + + if (!verify_hr (hr, "Failed to CreateFontFromLOGFONT")) + { + uniscribe_font->dwrite_skip_font = true; + *face = NULL; + return 0.0; + } + + hr = font->lpVtbl->CreateFontFace (font, face); + RELEASE_COM (font); + if (!verify_hr (hr, "Failed to create DWriteFontFace")) + { + uniscribe_font->dwrite_skip_font = true; + *face = NULL; + return 0.0; + } + + /* Cache this FontFace. */ + uniscribe_font->dwrite_font_size = abs (logfont.lfHeight); + uniscribe_font->dwrite_cache = *face; + + return abs (logfont.lfHeight); +} + +void +w32_dwrite_free_cached_face (void *cache) +{ + if (cache) + RELEASE_COM ((IDWriteFontFace *) cache); +} + +static float +convert_metrics_sz (int sz, float font_size, int units_per_em) +{ + return (float) sz * font_size / units_per_em; +} + +/* Does not fill in the ascent and descent fields of metrics. */ +static bool +text_extents_internal (IDWriteFontFace *dwrite_font_face, + float font_size, const unsigned *code, + int nglyphs, struct font_metrics *metrics) +{ + HRESULT hr; + + DWRITE_FONT_METRICS dwrite_font_metrics; + dwrite_font_face->lpVtbl->GetMetrics (dwrite_font_face, + &dwrite_font_metrics); + + UINT16 *indices = alloca (nglyphs * sizeof (UINT16)); + for (int i = 0; i < nglyphs; i++) + indices[i] = code[i]; + + DWRITE_GLYPH_METRICS* gmetrics = + alloca (nglyphs * sizeof (DWRITE_GLYPH_METRICS)); + + hr = dwrite_font_face->lpVtbl->GetGdiCompatibleGlyphMetrics (dwrite_font_face, + font_size, + 1.0, + NULL, + TRUE, + indices, + nglyphs, + gmetrics, + false); + if (!verify_hr (hr, "Failed to GetGdiCompatibleGlyphMetrics")) + return false; + + float width = 0; + int du_per_em = dwrite_font_metrics.designUnitsPerEm; + + for (int i = 0; i < nglyphs; i++) + { + float advance = + convert_metrics_sz (gmetrics[i].advanceWidth, font_size, du_per_em); + + width += advance; + + float lbearing = + round (convert_metrics_sz (gmetrics[i].leftSideBearing, font_size, + du_per_em)); + float rbearing = + round (advance - + convert_metrics_sz (gmetrics[i].rightSideBearing, + font_size, du_per_em)); + if (i == 0) + { + metrics->lbearing = lbearing; + metrics->rbearing = rbearing; + } + if (metrics->lbearing > lbearing) + metrics->lbearing = lbearing; + if (metrics->rbearing < rbearing) + metrics->rbearing = rbearing; + } + metrics->width = round(width); + + return true; +} + +unsigned +w32_dwrite_encode_char (struct font *font, int c) +{ + HRESULT hr; + IDWriteFontFace *dwrite_font_face; + UINT16 index; + + get_font_face (font, &dwrite_font_face); + if (dwrite_font_face == NULL) + return FONT_INVALID_CODE; + hr = dwrite_font_face->lpVtbl->GetGlyphIndices (dwrite_font_face, + &c, 1, &index); + if (verify_hr (hr, "Failed to GetGlyphIndices")) + { + if (index == 0) + return FONT_INVALID_CODE; + return index; + } + ((struct uniscribe_font_info *) font)->dwrite_skip_font = true; + return FONT_INVALID_CODE; +} + +bool +w32_dwrite_text_extents (struct font *font, const unsigned *code, int nglyphs, + struct font_metrics *metrics) +{ + IDWriteFontFace *dwrite_font_face; + + float font_size = get_font_face (font, &dwrite_font_face); + + if (dwrite_font_face == NULL) + return false; + + /* We can get fonts with a size of 0. GDI handles this by using a default + size. We do the same. */ + if (font_size <= 0.0f) + font_size = FRAME_LINE_HEIGHT (XFRAME (selected_frame)); + + metrics->ascent = font->ascent; + metrics->descent = font->descent; + + return text_extents_internal (dwrite_font_face, font_size, code, nglyphs, + metrics); +} + +/* Never call Release on the value returned by this function, as it is + reused. */ +static IDWriteBitmapRenderTarget * +get_bitmap_render_target (HDC hdc, int width, int height) +{ + HRESULT hr; + static IDWriteBitmapRenderTarget *brt = NULL; + static SIZE size = {0, 0}; + + if (brt) + { + /* Check if we need to make a bigger one. */ + if (width <= size.cx && height <= size.cy) + return brt; + RELEASE_COM (brt); + } + + if (width > size.cx) + size.cx = width; + if (height > size.cy) + size.cy = height; + + hr = gdi_interop->lpVtbl->CreateBitmapRenderTarget (gdi_interop, + hdc, + size.cx, size.cy, + &brt); + if (!verify_hr (hr, "Failed to CreateBitmapRenderTarget")) + return NULL; + + /* We handle high dpi displays by incresing font size, so override + PixelsPerDip. */ + brt->lpVtbl->SetPixelsPerDip (brt, 1.0); + + /* The SetTextAntialiasMode method is only available in + IDWriteBitmapRenderTarget1. */ + IDWriteBitmapRenderTarget1 *brt1; + hr = brt->lpVtbl->QueryInterface (brt, + &IID_IDWriteBitmapRenderTarget1, + (void **) &brt1); + /* This error should not happen, but is not catastrofic */ + if (verify_hr (hr, "Failed to QueryInterface for IDWriteBitmapRenderTarget1")) + { + brt1->lpVtbl->SetTextAntialiasMode (brt1, ANTIALIAS_MODE); + RELEASE_COM (brt1); + } + + return brt; +} + +void +w32_initialize_direct_write (void) +{ + direct_write_available = false; + HMODULE direct_write = LoadLibrary ("dwrite.dll"); + if (!direct_write) + return; + + /* This is only used here, no need to define it globally. */ + typedef HRESULT (WINAPI *DWCreateFactory) (DWRITE_FACTORY_TYPE f, REFIID r, IUnknown** u); + + DWCreateFactory dw_create_factory + = (DWCreateFactory) get_proc_addr (direct_write, + "DWriteCreateFactory"); + + if (!dw_create_factory) + { + FreeLibrary (direct_write); + return; + } + + HRESULT hr = dw_create_factory (DWRITE_FACTORY_TYPE_SHARED, + &IID_IDWriteFactory, + (IUnknown **) &dwrite_factory); + if (FAILED (hr)) + { + DebPrint (("DirectWrite HRESULT failed: (%d) CreateFactory\n", hr)); + FreeLibrary (direct_write); + eassert (SUCCEEDED (hr)); + return; + } + + hr = dwrite_factory->lpVtbl->QueryInterface (dwrite_factory, + &IID_IDWriteFactory2, + (void **) &dwrite_factory2); + + if (FAILED (hr)) + { + DebPrint (("DirectWrite HRESULT failed: (%d) QueryInterface IDWriteFactory2\n", hr)); + RELEASE_COM (dwrite_factory); + FreeLibrary (direct_write); + eassert (SUCCEEDED (hr)); + return; + } + + hr = dwrite_factory->lpVtbl->GetGdiInterop (dwrite_factory, + &gdi_interop); + if (FAILED (hr)) + { + DebPrint (("DirectWrite HRESULT failed: (%d) GetGdiInterop\n", hr)); + RELEASE_COM (dwrite_factory); + RELEASE_COM (dwrite_factory2); + FreeLibrary (direct_write); + eassert (SUCCEEDED (hr)); + return; + } + + IDWriteRenderingParams *def; + + hr = dwrite_factory->lpVtbl->CreateRenderingParams (dwrite_factory, + &def); + if (FAILED (hr)) + { + DebPrint (("DirectWrite HRESULT failed: (%d) CreateRenderingParams\n", hr)); + RELEASE_COM (dwrite_factory); + RELEASE_COM (dwrite_factory2); + RELEASE_COM (gdi_interop); + FreeLibrary (direct_write); + eassert (SUCCEEDED (hr)); + return; + } + + /* range: [0.0, 1.0] */ + float enhanced_contrast = def->lpVtbl->GetEnhancedContrast (def); + /* range: [0.0, 1.0] */ + float clear_type_level = def->lpVtbl->GetClearTypeLevel (def); + /* range: (0.0, 256.0] */ + float gamma = 1.4; /* def->lpVtbl->GetGamma(def); */ + + hr = dwrite_factory->lpVtbl->CreateCustomRenderingParams (dwrite_factory, + gamma, + enhanced_contrast, + clear_type_level, + def->lpVtbl->GetPixelGeometry(def), + RENDERING_MODE, + &rendering_params); + + RELEASE_COM (def); + + if (FAILED (hr)) + { + DebPrint (("DirectWrite HRESULT failed: (%d) CreateCustomRenderingParams\n", hr)); + RELEASE_COM (dwrite_factory); + RELEASE_COM (dwrite_factory2); + RELEASE_COM (gdi_interop); + FreeLibrary (direct_write); + eassert (SUCCEEDED (hr)); + return; + } + + direct_write_available = true; + + w32_inhibit_dwrite = false; +} + +bool +w32_dwrite_draw (HDC hdc, int x, int y, unsigned *glyphs, int len, + COLORREF color, struct font *font) +{ + HRESULT hr; + IDWriteFontFace *dwrite_font_face; + + struct uniscribe_font_info *uniscribe_font = + (struct uniscribe_font_info *) font; + + /* What we get as y is the baseline position. */ + y -= font->ascent; + + float font_size = get_font_face (font, &dwrite_font_face); + if (dwrite_font_face == NULL) + return false; + + struct font_metrics metrics; + if (!text_extents_internal (dwrite_font_face, font_size, glyphs, len, + &metrics)) + { + uniscribe_font->dwrite_skip_font = true; + return false; + } + + int bitmap_width = metrics.width + metrics.rbearing; + int bitmap_height = font->ascent + font->descent; + + /* We never release this, get_bitmap_render_target reuses it. */ + IDWriteBitmapRenderTarget *bitmap_render_target = + get_bitmap_render_target (hdc, bitmap_width, bitmap_height); + + /* If this fails, completely disable DirectWrite. */ + if (bitmap_render_target == NULL) + { + direct_write_available = false; + return false; + } + + /* This DC can't be released. */ + HDC text_dc = bitmap_render_target->lpVtbl->GetMemoryDC + (bitmap_render_target); + + /* Copy the background pixel to the render target bitmap. */ + BitBlt (text_dc, 0, 0, bitmap_width, bitmap_height, hdc, x, y, SRCCOPY); + + UINT16 *indices = alloca (len * sizeof (UINT16)); + + for (int i = 0; i < len; i++) + indices[i] = glyphs[i]; + + FLOAT *advances = alloca (len * sizeof (FLOAT)); + + for (int i = 0; i < len; i++) + { + if (!text_extents_internal (dwrite_font_face, font_size, glyphs + i, 1, + &metrics)) + { + uniscribe_font->dwrite_skip_font = true; + return false; + } + advances[i] = metrics.width; + } + + DWRITE_GLYPH_RUN glyph_run; + glyph_run.fontFace = dwrite_font_face; + glyph_run.fontEmSize = font_size; + glyph_run.glyphIndices = indices; + glyph_run.glyphCount = len; + glyph_run.isSideways = false; + glyph_run.bidiLevel = 0; + glyph_run.glyphOffsets = NULL; + glyph_run.glyphAdvances = advances; + + IDWriteColorGlyphRunEnumerator *layers; + /* This call will tell us if we hace to handle any color glyph. */ + hr = dwrite_factory2->lpVtbl->TranslateColorGlyphRun (dwrite_factory2, + 0, font->ascent, + &glyph_run, + NULL, + MEASURING_MODE, + NULL, + 0, + &layers); + + /* No color. Just draw the GlyphRun. */ + if (hr == DWRITE_E_NOCOLOR) + bitmap_render_target->lpVtbl->DrawGlyphRun (bitmap_render_target, + 0, font->ascent, + MEASURING_MODE, + &glyph_run, + rendering_params, + color, + NULL); + else + { + /* If there were color glyphs, layers contains a list of GlyphRun + with a color and a position for each. We draw them + individually. */ + if (!verify_hr (hr, "Failed at TranslateColorGlyphRun")) + { + uniscribe_font->dwrite_skip_font = true; + RELEASE_COM (layers); + return false; + } + for (;;) { + HRESULT hr; + BOOL more_layers; + const DWRITE_COLOR_GLYPH_RUN *layer; + + hr = layers->lpVtbl->MoveNext (layers, &more_layers); + if (!verify_hr (hr, "Failed at MoveNext")) + { + uniscribe_font->dwrite_skip_font = true; + RELEASE_COM (layers); + return false; + } + if (!more_layers) + break; + hr = layers->lpVtbl->GetCurrentRun (layers, &layer); + if (!verify_hr (hr, "Failed at GetCurrentRun")) + { + uniscribe_font->dwrite_skip_font = true; + RELEASE_COM (layers); + return false; + } + hr = bitmap_render_target->lpVtbl->DrawGlyphRun + (bitmap_render_target, + layer->baselineOriginX, + layer->baselineOriginY, + MEASURING_MODE, + &layer->glyphRun, + rendering_params, + RGB (layer->runColor.r * 255, + layer->runColor.g * 255, + layer->runColor.b * 255), + NULL); + if (!verify_hr (hr, "Failed at GetCurrentRun")) + { + uniscribe_font->dwrite_skip_font = true; + RELEASE_COM (layers); + return false; + } + } + RELEASE_COM (layers); + } + + /* Finally, copy the rendered text back to the original DC. */ + BitBlt (hdc, x, y, bitmap_width, bitmap_height, text_dc, 0, 0, SRCCOPY); + + return true; +} + +/* Returns true if DirectWrite is to be used: + - It is available. + - The font is handled by HarfBuzz. + - w32-inhibit-dwrite is false. + - The font has not been marked after a failed DirectWrite operation. + */ +bool +w32_use_direct_write (struct w32font_info *w32font) +{ +#ifdef HAVE_HARFBUZZ + return direct_write_available + && w32font->font.driver == &harfbuzz_font_driver + && !w32_inhibit_dwrite + && !((struct uniscribe_font_info *) w32font)->dwrite_skip_font; +#else + return false; +#endif +} diff --git a/src/w32font.c b/src/w32font.c index efb42d80336..05e5a067f20 100644 --- a/src/w32font.c +++ b/src/w32font.c @@ -452,6 +452,10 @@ w32font_text_extents (struct font *font, const unsigned *code, memset (metrics, 0, sizeof (struct font_metrics)); + if (w32_use_direct_write (w32_font)) + if (w32_dwrite_text_extents (font, code, nglyphs, metrics)) + return; + for (i = 0, first = true; i < nglyphs; i++) { struct w32_metric_cache *char_metric; @@ -706,22 +710,31 @@ w32font_draw (struct glyph_string *s, int from, int to, int i; for (i = 0; i < len; i++) - { - WCHAR c = s->char2b[from + i] & 0xFFFF; - ExtTextOutW (s->hdc, x + i, y, options, NULL, &c, 1, NULL); - } + if (!w32_use_direct_write (w32font) || + !w32_dwrite_draw (s->hdc, x, y, s->char2b + from, 1, + GetTextColor(s->hdc), s->font)) + { + WCHAR c = s->char2b[from + i] & 0xFFFF; + ExtTextOutW (s->hdc, x + i, y, options, NULL, &c, 1, NULL); + } } else { - /* The number of glyphs in a glyph_string cannot be larger than - the maximum value of the 'used' member of a glyph_row, so we - are OK using alloca here. */ - eassert (len <= SHRT_MAX); - WCHAR *chars = alloca (len * sizeof (WCHAR)); - int j; - for (j = 0; j < len; j++) - chars[j] = s->char2b[from + j] & 0xFFFF; - ExtTextOutW (s->hdc, x, y, options, NULL, chars, len, NULL); + if (!w32_use_direct_write (w32font) || + !w32_dwrite_draw (s->hdc, x, y, + s->char2b + from, len, GetTextColor(s->hdc), + s->font)) + { + /* The number of glyphs in a glyph_string cannot be larger than + the maximum value of the 'used' member of a glyph_row, so we + are OK using alloca here. */ + eassert (len <= SHRT_MAX); + WCHAR *chars = alloca (len * sizeof (WCHAR)); + int j; + for (j = 0; j < len; j++) + chars[j] = s->char2b[from + j] & 0xFFFF; + ExtTextOutW (s->hdc, x, y, options, NULL, chars, len, NULL); + } } /* Restore clip region. */ diff --git a/src/w32font.h b/src/w32font.h index 3f780c1d866..68d28156125 100644 --- a/src/w32font.h +++ b/src/w32font.h @@ -57,6 +57,26 @@ #define W32METRIC_FAIL 2 HFONT hfont; }; +/* Extension of w32font_info used by Uniscribe and HarfBuzz backends. */ +struct uniscribe_font_info +{ + struct w32font_info w32_font; + /* 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; + /* This is used by DirectWrite to store the FontFace object. + DirectWrite works on top of the HarfBuzz backend, modifying some + calls. If there are problems manipulating this font, + dwrite_skip_font is set to true. Future operations will not use + DirectWrite and fall back to the HarfBuzz backend. */ + void *dwrite_cache; + float dwrite_font_size; + bool dwrite_skip_font; +}; + /* Macros for getting OS specific information from a font struct. */ #define FONT_HANDLE(f) (((struct w32font_info *)(f))->hfont) #define FONT_TEXTMETRIC(f) (((struct w32font_info *)(f))->metrics) @@ -84,6 +104,16 @@ #define CACHE_BLOCKSIZE 128 Lisp_Object intern_font_name (char *); +/* Function prototypes for DirectWrite. */ +void w32_initialize_direct_write (void); +bool w32_use_direct_write (struct w32font_info *w32font); +bool w32_dwrite_draw (HDC hdc, int x, int y, unsigned *glyphs, int len, + COLORREF color, struct font *font ); +bool w32_dwrite_text_extents (struct font *font, const unsigned *code, + int nglyphs, struct font_metrics *metrics); +unsigned w32_dwrite_encode_char (struct font *font, int c); +void w32_dwrite_free_cached_face(void *cache); + extern void globals_of_w32font (void); #endif diff --git a/src/w32uniscribe.c b/src/w32uniscribe.c index b77bf56b8cf..98e072ce2be 100644 --- a/src/w32uniscribe.c +++ b/src/w32uniscribe.c @@ -44,18 +44,6 @@ #define _WIN32_WINNT 0x0600 #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; - /* 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; /* EnumFontFamiliesEx callback. */ @@ -200,6 +188,9 @@ uniscribe_open (struct frame *f, Lisp_Object font_entity, int pixel_size) /* Initialize the cache for this font. */ uniscribe_font->cache = NULL; + uniscribe_font->dwrite_cache = NULL; + + uniscribe_font->dwrite_skip_font = false; /* Uniscribe and HarfBuzz backends use glyph indices. */ uniscribe_font->w32_font.glyph_idx = ETO_GLYPH_INDEX; @@ -221,6 +212,7 @@ uniscribe_close (struct font *font) = (struct uniscribe_font_info *) font; #ifdef HAVE_HARFBUZZ + w32_dwrite_free_cached_face (uniscribe_font->dwrite_cache); if (uniscribe_font->w32_font.font.driver == &harfbuzz_font_driver && uniscribe_font->cache) hb_font_destroy ((hb_font_t *) uniscribe_font->cache); @@ -1372,6 +1364,17 @@ 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); + + if (w32_use_direct_write (&uniscribe_font->w32_font)) + { + unsigned encoded = w32_dwrite_encode_char (font, c); + + /* The call to w32_dwrite_encode_char may fail, disabling + DirectWrite for this font. So check again. */ + if (w32_use_direct_write (&uniscribe_font->w32_font)) + return encoded; + } + hb_font_t *hb_font = uniscribe_font->cache; /* First time we use this font with HarfBuzz, create the hb_font_t @@ -1510,6 +1513,9 @@ w32hb_get_variation_glyphs (struct font *font, int c, unsigned variations[256]) void syms_of_w32uniscribe (void) { + DEFVAR_BOOL ("w32-inhibit-dwrite", w32_inhibit_dwrite, + doc: /* If t, don't use DirectWrite. */); + pdumper_do_now_and_after_load (syms_of_w32uniscribe_for_pdumper); } @@ -1624,5 +1630,8 @@ syms_of_w32uniscribe_for_pdumper (void) harfbuzz_font_driver.combining_capability = hbfont_combining_capability; harfbuzz_font_driver.begin_hb_font = w32hb_begin_font; register_font_driver (&harfbuzz_font_driver, NULL); + + w32_initialize_direct_write (); + #endif /* HAVE_HARFBUZZ */ } -- 2.35.1.windows.2