vcl/source/font/PhysicalFontFace.cxx |  133 ++++++++++++++++++++++++++++++++++-
 1 file changed, 132 insertions(+), 1 deletion(-)

New commits:
commit 46a382e244bd81c85029a994d9aa2ba891e9686c
Author:     Khaled Hosny <[email protected]>
AuthorDate: Fri Feb 20 11:47:53 2026 +0200
Commit:     Khaled Hosny <[email protected]>
CommitDate: Sat Feb 21 07:43:02 2026 +0100

    Generate PostScript name for variable font arbitrary instances
    
    Implements Adobe Technical Note #5902: “Generating PostScript Names for 
Fonts
    Using OpenType Font Variations”
    
https://adobe-type-tools.github.io/font-tech-notes/pdfs/5902.AdobePSNameGeneration.pdf
    
    Change-Id: I0f83bd9565c3bafa5e82265535dbbed4c95fc571
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199864
    Reviewed-by: Khaled Hosny <[email protected]>
    Tested-by: Jenkins

diff --git a/vcl/source/font/PhysicalFontFace.cxx 
b/vcl/source/font/PhysicalFontFace.cxx
index abd874ede001..40b5efa2848c 100644
--- a/vcl/source/font/PhysicalFontFace.cxx
+++ b/vcl/source/font/PhysicalFontFace.cxx
@@ -351,6 +351,72 @@ OUString GetNamedInstancePSName(const PhysicalFontFace& 
rFontFace,
 
     return OUString();
 }
+
+// Implements Adobe Technical Note #5902: “Generating PostScript Names for 
Fonts
+// Using OpenType Font Variations”
+// 
https://adobe-type-tools.github.io/font-tech-notes/pdfs/5902.AdobePSNameGeneration.pdf
+OUString GenerateVariableFontPSName(const PhysicalFontFace& rFace,
+                                    const std::vector<hb_variation_t>& 
rVariations)
+{
+    hb_face_t* pHbFace = rFace.GetHbFace();
+    OUString aPrefix = rFace.GetName(NAME_ID_VARIATIONS_PS_PREFIX);
+    if (aPrefix.isEmpty())
+    {
+        aPrefix = rFace.GetName(NAME_ID_TYPOGRAPHIC_FAMILY);
+        if (aPrefix.isEmpty())
+            aPrefix = rFace.GetName(NAME_ID_FONT_FAMILY);
+    }
+
+    if (aPrefix.isEmpty())
+        return OUString();
+
+    OUStringBuffer aName;
+    for (sal_Int32 i = 0; i < aPrefix.getLength(); ++i)
+    {
+        auto c = aPrefix[i];
+        if (rtl::isAsciiAlphanumeric(c))
+            aName.append(c);
+    }
+
+    if (auto nIndex = GetNamedInstanceIndex(pHbFace, rVariations))
+    {
+        aName.append('-');
+        auto nPSNameID = 
hb_ot_var_named_instance_get_subfamily_name_id(pHbFace, *nIndex);
+        OUString aSubFamilyName = 
rFace.GetName(static_cast<NameID>(nPSNameID));
+        for (sal_Int32 i = 0; i < aSubFamilyName.getLength(); ++i)
+        {
+            auto c = aSubFamilyName[i];
+            if (rtl::isAsciiAlphanumeric(c))
+                aName.append(c);
+        }
+    }
+    else
+    {
+        for (const auto& rVariation : rVariations)
+        {
+            hb_ot_var_axis_info_t info;
+            if (hb_ot_var_find_axis_info(pHbFace, rVariation.tag, &info))
+            {
+                if (rVariation.value == info.default_value)
+                    continue;
+                char aTag[5];
+                hb_tag_to_string(rVariation.tag, aTag);
+                aName.append("_" + OUString::number(rVariation.value)
+                             + o3tl::trim(OUString::createFromAscii(aTag)));
+            }
+        }
+    }
+
+    if (aName.getLength() > 127)
+    {
+        auto nIndex = aName.indexOf(u'-') + 1;
+        auto aHash = 
static_cast<sal_uInt32>(aName.copy(nIndex).makeStringAndClear().hashCode());
+        aName.truncate(nIndex);
+        aName.append(OUString::number(aHash, 16).toAsciiUpperCase() + "...");
+    }
+
+    return aName.makeStringAndClear();
+}
 }
 
 // These are “private” HarfBuzz metrics tags, they are supported by not exposed
