configure.ac                                    |    5 
 external/cairo/Library_cairo.mk                 |    1 
 external/fontconfig/StaticLibrary_fontconfig.mk |    6 
 external/fontconfig/configs/wnt_fonts.conf      |    2 
 external/fontconfig/windowsfonts.patch          |  202 +++++++++++++++++++++++-
 external/freetype/StaticLibrary_freetype.mk     |    2 
 external/freetype/UnpackedTarball_freetype.mk   |    1 
 external/freetype/windows.patch                 |   23 ++
 vcl/Library_vcl.mk                              |    1 
 9 files changed, 239 insertions(+), 4 deletions(-)

New commits:
commit 80f8fe8479914266492f90bd79916d0181927a06
Author:     Tor Lillqvist <[email protected]>
AuthorDate: Tue Dec 30 16:25:06 2025 +0200
Commit:     Tor Lillqvist <[email protected]>
CommitDate: Sat Jan 17 21:49:57 2026 +0100

    Make also fonts installed from the Microsoft Store show up in CODA-W
    
    This was much more complicated than expected. Most of that thanks to
    the inscrutability of fontconfig.
    
    Fonts from the Microsoft Store get installed in a separate folder per
    product, under the WindowsApps folder. Each such font product folder
    contains one or several TrueType font files, and metadata files, and
    preview images, etc.
    
    Add a C++ source file to fontconfig that uses DirectWrite to enumerate
    the available fonts. Take only those with a source type of
    DWRITE_FONT_SOURCE_TYPE_APPX_PACKAGE into account. Other fonts are
    found as before, looking in the system fonts folder (typically
    C:\Windows\Fonts) and in the per-user fonts folder (typically
    C:\Users\username\AppData\Local\Microsoft\Windows\Fonts).
    
    This C++ code requires headers from the winrt tree in the Windows SDK,
    so add that to SOLARINC.
    
    Sadly, thanks to the way fontconfig works (scanning directories for
    font files), I need to let it scan the folders of font products from
    the Microsoft Store to find the font files, even if the DirectWrite
    API would already give us the font file names directly. But I didn't
    want to modify fontconfig too much, so oh well.
    
    There was something weird in how fontconfig calculates the hash for
    directory names to be used in its cache files. It ended up creating
    more and more cache files for the same directories each time CODA-W
    was run. I could not understand what was going on, but in any case,
    just making sure that FcConfigMapFontPath() and FcConfigMapSalt()
    don't do anything on Windows seems to help.
    
    Also change the way freetype is built so that only the special
    FT_Done_MM_Var is exported, not all public functions. This was
    necessary because for some unknown reason, when linking the vcl DLL I
    started getting errors for the freetype symbols that happen to be
    linked into also the cairo DLL. If we eventually can switch to
    building all of core statically for CODA-W this problem will go away.
    
    Also stop defining FT_DEBUG_LOGGING, such logging will not show up
    anywhere for end-users, and fixing problems in freetype on Windows
    will have to be done by reproducing and debugging in the VS debugger
    anyway.
    
    There is still much to do to make fontconfig (and probably freetype
    and cairo) work really correctly on Windows. The most obvious thing is
    that the file name handling is broken. This is perhaps understandable
    as the port of that code to Windows is from over 20 years ago, I
    think.
    
    It works for file and folder names in ASCII, but most likely not for
    others. At least the user profile directory, under which per-user font
    files are stored, will definitely have non-ASCII in many cases. We
    should modify the code to use <tools/UnixWrappers.h>, so that all file
    system access is done using proper wide character (UTF-16)
    APIs. Pathnames that these libraries handle will then be in UTF-8. The
    opendir() etc emulation in fontconfig should also be replaced by
    adding such to UnixWrappers.h instead.
    
    Signed-off-by: Tor Lillqvist <[email protected]>
    Change-Id: I4bc6e32dcc32ff74b79679edacee1aecebd774c7
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/196336
    Reviewed-by: Stephan Bergmann <[email protected]>
    Tested-by: Tor Lillqvist <[email protected]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197507
    Tested-by: Jenkins
    Reviewed-by: Tor Lillqvist <[email protected]>

