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'