@@ -422,7 +488,11 @@ bool 
PhysicalFontFace::CreateFontSubset(std::vector<sal_uInt8>& rOutBuffer,
 
     // If this is a named instance and it has a PostScript name, we want to 
use it.
     if (bIsVariableFont)
+    {
         rInfo.m_aPSName = GetNamedInstancePSName(*this, rVariations);
+        if (rInfo.m_aPSName.isEmpty() && !rVariations.empty())
+            rInfo.m_aPSName = GenerateVariableFontPSName(*this, rVariations);
+    }
     if (rInfo.m_aPSName.isEmpty())
         rInfo.m_aPSName = GetName(NAME_ID_POSTSCRIPT_NAME);
 
commit 23dd80ffe5c73a082acd1ce1bee4edd0fe870b67
Author:     Khaled Hosny <[email protected]>
AuthorDate: Fri Feb 20 15:08:08 2026 +0200
Commit:     Khaled Hosny <[email protected]>
CommitDate: Sat Feb 21 07:42:49 2026 +0100

    Generate PostScript name for variable font named instances
    
    Named instances can have a PostScript name which we should use instead
    of the font’s PostScript name which would be the same for all instances.
    
    Change-Id: I9343fcb0cefabcfd26ceaefca5059db73da7d18c
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199634
    Reviewed-by: Khaled Hosny <[email protected]>
    Tested-by: Jenkins

diff --git a/vcl/source/font/PhysicalFontFace.cxx 
b/vcl/source/font/PhysicalFontFace.cxx
index 8569a35ecb85..abd874ede001 100644
--- a/vcl/source/font/PhysicalFontFace.cxx
+++ b/vcl/source/font/PhysicalFontFace.cxx
@@ -37,6 +37,7 @@
 #include <comphelper/scopeguard.hxx>
 
 #include <string_view>
+#include <optional>
 
 #include <hb-ot.h>
 #include <hb-subset.h>
@@ -297,6 +298,61 @@ bool 
PhysicalFontFace::GetFontCapabilities(vcl::FontCapabilities& rFontCapabilit
     return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
 }
 
+namespace
+{
+std::optional<unsigned int> GetNamedInstanceIndex(hb_face_t* pHbFace,
+                                                  const 
std::vector<hb_variation_t>& rVariations)
+{
+    unsigned int nAxes = hb_ot_var_get_axis_count(pHbFace);
+    std::vector<hb_ot_var_axis_info_t> aAxisInfos(nAxes);
+    hb_ot_var_get_axis_infos(pHbFace, 0, &nAxes, aAxisInfos.data());
+
+    // Pre-fill the coordinates with axes defaults
+    std::vector<float> aCurrentCoords(nAxes);
+    for (unsigned int i = 0; i < nAxes; ++i)
+        aCurrentCoords[i] = aAxisInfos[i].default_value;
+
+    // Then update coordinates with the current variations
+    hb_ot_var_axis_info_t info;
+    for (const auto& rVariation : rVariations)
+    {
+        if (hb_ot_var_find_axis_info(pHbFace, rVariation.tag, &info))
+            aCurrentCoords[info.axis_index] = rVariation.value;
+    }
+
+    // Find a named instance that matches the current coordinates and return 
its index
+    unsigned int nInstances = hb_ot_var_get_named_instance_count(pHbFace);
+    std::vector<float> aInstanceCoords(nAxes);
+    for (unsigned int i = 0; i < nInstances; ++i)
+    {
+        unsigned int nInstanceAxes = nAxes;
+        if (hb_ot_var_named_instance_get_design_coords(pHbFace, i, 
&nInstanceAxes,
+                                                       aInstanceCoords.data())
+            && aInstanceCoords == aCurrentCoords)
+        {
+            return i;
+        }
+    }
+
+    return std::nullopt;
+}
+
+OUString GetNamedInstancePSName(const PhysicalFontFace& rFontFace,
+                                const std::vector<hb_variation_t>& rVariations)
+{
+    hb_face_t* pHbFace = rFontFace.GetHbFace();
+    auto nIndex = GetNamedInstanceIndex(pHbFace, rVariations);
+    if (nIndex)
+    {
+        auto nPSNameID = 
hb_ot_var_named_instance_get_postscript_name_id(pHbFace, *nIndex);
+        if (nPSNameID != HB_OT_NAME_ID_INVALID)
+            return rFontFace.GetName(static_cast<NameID>(nPSNameID));
+    }
+
+    return OUString();
+}
+}
+
 // These are “private” HarfBuzz metrics tags, they are supported by not exposed
 // in the public header. They are safe to use, HarfBuzz just does not want to
 // advertise them.
@@ -363,7 +419,12 @@ bool 
PhysicalFontFace::CreateFontSubset(std::vector<sal_uInt8>& rOutBuffer,
         return false;
 
     // Fill FontSubsetInfo
-    rInfo.m_aPSName = GetName(NAME_ID_POSTSCRIPT_NAME);
+
+    // If this is a named instance and it has a PostScript name, we want to 
use it.
+    if (bIsVariableFont)
+        rInfo.m_aPSName = GetNamedInstancePSName(*this, rVariations);
+    if (rInfo.m_aPSName.isEmpty())
+        rInfo.m_aPSName = GetName(NAME_ID_POSTSCRIPT_NAME);
 
     auto nUPEM = UnitsPerEm();
 

Reply via email to