diff --git a/configure.ac b/configure.ac
index 173a26b3bd55..5a3c91dedbd4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7372,6 +7372,11 @@ the  Windows SDK are installed.])
         elif test -d "$WINDOWS_SDK_HOME_unix/Include/$winsdklibsubdir/um"; then
             SOLARINC="$SOLARINC 
-I$WINDOWS_SDK_HOME/Include/$winsdklibsubdir/um 
-I$WINDOWS_SDK_HOME/Include/$winsdklibsubdir/shared"
         fi
+        if test "$USE_HEADLESS_CODE" = "TRUE"; then
+            if test -d 
"$WINDOWS_SDK_HOME_unix/Include/$winsdklibsubdir/winrt"; then
+                SOLARINC="$SOLARINC 
-I$WINDOWS_SDK_HOME/Include/$winsdklibsubdir/winrt"
+            fi
+        fi
     fi
 
     dnl TODO: solenv/bin/modules/installer/windows/msiglobal.pm wants to use a
diff --git a/external/cairo/Library_cairo.mk b/external/cairo/Library_cairo.mk
index eef60531c1c5..c127b5ad37ec 100644
--- a/external/cairo/Library_cairo.mk
+++ b/external/cairo/Library_cairo.mk
@@ -49,6 +49,7 @@ $(eval $(call gb_Library_use_externals,cairo,\
 ))
 
 $(eval $(call gb_Library_use_system_win32_libs,cairo,\
+    dwrite \
     ole32 \
     shell32 \
 ))
diff --git a/external/fontconfig/StaticLibrary_fontconfig.mk 
b/external/fontconfig/StaticLibrary_fontconfig.mk
index b06e8c3ed18c..c4928102d3ce 100644
--- a/external/fontconfig/StaticLibrary_fontconfig.mk
+++ b/external/fontconfig/StaticLibrary_fontconfig.mk
@@ -66,4 +66,10 @@ $(eval $(call 
gb_StaticLibrary_add_generated_cobjects,fontconfig,\
        ) \
 ))
 
+$(eval $(call gb_StaticLibrary_add_generated_exception_objects,fontconfig,\
+       $(addprefix UnpackedTarball/fontconfig/src/, \
+               fcdwrite \
+       ) \
+))
+
 # vim: set noet sw=4 ts=4:
diff --git a/external/fontconfig/configs/wnt_fonts.conf 
b/external/fontconfig/configs/wnt_fonts.conf
index 2aad205cc774..3b6549838941 100644
--- a/external/fontconfig/configs/wnt_fonts.conf
+++ b/external/fontconfig/configs/wnt_fonts.conf
@@ -26,6 +26,7 @@
 
        <dir>WINDOWSFONTDIR</dir>
        <dir>WINDOWSUSERFONTDIR</dir>
+       <dir>WINDOWSAPPXFONTDIRS</dir>
 
 <!--
   Accept deprecated 'mono' alias, replacing it with 'monospace'
diff --git a/external/fontconfig/windowsfonts.patch 
b/external/fontconfig/windowsfonts.patch
index 96b60bfbe35d..173a3455a28a 100644
--- a/external/fontconfig/windowsfonts.patch
+++ b/external/fontconfig/windowsfonts.patch
@@ -20,9 +20,46 @@ well.
  #  define FC_SEARCH_PATH_SEPARATOR ';'
  #  define FC_DIR_SEPARATOR         '\'
  #  define FC_DIR_SEPARATOR_S       "\"
+--- src/fccfg.c
++++ src/fccfg.c
+@@ -658,6 +658,9 @@
+ FcConfigMapFontPath (FcConfig      *config,
+                      const FcChar8 *path)
+ {
++#ifdef _WIN32
++    return 0;
++#else
+     FcStrList     *list;
+     FcChar8       *dir;
+     const FcChar8 *map, *rpath;
+@@ -687,12 +687,16 @@
+       retval[len] = 0;
+     }
+     return retval;
++#endif
+ }
+ 
+ const FcChar8 *
+ FcConfigMapSalt (FcConfig      *config,
+                  const FcChar8 *path)
+ {
++#ifdef _WIN32
++    return 0;
++#else
+     FcStrList *list;
+     FcChar8   *dir;
+ 
+@@ -707,6 +707,7 @@
+       return NULL;
+ 
+     return FcStrTripleThird (dir);
++#endif
+ }
+ 
+ FcBool
 --- src/fcxml.c
 +++ src/fcxml.c
