sw/qa/extras/uiwriter/data/embed-unrestricted1.odt |binary
 sw/qa/extras/uiwriter/data/embed-unrestricted2.odt |binary
 sw/qa/extras/uiwriter/uiwriter10.cxx               |   52 ++++++++++++++++++++
 sw/qa/inc/swmodeltestbase.hxx                      |    2 
 sw/qa/unit/swmodeltestbase.cxx                     |    4 +
 vcl/inc/win/salgdi.h                               |    2 
 vcl/win/gdi/salfont.cxx                            |   54 ---------------------
 vcl/win/gdi/salgdi_gdiplus.cxx                     |   24 +++++++++
 8 files changed, 83 insertions(+), 55 deletions(-)

New commits:
commit e9bdc5e28993c12dfcdd8837e5fabd89f114dcd3
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Fri Aug 8 11:20:34 2025 +0500
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Fri Aug 8 17:04:56 2025 +0200

    tdf#167849: unit test
    
    After commit 87ef004115d07c16fe4899b3d423cc16a12a0fce (tdf#167849: don't
    release embedded fonts in WinSalGraphics::ClearDevFontCache, 2025-08-07).
    
    Change-Id: I4b2ff7e8c3a9bd71befe537d76308e18968c99c8
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189145
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>

diff --git a/sw/qa/extras/uiwriter/data/embed-unrestricted1.odt 
b/sw/qa/extras/uiwriter/data/embed-unrestricted1.odt
new file mode 100644
index 000000000000..168d10051048
Binary files /dev/null and b/sw/qa/extras/uiwriter/data/embed-unrestricted1.odt 
differ
diff --git a/sw/qa/extras/uiwriter/data/embed-unrestricted2.odt 
b/sw/qa/extras/uiwriter/data/embed-unrestricted2.odt
new file mode 100644
index 000000000000..f3f52a82ddc5
Binary files /dev/null and b/sw/qa/extras/uiwriter/data/embed-unrestricted2.odt 
differ
diff --git a/sw/qa/extras/uiwriter/uiwriter10.cxx 
b/sw/qa/extras/uiwriter/uiwriter10.cxx
index dc7af3eb6687..dad4bda7b3d1 100644
--- a/sw/qa/extras/uiwriter/uiwriter10.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter10.cxx
@@ -9,6 +9,8 @@
 
 #include <swmodeltestbase.hxx>
 
+#include <com/sun/star/awt/Toolkit.hpp>
+#include <com/sun/star/awt/XFontMappingUse.hpp>
 #include <com/sun/star/chart/XChartDocument.hpp>
 #include <com/sun/star/chart2/XChartDocument.hpp>
 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
@@ -2096,6 +2098,56 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest5, testTdf166012)
                          getProperty<short>(getRun(getParagraph(1), 1), 
u"CharScriptHint"_ustr));
 }
 
+#if !defined(MACOSX)
+CPPUNIT_TEST_FIXTURE(SwUiWriterTest5, testTdf167849)
+{
+    auto assertNoSubstitution = [](const uno::Reference<awt::XFontMappingUse>& 
xUse, int pass) {
+        const auto trackingData = xUse->finishTrackingFontMappingUse();
+        for (const auto& element : trackingData)
+        {
+            for (const auto& used : element.usedFonts)
+            {
+                OUString message = "Pass " + OUString::number(pass) + ": " + 
element.originalFont
+                                   + " substituted with " + used;
+                CPPUNIT_ASSERT_MESSAGE(message.toUtf8().getStr(),
+                                       used.startsWith(element.originalFont));
+            }
+        }
+        xUse->startTrackingFontMappingUse();
+    };
+
+    auto xFontMappingUse = 
awt::Toolkit::create(comphelper::getProcessComponentContext())
+                               .queryThrow<awt::XFontMappingUse>();
+    xFontMappingUse->startTrackingFontMappingUse();
+
+    // Given two documents with embedded fonts, that will not require 
substitution, if present:
+
+    // Load the first document
+    createSwDoc("embed-unrestricted1.odt");
+    // At this point, 'Manbow Solid' embedded font is loaded
+    std::swap(mxComponent, mxComponent2); // keep it from unloading upon the 
next load
+
+    assertNoSubstitution(xFontMappingUse, 1);
+
+    // Load the second document
+    createSwDoc("embed-unrestricted2.odt");
+    // At this point, 'Unsteady Oversteer' font is also loaded
+
+    assertNoSubstitution(xFontMappingUse, 2);
+
+    // Re-layout both documents; both fonts must still be loaded
+    calcLayout(true);
+    std::swap(mxComponent, mxComponent2);
+    calcLayout(true);
+
+    // Without the fix, it would fail with
+    // - Pass 3: Manbow Solid substituted with Liberation Serif/Regular
+    // because loading the second document unregistered the embedded font from 
the first one.
+    assertNoSubstitution(xFontMappingUse, 3);
+    xFontMappingUse->finishTrackingFontMappingUse();
+}
+#endif
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/qa/inc/swmodeltestbase.hxx b/sw/qa/inc/swmodeltestbase.hxx
index 7bac6f7dd076..c77a0d212141 100644
--- a/sw/qa/inc/swmodeltestbase.hxx
+++ b/sw/qa/inc/swmodeltestbase.hxx
@@ -98,7 +98,7 @@ protected:
         CPPUNIT_FAIL( "verify method must be overridden" );
     }
 