-@@ -58,11 +58,9 @@
+@@ -58,11 +58,10 @@
  
  #ifdef _WIN32
  #  include <mbstring.h>
@@ -33,10 +70,19 @@ well.
 -pfnSHGetFolderPathA          pSHGetFolderPathA = NULL;
 -static void
 -_ensureWin32GettersReady ();
++extern char* appx_fonts_get_dirs();
  #endif
  
  static void
-@@ -1330,23 +1330,29 @@
+@@ -1264,6 +1264,7 @@
+ #endif
+     FcChar8  *parent = NULL, *retval = NULL;
+     FcStrSet *e = NULL;
++    FcBool dontInsertRetval = FcFalse;
+ 
+     if (prefix) {
+       if (FcStrCmp (prefix, (const FcChar8 *)"xdg") == 0) {
+@@ -1330,23 +1330,42 @@
        strcat ((char *)path, "\..\share\fonts");
      } else if (strcmp ((const char *)path, "WINDOWSUSERFONTDIR") == 0) {
        path = buffer;
@@ -73,9 +119,31 @@ well.
 +      path = buffer;
 +      WideCharToMultiByte(CP_UTF8, 0, wpath, wcslen(wpath), path, 
size_needed, NULL, NULL);
 +      CoTaskMemFree (wpath);
++    } else if (strcmp ((const char *)path, "WINDOWSAPPXFONTDIRS") == 0) {
++      char *appxFontDirs = appx_fonts_get_dirs();
++      char *semicolon;
++      char *p = appxFontDirs;
++      e = FcStrSetCreate ();
++      while ((semicolon = strchr(p, ';')) != NULL) {
++          *semicolon = '
++          FcStrSetAdd (e, (FcChar8*)p);
++          p = semicolon + 1;
++      }
++      free(appxFontDirs);
++      path = NULL;
++       dontInsertRetval = FcTrue;
      } else {
        if (!prefix) {
            if (!FcStrIsAbsoluteFilename (path) && path[0] != '~')
+@@ -1388,7 +1388,7 @@
+           e->strs[i] = s;
+       }
+     }
+-    if (!FcStrSetInsert (e, retval, 0)) {
++    if (!dontInsertRetval && !FcStrSetInsert (e, retval, 0)) {
+       FcStrSetDestroy (e);
+       e = NULL;
+     }
 @@ -2288,7 +2288,7 @@
        char   szFPath[MAX_PATH + 1];
        size_t len;
@@ -119,3 +187,133 @@ well.
 
  #define __fcxml__
  #include "fcaliastail.h"
+--- src/fcdwrite.cxx
++++ src/fcdwrite.cxx
+@@ -0,0 +1,127 @@
++#undef _WIN32_WINNT
++#define _WIN32_WINNT 0x0A00
++
++#include <windows.h>
++#include <dwrite_3.h>
++#include <wrl/client.h>
++
++#include <cstring>
++#include <iostream>
++#include <set>
++#include <string>
++#include <vector>
++
++using Microsoft::WRL::ComPtr;
++
++static std::string wide_string_to_string(const std::wstring& wide_string)
++{
++    if (wide_string.empty()) {
++      return "";
++    }
++
++    const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, 
wide_string.data(), (int)wide_string.size(), nullptr, 0, nullptr, nullptr);
++    if (size_needed <= 0) {
++      std::wcerr << L"WideCharToMultiByte() failed: " + 
std::to_wstring(size_needed) << std::endl;
++      return "";
++    }
++
++    std::string result(size_needed, 0);
++    WideCharToMultiByte(CP_UTF8, 0, wide_string.data(), 
(int)wide_string.size(), result.data(), size_needed, nullptr, nullptr);
++    return result;
++}
++
++extern "C" char* appx_fonts_get_dirs()
++{
++    // We assume COM has been initialised by the surrounding app
++    HRESULT hr;
++    ComPtr<IDWriteFactory7> factory;
++
++    hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
++                            __uuidof(IDWriteFactory7),
++                            
reinterpret_cast<IUnknown**>(factory.GetAddressOf()));
++
++    if (FAILED(hr)) {
++      std::wcerr << L"Failed to create DirectWrite factory
";
++      return nullptr;
++    }
++
++    // Get the unified system font set (covers system, user, Store fonts)
++    ComPtr<IDWriteFontSet2> fontSet;
++    hr = factory->GetSystemFontSet(FALSE, &fontSet);
++    if (FAILED(hr)) {
++      std::wcerr << L"GetSystemFontSet failed
";
++      return nullptr;
++    }
++
++    ComPtr<IDWriteFontSet3> fontSet3;
++    hr = fontSet.As(&fontSet3);
++    if (FAILED(hr)) {
++      std::wcerr << L"Could not get IDWriteFontSet3 from IDWriteFontSet2
";
++      return nullptr;
++    }
++
++    std::set<std::wstring> folders;
++    UINT32 count = fontSet->GetFontCount();
++
++    for (UINT32 i = 0; i < count; ++i) {
++      auto sourceType = fontSet3->GetFontSourceType(i);
++
++      // We are interested only in the fonts from the Store, i.e. APPX ones, 
here.
++      if (sourceType != DWRITE_FONT_SOURCE_TYPE_APPX_PACKAGE)
++          continue;
++
++      ComPtr<IDWriteFontFaceReference> faceRef;
++      if (FAILED(fontSet->GetFontFaceReference(i, &faceRef)))
++          continue;
++
++      // Resolve to actual font face to access files
++      ComPtr<IDWriteFontFace3> face;
++      if (FAILED(faceRef->CreateFontFace(&face)))
++          continue;
++
++      UINT32 fileCount = 0;
++      face->GetFiles(&fileCount, nullptr);
++
++      std::vector<ComPtr<IDWriteFontFile>> files(fileCount);
++      face->GetFiles(&fileCount, 
reinterpret_cast<IDWriteFontFile**>(files.data()));
++
++      for (UINT32 f = 0; f < fileCount; ++f) {
++          const void* refKey = nullptr;
++          UINT32 refKeySize = 0;
++
++          files[f]->GetReferenceKey(&refKey, &refKeySize);
++
++          ComPtr<IDWriteFontFileLoader> loader;
++          files[f]->GetLoader(&loader);
++
++          ComPtr<IDWriteLocalFontFileLoader> localLoader;
++          if (SUCCEEDED(loader.As(&localLoader))) {
++              UINT32 pathLen = 0;
++              hr = localLoader->GetFilePathLengthFromKey(refKey, refKeySize, 
&pathLen);
++              if (SUCCEEDED(hr)) {
++                  std::wstring path(pathLen + 1, L'
++                  hr = localLoader->GetFilePathFromKey(refKey, refKeySize, 
path.data(), pathLen + 1);
++
++                  // We actually only want the folders that such
++                  // fonts are in. Fontconfig will, stupidly enough,
++                  // scan the whole folder looking for font files,
++                  // even if we here would already know the font
++                  // files. But I can't be arsed to re-wire
++                  // fontconfig any more than absolutely necessary,
++                  // it is complicated enough as is.
++
++                  if (SUCCEEDED(hr)) {
++                      auto lastBackslash = path.find_last_of(L'\');
++                      if (lastBackslash != std::wstring::npos)
++                          folders.insert(path.substr(0, lastBackslash));
++                  }
++              }
++          }
++      }
++    }
++
++    std::string result;
++    for (const auto& i : folders)
++      result += wide_string_to_string(i) + ";";
++    return _strdup(result.c_str());
++}
diff --git a/external/freetype/StaticLibrary_freetype.mk 
b/external/freetype/StaticLibrary_freetype.mk
index acb9ec6be3ae..11880f0a1118 100644
--- a/external/freetype/StaticLibrary_freetype.mk
+++ b/external/freetype/StaticLibrary_freetype.mk
@@ -26,8 +26,6 @@ $(eval $(call gb_StaticLibrary_add_defs,freetype,\
        -DDLG_STATIC \
        -DZ_PREFIX \
        -DFT2_BUILD_LIBRARY \
-       -DDLL_EXPORT \
-       -DFT_DEBUG_LOGGING \
 ))
 
 $(eval $(call gb_StaticLibrary_add_generated_cobjects,freetype,\
diff --git a/external/freetype/UnpackedTarball_freetype.mk 
b/external/freetype/UnpackedTarball_freetype.mk
index bf0d78120ae8..f7fb7dabb450 100644
--- a/external/freetype/UnpackedTarball_freetype.mk
+++ b/external/freetype/UnpackedTarball_freetype.mk
@@ -15,6 +15,7 @@ $(eval $(call gb_UnpackedTarball_add_patches,freetype,\
        external/freetype/freetype-2.6.5.patch.1 \
        external/freetype/ubsan.patch \
        external/freetype/freetype-fd-hack.patch.0 \
+       external/freetype/windows.patch \
 ))
 
 # Enable FreeType's FT_DEBUG_LOGGING at least in --enable-dbgutil 
DISABLE_DYNLOADING builds (in
diff --git a/external/freetype/windows.patch b/external/freetype/windows.patch
new file mode 100644
index 000000000000..b1b79232223b
--- /dev/null
+++ b/external/freetype/windows.patch
@@ -0,0 +1,23 @@
+When building for CODA-W, we build freetype as a static library, and
+we don't define DLL_EXPORT as there in general is no need to export
+freetype symbols from the (dynamkic) library that freetype gets linked
+into, cairo.dll and vcllo.dll (or mergedlo.dll). But there is one
+exception: FT_Done_MM_Var *must* be exported as the code in
+dlFT_Done_MM_Var() in vcl/unx/generic/glyphs/freetype_glyphcache.cxx
+wants to look it up and call it.
+
+--- include/freetype/ftmm.h
++++ include/freetype/ftmm.h
+@@ -400,7 +400,12 @@
+    * @return:
+    *   FreeType error code.  0~means success.
+    */
++#if defined( _WIN32 ) && defined( FT2_BUILD_LIBRARY ) && !defined( DLL_EXPORT 
)
++  /* FT_EXPORT is defined as a no-op but for CODA-W we *must* export 
FT_Done_MM_Var */
++   __declspec( dllexport ) FT_Error
++#else
+   FT_EXPORT( FT_Error )
++#endif 
+   FT_Done_MM_Var( FT_Library   library,
+                   FT_MM_Var   *amaster );
+
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index e1cecc1b4594..e0d7cd43f172 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -844,6 +844,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
 ))
 
 $(eval $(call gb_Library_use_system_win32_libs,vcl,\
+    dwrite \
     ole32 \
     setupapi \
     shell32 \
commit fd304eaa937ec9b28f181c9143ba16d486bf0e45
Author:     Tor Lillqvist <[email protected]>
AuthorDate: Sun Nov 30 19:14:01 2025 +0100
Commit:     Tor Lillqvist <[email protected]>
CommitDate: Sat Jan 17 21:49:46 2026 +0100

    Look for fonts also in WINDOWSUSERFONTDIR
    
    That is where fonts installed by the user goes.
    
    Change-Id: If23c542fee8a832da723d1165d38669b24b2fb57
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197505
    Tested-by: Jenkins
    Reviewed-by: Tor Lillqvist <[email protected]>

diff --git a/external/fontconfig/configs/wnt_fonts.conf 
b/external/fontconfig/configs/wnt_fonts.conf
index 5dbcff5d2902..2aad205cc774 100644
--- a/external/fontconfig/configs/wnt_fonts.conf
+++ b/external/fontconfig/configs/wnt_fonts.conf
@@ -25,6 +25,7 @@
 <!-- Font directory list -->
 
        <dir>WINDOWSFONTDIR</dir>
+       <dir>WINDOWSUSERFONTDIR</dir>
 
 <!--
   Accept deprecated 'mono' alias, replacing it with 'monospace'

Reply via email to