-    void calcLayout();
+    void calcLayout(bool bRecalc = false);
 
     /// Get the body text of the whole document.
     OUString getBodyText() const;
diff --git a/sw/qa/unit/swmodeltestbase.cxx b/sw/qa/unit/swmodeltestbase.cxx
index 69aab12a38ba..523c5186d358 100644
--- a/sw/qa/unit/swmodeltestbase.cxx
+++ b/sw/qa/unit/swmodeltestbase.cxx
@@ -89,8 +89,10 @@ void SwModelTestBase::dumpLayout(SwDoc* pDoc)
     xmlFreeTextWriter(pXmlWriter);
 }
 
-void SwModelTestBase::calcLayout()
+void SwModelTestBase::calcLayout(bool bRecalc)
 {
+    if (bRecalc)
+        
getSwDoc()->getIDocumentLayoutAccess().GetCurrentViewShell()->Reformat();
     getSwDoc()->getIDocumentLayoutAccess().GetCurrentViewShell()->CalcLayout();
 }
 
commit e2e1c5cb10cc33878495fa767d0c2a21459f924d
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Fri Aug 8 16:06:05 2025 +0500
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Fri Aug 8 17:04:48 2025 +0200

    Use Gdiplus::PrivateFontCollection to obtain family name from TTF
    
    This avoids the path length limitation of CreateScalableFontResourceW,
    which manifests e.g. in CI - see 
https://ci.libreoffice.org/job/gerrit_windows/203249/console:
    
      warn:vcl.fonts:3512:11352:vcl/win/gdi/salfont.cxx:962: 
CreateScalableFontResource failed for C:      
warn:vcl.fonts:3512:11352:vcl/win/gdi/salfont.cxx:994: error extracting font 
family from 
file:///C:/cygwin64/home/tdf/jenkins/workspace/gerrit_windows/workdir/CppunitTest/sw_uiwriter10.test.user/user/temp/embeddedfonts/fromdocs/Manbow
 Solid0.ttf
    
    The implementation is put into vcl/win/gdi/salgdi_gdiplus.cxx, because
    the GDI+ headers create conflicts when put into vcl/win/gdi/salfont.cxx;
    the file name of salgdi_gdiplus.cxx looked relevant (although I don't
    see any actual GDI+-related code there).
    
    By the way, this new implementation seems to also perform better: in a
    test, it finished in ~3 ms in a dbgutil build, while the previous code
    took about 8 ms in an optimized build.
    
    Change-Id: I95b09847e2fcba13489050d3a61a56776d8c8e47
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189186
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Tested-by: Jenkins

diff --git a/vcl/inc/win/salgdi.h b/vcl/inc/win/salgdi.h
index 20e28c7c03bb..ed6ef27d45a1 100644
--- a/vcl/inc/win/salgdi.h
+++ b/vcl/inc/win/salgdi.h
@@ -281,6 +281,8 @@ private:
     // local helpers
     void DrawTextLayout(const GenericSalLayout&, HDC, bool bUseDWrite, bool 
bRenderingModeNatural);
 
+    static OUString getFontFamilyNameFromTTF(const OUString& url);
+
 protected:
     std::unique_ptr<SalGraphicsImpl> mpImpl;
     WinSalGraphicsImplBase * mWinSalGraphicsImplBase;
diff --git a/vcl/win/gdi/salfont.cxx b/vcl/win/gdi/salfont.cxx
index 7f5252adfbd9..2ca870aad98d 100644
--- a/vcl/win/gdi/salfont.cxx
+++ b/vcl/win/gdi/salfont.cxx
@@ -933,62 +933,10 @@ void ImplReleaseTempFonts(SalData& rSalData)
     }
 }
 
-static OUString lcl_GetFontFamilyName(std::u16string_view rFontFileURL)
-{
-    // Create temporary file name
-    OUString aTempFileURL;
-    if (osl::File::E_None != osl::File::createTempFile(nullptr, nullptr, 
&aTempFileURL))
-        return OUString();
-    osl::File::remove(aTempFileURL);
-    OUString aResSystemPath;
-    osl::FileBase::getSystemPathFromFileURL(aTempFileURL, aResSystemPath);
-
-    // Create font resource file (.fot)
-    // There is a limit of 127 characters for the full path passed via 
lpszFile, so we have to
-    // split the font URL and pass it as two parameters. As a result we can't 
use
-    // CreateScalableFontResource for renaming, as it now expects the font in 
the system path.
-    // But it's still good to use it for family name extraction, we're 
currently after.
-    // BTW: it doesn't help to prefix the lpszFile with \?\ to support larger 
paths.
-    // TODO: use TTLoadEmbeddedFont (needs an EOT as input, so we have to add 
a header to the TTF)
-    // TODO: forward the EOT from the AddTempDevFont call side, if VCL 
supports it
-    INetURLObject aTTFUrl(rFontFileURL);
-    // GetBase() strips the extension
-    OUString aFilename = 
aTTFUrl.GetLastName(INetURLObject::DecodeMechanism::WithCharset);
-    if (!CreateScalableFontResourceW(0, o3tl::toW(aResSystemPath.getStr()),
-            o3tl::toW(aFilename.getStr()), 
o3tl::toW(aTTFUrl.GetPath().getStr())))
-    {
-        sal_uInt32 nError = GetLastError();
-        SAL_WARN("vcl.fonts", "CreateScalableFontResource failed for " << 
aResSystemPath << " "
-                              << aFilename << " " << aTTFUrl.GetPath() << " " 
<< nError);
-        return OUString();
-    }
-
-    // Open and read the font resource file
-    osl::File aFotFile(aTempFileURL);
-    if (osl::FileBase::E_None != aFotFile.open(osl_File_OpenFlag_Read))
-        return OUString();
-
-    sal_uInt64  nBytesRead = 0;
-    char        aBuffer[4096];
-    aFotFile.read( aBuffer, sizeof( aBuffer ), nBytesRead );
-    // clean up temporary resource file
-    aFotFile.close();
-    osl::File::remove(aTempFileURL);
-
-    // retrieve font family name from byte offset 0x4F6
-    static const sal_uInt64 nNameOfs = 0x4F6;
-    sal_uInt64 nPos = nNameOfs;
-    for (; (nPos < nBytesRead) && (aBuffer[nPos] != 0); nPos++);
-    if (nPos >= nBytesRead || (nPos == nNameOfs))
-        return OUString();
-
-    return OUString(aBuffer + nNameOfs, nPos - nNameOfs, 
osl_getThreadTextEncoding());
-}
-
 bool WinSalGraphics::AddTempDevFont(vcl::font::PhysicalFontCollection* 
pFontCollection,
                                     const OUString& rFontFileURL, const 
OUString& rFontName)
 {
-    OUString aFontFamily = lcl_GetFontFamilyName(rFontFileURL);
+    OUString aFontFamily = getFontFamilyNameFromTTF(rFontFileURL);
     if (aFontFamily.isEmpty())
     {
         SAL_WARN("vcl.fonts", "error extracting font family from " << 
rFontFileURL);
diff --git a/vcl/win/gdi/salgdi_gdiplus.cxx b/vcl/win/gdi/salgdi_gdiplus.cxx
index 45b169b480fc..ba64c92e59d8 100644
--- a/vcl/win/gdi/salgdi_gdiplus.cxx
+++ b/vcl/win/gdi/salgdi_gdiplus.cxx
@@ -26,6 +26,13 @@
 
 #include "gdiimpl.hxx"
 
+#include <prewin.h>
+#include <gdiplus.h>
+#include <postwin.h>
+
+#include <osl/file.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+
 void WinSalGraphics::drawPolyPolygon(
     const basegfx::B2DHomMatrix& rObjectToDevice,
     const basegfx::B2DPolyPolygon& rPolyPolygon,
@@ -85,4 +92,21 @@ bool WinSalGraphics::hasFastDrawTransformedBitmap() const
     return mpImpl->hasFastDrawTransformedBitmap();
 }
 
+// static
+OUString WinSalGraphics::getFontFamilyNameFromTTF(const OUString& url)
+{
+    OUString localFileName;
+    osl::FileBase::getSystemPathFromFileURL(url, localFileName);
+    Gdiplus::PrivateFontCollection collection;
+    collection.AddFontFile(o3tl::toW(localFileName.getStr()));
+    if (collection.GetFamilyCount() == 0)
+        return {};
+    Gdiplus::FontFamily aFamily;
+    if (INT ret; collection.GetFamilies(1, &aFamily, &ret) != 
Gdiplus::Status::Ok || ret < 1)
+        return {};
+    WCHAR familyName[LF_FACESIZE]{};
+    aFamily.GetFamilyName(familyName, LANG_NEUTRAL);
+    return OUString(o3tl::toU(familyName));
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to