Rebased ref, commits from common ancestor:
commit f63faa7bb06cb56b519b001973e3425216c4539b
Author:     Khaled Hosny <[email protected]>
AuthorDate: Fri Feb 27 03:46:19 2026 +0200
Commit:     Khaled Hosny <[email protected]>
CommitDate: Sun Mar 1 18:32:51 2026 +0200

    tdf#153368: Support optical size for variable fonts, part 7
    
    Enable in Draw/Impress.
    
    WIP.
    
    Change-Id: Icdfa639836efeb7ae8baa9ffadef35b6ccf28646

diff --git a/include/editeng/unoprnms.hxx b/include/editeng/unoprnms.hxx
index 33662cf3ac9f..b5d354d695bf 100644
--- a/include/editeng/unoprnms.hxx
+++ b/include/editeng/unoprnms.hxx
@@ -319,6 +319,7 @@ inline constexpr OUString UNO_NAME_EDIT_CHAR_FONTCHARSET = 
u"CharFontCharSet"_us
 inline constexpr OUString UNO_NAME_EDIT_CHAR_FONTPITCH = u"CharFontPitch"_ustr;
 inline constexpr OUString UNO_NAME_EDIT_CHAR_POSTURE = u"CharPosture"_ustr;
 inline constexpr OUString UNO_NAME_EDIT_CHAR_WEIGHT = u"CharWeight"_ustr;
+inline constexpr OUString UNO_NAME_EDIT_CHAR_OPTICALSIZING = 
u"CharOpticalSizing"_ustr;
 inline constexpr OUString UNO_NAME_EDIT_CHAR_LOCALE = u"CharLocale"_ustr;
 
 inline constexpr OUString UNO_NAME_EDIT_CHAR_HEIGHT_ASIAN = 
u"CharHeightAsian"_ustr;
diff --git a/include/editeng/unotext.hxx b/include/editeng/unotext.hxx
index 37e82293dbb3..65824411a0bd 100644
--- a/include/editeng/unotext.hxx
+++ b/include/editeng/unotext.hxx
@@ -137,7 +137,8 @@ struct SfxItemPropertyMapEntry;
     { UNO_NAME_EDIT_CHAR_RUBY_TEXT,           EE_CHAR_RUBY,           
::cppu::UnoType<OUString>::get(), 0, MID_RUBY_TEXT }, \
     { UNO_NAME_EDIT_CHAR_RUBY_ADJUST,         EE_CHAR_RUBY,           
::cppu::UnoType<sal_Int16>::get(), 0, MID_RUBY_ADJUST }, \
     { UNO_NAME_EDIT_CHAR_RUBY_POSITION,       EE_CHAR_RUBY,           
::cppu::UnoType<sal_Int16>::get(), 0, MID_RUBY_POSITION }, \
-    { UNO_NAME_EDIT_CHAR_SCRIPT_HINT,         EE_CHAR_SCRIPT_HINT,    
::cppu::UnoType<sal_Int16>::get(), 0, MID_SCRIPTHINT }
+    { UNO_NAME_EDIT_CHAR_SCRIPT_HINT,         EE_CHAR_SCRIPT_HINT,    
::cppu::UnoType<sal_Int16>::get(), 0, MID_SCRIPTHINT }, \
+    { UNO_NAME_EDIT_CHAR_OPTICALSIZING,       EE_CHAR_OPTICALSIZING,  
::cppu::UnoType<bool>::get(), 0, 0 }
 
 
 #define SVX_UNOEDIT_FONT_PROPERTIES \
diff --git a/sd/qa/unit/export-tests.cxx b/sd/qa/unit/export-tests.cxx
index 386b42fcdf3f..836d97527479 100644
--- a/sd/qa/unit/export-tests.cxx
+++ b/sd/qa/unit/export-tests.cxx
@@ -2558,6 +2558,74 @@ CPPUNIT_TEST_FIXTURE(SdExportTest, 
testTableBordersTransparancy)
                          Color(ColorTransparency, aBorderLine.Color));
 }
 
+// CharOpticalSizing
+CPPUNIT_TEST_FIXTURE(SdExportTest, testOpticalSizing)
+{
+    createSdImpressDoc();
+
+    uno::Reference<drawing::XDrawPagesSupplier> 
xDrawPagesSupplier(mxComponent, uno::UNO_QUERY);
+    uno::Reference<drawing::XDrawPages> xDrawPages = 
xDrawPagesSupplier->getDrawPages();
+    uno::Reference<drawing::XDrawPage> 
xDrawPage(xDrawPages->insertNewByIndex(0),
+                                                 uno::UNO_SET_THROW);
+    uno::Reference<drawing::XShapes> xShapes(xDrawPage, uno::UNO_QUERY_THROW);
+
+    uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, 
uno::UNO_QUERY);
+    uno::Reference<drawing::XShape> xNewShape(
+        xFactory->createInstance(u"com.sun.star.drawing.TextShape"_ustr), 
uno::UNO_QUERY_THROW);
+    xShapes->add(xNewShape);
+
+    uno::Reference<text::XText> xText(xNewShape, uno::UNO_QUERY_THROW);
+    xText->setString(u"test"_ustr);
+
+    uno::Reference<beans::XPropertySet> xNewShapeProps(xNewShape, 
uno::UNO_QUERY_THROW);
+    uno::Reference<text::XTextRange> const 
xNewParagraph(getParagraphFromShape(0, xNewShapeProps));
+    uno::Reference<text::XTextRange> xNewRun(getRunFromParagraph(0, 
xNewParagraph));
+    uno::Reference<beans::XPropertySet> xNewRunProps(xNewRun, 
uno::UNO_QUERY_THROW);
+
+    // it should be true by default for new documents
+    CPPUNIT_ASSERT_EQUAL(true,
+                         
xNewRunProps->getPropertyValue(u"CharOpticalSizing"_ustr).get<bool>());
+
+    //XXXX
+
+    // Setting it manually should set it in contents
+    xNewRunProps->setPropertyValue(u"CharOpticalSizing"_ustr, uno::Any(false));
+    save(TestFilter::ODP);
+    xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr);
+    assertXPath(pXmlDoc, 
"//style:style/style:text-properties[@loext:font-optical-sizing='none']",
+                1);
+
+    xNewRunProps->setPropertyValue(u"CharOpticalSizing"_ustr, uno::Any(true));
+    save(TestFilter::ODP);
+    pXmlDoc = parseExport(u"content.xml"_ustr);
+    assertXPath(pXmlDoc, 
"//style:style/style:text-properties[@loext:font-optical-sizing='auto']",
+                1);
+
+    saveAndReload(TestFilter::ODP);
+    uno::Reference<beans::XPropertySet> xNewShapeProps2(getShapeFromPage(0, 
1));
+    uno::Reference<text::XTextRange> const xNewParagraph2(
+        getParagraphFromShape(0, xNewShapeProps2));
+    uno::Reference<text::XTextRange> xNewRun2(getRunFromParagraph(0, 
xNewParagraph2));
+    uno::Reference<beans::XPropertySet> xNewRunProps2(xNewRun2, 
uno::UNO_QUERY_THROW);
+    // and it should be true after save-and-reload
+    CPPUNIT_ASSERT_EQUAL(true,
+                         
xNewRunProps2->getPropertyValue(u"CharOpticalSizing"_ustr).get<bool>());
+
+    pXmlDoc = parseExport(u"content.xml"_ustr);
+    assertXPath(pXmlDoc, 
"//style:style/style:text-properties[@loext:font-optical-sizing='auto']",
+                1);
+
+    dispose();
+
+    createSdImpressDoc("odp/transparent_background.odp");
+    uno::Reference<beans::XPropertySet> xShape(getShapeFromPage(1, 0));
+    uno::Reference<text::XTextRange> const xParagraph(getParagraphFromShape(0, 
xShape));
+    uno::Reference<text::XTextRange> xRun(getRunFromParagraph(0, xParagraph));
+    uno::Reference<beans::XPropertySet> xRunProps(xRun, uno::UNO_QUERY_THROW);
+    // but it should be false by default for old documents
+    CPPUNIT_ASSERT_EQUAL(false, 
xRunProps->getPropertyValue(u"CharOpticalSizing"_ustr).get<bool>());
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/core/drawdoc4.cxx b/sd/source/core/drawdoc4.cxx
index 772507eb04b9..a089e446ddb5 100644
--- a/sd/source/core/drawdoc4.cxx
+++ b/sd/source/core/drawdoc4.cxx
@@ -78,6 +78,7 @@
 #include <editeng/udlnitem.hxx>
 #include <editeng/contouritem.hxx>
 #include <editeng/emphasismarkitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <editeng/fontitem.hxx>
 #include <editeng/shdditem.hxx>
 #include <editeng/cmapitem.hxx>
@@ -274,6 +275,7 @@ void SdDrawDocument::CreateLayoutTemplates()
 
     // #i16874# enable kerning by default but only for new documents
     rISet.Put(SvxAutoKernItem(true, EE_CHAR_PAIRKERNING));
+    rISet.Put(SvxOpticalSizingItem(true, EE_CHAR_OPTICALSIZING));
 
     // Bullet
     // BulletItem and BulletFont for title and outline
diff --git a/sd/source/core/stlpool.cxx b/sd/source/core/stlpool.cxx
index 7316a8f6a547..cb9773c2baf0 100644
--- a/sd/source/core/stlpool.cxx
+++ b/sd/source/core/stlpool.cxx
@@ -41,6 +41,7 @@
 #include <editeng/ulspitem.hxx>
 #include <editeng/numitem.hxx>
 #include <editeng/cmapitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <svl/hint.hxx>
 #include <editeng/charreliefitem.hxx>
 #include <editeng/emphasismarkitem.hxx>
@@ -249,6 +250,7 @@ void 
SdStyleSheetPool::CreateLayoutStyleSheets(std::u16string_view rLayoutName,
                 rSet.Put( makeSdrTextAutoGrowHeightItem(false) );
                 // #i16874# enable kerning by default but only for new 
documents
                 rSet.Put( SvxAutoKernItem( true, EE_CHAR_PAIRKERNING ) );
+                rSet.Put(SvxOpticalSizingItem(true, EE_CHAR_OPTICALSIZING));
 
                 vcl::Font f( GetBulletFont() );
                 PutNumBulletItem( pSheet, f );
@@ -359,6 +361,7 @@ void 
SdStyleSheetPool::CreateLayoutStyleSheets(std::u16string_view rLayoutName,
         rTitleSet.Put( SdrTextVertAdjustItem( SDRTEXTVERTADJUST_CENTER ) );
         // #i16874# enable kerning by default but only for new documents
         rTitleSet.Put( SvxAutoKernItem( true, EE_CHAR_PAIRKERNING ) );
+        rTitleSet.Put(SvxOpticalSizingItem(true, EE_CHAR_OPTICALSIZING));
 
         aBulletFont.SetFontSize(Size(0,1552));                  // 44 pt
         PutNumBulletItem( pSheet, aBulletFont );
@@ -405,6 +408,7 @@ void 
SdStyleSheetPool::CreateLayoutStyleSheets(std::u16string_view rLayoutName,
         rSubtitleSet.Put( SdrTextVertAdjustItem( SDRTEXTVERTADJUST_CENTER ) );
         // #i16874# enable kerning by default but only for new documents
         rSubtitleSet.Put( SvxAutoKernItem( true, EE_CHAR_PAIRKERNING ) );
+        rSubtitleSet.Put(SvxOpticalSizingItem(true, EE_CHAR_OPTICALSIZING));
         aSvxLRSpaceItem.SetTextLeft(SvxIndentValue::zero());
         rSubtitleSet.Put(aSvxLRSpaceItem);
 
@@ -454,6 +458,7 @@ void 
SdStyleSheetPool::CreateLayoutStyleSheets(std::u16string_view rLayoutName,
                                      SvxIndentValue::twips(-600.0), 
EE_PARA_LRSPACE));
         // #i16874# enable kerning by default but only for new documents
         rNotesSet.Put( SvxAutoKernItem( true, EE_CHAR_PAIRKERNING ) );
+        rNotesSet.Put(SvxOpticalSizingItem(true, EE_CHAR_OPTICALSIZING));
 
 /* #i35937# */
 
@@ -479,6 +484,7 @@ void 
SdStyleSheetPool::CreateLayoutStyleSheets(std::u16string_view rLayoutName,
         // #i16874# enable kerning by default but only for new documents
         rBackgroundObjectsSet.Put( SvxAutoKernItem( true, EE_CHAR_PAIRKERNING 
) );
         
rBackgroundObjectsSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_BLOCK));
+        rBackgroundObjectsSet.Put(SvxOpticalSizingItem(true, 
EE_CHAR_OPTICALSIZING));
     }
 
     /**************************************************************************
@@ -498,6 +504,7 @@ void 
SdStyleSheetPool::CreateLayoutStyleSheets(std::u16string_view rLayoutName,
         rBackgroundSet.Put(XFillStyleItem(drawing::FillStyle_NONE));
         // #i16874# enable kerning by default but only for new documents
         rBackgroundSet.Put( SvxAutoKernItem( true, EE_CHAR_PAIRKERNING ) );
+        rBackgroundSet.Put(SvxOpticalSizingItem(true, EE_CHAR_OPTICALSIZING));
     }
 
     DBG_ASSERT( !bCheck || !bCreated, "missing layout style sheets detected!" 
);
commit f1d74fb245ee04f40743c81f8f75bf05ac938511
Author:     Khaled Hosny <[email protected]>
AuthorDate: Thu Feb 26 14:28:03 2026 +0200
Commit:     Khaled Hosny <[email protected]>
CommitDate: Sun Mar 1 18:32:51 2026 +0200

    tdf#153368: Support optical size for variable fonts, part 6
    
    Add Optical Font Sizing checkbox to the Position page of Character
    dialog. I’m not sure if this is the best place, but I have no better
    idea.
    
    Change-Id: Ice019da856dbd9f1195941706f26b175bb931c97

diff --git a/cui/source/inc/chardlg.hxx b/cui/source/inc/chardlg.hxx
index e3f1240774f6..ef2b99761410 100644
--- a/cui/source/inc/chardlg.hxx
+++ b/cui/source/inc/chardlg.hxx
@@ -272,6 +272,7 @@ private:
     std::unique_ptr<weld::MetricSpinButton> m_xKerningMF;
     std::unique_ptr<weld::CheckButton> m_xPairKerningBtn;
     std::unique_ptr<weld::CheckButton> m_xNoHyphenationBtn;
+    std::unique_ptr<weld::CheckButton> m_xOpticalSizingBtn;
 
     void                Initialize();
     void                UpdatePreview_Impl( sal_uInt8 nProp, sal_uInt8 
nEscProp, short nEsc );
@@ -284,6 +285,7 @@ private:
     DECL_LINK(KerningModifyHdl_Impl, weld::MetricSpinButton&, void);
     DECL_LINK(ValueChangedHdl_Impl, weld::MetricSpinButton&, void);
     DECL_LINK(ScaleWidthModifyHdl_Impl, weld::MetricSpinButton&, void);
+    DECL_LINK(OpticalSizingHdl_Impl, weld::Toggleable&, void);
     void FontModifyHdl_Impl();
 
 public:
diff --git a/cui/source/tabpages/chardlg.cxx b/cui/source/tabpages/chardlg.cxx
index a1a0130ac6c6..8b86acec9a8b 100644
--- a/cui/source/tabpages/chardlg.cxx
+++ b/cui/source/tabpages/chardlg.cxx
@@ -44,6 +44,7 @@
 #include <editeng/autokernitem.hxx>
 #include <editeng/nhypitem.hxx>
 #include <editeng/colritem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <dialmgr.hxx>
 #include <sfx2/htmlmode.hxx>
 #include <svx/cuicharmap.hxx>
@@ -78,7 +79,8 @@ const WhichRangesContainer 
SvxCharNamePage::pNameRanges(svl::Items<
     SID_ATTR_CHAR_COLOR, SID_ATTR_CHAR_COLOR,
     SID_ATTR_CHAR_LANGUAGE, SID_ATTR_CHAR_LANGUAGE,
     SID_ATTR_CHAR_CJK_FONT, SID_ATTR_CHAR_CJK_WEIGHT,
-    SID_ATTR_CHAR_CTL_FONT, SID_ATTR_CHAR_CTL_WEIGHT
+    SID_ATTR_CHAR_CTL_FONT, SID_ATTR_CHAR_CTL_WEIGHT,
+    SID_ATTR_CHAR_OPTICAL_SIZING, SID_ATTR_CHAR_OPTICAL_SIZING
 >);
 
 const WhichRangesContainer SvxCharEffectsPage::pEffectsRanges(svl::Items<
@@ -373,7 +375,8 @@ namespace
                     const SvxLanguageBox* _pLanguageLB,
                     const FontList* _pFontList,
                     sal_uInt16 _nFontWhich,
-                    sal_uInt16 _nFontHeightWhich)
+                    sal_uInt16 _nFontHeightWhich,
+                    sal_uInt16 _nOpticalSizingWhich)
     {
         Size aSize = _rFont.GetFontSize();
         aSize.setWidth( 0 );
@@ -431,6 +434,10 @@ namespace
         _rFont.SetWeight( aFontMetrics.GetWeightMaybeAskConfig() );
         _rFont.SetItalic( aFontMetrics.GetItalicMaybeAskConfig() );
         _rFont.SetFontSize( aFontMetrics.GetFontSize() );
+        bool bOpticalSizing = false;
+        if (_pPage->GetItemSet().GetItemState(_nOpticalSizingWhich) >= 
SfxItemState::DEFAULT)
+            bOpticalSizing = static_cast<const 
SvxOpticalSizingItem&>(_pPage->GetItemSet().Get(_nOpticalSizingWhich)).GetValue();
+        _rFont.SetOpticalSizing(bOpticalSizing);
 
         return aFontMetrics;
     }
@@ -448,21 +455,21 @@ void SvxCharNamePage::UpdatePreview_Impl()
     FontMetric aWestFontMetric = calcFontMetrics(rFont, this, 
m_xWestFontNameLB.get(),
         m_xWestFontStyleLB.get(), m_xWestFontSizeLB.get(), 
m_xWestFontLanguageLB.get(),
         &rFontList, GetWhich(SID_ATTR_CHAR_FONT),
-        GetWhich(SID_ATTR_CHAR_FONTHEIGHT));
+        GetWhich(SID_ATTR_CHAR_FONTHEIGHT), 
GetWhich(SID_ATTR_CHAR_OPTICAL_SIZING));
 
     m_xWestFontTypeFT->set_label(rFontList.GetFontMapText(aWestFontMetric));
 
     FontMetric aEastFontMetric = calcFontMetrics(rCJKFont, this, 
m_xEastFontNameLB.get(),
         m_xEastFontStyleLB.get(), m_xEastFontSizeLB.get(), 
m_xEastFontLanguageLB.get(),
         &rFontList, GetWhich(SID_ATTR_CHAR_CJK_FONT),
-        GetWhich(SID_ATTR_CHAR_CJK_FONTHEIGHT));
+        GetWhich(SID_ATTR_CHAR_CJK_FONTHEIGHT), 
GetWhich(SID_ATTR_CHAR_OPTICAL_SIZING));
 
     m_xEastFontTypeFT->set_label(rFontList.GetFontMapText(aEastFontMetric));
 
     FontMetric aCTLFontMetric = calcFontMetrics(rCTLFont,
         this, m_xCTLFontNameLB.get(), m_xCTLFontStyleLB.get(), 
m_xCTLFontSizeLB.get(),
         m_xCTLFontLanguageLB.get(), &rFontList, 
GetWhich(SID_ATTR_CHAR_CTL_FONT),
-        GetWhich(SID_ATTR_CHAR_CTL_FONTHEIGHT));
+        GetWhich(SID_ATTR_CHAR_CTL_FONTHEIGHT), 
GetWhich(SID_ATTR_CHAR_OPTICAL_SIZING));
 
     m_xCTLFontTypeFT->set_label(rFontList.GetFontMapText(aCTLFontMetric));
 
@@ -1241,6 +1248,7 @@ bool SvxCharNamePage::FillItemSet( SfxItemSet* rSet )
     bool bModified = FillItemSet_Impl( *rSet, Western );
     bModified |= FillItemSet_Impl( *rSet, Asian );
     bModified |= FillItemSet_Impl( *rSet, Ctl );
+
     return bModified;
 }
 
@@ -2443,6 +2451,7 @@ SvxCharPositionPage::SvxCharPositionPage(weld::Container* 
pPage, weld::DialogCon
     , m_xKerningMF(m_xBuilder->weld_metric_spin_button(u"kerningsb"_ustr, 
FieldUnit::POINT))
     , m_xPairKerningBtn(m_xBuilder->weld_check_button(u"pairkerning"_ustr))
     , m_xNoHyphenationBtn(m_xBuilder->weld_check_button(u"nohyphenation"_ustr))
+    , m_xOpticalSizingBtn(m_xBuilder->weld_check_button(u"opticalsizing"_ustr))
 {
     m_xPreviewWin.reset(new weld::CustomWeld(*m_xBuilder, u"preview"_ustr, 
m_aPreviewWin));
 #ifdef IOS
@@ -2486,6 +2495,7 @@ void SvxCharPositionPage::Initialize()
     m_xFitToLineCB->connect_toggled(LINK(this, SvxCharPositionPage, 
FitToLineHdl_Impl));
     m_xKerningMF->connect_value_changed(LINK(this, SvxCharPositionPage, 
KerningModifyHdl_Impl));
     m_xScaleWidthMF->connect_value_changed(LINK(this, SvxCharPositionPage, 
ScaleWidthModifyHdl_Impl));
+    m_xOpticalSizingBtn->connect_toggled(LINK(this, SvxCharPositionPage, 
OpticalSizingHdl_Impl));
 }
 
 void SvxCharPositionPage::UpdatePreview_Impl( sal_uInt8 nProp, sal_uInt8 
nEscProp, short nEsc )
@@ -2639,6 +2649,20 @@ IMPL_LINK_NOARG(SvxCharPositionPage, 
ScaleWidthModifyHdl_Impl, weld::MetricSpinB
     
m_aPreviewWin.SetFontWidthScale(sal_uInt16(m_xScaleWidthMF->get_value(FieldUnit::PERCENT)));
 }
 
+IMPL_LINK_NOARG(SvxCharPositionPage, OpticalSizingHdl_Impl, weld::Toggleable&, 
void)
+{
+    SvxFont& rFont = GetPreviewFont();
+    SvxFont& rCJKFont = GetPreviewCJKFont();
+    SvxFont& rCTLFont = GetPreviewCTLFont();
+
+    bool bOpticalSizing = m_xOpticalSizingBtn->get_active();
+    rFont.SetOpticalSizing(bOpticalSizing);
+    rCJKFont.SetOpticalSizing(bOpticalSizing);
+    rCTLFont.SetOpticalSizing(bOpticalSizing);
+
+    m_aPreviewWin.Invalidate();
+}
+
 DeactivateRC SvxCharPositionPage::DeactivatePage( SfxItemSet* _pSet )
 {
     if ( _pSet )
@@ -2818,6 +2842,20 @@ void SvxCharPositionPage::Reset( const SfxItemSet* rSet )
     else
         m_xNoHyphenationBtn->set_active(false);
 
+    // Optical Sizing
+    nWhich = GetWhich(SID_ATTR_CHAR_OPTICAL_SIZING);
+    if (rSet->GetItemState(nWhich) >= SfxItemState::DEFAULT)
+    {
+        const SvxOpticalSizingItem& rItem = static_cast<const 
SvxOpticalSizingItem&>(rSet->Get(nWhich));
+        m_xOpticalSizingBtn->set_active(rItem.GetValue());
+    }
+    else
+        m_xOpticalSizingBtn->set_active(false);
+    bool bOpticalSizing = m_xOpticalSizingBtn->get_active();
+    rFont.SetOpticalSizing(bOpticalSizing);
+    rCJKFont.SetOpticalSizing(bOpticalSizing);
+    rCTLFont.SetOpticalSizing(bOpticalSizing);
+
     // Scale Width
     nWhich = GetWhich( SID_ATTR_CHAR_SCALEWIDTH );
     if ( rSet->GetItemState( nWhich ) >= SfxItemState::DEFAULT )
@@ -2897,6 +2935,7 @@ void SvxCharPositionPage::ChangesApplied()
     m_xKerningMF->save_value();
     m_xPairKerningBtn->save_state();
     m_xNoHyphenationBtn->save_state();
+    m_xOpticalSizingBtn->save_state();
 }
 
 bool SvxCharPositionPage::FillItemSet( SfxItemSet* rSet )
@@ -2998,6 +3037,16 @@ bool SvxCharPositionPage::FillItemSet( SfxItemSet* rSet )
     else if ( SfxItemState::DEFAULT == rOldSet.GetItemState( nWhich, false ) )
         rSet->InvalidateItem(nWhich);
 
+    // Optical Sizing
+    nWhich = GetWhich(SID_ATTR_CHAR_OPTICAL_SIZING);
+    if (m_xOpticalSizingBtn->get_state_changed_from_saved())
+    {
+        rSet->Put(SvxOpticalSizingItem(m_xOpticalSizingBtn->get_active(), 
GetWhich(SID_ATTR_CHAR_OPTICAL_SIZING)));
+        bModified = true;
+    }
+    else if (SfxItemState::DEFAULT == rOldSet.GetItemState(nWhich, false))
+        rSet->InvalidateItem(nWhich);
+
     // Scale Width
     nWhich = GetWhich( SID_ATTR_CHAR_SCALEWIDTH );
     if (m_xScaleWidthMF->get_value_changed_from_saved())
diff --git a/cui/uiconfig/ui/positionpage.ui b/cui/uiconfig/ui/positionpage.ui
index a959c7da7ee0..ad8569eeb696 100644
--- a/cui/uiconfig/ui/positionpage.ui
+++ b/cui/uiconfig/ui/positionpage.ui
@@ -499,6 +499,54 @@
         <property name="position">3</property>
       </packing>
     </child>
+    <child>
+      <object class="GtkFrame" id="frame9">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="label-xalign">0</property>
+        <property name="shadow-type">none</property>
+        <child>
+          <object class="GtkBox" id="box9">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="margin-start">12</property>
+            <property name="margin-top">6</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkCheckButton" id="opticalsizing">
+                <property name="label" translatable="yes" 
context="positionpage|opticalsizing">Auto</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
+                <property name="tooltip-text" translatable="yes" 
context="positionpage|opticalsizing">Enable optical font sizing based on font's 
point size for fonts that support it</property>
+                <property name="use-underline">True</property>
+                <property name="draw-indicator">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+        <child type="label">
+          <object class="GtkLabel" id="label25">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="label" translatable="yes" 
context="positionpage|label25">Optical Font Sizing</property>
+            <attributes>
+              <attribute name="weight" value="bold"/>
+            </attributes>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">4</property>
+      </packing>
+    </child>
     <child>
       <object class="GtkScrolledWindow">
         <property name="visible">True</property>
@@ -534,7 +582,7 @@
         <property name="expand">False</property>
         <property name="fill">True</property>
         <property name="pack-type">end</property>
-        <property name="position">4</property>
+        <property name="position">5</property>
       </packing>
     </child>
   </object>
commit 695857a4bf80cf450d54bb740fd4a10a69141a71
Author:     Khaled Hosny <[email protected]>
AuthorDate: Thu Feb 26 22:35:37 2026 +0200
Commit:     Khaled Hosny <[email protected]>
CommitDate: Sun Mar 1 18:32:51 2026 +0200

    tdf#153368: Support optical size for variable fonts, part 5
    
    Support import/export of font-optical-sizing in HTML filter.
    
    Change-Id: Ib45b3152b57e674fc1e0b4b4b2359634909d15a0

diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx 
b/sw/qa/extras/htmlexport/htmlexport.cxx
index 20cfe024500f..85fa29ec4c5d 100644
--- a/sw/qa/extras/htmlexport/htmlexport.cxx
+++ b/sw/qa/extras/htmlexport/htmlexport.cxx
@@ -1651,6 +1651,33 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, 
testReqifNoTypeInUL)
     // Without the accompanying fix in place, this test would have failed
     assertXPathNoAttribute(pXmlDoc, 
"//reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:ul", "type");
 }
+
+CPPUNIT_TEST_FIXTURE(HtmlExportTest, testOpticalSizing)
+{
+    createSwDoc();
+    uno::Reference<text::XTextRange> xRun = getRun(getParagraph(1), 1);
+    xRun->setString(u"text"_ustr);
+    uno::Reference<beans::XPropertySet> xCursor(xRun, uno::UNO_QUERY);
+
+    xCursor->setPropertyValue(u"CharOpticalSizing"_ustr, uno::Any(false));
+    saveAndReload(TestFilter::HTML_WRITER);
+
+    uno::Reference<beans::XPropertySet> xRetCursor(getRun(getParagraph(1), 1), 
uno::UNO_QUERY);
+    CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRetCursor, 
u"CharOpticalSizing"_ustr));
+
+    dispose();
+
+    createSwDoc();
+    uno::Reference<text::XTextRange> xRun2 = getRun(getParagraph(1), 1);
+    xRun2->setString(u"text"_ustr);
+    uno::Reference<beans::XPropertySet> xCursor2(xRun2, uno::UNO_QUERY);
+
+    xCursor2->setPropertyValue(u"CharOpticalSizing"_ustr, uno::Any(true));
+    saveAndReload(TestFilter::HTML_WRITER);
+
+    uno::Reference<beans::XPropertySet> xRetCursor2(getRun(getParagraph(1), 
1), uno::UNO_QUERY);
+    CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xRetCursor2, 
u"CharOpticalSizing"_ustr));
+}
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 
diff --git a/sw/source/filter/html/css1atr.cxx 
b/sw/source/filter/html/css1atr.cxx
index 6f80bb53ff47..12334aaac59d 100644
--- a/sw/source/filter/html/css1atr.cxx
+++ b/sw/source/filter/html/css1atr.cxx
@@ -48,6 +48,7 @@
 #include <editeng/spltitem.hxx>
 #include <editeng/orphitem.hxx>
 #include <editeng/charhiddenitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <svx/xoutbmp.hxx>
 #include <svx/svdobj.hxx>
 #include <editeng/langitem.hxx>
@@ -2549,6 +2550,16 @@ static SwHTMLWriter& OutCSS1_SvxHidden( SwHTMLWriter& 
rWrt, const SfxPoolItem& r
     return rWrt;
 }
 
+static SwHTMLWriter& OutCSS1_SvxOpticalSizing( SwHTMLWriter& rWrt, const 
SfxPoolItem& rHt )
+{
+    if( static_cast<const SvxOpticalSizingItem&>(rHt).GetValue() )
+        rWrt.OutCSS1_PropertyAscii( sCSS1_P_font_optical_sizing, sCSS1_PV_auto 
);
+    else
+        rWrt.OutCSS1_PropertyAscii( sCSS1_P_font_optical_sizing, sCSS1_PV_none 
);
+
+    return rWrt;
+}
+
 static SwHTMLWriter& OutCSS1_SvxFontWeight( SwHTMLWriter& rWrt, const 
SfxPoolItem& rHt )
 {
     sal_uInt16 nScript = CSS1_OUTMODE_WESTERN;
@@ -3465,7 +3476,7 @@ SwAttrFnTab const aCSS1AttrFnTab = {
 /* RES_CHRATR_BIDIRTL */            nullptr,
 /* RES_CHRATR_UNUSED3 */            nullptr,
 /* RES_CHRATR_SCRIPT_HINT */        nullptr,
-/* RES_CHRATR_OPTICAL_SIZING */     nullptr,
+/* RES_CHRATR_OPTICAL_SIZING */     OutCSS1_SvxOpticalSizing,
 
 /* RES_TXTATR_REFMARK */            nullptr,
 /* RES_TXTATR_TOXMARK */            nullptr,
diff --git a/sw/source/filter/html/css1kywd.hxx 
b/sw/source/filter/html/css1kywd.hxx
index 2548cd3a9537..8332083ad0f1 100644
--- a/sw/source/filter/html/css1kywd.hxx
+++ b/sw/source/filter/html/css1kywd.hxx
@@ -65,6 +65,7 @@ constexpr inline std::string_view sCSS1_PV_italic = "italic";
 constexpr inline std::string_view sCSS1_PV_oblique = "oblique";
 
 constexpr inline std::string_view sCSS1_P_font_variant = "font-variant";
+constexpr inline std::string_view sCSS1_P_font_optical_sizing = 
"font-optical-sizing";
 
 //constexpr inline std::string_view sCSS1_PV_normal = "normal";
 constexpr inline std::string_view sCSS1_PV_small_caps = "small-caps";
diff --git a/sw/source/filter/html/htmlatr.cxx 
b/sw/source/filter/html/htmlatr.cxx
index d917fea69094..cc33bf540ff4 100644
--- a/sw/source/filter/html/htmlatr.cxx
+++ b/sw/source/filter/html/htmlatr.cxx
@@ -45,6 +45,7 @@
 #include <editeng/lrspitem.hxx>
 #include <editeng/langitem.hxx>
 #include <editeng/frmdiritem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <fchrfmt.hxx>
 #include <fmtautofmt.hxx>
 #include <fmtfsize.hxx>
@@ -3316,7 +3317,7 @@ const SwAttrFnTab aHTMLAttrFnTab = {
 /* RES_CHRATR_BIDIRTL */            nullptr,
 /* RES_CHRATR_UNUSED3 */            nullptr,
 /* RES_CHRATR_SCRIPT_HINT */        nullptr,
-/* RES_CHRATR_OPTICAL_SIZING */     nullptr,
+/* RES_CHRATR_OPTICAL_SIZING */     OutHTML_CSS1Attr,
 
 /* RES_TXTATR_REFMARK */            nullptr,
 /* RES_TXTATR_TOXMARK */            nullptr,
diff --git a/sw/source/filter/html/htmlcss1.cxx 
b/sw/source/filter/html/htmlcss1.cxx
index 5229d3c69eb5..cc0bb8d90cd8 100644
--- a/sw/source/filter/html/htmlcss1.cxx
+++ b/sw/source/filter/html/htmlcss1.cxx
@@ -1598,6 +1598,9 @@ HTMLAttr **SwHTMLParser::GetAttrTabEntry( sal_uInt16 
nWhich )
     case RES_CHRATR_BACKGROUND:
         ppAttr = &m_xAttrTab->pCharBrush;
         break;
+    case RES_CHRATR_OPTICAL_SIZING:
+        ppAttr = &m_xAttrTab->pOpticalSizing;
+        break;
     case RES_CHRATR_BOX:
         ppAttr = &m_xAttrTab->pCharBox;
         break;
diff --git a/sw/source/filter/html/svxcss1.cxx 
b/sw/source/filter/html/svxcss1.cxx
index 69cc2f381cd4..ffd8dd0a7982 100644
--- a/sw/source/filter/html/svxcss1.cxx
+++ b/sw/source/filter/html/svxcss1.cxx
@@ -52,6 +52,7 @@
 #include <editeng/widwitem.hxx>
 #include <editeng/frmdiritem.hxx>
 #include <editeng/orphitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <utility>
 #include <vcl/metric.hxx>
 #include <vcl/svapp.hxx>
@@ -283,6 +284,7 @@ struct SvxCSS1ItemIds
     sal_uInt16 nKerning;
     sal_uInt16 nCaseMap;
     sal_uInt16 nBlink;
+    sal_uInt16 nOpticalSizing;
 
     sal_uInt16 nLineSpacing;
     sal_uInt16 nAdjust;
@@ -738,6 +740,7 @@ SvxCSS1Parser::SvxCSS1Parser( SfxItemPool& rPool, OUString 
aBaseURL,
     aItemIds.nKerning = initTrueWhich( SID_ATTR_CHAR_KERNING );
     aItemIds.nCaseMap = initTrueWhich( SID_ATTR_CHAR_CASEMAP );
     aItemIds.nBlink = initTrueWhich( SID_ATTR_FLASH );
+    aItemIds.nOpticalSizing = initTrueWhich( SID_ATTR_CHAR_OPTICAL_SIZING );
 
     aItemIds.nLineSpacing = initTrueWhich( SID_ATTR_PARA_LINESPACE );
     aItemIds.nAdjust = initTrueWhich( SID_ATTR_PARA_ADJUST );
@@ -1247,6 +1250,28 @@ static void ParseCSS1_font_variant( const CSS1Expression 
*pExpr,
     }
 }
 
+static void ParseCSS1_font_optical_sizing( const CSS1Expression *pExpr,
+                                    SfxItemSet &rItemSet,
+                                    SvxCSS1PropertyInfo& /*rPropInfo*/,
+                                    const SvxCSS1Parser& /*rParser*/ )
+{
+    assert(pExpr && "no expression");
+
+    switch( pExpr->GetType() )
+    {
+    case CSS1_IDENT:
+        {
+            if( o3tl::equalsIgnoreAsciiCase( pExpr->GetString(), sCSS1_PV_auto 
) )
+                rItemSet.Put( SvxOpticalSizingItem( true, 
aItemIds.nOpticalSizing ) );
+            else if( o3tl::equalsIgnoreAsciiCase( pExpr->GetString(), 
sCSS1_PV_none ) )
+                rItemSet.Put( SvxOpticalSizingItem( false, 
aItemIds.nOpticalSizing ) );
+            break;
+        }
+    default:
+        break;
+    }
+}
+
 static void ParseCSS1_text_transform( const CSS1Expression *pExpr,
                                     SfxItemSet &rItemSet,
                                     SvxCSS1PropertyInfo& /*rPropInfo*/,
@@ -3099,6 +3124,7 @@ CSS1PropEntry constexpr aCSS1PropFnTab[] =
     { sCSS1_P_float, ParseCSS1_float },
     { sCSS1_P_font, ParseCSS1_font },
     { sCSS1_P_font_family, ParseCSS1_font_family },
+    { sCSS1_P_font_optical_sizing, ParseCSS1_font_optical_sizing },
     { sCSS1_P_font_size, ParseCSS1_font_size },
     { sCSS1_P_font_style, ParseCSS1_font_style },
     { sCSS1_P_font_variant, ParseCSS1_font_variant },
diff --git a/sw/source/filter/html/swhtml.hxx b/sw/source/filter/html/swhtml.hxx
index 151189aa7acf..5147d8e8c6fa 100644
--- a/sw/source/filter/html/swhtml.hxx
+++ b/sw/source/filter/html/swhtml.hxx
@@ -128,6 +128,7 @@ struct HTMLAttrTable
     HTMLAttr* pLanguageCJK;
     HTMLAttr* pLanguageCTL;
     HTMLAttr* pCharBox;
+    HTMLAttr* pOpticalSizing;
 };
 
 class HTMLAttr
commit 2aed32b1be83de428d7a19db58742917fc0bf7ae
Author:     Khaled Hosny <[email protected]>
AuthorDate: Thu Feb 26 17:48:05 2026 +0200
Commit:     Khaled Hosny <[email protected]>
CommitDate: Sun Mar 1 18:32:50 2026 +0200

    Clamp variation values to axis min and max
    
    Avoids creating needless PDF subsets for instances that are effectively
    the same.
    
    Change-Id: I9ce04f99a77cb4d6fde5456d6de928b9f46670e2

diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx 
b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
index 71ee4c282fc9..3789f713dc4e 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -2197,10 +2197,9 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testOpticalSizing)
         }
     }
 
-    std::set<OString> aExpected{ "Fraunces_150opsz_400wght"_ostr, 
"Fraunces_144opsz_400wght"_ostr,
-                                 "Fraunces_80opsz_400wght"_ostr,  
"Fraunces_60opsz_400wght"_ostr,
-                                 "Fraunces_40opsz_400wght"_ostr,  
"Fraunces_20opsz_400wght"_ostr,
-                                 "Fraunces_6opsz_400wght"_ostr,   
"Fraunces-Regular"_ostr };
+    std::set<OString> aExpected{ "Fraunces_144opsz_400wght"_ostr, 
"Fraunces_80opsz_400wght"_ostr,
+                                 "Fraunces_60opsz_400wght"_ostr,  
"Fraunces_40opsz_400wght"_ostr,
+                                 "Fraunces_20opsz_400wght"_ostr,  
"Fraunces-Regular"_ostr };
 
     CPPUNIT_ASSERT_EQUAL(aExpected, aFontNames);
 #endif
diff --git a/vcl/source/font/LogicalFontInstance.cxx 
b/vcl/source/font/LogicalFontInstance.cxx
index edbe0657e69e..fe9f6b3e7ac0 100644
--- a/vcl/source/font/LogicalFontInstance.cxx
+++ b/vcl/source/font/LogicalFontInstance.cxx
@@ -70,8 +70,13 @@ const std::vector<hb_variation_t>& 
LogicalFontInstance::GetVariations() const
         if (m_bOpticalSizing)
             aVariations.push_back({ HB_TAG('o', 'p', 's', 'z'), m_fPointSize 
});
 
-        for (const auto& rVariation : aVariations)
+        hb_face_t* pHbFace = GetFontFace()->GetHbFace();
+        for (auto& rVariation : aVariations)
         {
+            hb_ot_var_axis_info_t info;
+            if (hb_ot_var_find_axis_info(pHbFace, rVariation.tag, &info))
+                rVariation.value = std::clamp(rVariation.value, 
info.min_value, info.max_value);
+
             auto it = std::find_if(mxVariations->begin(), mxVariations->end(),
                                    [&rVariation](const hb_variation_t& rOther) 
{
                                        return rOther.tag == rVariation.tag;
commit c4cf10f92262dc10a52384e97f61bfb683ae587d
Author:     Khaled Hosny <[email protected]>
AuthorDate: Thu Feb 26 14:11:21 2026 +0200
Commit:     Khaled Hosny <[email protected]>
CommitDate: Sun Mar 1 18:32:50 2026 +0200

    tdf#153368: Support optical size for variable fonts, part 4
    
    Support saving/loading font-optical-sizing in Writer. The
    font-optical-sizing property is enabled on the standard style for new
    documents, and for old documents it remains unset for backward
    compatibility.
    
    Add no-op stubs to DOCX, RTF, WW8, and HTML filters.
    
    Change-Id: I87e3de603785dbd588099bfbd72e13e4538fc420

diff --git a/sw/inc/charatr.hxx b/sw/inc/charatr.hxx
index 3bea7a6d38e7..677088b44d11 100644
--- a/sw/inc/charatr.hxx
+++ b/sw/inc/charatr.hxx
@@ -43,6 +43,7 @@
 #include <editeng/langitem.hxx>
 #include <editeng/colritem.hxx>
 #include <editeng/scripthintitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 
 // implementation of the character attribute methods of SwAttrSet
 
@@ -112,6 +113,8 @@ inline const SvxCharReliefItem   &SwAttrSet::GetCharRelief( 
bool bInP ) const
     {   return Get( RES_CHRATR_RELIEF, bInP ); }
 inline const SvxCharHiddenItem   &SwAttrSet::GetCharHidden( bool bInP ) const
     {   return Get( RES_CHRATR_HIDDEN, bInP ); }
+inline const SvxOpticalSizingItem&SwAttrSet::GetOpticalSizing(bool bInP) const
+    {   return Get( RES_CHRATR_OPTICAL_SIZING,bInP); }
 
 // implementation of the character attribute methods of SwFormat
 
diff --git a/sw/inc/hintids.hxx b/sw/inc/hintids.hxx
index 423dafdcea08..59f0c2d51672 100644
--- a/sw/inc/hintids.hxx
+++ b/sw/inc/hintids.hxx
@@ -76,6 +76,7 @@ class SvxLanguageItem;
 class SvxLineSpacingItem;
 class SvxNoHyphenItem;
 class SvxOpaqueItem;
+class SvxOpticalSizingItem;
 class SvxOrphansItem;
 class SvxOverlineItem;
 class SvxPaperBinItem;
@@ -244,7 +245,9 @@ inline constexpr TypedWhichId<SfxGrabBagItem> 
RES_CHRATR_GRABBAG(RES_CHRATR_BEGI
 inline constexpr TypedWhichId<SfxInt16Item> 
RES_CHRATR_BIDIRTL(RES_CHRATR_BEGIN + 43);
 inline constexpr TypedWhichId<SfxInt16Item> 
RES_CHRATR_UNUSED3(RES_CHRATR_BEGIN + 44);
 inline constexpr TypedWhichId<SvxScriptHintItem> 
RES_CHRATR_SCRIPT_HINT(RES_CHRATR_BEGIN + 45);
-inline constexpr sal_uInt16 RES_CHRATR_END(RES_CHRATR_BEGIN + 46);
+inline constexpr TypedWhichId<SvxOpticalSizingItem> 
RES_CHRATR_OPTICAL_SIZING(RES_CHRATR_BEGIN
+                                                                              
+ 46);
+inline constexpr sal_uInt16 RES_CHRATR_END(RES_CHRATR_BEGIN + 47);
 
 // this Attribute used only in a TextNodes SwpAttr-Array
 inline constexpr sal_uInt16 RES_TXTATR_BEGIN(RES_CHRATR_END);
diff --git a/sw/inc/swatrset.hxx b/sw/inc/swatrset.hxx
index 67709a849665..af13c71880ca 100644
--- a/sw/inc/swatrset.hxx
+++ b/sw/inc/swatrset.hxx
@@ -52,6 +52,7 @@ class SvxCharRotateItem;
 class SvxCharReliefItem;
 class SvxCharHiddenItem;
 class SvxScriptHintItem;
+class SvxOpticalSizingItem;
 
 // Frame attributes
 class SwFormatFillOrder;
@@ -244,6 +245,7 @@ public:
     inline const SvxCharRotateItem        &GetCharRotate( bool = true ) const;
     inline const SvxCharReliefItem        &GetCharRelief( bool = true ) const;
     inline const SvxCharHiddenItem      &GetCharHidden( bool = true ) const;
+    inline const SvxOpticalSizingItem   &GetOpticalSizing( bool = true ) const;
 
     // Frame attributes. Implementation in frmatr.hxx.
     inline const SwFormatFillOrder       &GetFillOrder( bool = true ) const;
diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx
index 3d9270fdb429..e30a28c26581 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -151,6 +151,7 @@ inline constexpr OUString UNO_NAME_CHAR_PROP_HEIGHT_COMPLEX 
= u"CharPropHeightCo
 inline constexpr OUString UNO_NAME_CHAR_DIFF_HEIGHT_COMPLEX = 
u"CharDiffHeightComplex"_ustr;
 inline constexpr OUString UNO_NAME_CHAR_ESCAPEMENT_HEIGHT = 
u"CharEscapementHeight"_ustr;
 inline constexpr OUString UNO_NAME_CHAR_TRANSPARENCE = 
u"CharTransparence"_ustr;
+inline constexpr OUString UNO_NAME_CHAR_OPTICAL_SIZING = 
u"CharOpticalSizing"_ustr;
 inline constexpr OUString UNO_NAME_HIDE_TAB_LEADER_AND_PAGE_NUMBERS
     = u"HideTabLeaderAndPageNumber"_ustr;
 inline constexpr OUString UNO_NAME_TAB_IN_TOC = u"TabInTOC"_ustr;
diff --git a/sw/qa/extras/odfexport/odfexport.cxx 
b/sw/qa/extras/odfexport/odfexport.cxx
index 153b1a56197e..bfbd1092a301 100644
--- a/sw/qa/extras/odfexport/odfexport.cxx
+++ b/sw/qa/extras/odfexport/odfexport.cxx
@@ -1558,7 +1558,40 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf60700_directories)
     CPPUNIT_ASSERT_EQUAL(0, nMatches);
 }
 
+// CharOpticalSizing
+CPPUNIT_TEST_FIXTURE(Test, testOpticalSizing)
+{
+    createSwDoc();
+    uno::Reference<beans::XPropertySet> xCursor(getRun(getParagraph(1), 1), 
uno::UNO_QUERY);
+    // it should be true by default for new documents
+    CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xCursor, 
u"CharOpticalSizing"_ustr));
+
+    // Setting it manually should set it in contents
+    xCursor->setPropertyValue(u"CharOpticalSizing"_ustr, uno::Any(false));
+    save(TestFilter::ODT);
+    xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr);
+    assertXPath(pXmlDoc, 
"//style:style/style:text-properties[@loext:font-optical-sizing='none']", 1);
+
+    xCursor->setPropertyValue(u"CharOpticalSizing"_ustr, uno::Any(true));
+    save(TestFilter::ODT);
+    pXmlDoc = parseExport(u"content.xml"_ustr);
+    assertXPath(pXmlDoc, 
"//style:style/style:text-properties[@loext:font-optical-sizing='auto']", 1);
+
+    saveAndReload(TestFilter::ODT);
+    uno::Reference<beans::XPropertySet> xRun(getRun(getParagraph(1), 1), 
uno::UNO_QUERY);
+    // and it should be true after save-and-reload
+    CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xRun, 
u"CharOpticalSizing"_ustr));
 
+    pXmlDoc = parseExport(u"content.xml"_ustr);
+    assertXPath(pXmlDoc, 
"//style:style/style:text-properties[@loext:font-optical-sizing='auto']", 1);
+
+    dispose();
+
+    createSwDoc("allow-overlap.odt");
+    uno::Reference<beans::XPropertySet> xCursor2(getRun(getParagraph(1), 1), 
uno::UNO_QUERY);
+    // but it should be false by default for old documents
+    CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xCursor2, 
u"CharOpticalSizing"_ustr));
+}
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/qa/uitest/styleInspector/styleInspector.py 
b/sw/qa/uitest/styleInspector/styleInspector.py
index 70554e5307c1..fec8f514abc0 100644
--- a/sw/qa/uitest/styleInspector/styleInspector.py
+++ b/sw/qa/uitest/styleInspector/styleInspector.py
@@ -26,7 +26,7 @@ class styleNavigator(UITestCase):
             # The cursor is on text without formatting and default style
             self.assertEqual(1, len(xListBox.getChild('0').getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text'])
-            self.assertEqual(157, 
len(xListBox.getChild('0').getChild('0').getChildren()))
+            self.assertEqual(158, 
len(xListBox.getChild('0').getChild('0').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('1').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('2').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('3').getChildren()))
@@ -36,7 +36,7 @@ class styleNavigator(UITestCase):
             # The cursor is on text with direct formatting
             self.assertEqual(1, len(xListBox.getChild('0').getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text'])
-            self.assertEqual(157, 
len(xListBox.getChild('0').getChild('0').getChildren()))
+            self.assertEqual(158, 
len(xListBox.getChild('0').getChild('0').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('1').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('2').getChildren()))
 
@@ -54,7 +54,7 @@ class styleNavigator(UITestCase):
             # The cursor is on text with paragraph direct formatting
             self.assertEqual(1, len(xListBox.getChild('0').getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text'])
-            self.assertEqual(157, 
len(xListBox.getChild('0').getChild('0').getChildren()))
+            self.assertEqual(158, 
len(xListBox.getChild('0').getChild('0').getChildren()))
 
             xParDirFormatting = xListBox.getChild('1')
             self.assertEqual(7, len(xParDirFormatting.getChildren()))
@@ -75,7 +75,7 @@ class styleNavigator(UITestCase):
             xParStyle = xListBox.getChild('0')
             self.assertEqual(3, len(xParStyle.getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xParStyle.getChild('0'))['Text'])
-            self.assertEqual(157, len(xParStyle.getChild('0').getChildren()))
+            self.assertEqual(158, len(xParStyle.getChild('0').getChildren()))
             self.assertEqual("Heading  ", 
get_state_as_dict(xParStyle.getChild('1'))['Text'])
             self.assertEqual(28, len(xParStyle.getChild('1').getChildren()))
 
@@ -116,7 +116,7 @@ class styleNavigator(UITestCase):
             xParStyle = xListBox.getChild('0')
             self.assertEqual(3, len(xParStyle.getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xParStyle.getChild('0'))['Text'])
-            self.assertEqual(157, len(xParStyle.getChild('0').getChildren()))
+            self.assertEqual(158, len(xParStyle.getChild('0').getChildren()))
             self.assertEqual("Body Text        ", 
get_state_as_dict(xParStyle.getChild('1'))['Text'])
             self.assertEqual(6, len(xParStyle.getChild('1').getChildren()))
 
@@ -151,7 +151,7 @@ class styleNavigator(UITestCase):
             # The cursor is on text without metadata
             self.assertEqual(1, len(xListBox.getChild('0').getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text'])
-            self.assertEqual(157, 
len(xListBox.getChild('0').getChild('0').getChildren()))
+            self.assertEqual(158, 
len(xListBox.getChild('0').getChild('0').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('1').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('2').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('3').getChildren()))
@@ -161,7 +161,7 @@ class styleNavigator(UITestCase):
             # The cursor is on text with paragraph metadata showed under 
direct paragraph formatting
             self.assertEqual(1, len(xListBox.getChild('0').getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text'])
-            self.assertEqual(157, 
len(xListBox.getChild('0').getChild('0').getChildren()))
+            self.assertEqual(158, 
len(xListBox.getChild('0').getChild('0').getChildren()))
 
             xParDirFormatting = xListBox.getChild('1')
             self.assertEqual(1, len(xParDirFormatting.getChildren()))
@@ -214,7 +214,7 @@ class styleNavigator(UITestCase):
             # The cursor is on text without metadata
             self.assertEqual(1, len(xListBox.getChild('0').getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text'])
-            self.assertEqual(157, 
len(xListBox.getChild('0').getChild('0').getChildren()))
+            self.assertEqual(158, 
len(xListBox.getChild('0').getChild('0').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('1').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('2').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('3').getChildren()))
@@ -224,7 +224,7 @@ class styleNavigator(UITestCase):
             # The cursor is on text with paragraph metadata showed under 
direct paragraph formatting
             self.assertEqual(1, len(xListBox.getChild('1').getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xListBox.getChild('1').getChild('0'))['Text'])
-            self.assertEqual(157, 
len(xListBox.getChild('1').getChild('0').getChildren()))
+            self.assertEqual(158, 
len(xListBox.getChild('1').getChild('0').getChildren()))
 
             # Outer bookmark
             xBookmarkFormatting = xListBox.getChild('0')
@@ -271,7 +271,7 @@ class styleNavigator(UITestCase):
             # The cursor is on text without metadata
             self.assertEqual(1, len(xListBox.getChild('0').getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text'])
-            self.assertEqual(157, 
len(xListBox.getChild('0').getChild('0').getChildren()))
+            self.assertEqual(158, 
len(xListBox.getChild('0').getChild('0').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('1').getChildren()))
             self.assertEqual(0, len(xListBox.getChild('2').getChildren()))
 
diff --git a/sw/qa/uitest/styleInspector/tdf137513.py 
b/sw/qa/uitest/styleInspector/tdf137513.py
index de29a3be0588..b499dd5bc5e1 100644
--- a/sw/qa/uitest/styleInspector/tdf137513.py
+++ b/sw/qa/uitest/styleInspector/tdf137513.py
@@ -35,7 +35,7 @@ class tdf137513(UITestCase):
             self.assertEqual(2, len(xListBox.getChild('0').getChildren()))
             self.assertEqual("Default Paragraph Style  ", 
get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text'])
             self.assertEqual("Table Contents   ", 
get_state_as_dict(xListBox.getChild('0').getChild('1'))['Text'])
-            self.assertEqual(157, 
len(xListBox.getChild('0').getChild('0').getChildren()))
+            self.assertEqual(158, 
len(xListBox.getChild('0').getChild('0').getChildren()))
 
             xTableContent = xListBox.getChild('0').getChild('1')
             self.assertEqual(5, len(xTableContent.getChildren()))
diff --git a/sw/source/core/bastyp/init.cxx b/sw/source/core/bastyp/init.cxx
index 9ceb9195a425..c7fd89b47244 100644
--- a/sw/source/core/bastyp/init.cxx
+++ b/sw/source/core/bastyp/init.cxx
@@ -55,6 +55,7 @@
 #include <editeng/lspcitem.hxx>
 #include <editeng/nhypitem.hxx>
 #include <editeng/opaqitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <editeng/orphitem.hxx>
 #include <editeng/paravertalignitem.hxx>
 #include <editeng/pbinitem.hxx>
@@ -332,6 +333,7 @@ std::unique_ptr<ItemInfoPackage> 
createItemInfoPackageSwAttributes()
             { RES_CHRATR_BIDIRTL, new SfxInt16Item( RES_CHRATR_BIDIRTL, 
sal_Int16(-1) ), 0, SFX_ITEMINFOFLAG_NONE },
             { RES_CHRATR_UNUSED3, new SfxVoidItem( RES_CHRATR_UNUSED3 ), 0, 
SFX_ITEMINFOFLAG_NONE },
             { RES_CHRATR_SCRIPT_HINT, new SvxScriptHintItem( 
RES_CHRATR_SCRIPT_HINT ), SID_ATTR_CHAR_SCRIPT_HINT, SFX_ITEMINFOFLAG_NONE },
+            { RES_CHRATR_OPTICAL_SIZING, new SvxOpticalSizingItem( false, 
RES_CHRATR_OPTICAL_SIZING ), SID_ATTR_CHAR_OPTICAL_SIZING, 
SFX_ITEMINFOFLAG_NONE },
 
             { RES_TXTATR_REFMARK, new SwFormatRefMark( SwMarkName() ),  0, 
SFX_ITEMINFOFLAG_NONE },
             { RES_TXTATR_TOXMARK, createSwTOXMarkForItemInfoPackage(),  0, 
SFX_ITEMINFOFLAG_NONE },
diff --git a/sw/source/core/doc/DocumentStylePoolManager.cxx 
b/sw/source/core/doc/DocumentStylePoolManager.cxx
index 4b1667a37f05..17ddfbeb49d3 100644
--- a/sw/source/core/doc/DocumentStylePoolManager.cxx
+++ b/sw/source/core/doc/DocumentStylePoolManager.cxx
@@ -57,6 +57,7 @@
 #include <editeng/charrotateitem.hxx>
 #include <editeng/emphasismarkitem.hxx>
 #include <editeng/scriptspaceitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <o3tl/unit_conversion.hxx>
 #include <svx/strings.hrc>
 #include <svx/dialmgr.hxx>
diff --git a/sw/source/core/doc/dbgoutsw.cxx b/sw/source/core/doc/dbgoutsw.cxx
index 9b739d2575aa..08049eec18b0 100644
--- a/sw/source/core/doc/dbgoutsw.cxx
+++ b/sw/source/core/doc/dbgoutsw.cxx
@@ -126,6 +126,7 @@ static std::map<sal_uInt16,OUString> & GetItemWhichMap()
         { RES_CHRATR_HIDDEN , "CHRATR_HIDDEN" },
         { RES_CHRATR_BOX , "CHRATR_BOX" },
         { RES_CHRATR_SHADOW , "CHRATR_SHADOW" },
+        { RES_CHRATR_OPTICAL_SIZING , "CHRATR_OPTICAL_SIZING" },
         { RES_TXTATR_AUTOFMT , "TXTATR_AUTOFMT" },
         { RES_TXTATR_INETFMT , "TXTATR_INETFMT" },
         { RES_TXTATR_REFMARK , "TXTATR_REFMARK" },
diff --git a/sw/source/core/inc/swfntcch.hxx b/sw/source/core/inc/swfntcch.hxx
index 43c4c0e43e80..6bb893ca0edb 100644
--- a/sw/source/core/inc/swfntcch.hxx
+++ b/sw/source/core/inc/swfntcch.hxx
@@ -18,7 +18,7 @@
  */
 #pragma once
 
-#define NUM_DEFAULT_VALUES 40
+#define NUM_DEFAULT_VALUES 41
 
 #include "swfont.hxx"
 
diff --git a/sw/source/core/inc/swfont.hxx b/sw/source/core/inc/swfont.hxx
index a99fdb6fc3a6..afe346863acc 100644
--- a/sw/source/core/inc/swfont.hxx
+++ b/sw/source/core/inc/swfont.hxx
@@ -103,6 +103,7 @@ class SwSubFont final : public SvxFont
     inline void SetWordLineMode( const bool bWordLineMode );
     inline void SetEmphasisMark( const FontEmphasisMark eValue );
     inline void SetRelief( const FontRelief eNew );
+    inline void SetOpticalSizing( bool bOpticalSizing );
 
     // methods for sub-/superscript
     inline void SetEscapement( const short nNewEsc );
@@ -235,6 +236,7 @@ public:
     inline void SetFixKerning( const short nNewKern );
     inline void SetCaseMap( const SvxCaseMap eNew );
     inline void SetEmphasisMark( const FontEmphasisMark eValue );
+    inline void SetOpticalSizing( bool bOpticalSizing );
 
     // methods for sub-/superscript
     inline void SetEscapement( const short nNewEsc );
@@ -707,6 +709,20 @@ inline void SwFont::SetEmphasisMark( const 
FontEmphasisMark eValue )
     m_aSub[SwFontScript::CTL].SetEmphasisMark( eValue );
 }
 
+inline void SwSubFont::SetOpticalSizing( bool bOpticalSizing )
+{
+    m_nFontCacheId = nullptr;
+    Font::SetOpticalSizing( bOpticalSizing );
+}
+
+inline void SwFont::SetOpticalSizing( bool bOpticalSizing )
+{
+    m_bFontChg = true;
+    m_aSub[SwFontScript::Latin].SetOpticalSizing( bOpticalSizing );
+    m_aSub[SwFontScript::CJK].SetOpticalSizing( bOpticalSizing );
+    m_aSub[SwFontScript::CTL].SetOpticalSizing( bOpticalSizing );
+}
+
 inline void SwFont::SetPropWidth( const sal_uInt16 nNew )
 {
     if( nNew != m_aSub[SwFontScript::Latin].GetPropWidth() )
diff --git a/sw/source/core/layout/wsfrm.cxx b/sw/source/core/layout/wsfrm.cxx
index f28276de6c93..b3afcb4999cd 100644
--- a/sw/source/core/layout/wsfrm.cxx
+++ b/sw/source/core/layout/wsfrm.cxx
@@ -2622,6 +2622,7 @@ void SwContentFrame::UpdateAttr_( const SfxPoolItem* 
pOld, const SfxPoolItem* pN
         case RES_CHRATR_ESCAPEMENT:
         case RES_CHRATR_CONTOUR:
         case RES_CHRATR_NOHYPHEN:
+        case RES_CHRATR_OPTICAL_SIZING:
         case RES_PARATR_NUMRULE:
             rInvFlags |= SwContentFrameInvFlags::SetCompletePaint;
             break;
diff --git a/sw/source/core/text/atrhndl.hxx b/sw/source/core/text/atrhndl.hxx
index 979b99800124..f4ebc728ce7d 100644
--- a/sw/source/core/text/atrhndl.hxx
+++ b/sw/source/core/text/atrhndl.hxx
@@ -18,7 +18,7 @@
  */
 
 #pragma once
-#define NUM_ATTRIBUTE_STACKS 46
+#define NUM_ATTRIBUTE_STACKS 47
 
 #include <vector>
 #include <swfntcch.hxx>
diff --git a/sw/source/core/text/atrstck.cxx b/sw/source/core/text/atrstck.cxx
index 1d8c0ba479f5..caf108486f09 100644
--- a/sw/source/core/text/atrstck.cxx
+++ b/sw/source/core/text/atrstck.cxx
@@ -44,6 +44,7 @@
 #include <editeng/boxitem.hxx>
 #include <editeng/nhypitem.hxx>
 #include <editeng/shaditem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <viewopt.hxx>
 #include <charfmt.hxx>
 #include <fchrfmt.hxx>
@@ -115,18 +116,19 @@ const sal_uInt8 StackPos[ RES_TXTATR_WITHEND_END - 
RES_CHRATR_BEGIN + 1 ] =
      0, // RES_CHRATR_GRABBAG,                   // 43
      0, // RES_CHRATR_BIDIRTL,                   // 44
      0, // RES_CHRATR_UNUSED3,                   // 45
-    39, // RES_CHRATR_SCRIPT_HINT,               // 46
-    40, // RES_TXTATR_REFMARK,                   // 47
-    41, // RES_TXTATR_TOXMARK,                   // 48
-    42, // RES_TXTATR_META,                      // 49
-    42, // RES_TXTATR_METAFIELD,                 // 50
+    39, // RES_CHRATR_SCRIPT_HINT,               // 45
+    40, // RES_CHRATR_OPTICAL_SIZING             // 46
+    41, // RES_TXTATR_REFMARK,                   // 47
+    42, // RES_TXTATR_TOXMARK,                   // 48
+    43, // RES_TXTATR_META,                      // 49
+    43, // RES_TXTATR_METAFIELD,                 // 50
      0, // RES_TXTATR_AUTOFMT,                   // 51
      0, // RES_TXTATR_INETFMT                    // 52
      0, // RES_TXTATR_CHARFMT,                   // 53
-    43, // RES_TXTATR_CJK_RUBY,                  // 54
+    44, // RES_TXTATR_CJK_RUBY,                  // 54
      0, // RES_TXTATR_UNKNOWN_CONTAINER,         // 55
-    44, // RES_TXTATR_INPUTFIELD                 // 56
-    45, // RES_TXTATR_CONTENTCONTROL             // 57
+    45, // RES_TXTATR_INPUTFIELD                 // 56
+    46, // RES_TXTATR_CONTENTCONTROL             // 57
 };
 
 namespace CharFormat
@@ -831,6 +833,9 @@ void SwAttrHandler::FontChg(const SfxPoolItem& rItem, 
SwFont& rFnt, bool bPush )
                 rFnt.SetVertical(m_pDefaultArray[ nRotateStack 
]->StaticWhichCast(RES_CHRATR_ROTATE).GetValue(), m_bVertLayout);
             break;
         }
+        case RES_CHRATR_OPTICAL_SIZING :
+            rFnt.SetOpticalSizing( 
rItem.StaticWhichCast(RES_CHRATR_OPTICAL_SIZING).GetValue() );
+            break;
         case RES_TXTATR_CJK_RUBY :
             rFnt.SetVertical( 0_deg10, m_bVertLayout );
             break;
diff --git a/sw/source/core/txtnode/swfont.cxx 
b/sw/source/core/txtnode/swfont.cxx
index ef33a4e6bae8..e7cc710827af 100644
--- a/sw/source/core/txtnode/swfont.cxx
+++ b/sw/source/core/txtnode/swfont.cxx
@@ -665,6 +665,8 @@ void SwFont::SetDiffFnt( const SfxItemSet *pAttrSet,
         const SvxTwoLinesItem* pTwoLinesItem = pAttrSet->GetItemIfSet( 
RES_CHRATR_TWO_LINES );
         if( pTwoLinesItem && pTwoLinesItem->GetValue() )
             SetVertical( 0_deg10 );
+        if( const SvxOpticalSizingItem* pItem = pAttrSet->GetItemIfSet( 
RES_CHRATR_OPTICAL_SIZING ) )
+            SetOpticalSizing( pItem->GetValue() );
     }
     else
     {
@@ -854,6 +856,7 @@ SwFont::SwFont( const SwAttrSet* pAttrSet,
         m_aSub[ SwFontScript::CJK ].m_bSmallCapsPercentage66 = true;
         m_aSub[ SwFontScript::CTL ].m_bSmallCapsPercentage66 = true;
     }
+    SetOpticalSizing( pAttrSet->GetOpticalSizing().GetValue() );
 }
 
 SwFont::~SwFont()
diff --git a/sw/source/core/unocore/unomap1.cxx 
b/sw/source/core/unocore/unomap1.cxx
index 184c46aafb98..d73181f1157a 100644
--- a/sw/source/core/unocore/unomap1.cxx
+++ b/sw/source/core/unocore/unomap1.cxx
@@ -206,6 +206,7 @@ std::span<const SfxItemPropertyMapEntry> 
SwUnoPropertyMapProvider::GetCharStyleP
         { UNO_NAME_CHAR_OVERLINE_HAS_COLOR, RES_CHRATR_OVERLINE ,  
cppu::UnoType<bool>::get(),            PROPERTY_NONE, MID_TL_HASCOLOR},
         { UNO_NAME_CHAR_KERNING, RES_CHRATR_KERNING    ,  
cppu::UnoType<sal_Int16>::get()  ,         PROPERTY_NONE,  CONVERT_TWIPS},
         { UNO_NAME_CHAR_NO_HYPHENATION, RES_CHRATR_NOHYPHEN   ,   
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE,     0},
+        { UNO_NAME_CHAR_OPTICAL_SIZING, RES_CHRATR_OPTICAL_SIZING  ,  
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE,     0},
         { UNO_NAME_CHAR_SHADOWED, RES_CHRATR_SHADOWED  ,  
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE, 0},
         { UNO_NAME_CHAR_CONTOURED, RES_CHRATR_CONTOUR,    
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE, 0},
         { UNO_NAME_CHAR_WORD_MODE, 
RES_CHRATR_WORDLINEMODE,cppu::UnoType<bool>::get()  ,    PROPERTY_NONE,     0},
@@ -278,6 +279,7 @@ std::span<const SfxItemPropertyMapEntry>  
SwUnoPropertyMapProvider::GetAutoCharS
         { UNO_NAME_CHAR_OVERLINE_HAS_COLOR, RES_CHRATR_OVERLINE ,  
cppu::UnoType<bool>::get(),              PROPERTY_NONE, MID_TL_HASCOLOR},
         { UNO_NAME_CHAR_KERNING, RES_CHRATR_KERNING    ,  
cppu::UnoType<sal_Int16>::get()  ,         PROPERTY_NONE,  CONVERT_TWIPS},
         { UNO_NAME_CHAR_NO_HYPHENATION, RES_CHRATR_NOHYPHEN   ,   
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE,     0},
+        { UNO_NAME_CHAR_OPTICAL_SIZING, RES_CHRATR_OPTICAL_SIZING  ,  
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE,     0},
         { UNO_NAME_CHAR_SHADOWED, RES_CHRATR_SHADOWED  ,  
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE, 0},
         { UNO_NAME_CHAR_CONTOURED, RES_CHRATR_CONTOUR,    
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE, 0},
         { UNO_NAME_CHAR_WORD_MODE, 
RES_CHRATR_WORDLINEMODE,cppu::UnoType<bool>::get()  ,    PROPERTY_NONE,     0},
diff --git a/sw/source/core/unocore/unomapproperties.hxx 
b/sw/source/core/unocore/unomapproperties.hxx
index 62fa63945d35..015c08af042b 100644
--- a/sw/source/core/unocore/unomapproperties.hxx
+++ b/sw/source/core/unocore/unomapproperties.hxx
@@ -172,6 +172,7 @@
         CTL_FONT_PROPERTIES \
         { UNO_NAME_CHAR_KERNING,                        RES_CHRATR_KERNING,    
        cppu::UnoType<sal_Int16>::get(),         PropertyAttribute::MAYBEVOID, 
CONVERT_TWIPS                          }, \
         { UNO_NAME_CHAR_NO_HYPHENATION,                 RES_CHRATR_NOHYPHEN,   
        cppu::UnoType<bool>::get(),       PropertyAttribute::MAYBEVOID, 0       
                               }, \
+        { UNO_NAME_CHAR_OPTICAL_SIZING,                 
RES_CHRATR_OPTICAL_SIZING,     cppu::UnoType<bool>::get(),       
PropertyAttribute::MAYBEVOID, 0                                      }, \
         { UNO_NAME_CHAR_SHADOWED,                       RES_CHRATR_SHADOWED,   
        cppu::UnoType<bool>::get(),       PropertyAttribute::MAYBEVOID, 0       
                               }, \
         { UNO_NAME_CHAR_CONTOURED,                      RES_CHRATR_CONTOUR,    
        cppu::UnoType<bool>::get(),       PropertyAttribute::MAYBEVOID, 0       
                               }, \
         { UNO_NAME_DROP_CAP_FORMAT,                     RES_PARATR_DROP,       
        cppu::UnoType<css::style::DropCapFormat>::get(),    
PropertyAttribute::MAYBEVOID, MID_DROPCAP_FORMAT     | CONVERT_TWIPS }, \
@@ -446,6 +447,7 @@
                     { UNO_NAME_PARA_FIRST_LINE_INDENT_RELATIVE, 
RES_MARGIN_FIRSTLINE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 
MID_FIRST_LINE_REL_INDENT|CONVERT_TWIPS},\
                     { UNO_NAME_CHAR_KERNING, RES_CHRATR_KERNING    ,  
cppu::UnoType<sal_Int16>::get()  ,         PROPERTY_NONE,  CONVERT_TWIPS},\
                     { UNO_NAME_CHAR_NO_HYPHENATION, RES_CHRATR_NOHYPHEN   ,   
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE,     0},\
+                    { UNO_NAME_CHAR_OPTICAL_SIZING, RES_CHRATR_OPTICAL_SIZING 
,  cppu::UnoType<bool>::get()  ,       PROPERTY_NONE,     0},\
                     { UNO_NAME_CHAR_SHADOWED, RES_CHRATR_SHADOWED  ,  
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE, 0},\
                     { UNO_NAME_CHAR_CONTOURED, RES_CHRATR_CONTOUR,    
cppu::UnoType<bool>::get()  ,       PROPERTY_NONE, 0},\
                     { UNO_NAME_DROP_CAP_FORMAT, RES_PARATR_DROP,        
cppu::UnoType<css::style::DropCapFormat>::get()  , PROPERTY_NONE, 
MID_DROPCAP_FORMAT|CONVERT_TWIPS     },\
diff --git a/sw/source/filter/html/css1atr.cxx 
b/sw/source/filter/html/css1atr.cxx
index 42e884c1b2eb..6f80bb53ff47 100644
--- a/sw/source/filter/html/css1atr.cxx
+++ b/sw/source/filter/html/css1atr.cxx
@@ -3465,6 +3465,7 @@ SwAttrFnTab const aCSS1AttrFnTab = {
 /* RES_CHRATR_BIDIRTL */            nullptr,
 /* RES_CHRATR_UNUSED3 */            nullptr,
 /* RES_CHRATR_SCRIPT_HINT */        nullptr,
+/* RES_CHRATR_OPTICAL_SIZING */     nullptr,
 
 /* RES_TXTATR_REFMARK */            nullptr,
 /* RES_TXTATR_TOXMARK */            nullptr,
diff --git a/sw/source/filter/html/htmlatr.cxx 
b/sw/source/filter/html/htmlatr.cxx
index 1a5076dc1fa3..d917fea69094 100644
--- a/sw/source/filter/html/htmlatr.cxx
+++ b/sw/source/filter/html/htmlatr.cxx
@@ -3316,6 +3316,7 @@ const SwAttrFnTab aHTMLAttrFnTab = {
 /* RES_CHRATR_BIDIRTL */            nullptr,
 /* RES_CHRATR_UNUSED3 */            nullptr,
 /* RES_CHRATR_SCRIPT_HINT */        nullptr,
+/* RES_CHRATR_OPTICAL_SIZING */     nullptr,
 
 /* RES_TXTATR_REFMARK */            nullptr,
 /* RES_TXTATR_TOXMARK */            nullptr,
diff --git a/sw/source/filter/ww8/attributeoutputbase.hxx 
b/sw/source/filter/ww8/attributeoutputbase.hxx
index 2d46bef3842b..2f4000819959 100644
--- a/sw/source/filter/ww8/attributeoutputbase.hxx
+++ b/sw/source/filter/ww8/attributeoutputbase.hxx
@@ -48,6 +48,7 @@ class SvxShadowedItem;
 class SvxUnderlineItem;
 class SvxWeightItem;
 class SvxAutoKernItem;
+class SvxOpticalSizingItem;
 class SvxBlinkItem;
 class SvxBrushItem;
 class XFillStyleItem;
@@ -399,6 +400,9 @@ protected:
     /// Sfx item RES_CHRATR_AUTOKERN
     virtual void CharAutoKern( const SvxAutoKernItem& ) = 0;
 
+    /// Sfx item RES_CHRATR_OPTICAL_SIZING
+    virtual void CharOpticalSizing( const SvxOpticalSizingItem& ) = 0;
+
     /// Sfx item RES_CHRATR_BLINK
     virtual void CharAnimatedText( const SvxBlinkItem& ) = 0;
 
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index 8d4b05c44265..02033dd52f12 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -56,6 +56,7 @@
 #include <oox/export/drawingml.hxx>
 
 #include <editeng/autokernitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <editeng/unoprnms.hxx>
 #include <editeng/fontitem.hxx>
 #include <editeng/tstpitem.hxx>
@@ -5259,6 +5260,9 @@ void DocxAttributeOutput::OutputDefaultItem(const 
SfxPoolItem& rHt)
         case RES_CHRATR_AUTOKERN:
             bMustWrite = rHt.StaticWhichCast(RES_CHRATR_AUTOKERN).GetValue();
             break;
+        case RES_CHRATR_OPTICAL_SIZING:
+            bMustWrite = 
rHt.StaticWhichCast(RES_CHRATR_OPTICAL_SIZING).GetValue();
+            break;
         case RES_CHRATR_BLINK:
             bMustWrite = rHt.StaticWhichCast(RES_CHRATR_BLINK).GetValue();
             break;
@@ -8396,6 +8400,11 @@ void DocxAttributeOutput::CharAutoKern( const 
SvxAutoKernItem& rAutoKern )
     m_pSerializer->singleElementNS(XML_w, XML_kern, FSNS(XML_w, XML_val), 
sFontSize);
 }
 
+void DocxAttributeOutput::CharOpticalSizing( const SvxOpticalSizingItem& )
+{
+    // MSOffice has no equivalent for optical sizing, so nothing is exported.
+}
+
 void DocxAttributeOutput::CharAnimatedText( const SvxBlinkItem& rBlink )
 {
     if ( rBlink.GetValue() )
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx 
b/sw/source/filter/ww8/docxattributeoutput.hxx
index ca9db3e30ece..45d6052766ab 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -586,6 +586,9 @@ protected:
     /// Sfx item RES_CHRATR_AUTOKERN
     virtual void CharAutoKern( const SvxAutoKernItem& ) override;
 
+    /// Sfx item RES_CHRATR_OPTICAL_SIZING
+    virtual void CharOpticalSizing( const SvxOpticalSizingItem& ) override;
+
     /// Sfx item RES_CHRATR_BLINK
     virtual void CharAnimatedText( const SvxBlinkItem& rBlink ) override;
 
diff --git a/sw/source/filter/ww8/rtfattributeoutput.cxx 
b/sw/source/filter/ww8/rtfattributeoutput.cxx
index e4051aa159f9..9e1c91879c31 100644
--- a/sw/source/filter/ww8/rtfattributeoutput.cxx
+++ b/sw/source/filter/ww8/rtfattributeoutput.cxx
@@ -49,6 +49,7 @@
 #include <editeng/contouritem.hxx>
 #include <editeng/shdditem.hxx>
 #include <editeng/autokernitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <editeng/emphasismarkitem.hxx>
 #include <editeng/twolinesitem.hxx>
 #include <editeng/charscaleitem.hxx>
@@ -516,6 +517,9 @@ void RtfAttributeOutput::OutputFormattingItem(const 
SfxPoolItem& item, OStringBu
         case RES_CHRATR_AUTOKERN:
             OutputCharAutoKern(item.StaticWhichCast(RES_CHRATR_AUTOKERN), buf);
             break;
+        case RES_CHRATR_OPTICAL_SIZING:
+            
OutputCharOpticalSizing(item.StaticWhichCast(RES_CHRATR_OPTICAL_SIZING), buf);
+            break;
         case RES_CHRATR_BLINK:
             OutputCharAnimatedText(item.StaticWhichCast(RES_CHRATR_BLINK), 
buf);
             break;
@@ -3205,12 +3209,22 @@ void RtfAttributeOutput::CharAutoKern(const 
SvxAutoKernItem& rAutoKern)
     m_aCharFormatting.Put(rAutoKern);
 }
 
+void RtfAttributeOutput::CharOpticalSizing(const SvxOpticalSizingItem& 
rOpticalSizing)
+{
+    m_aCharFormatting.Put(rOpticalSizing);
+}
+
 void RtfAttributeOutput::OutputCharAutoKern(const SvxAutoKernItem& rAutoKern, 
OStringBuffer& buf)
 {
     buf.append(OOO_STRING_SVTOOLS_RTF_KERNING);
     buf.append(static_cast<sal_Int32>(rAutoKern.GetValue() ? 1 : 0));
 }
 
+void RtfAttributeOutput::OutputCharOpticalSizing(const SvxOpticalSizingItem&, 
OStringBuffer&)
+{
+    // MSOffice has no equivalent for optical sizing, so nothing is exported.
+}
+
 void RtfAttributeOutput::CharAnimatedText(const SvxBlinkItem& rBlink)
 {
     m_aCharFormatting.Put(rBlink);
diff --git a/sw/source/filter/ww8/rtfattributeoutput.hxx 
b/sw/source/filter/ww8/rtfattributeoutput.hxx
index 289773a1477d..8d6aeabe2c5c 100644
--- a/sw/source/filter/ww8/rtfattributeoutput.hxx
+++ b/sw/source/filter/ww8/rtfattributeoutput.hxx
@@ -301,6 +301,9 @@ protected:
     /// Sfx item RES_CHRATR_AUTOKERN
     void CharAutoKern(const SvxAutoKernItem& rAutoKern) override;
 
+    /// Sfx item RES_CHRATR_OPTICAL_SIZING
+    void CharOpticalSizing(const SvxOpticalSizingItem& rOpticalSizing) 
override;
+
     /// Sfx item RES_CHRATR_BLINK
     void CharAnimatedText(const SvxBlinkItem& rBlink) override;
 
@@ -554,6 +557,8 @@ private:
                                   bool assoc) const;
     static void OutputCharWeightAssoc(const SvxWeightItem& rWeight, 
OStringBuffer& buf, bool assoc);
     static void OutputCharAutoKern(const SvxAutoKernItem& rAutoKern, 
OStringBuffer& buf);
+    static void OutputCharOpticalSizing(const SvxOpticalSizingItem& 
rOpticalSizing,
+                                        OStringBuffer& buf);
     static void OutputCharAnimatedText(const SvxBlinkItem& rBlink, 
OStringBuffer& buf);
     void OutputCharBackground(const SvxBrushItem& rBrush, OStringBuffer& buf) 
const;
     static void OutputCharRotate(const SvxCharRotateItem& rRotate, 
OStringBuffer& buf);
diff --git a/sw/source/filter/ww8/ww8atr.cxx b/sw/source/filter/ww8/ww8atr.cxx
index 12d10022332a..414555bf6525 100644
--- a/sw/source/filter/ww8/ww8atr.cxx
+++ b/sw/source/filter/ww8/ww8atr.cxx
@@ -64,6 +64,7 @@
 #include <editeng/contouritem.hxx>
 #include <editeng/shdditem.hxx>
 #include <editeng/autokernitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <editeng/pbinitem.hxx>
 #include <editeng/emphasismarkitem.hxx>
 #include <editeng/twolinesitem.hxx>
@@ -1316,6 +1317,11 @@ void WW8AttributeOutput::CharAutoKern( const 
SvxAutoKernItem& rAutoKern )
     m_rWW8Export.InsUInt16( rAutoKern.GetValue() ? 2 : 0 );
 }
 
+void WW8AttributeOutput::CharOpticalSizing( const SvxOpticalSizingItem& )
+{
+    // MSOffice has no equivalent for optical sizing, so nothing is exported.
+}
+
 void WW8AttributeOutput::CharAnimatedText( const SvxBlinkItem& rBlink )
 {
     m_rWW8Export.InsUInt16( NS_sprm::CSfxText::val );
@@ -5763,6 +5769,9 @@ void AttributeOutputBase::OutputItem( const SfxPoolItem& 
rHt )
         case RES_CHRATR_AUTOKERN:
             CharAutoKern(rHt.StaticWhichCast(RES_CHRATR_AUTOKERN));
             break;
+        case RES_CHRATR_OPTICAL_SIZING:
+            CharOpticalSizing(rHt.StaticWhichCast(RES_CHRATR_OPTICAL_SIZING));
+            break;
         case RES_CHRATR_BLINK:
             CharAnimatedText(rHt.StaticWhichCast(RES_CHRATR_BLINK));
             break;
diff --git a/sw/source/filter/ww8/ww8attributeoutput.hxx 
b/sw/source/filter/ww8/ww8attributeoutput.hxx
index 501959b546d7..7c1ef75cdd06 100644
--- a/sw/source/filter/ww8/ww8attributeoutput.hxx
+++ b/sw/source/filter/ww8/ww8attributeoutput.hxx
@@ -252,6 +252,9 @@ protected:
     /// Sfx item RES_CHRATR_AUTOKERN
     virtual void CharAutoKern( const SvxAutoKernItem& ) override;
 
+    /// Sfx item RES_CHRATR_OPTICAL_SIZING
+    virtual void CharOpticalSizing( const SvxOpticalSizingItem& ) override;
+
     /// Sfx item RES_CHRATR_BLINK
     virtual void CharAnimatedText( const SvxBlinkItem& ) override;
 
diff --git a/sw/source/uibase/app/docshini.cxx 
b/sw/source/uibase/app/docshini.cxx
index 4c24c8706c9c..f7a19b3e72e5 100644
--- a/sw/source/uibase/app/docshini.cxx
+++ b/sw/source/uibase/app/docshini.cxx
@@ -47,6 +47,7 @@
 #include <editeng/orphitem.hxx>
 #include <editeng/widwitem.hxx>
 #include <editeng/hyphenzoneitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <vcl/metric.hxx>
 #include <vcl/rendercontext/GetDefaultFontFlags.hxx>
 #include <vcl/svapp.hxx>
@@ -302,6 +303,9 @@ bool SwDocShell::InitNew( const uno::Reference < 
embed::XStorage >& xStor )
     //#i16874# AutoKerning as default for new documents
     m_xDoc->SetDefault( SvxAutoKernItem( true, RES_CHRATR_AUTOKERN ) );
 
+    // tdf#153368 Optical sizing as default for new documents
+    m_xDoc->SetDefault( SvxOpticalSizingItem( true, RES_CHRATR_OPTICAL_SIZING 
) );
+
     // #i42080# - Due to the several calls of method <SetDefault(..)>
     // at the document instance, the document is modified. Thus, reset this
     // status here. Note: In method <SubInitNew()> this is also done.
diff --git a/vcl/qa/cppunit/pdfexport/data/testOpticalSizing.odt 
b/vcl/qa/cppunit/pdfexport/data/testOpticalSizing.odt
new file mode 100644
index 000000000000..cb9130423caa
Binary files /dev/null and 
b/vcl/qa/cppunit/pdfexport/data/testOpticalSizing.odt differ
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx 
b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
index cc42c417c5b5..71ee4c282fc9 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -2167,6 +2167,45 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, 
testVariableFontPSName2)
 #endif
 }
 
+// This test docuemnt embeds a variable font with opsz axis, and sets the text 
in the same font but
+// different point sizes. The font should be embedded multiple times as 
different instances
+// corresponding to the different opsz values.
+CPPUNIT_TEST_FIXTURE(PdfExportTest, testOpticalSizing)
+{
+// Embedding variable fonts does not work on Linux, only the default instance 
is enumerated
+// https://bugs.documentfoundation.org/show_bug.cgi?id=155853
+#if defined MACOSX || defined _WIN32
+    loadFromFile(u"testOpticalSizing.odt");
+    save(TestFilter::PDF_WRITER);
+
+    vcl::filter::PDFDocument aDocument;
+    SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+    CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+    std::set<OString> aFontNames;
+    for (const auto& aElement : aDocument.GetElements())
+    {
+        auto pObject = 
dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
+        if (!pObject)
+            continue;
+        auto pType = 
dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+        if (pType && pType->GetValue() == "Font")
+        {
+            auto pName
+                = 
dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"_ostr));
+            aFontNames.insert(pName->GetValue().copy(7)); // skip the subset id
+        }
+    }
+
+    std::set<OString> aExpected{ "Fraunces_150opsz_400wght"_ostr, 
"Fraunces_144opsz_400wght"_ostr,
+                                 "Fraunces_80opsz_400wght"_ostr,  
"Fraunces_60opsz_400wght"_ostr,
+                                 "Fraunces_40opsz_400wght"_ostr,  
"Fraunces_20opsz_400wght"_ostr,
+                                 "Fraunces_6opsz_400wght"_ostr,   
"Fraunces-Regular"_ostr };
+
+    CPPUNIT_ASSERT_EQUAL(aExpected, aFontNames);
+#endif
+}
+
 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157679)
 {
     // Import the bugdoc and export as PDF.
commit bbd7f40dd83968093f2e80b5add338d38886ac43
Author:     Khaled Hosny <[email protected]>
AuthorDate: Thu Feb 26 14:10:59 2026 +0200
Commit:     Khaled Hosny <[email protected]>
CommitDate: Sun Mar 1 18:32:50 2026 +0200

    tdf#153368: Support optical size for variable fonts, part 3
    
    Add loext:font-optical-sizing attribute with two values, "auto" and
    "none", modeled after CSS font-optical-sizing:
    
https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-optical-sizing
    
    Change-Id: I05396f084dc81d0a797be0e7c0cd57991a6876e3

diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index 18d855cb5a6d..79e096c4c2b2 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -877,6 +877,7 @@ namespace xmloff::token {
         XML_FONT_NAME,
         XML_FONT_NAME_ASIAN,
         XML_FONT_NAME_COMPLEX,
+        XML_FONT_OPTICAL_SIZING,
         XML_FONT_PITCH,
         XML_FONT_PITCH_ASIAN,
         XML_FONT_PITCH_COMPLEX,
diff --git a/include/xmloff/xmltypes.hxx b/include/xmloff/xmltypes.hxx
index 69b00fa035c8..e734251d0a84 100644
--- a/include/xmloff/xmltypes.hxx
+++ b/include/xmloff/xmltypes.hxx
@@ -303,6 +303,8 @@
 
 #define XML_TYPE_TEXT_SCRIPT_TYPE       (XML_TEXT_TYPES_START + 134)
 
+#define XML_TYPE_TEXT_FONT_OPTICAL_SIZING (XML_TEXT_TYPES_START + 135)
+
 #endif // INCLUDED_XMLOFF_XMLTYPES_HXX
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng 
b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
index a3c7eb909c84..5faa53a792af 100644
--- a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
@@ -4009,6 +4009,18 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
     </rng:optional>
   </rng:define>
 
+  <!-- TODO no proposal -->
+  <rng:define name="style-text-properties-attlist" combine="interleave">
+    <rng:optional>
+      <rng:attribute name="loext:font-optical-sizing">
+        <rng:choice>
+          <rng:value>auto</rng:value>
+          <rng:value>none</rng:value>
+        </rng:choice>
+      </rng:attribute>
+    </rng:optional>
+  </rng:define>
+
   <!-- TODO no proposal -->
   <rng:define name="style-graphic-properties-elements" combine="interleave">
     <rng:optional>
diff --git a/xmloff/inc/xmlprop.hxx b/xmloff/inc/xmlprop.hxx
index c63ec8b44414..c73d8372f691 100644
--- a/xmloff/inc/xmlprop.hxx
+++ b/xmloff/inc/xmlprop.hxx
@@ -98,6 +98,7 @@ inline constexpr OUString PROP_CharLocale = 
u"CharLocale"_ustr;
 inline constexpr OUString PROP_CharLocaleAsian = u"CharLocaleAsian"_ustr;
 inline constexpr OUString PROP_CharLocaleComplex = u"CharLocaleComplex"_ustr;
 inline constexpr OUString PROP_CharNoHyphenation = u"CharNoHyphenation"_ustr;
+inline constexpr OUString PROP_CharOpticalSizing = u"CharOpticalSizing"_ustr;
 inline constexpr OUString PROP_CharOverline = u"CharOverline"_ustr;
 inline constexpr OUString PROP_CharOverlineColor = u"CharOverlineColor"_ustr;
 inline constexpr OUString PROP_CharOverlineHasColor = 
u"CharOverlineHasColor"_ustr;
diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index a6b9c680eb3a..d904495e4fb5 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -889,6 +889,7 @@ namespace xmloff::token {
         TOKEN( "font-name",                       XML_FONT_NAME ),
         TOKEN( "font-name-asian",                 XML_FONT_NAME_ASIAN ),
         TOKEN( "font-name-complex",               XML_FONT_NAME_COMPLEX ),
+        TOKEN( "font-optical-sizing",             XML_FONT_OPTICAL_SIZING ),
         TOKEN( "font-pitch",                      XML_FONT_PITCH ),
         TOKEN( "font-pitch-asian",                XML_FONT_PITCH_ASIAN ),
         TOKEN( "font-pitch-complex",              XML_FONT_PITCH_COMPLEX ),
diff --git a/xmloff/source/text/txtprhdl.cxx b/xmloff/source/text/txtprhdl.cxx
index 7c02820dd5ed..c411fa5be5e3 100644
--- a/xmloff/source/text/txtprhdl.cxx
+++ b/xmloff/source/text/txtprhdl.cxx
@@ -1393,6 +1393,10 @@ static const XMLPropertyHandler *GetPropertyHandler
     case XML_TYPE_TEXT_FONT_RELIEF:
         pHdl = new XMLConstantsPropertyHandler( pXML_FontRelief_Enum, 
XML_TOKEN_INVALID );
         break;
+    case XML_TYPE_TEXT_FONT_OPTICAL_SIZING:
+        pHdl = new XMLNamedBoolPropertyHdl( GetXMLToken( XML_AUTO ),
+                                            GetXMLToken( XML_NONE ) );
+        break;
     case XML_TYPE_TEXT_ROTATION_ANGLE:
         pHdl = new XMLTextRotationAnglePropHdl_Impl;
         break;
diff --git a/xmloff/source/text/txtprmap.cxx b/xmloff/source/text/txtprmap.cxx
index 0374aa514644..7aaeba0f6aa6 100644
--- a/xmloff/source/text/txtprmap.cxx
+++ b/xmloff/source/text/txtprmap.cxx
@@ -253,6 +253,7 @@ XMLPropertyMapEntry constexpr aXMLParaPropMap[] =
     MT_E( PROP_CharScaleWidth, XML_NAMESPACE_STYLE, XML_TEXT_SCALE, 
XML_TYPE_PERCENT16, 0 ),
     //RES_CHRATR_RELIEF
     MT_E( PROP_CharRelief, XML_NAMESPACE_STYLE, XML_FONT_RELIEF, 
XML_TYPE_TEXT_FONT_RELIEF, 0 ),
+    MAP_EXT( PROP_CharOpticalSizing, XML_NAMESPACE_LO_EXT, 
XML_FONT_OPTICAL_SIZING, XML_TYPE_TEXT_FONT_OPTICAL_SIZING | 
XML_TYPE_PROP_TEXT, 0 ),
     // RES_CHRATR_HIDDEN
     MT_E( PROP_CharHidden, XML_NAMESPACE_TEXT, XML_DISPLAY, 
XML_TYPE_TEXT_HIDDEN_AS_DISPLAY|MID_FLAG_SPECIAL_ITEM_IMPORT, CTF_TEXT_DISPLAY 
),
     // RES_CHRATR_OVERLINE
@@ -639,6 +640,7 @@ XMLPropertyMapEntry constexpr aXMLTextPropMap[] =
     MT_E( PROP_, XML_NAMESPACE_STYLE, XML_TEXT_COMBINE, 
XML_TYPE_TEXT_COMBINE_CHARACTERS|MID_FLAG_NO_PROPERTY, 
CTF_COMBINED_CHARACTERS_FIELD ),
     //RES_CHRATR_RELIEF
     MT_E( PROP_CharRelief, XML_NAMESPACE_STYLE, XML_FONT_RELIEF, 
XML_TYPE_TEXT_FONT_RELIEF, 0 ),
+    MAP_EXT( PROP_CharOpticalSizing, XML_NAMESPACE_LO_EXT, 
XML_FONT_OPTICAL_SIZING, XML_TYPE_TEXT_FONT_OPTICAL_SIZING | 
XML_TYPE_PROP_TEXT, 0 ),
     // RES_CHRATR_HIDDEN
     MT_E( PROP_CharHidden, XML_NAMESPACE_TEXT, XML_DISPLAY, 
XML_TYPE_TEXT_HIDDEN_AS_DISPLAY|MID_FLAG_SPECIAL_ITEM_IMPORT, CTF_TEXT_DISPLAY 
),
     // RES_CHRATR_OVERLINE
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index f268760695f5..9735eea566c5 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -794,6 +794,7 @@ font-kerning
 font-name
 font-name-asian
 font-name-complex
+font-optical-sizing
 font-pitch
 font-pitch-asian
 font-pitch-complex
commit e1208fd626b36dec1127f6512b03381f2ed5fc1c
Author:     Khaled Hosny <[email protected]>
AuthorDate: Thu Feb 26 14:10:42 2026 +0200
Commit:     Khaled Hosny <[email protected]>
CommitDate: Sun Mar 1 18:32:50 2026 +0200

    tdf#153368: Support optical size for variable fonts, part 2
    
    Add SvxOpticalSizingItem and EditEngine plumbing. Mostly mindlessly
    copying stuff here. Lets hope for the best.
    
    Change-Id: Idf0b062ff955fa35bd04c8e69c103e15887932f5

diff --git a/editeng/inc/editattr.hxx b/editeng/inc/editattr.hxx
index 210684d90f7f..8f9d2aeba0a6 100644
--- a/editeng/inc/editattr.hxx
+++ b/editeng/inc/editattr.hxx
@@ -394,6 +394,16 @@ public:
 };
 
 
+
+class EditCharAttribOpticalSizing final : public EditCharAttrib
+{
+public:
+    EditCharAttribOpticalSizing(SfxItemPool&, const SfxPoolItem&, sal_Int32 
nStart, sal_Int32 nEnd);
+
+    virtual void    SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override;
+};
+
+
 class EditCharAttribGrabBag final : public EditCharAttrib
 {
 public:
diff --git a/editeng/source/editeng/editattr.cxx 
b/editeng/source/editeng/editattr.cxx
index 06ac807367af..e657acfff90b 100644
--- a/editeng/source/editeng/editattr.cxx
+++ b/editeng/source/editeng/editattr.cxx
@@ -39,6 +39,7 @@
 #include <editeng/emphasismarkitem.hxx>
 #include <editeng/charreliefitem.hxx>
 #include <editeng/cmapitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 
 #include <editattr.hxx>
 
@@ -465,6 +466,19 @@ void EditCharAttribRelief::SetFont( SvxFont& rFont, 
OutputDevice* )
 }
 
 
+
+EditCharAttribOpticalSizing::EditCharAttribOpticalSizing(SfxItemPool& rPool, 
const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+    assert(rItem.Which() == EE_CHAR_OPTICALSIZING);
+}
+
+void EditCharAttribOpticalSizing::SetFont( SvxFont& rFont, OutputDevice* )
+{
+    rFont.SetOpticalSizing( static_cast<const 
SvxOpticalSizingItem*>(GetItem())->GetValue() );
+}
+
+
 EditCharAttribGrabBag::EditCharAttribGrabBag(SfxItemPool& rPool, const 
SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
 : EditCharAttrib(rPool, rItem, _nStart, _nEnd)
 {
diff --git a/editeng/source/editeng/editdoc.cxx 
b/editeng/source/editeng/editdoc.cxx
index 5b88a6095575..8112243c9f5c 100644
--- a/editeng/source/editeng/editdoc.cxx
+++ b/editeng/source/editeng/editdoc.cxx
@@ -45,6 +45,7 @@
 #include <editeng/lrspitem.hxx>
 #include <editeng/ulspitem.hxx>
 #include <editeng/lspcitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #if ENABLE_YRS
 #include <editeng/frmdiritem.hxx>
 #include <editeng/hngpnctitem.hxx>
@@ -321,6 +322,11 @@ EditCharAttrib* MakeCharAttrib( SfxItemPool& rPool, const 
SfxPoolItem& rAttr, sa
             return new EditCharAttribScriptHint(rPool, rAttr, nS, nE);
         }
         break;
+        case EE_CHAR_OPTICALSIZING:
+        {
+            return new EditCharAttribOpticalSizing(rPool, rAttr, nS, nE );
+        }
+        break;
         default:
         break;
     }
@@ -1224,7 +1230,13 @@ void YrsInsertAttribImplImpl(YrsWrite const& yw, 
SfxPoolItem const& rItm,
         // these aren't editable?
 //constexpr TypedWhichId<SvXMLAttrContainerItem> EE_CHAR_XMLATTRIBS     
(EE_CHAR_START+27);
 //constexpr TypedWhichId<SfxGrabBagItem>         EE_CHAR_GRABBAG        
(EE_CHAR_START+30);
-
+        case EE_CHAR_OPTICALSIZING:
+        {
+            SvxOpticalSizingItem const& rItem{static_cast<SvxOpticalSizingItem 
const&>(rItm)};
+            attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE);
+            attrName = "EE_CHAR_OPTICALSIZING";
+            break;
+        }
         default:
             assert(false);
     }
@@ -1581,6 +1593,8 @@ char const* YrsWhichToAttrName(sal_Int16 const nWhich)
             return "EE_PARA_JUST_METHOD";
         case EE_PARA_VER_JUST:
             return "EE_PARA_VER_JUST";
+        case EE_CHAR_OPTICALSIZING:
+            return "EE_CHAR_OPTICALSIZING";
         default:
             assert(false);
             abort();
@@ -1823,6 +1837,10 @@ void YrsImplInsertAttr(SfxItemSet & rSet, 
::std::vector<sal_uInt16> *const pRemo
     {
         nWhich = EE_PARA_VER_JUST;
     }
+    else if (strcmp(pKey, "EE_CHAR_OPTICALSIZING") == 0)
+    {
+        nWhich = EE_CHAR_OPTICALSIZING;
+    }
     else if (pKey[0] == 'E' && pKey[1] == 'E' && pKey[2] == '_')
     {
         abort();
@@ -2454,6 +2472,13 @@ void YrsImplInsertAttr(SfxItemSet & rSet, 
::std::vector<sal_uInt16> *const pRemo
             rSet.Put(item);
             break;
         }
+        case EE_CHAR_OPTICALSIZING:
+        {
+            yvalidate(rValue.tag == Y_JSON_BOOL);
+            SvxOpticalSizingItem const item{rValue.value.flag == Y_TRUE, 
nWhich};
+            rSet.Put(item);
+            break;
+        }
 
         default:
             assert(false);
@@ -3280,6 +3305,8 @@ void CreateFont( SvxFont& rFont, const SfxItemSet& rSet, 
bool bSearchInParent, S
         rFont.SetEmphasisMark( rSet.Get( EE_CHAR_EMPHASISMARK 
).GetEmphasisMark() );
     if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_RELIEF ) == 
SfxItemState::SET ) )
         rFont.SetRelief( rSet.Get( EE_CHAR_RELIEF ).GetValue() );
+    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OPTICALSIZING ) == 
SfxItemState::SET ) )
+        rFont.SetOpticalSizing( rSet.Get( EE_CHAR_OPTICALSIZING ).GetValue() );
 
     // Operator == compares the individual members of the font if the impl 
pointer is
     // not equal. If all members are the same, this assignment makes
diff --git a/editeng/source/editeng/eerdll.cxx 
b/editeng/source/editeng/eerdll.cxx
index c064110c136b..5c7069fe1174 100644
--- a/editeng/source/editeng/eerdll.cxx
+++ b/editeng/source/editeng/eerdll.cxx
@@ -67,6 +67,7 @@
 #include <editeng/xmlcnitm.hxx>
 #include <editeng/forbiddencharacterstable.hxx>
 #include <editeng/justifyitem.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <tools/mapunit.hxx>
 #include <tools/lazydelete.hxx>
 #include <svl/itempool.hxx>
@@ -160,6 +161,7 @@ ItemInfoPackage& getItemInfoPackageEditEngine()
             { EE_CHAR_BKGCOLOR, new SvxColorItem( COL_AUTO, EE_CHAR_BKGCOLOR 
), SID_ATTR_CHAR_BACK_COLOR, SFX_ITEMINFOFLAG_NONE  },
             { EE_CHAR_RUBY, new SvxRubyItem( EE_CHAR_RUBY ), 
SID_ATTR_CHAR_RUBY, SFX_ITEMINFOFLAG_NONE },
             { EE_CHAR_SCRIPT_HINT, new SvxScriptHintItem( EE_CHAR_SCRIPT_HINT 
), SID_ATTR_CHAR_SCRIPT_HINT, SFX_ITEMINFOFLAG_NONE },
+            { EE_CHAR_OPTICALSIZING, new SvxOpticalSizingItem( false, 
EE_CHAR_OPTICALSIZING ), SID_ATTR_CHAR_OPTICAL_SIZING, SFX_ITEMINFOFLAG_NONE },
             { EE_FEATURE_TAB, new SfxVoidItem( EE_FEATURE_TAB ), 0, 
SFX_ITEMINFOFLAG_NONE  },
             { EE_FEATURE_LINEBR, new SfxVoidItem( EE_FEATURE_LINEBR ), 0, 
SFX_ITEMINFOFLAG_NONE  },
             { EE_FEATURE_NOTCONV, new SvxColorItem( COL_RED, 
EE_FEATURE_NOTCONV ), SID_ATTR_CHAR_CHARSETCOLOR, SFX_ITEMINFOFLAG_NONE  },
diff --git a/editeng/source/items/textitem.cxx 
b/editeng/source/items/textitem.cxx
index 0714418d99ab..49927a9868e8 100644
--- a/editeng/source/items/textitem.cxx
+++ b/editeng/source/items/textitem.cxx
@@ -82,6 +82,7 @@
 #include <editeng/itemtype.hxx>
 #include <editeng/scripthintitem.hxx>
 #include <editeng/eerdll.hxx>
+#include <editeng/opticalsizingitem.hxx>
 #include <docmodel/color/ComplexColorJSON.hxx>
 #include <docmodel/uno/UnoComplexColor.hxx>
 #include <docmodel/color/ComplexColor.hxx>
@@ -110,6 +111,7 @@ SfxPoolItem* SvxEmphasisMarkItem::CreateDefault() {return 
new SvxEmphasisMarkIte
 SfxPoolItem* SvxCharRotateItem::CreateDefault() {return new 
SvxCharRotateItem(0_deg10, false, TypedWhichId<SvxCharRotateItem>(0));}
 SfxPoolItem* SvxCharScaleWidthItem::CreateDefault() {return new 
SvxCharScaleWidthItem(100, TypedWhichId<SvxCharScaleWidthItem>(0));}
 SfxPoolItem* SvxCharReliefItem::CreateDefault() {return new 
SvxCharReliefItem(FontRelief::NONE, 0);}
+SfxPoolItem* SvxOpticalSizingItem::CreateDefault() {return new 
SvxOpticalSizingItem(false, 0);}
 
 // class SvxFontListItem -------------------------------------------------
 
@@ -3023,4 +3025,32 @@ void SvxRsidItem::dumpAsXml(xmlTextWriterPtr pWriter) 
const
     (void)xmlTextWriterEndElement(pWriter);
 }
 
+// class SvxOpticalSizingItem --------------------------------------------
+
+SvxOpticalSizingItem::SvxOpticalSizingItem( const bool bOpticalSizing, const 
sal_uInt16 nId ) :
+    SfxBoolItem( nId, bOpticalSizing )
+{
+}
+
+SvxOpticalSizingItem* SvxOpticalSizingItem::Clone( SfxItemPool * ) const
+{
+    return new SvxOpticalSizingItem( *this );
+}
+
+bool SvxOpticalSizingItem::GetPresentation
+(
+    SfxItemPresentation /*ePres*/,
+    MapUnit             /*eCoreUnit*/,
+    MapUnit             /*ePresUnit*/,
+    OUString&           rText, const IntlWrapper& /*rIntl*/
+)   const
+{
+    TranslateId pId = RID_SVXITEMS_OPTICALSIZING_FALSE;
+
+    if ( GetValue() )
+        pId = RID_SVXITEMS_OPTICALSIZING_TRUE;
+    rText = EditResId(pId);
+    return true;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/include/editeng/editids.hrc b/include/editeng/editids.hrc
index c431aeb6760f..d49d486b5e71 100644
--- a/include/editeng/editids.hrc
+++ b/include/editeng/editids.hrc
@@ -68,6 +68,7 @@ class SvxWidowsItem;
 class SvxWordLineModeItem;
 class SvxScriptHintItem;
 class SvxFontListItem;
+class SvxOpticalSizingItem;
 
 /*
   These SID_SVX_START entries came from include/svx/svxids.hrc, avoid
@@ -188,6 +189,7 @@ class SvxFontListItem;
 #define SID_ATTR_PARA_GRABBAG                           
TypedWhichId<SfxGrabBagItem>( SID_EDIT_START + 78 )
 #define SID_ATTR_PARA_ADJUST_START                      ( SID_EDIT_START + 79 )
 #define SID_ATTR_PARA_ADJUST_END                        ( SID_EDIT_START + 80 )
+#define SID_ATTR_CHAR_OPTICAL_SIZING                    
TypedWhichId<SvxOpticalSizingItem>( SID_EDIT_START + 81 )
 
 
 #if 95 > (SID_EDIT_END-SID_EDIT_START)
diff --git a/include/editeng/editrids.hrc b/include/editeng/editrids.hrc
index 7990ae9a1e10..c39979a8f371 100644
--- a/include/editeng/editrids.hrc
+++ b/include/editeng/editrids.hrc
@@ -185,6 +185,8 @@
 #define RID_SVXITEMS_BLINK_FALSE                
NC_("RID_SVXITEMS_BLINK_FALSE", "Not Blinking")
 #define RID_SVXITEMS_AUTOKERN_TRUE              
NC_("RID_SVXITEMS_AUTOKERN_TRUE", "Pair Kerning")
 #define RID_SVXITEMS_AUTOKERN_FALSE             
NC_("RID_SVXITEMS_AUTOKERN_FALSE", "No pair kerning")
+#define RID_SVXITEMS_OPTICALSIZING_TRUE         
NC_("RID_SVXITEMS_OPTICALSIZING_TRUE", "Optical Sizing")
+#define RID_SVXITEMS_OPTICALSIZING_FALSE        
NC_("RID_SVXITEMS_OPTICALSIZING_FALSE", "No optical sizing")
 #define RID_SVXITEMS_NOHYPHENATION_TRUE         
NC_("RID_SVXITEMS_NOHYPHENATION_TRUE", "No hyphenation")
 #define RID_SVXITEMS_WORDLINE_TRUE              
NC_("RID_SVXITEMS_WORDLINE_TRUE", "Individual words")
 #define RID_SVXITEMS_WORDLINE_FALSE             
NC_("RID_SVXITEMS_WORDLINE_FALSE", "Not Words Only")
diff --git a/include/editeng/eeitem.hxx b/include/editeng/eeitem.hxx
index 89b85207a24a..f9ffe91fea14 100644
--- a/include/editeng/eeitem.hxx
+++ b/include/editeng/eeitem.hxx
@@ -63,6 +63,7 @@ class SvxJustifyMethodItem;
 class SvxVerJustifyItem;
 class SvxRubyItem;
 class SvxScriptHintItem;
+class SvxOpticalSizingItem;
 
 /*
  * NOTE: Changes in this file will probably require
@@ -133,8 +134,9 @@ inline constexpr TypedWhichId<SfxGrabBagItem>         
EE_CHAR_GRABBAG        (EE
 inline constexpr TypedWhichId<SvxColorItem>           EE_CHAR_BKGCOLOR       
(EE_CHAR_START+31);
 inline constexpr TypedWhichId<SvxRubyItem>            EE_CHAR_RUBY           
(EE_CHAR_START+32);
 inline constexpr TypedWhichId<SvxScriptHintItem>      EE_CHAR_SCRIPT_HINT    
(EE_CHAR_START+33);
+inline constexpr TypedWhichId<SvxOpticalSizingItem>   EE_CHAR_OPTICALSIZING  
(EE_CHAR_START+34);
 
-inline constexpr sal_uInt16                           EE_CHAR_END            
(EE_CHAR_START + 33);
+inline constexpr sal_uInt16                           EE_CHAR_END            
(EE_CHAR_START + 34);
 
 inline constexpr sal_uInt16 EE_FEATURE_START   (EE_CHAR_END + 1);
 inline constexpr sal_uInt16 EE_FEATURE_TAB     (EE_FEATURE_START + 0);
diff --git a/include/editeng/opticalsizingitem.hxx 
b/include/editeng/opticalsizingitem.hxx
new file mode 100644
index 000000000000..7e1dca8f02ff
--- /dev/null
+++ b/include/editeng/opticalsizingitem.hxx
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <svl/eitem.hxx>
+#include <editeng/editengdllapi.h>
+
+// class SvxOpticalSizingItem ---------------------------------------------
+
+/*
+    [Description]
+    Attribute for Font Optical Sizing.
+*/
+
+class EDITENG_DLLPUBLIC SvxOpticalSizingItem final : public SfxBoolItem
+{
+public:
+    static SfxPoolItem* CreateDefault();
+    DECLARE_ITEM_TYPE_FUNCTION(SvxOpticalSizingItem)
+    SvxOpticalSizingItem(const bool bOpticalSizing /*= false*/, const 
sal_uInt16 nId);
+
+    // "pure virtual Methods" from SfxPoolItem
+    virtual SvxOpticalSizingItem* Clone(SfxItemPool* pPool = nullptr) const 
override;
+
+    virtual bool GetPresentation(SfxItemPresentation ePres, MapUnit 
eCoreMetric,
+                                 MapUnit ePresMetric, OUString& rText,
+                                 const IntlWrapper&) const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/include/svl/poolitem.hxx b/include/svl/poolitem.hxx
index 69bc63eee3e1..ca5a55dd4450 100644
--- a/include/svl/poolitem.hxx
+++ b/include/svl/poolitem.hxx
@@ -311,6 +311,7 @@ enum class SfxItemType : sal_uInt16
     SvxAdjustItemType,
     SvxAutoFrameDirectionItemType,
     SvxAutoKernItemType,
+    SvxOpticalSizingItemType,
     SvxB3DVectorItemType,
     SvxBitmapListItemType,
     SvxBlinkItemType,
commit d623349edd7ab61998eeb1ec50aa5d876dd1c607
Author:     Khaled Hosny <[email protected]>
AuthorDate: Thu Feb 26 14:10:17 2026 +0200
Commit:     Khaled Hosny <[email protected]>
CommitDate: Sun Mar 1 18:32:50 2026 +0200

    tdf#153368: Support optical size for variable fonts, part 1
    
    Font plumbing to enable opsz axis and set it to font’s point size.
    
    Change-Id: I3a4941591e47788dc659ba55d37c9f28d2f267d6

diff --git a/include/vcl/font.hxx b/include/vcl/font.hxx
index a7c1cc3c9659..2e4b20d5d037 100644
--- a/include/vcl/font.hxx
+++ b/include/vcl/font.hxx
@@ -137,6 +137,9 @@ public:
     short               GetFixKerning() const;
     bool                IsFixKerning() const;
 
+    void                SetOpticalSizing( bool bOpticalSizing );
+    bool                GetOpticalSizing() const;
+
     void                SetOutline( bool bOutline );
     bool                IsOutline() const;
     void                SetShadow( bool bShadow );
diff --git a/vcl/inc/font/FontSelectPattern.hxx 
b/vcl/inc/font/FontSelectPattern.hxx
index 8655ec7c4106..9dae3b41b810 100644
--- a/vcl/inc/font/FontSelectPattern.hxx
+++ b/vcl/inc/font/FontSelectPattern.hxx
@@ -68,6 +68,7 @@ public:
     LanguageType    meLanguage;                 // text language
     bool            mbVertical;                 // vertical mode of requested 
font
     bool            mbNonAntialiased;           // true if antialiasing is 
disabled
+    bool            mbOpticalSizing;            // true if optical sizing is 
enabled
 
     bool            mbEmbolden;                 // Force emboldening
     ItalicMatrix    maItalicMatrix;             // Force matrix for slant
diff --git a/vcl/inc/font/LogicalFontInstance.hxx 
b/vcl/inc/font/LogicalFontInstance.hxx
index 34ce611685b6..a11ed8607910 100644
--- a/vcl/inc/font/LogicalFontInstance.hxx
+++ b/vcl/inc/font/LogicalFontInstance.hxx
@@ -108,6 +108,20 @@ public: // TODO: make data members private
     }
     const std::vector<hb_variation_t>& GetVariations() const;
 
+    void SetOpticalSizing(bool bOpticalSizing)
+    {
+        m_bOpticalSizing = bOpticalSizing;
+        mxVariations.reset();
+    }
+    bool GetOpticalSizing() const { return m_bOpticalSizing; }
+
+    void SetPointSize(float fPointSize)
+    {
+        m_fPointSize = fPointSize;
+        mxVariations.reset();
+    }
+    float GetPointSize() const { return m_fPointSize; }
+
     const vcl::font::PhysicalFontFace* GetFontFace() const { return 
m_pFontFace.get(); }
     vcl::font::PhysicalFontFace* GetFontFace() { return m_pFontFace.get(); }
     const ImplFontCache* GetFontCache() const { return mpFontCache; }
@@ -157,6 +171,8 @@ private:
     std::optional<bool> m_xbIsGraphiteFont;
     std::vector<hb_variation_t> m_aVariations;
     mutable std::optional<std::vector<hb_variation_t>> mxVariations;
+    bool m_bOpticalSizing = false;
+    float m_fPointSize = 0;
 
     mutable hb_draw_funcs_t* m_pHbDrawFuncs = nullptr;
     basegfx::B2DPolygon m_aDrawPolygon;
diff --git a/vcl/inc/impfont.hxx b/vcl/inc/impfont.hxx
index 1e697b9ee3d1..0d04e2bc5447 100644
--- a/vcl/inc/impfont.hxx
+++ b/vcl/inc/impfont.hxx
@@ -128,7 +128,8 @@ private:
                         mbConfigLookup:1,   // config lookup should only be 
done once
                         mbShadow:1,
                         mbVertical:1,
-                        mbTransparent:1;    // compatibility, now on output 
device
+                        mbTransparent:1,    // compatibility, now on output 
device
+                        mbOpticalSizing:1;
 
     // deprecated variables - device independent
     Color               maColor;            // compatibility, now on output 
device
diff --git a/vcl/qa/cppunit/complextext.cxx b/vcl/qa/cppunit/complextext.cxx
index 5b4e36ac88ca..68ba2359f3d0 100644
--- a/vcl/qa/cppunit/complextext.cxx
+++ b/vcl/qa/cppunit/complextext.cxx
@@ -24,6 +24,7 @@ static std::ostream& operator<<(std::ostream& rStream, const 
std::vector<double>
 #include <vcl/virdev.hxx>
 // workaround MSVC2015 issue with std::unique_ptr
 #include <sallayout.hxx>
+#include <tools/mapunit.hxx>
 
 
 #if HAVE_MORE_FONTS
@@ -872,4 +873,53 @@ CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testTdf163761)
     }
 }
 
+// Load a variable font with opsz axis, and enable optical sizing.
+// In this particular font, at the two ends of the opsz axis, some characters 
produce different
+// glyphs. So we check that the glyphs are different for different point sizes.
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testOpticalSizing)
+{
+#if HAVE_MORE_FONTS
+    ScopedVclPtrInstance<VirtualDevice> pOutDev;
+    pOutDev->SetMapMode(MapMode(MapUnit::MapPoint));
+
+    bool bAdded = addFont(pOutDev, u"Fraunces-VariableFont_opsz,wght.ttf", 
u"Fraunces");
+    CPPUNIT_ASSERT_EQUAL(true, bAdded);
+
+    auto aText = u"nh"_ustr;
+
+    // Test with small font size
+    vcl::Font aFont1{ u"Fraunces"_ustr, u"Regular"_ustr, Size{ 0, 9 } };
+    aFont1.SetOpticalSizing(true);
+    pOutDev->SetFont(aFont1);
+
+    auto pLayout1 = pOutDev->ImplLayout(aText, /*nIndex*/ 0, /*nLen*/ 
aText.getLength());
+
+    std::vector<sal_GlyphId> aGlyphs1;
+    const GlyphItem* pGlyph = nullptr;
+    basegfx::B2DPoint stPos;
+    int nCurrPos = 0;
+    while (pLayout1->GetNextGlyph(&pGlyph, stPos, nCurrPos))
+        aGlyphs1.push_back(pGlyph->glyphId());
+
+    // Test with large font size
+    vcl::Font aFont2{ u"Fraunces"_ustr, u"Regular"_ustr, Size{ 0, 144 } };
+    aFont2.SetOpticalSizing(true);
+    pOutDev->SetFont(aFont2);
+
+    auto pLayout2 = pOutDev->ImplLayout(aText, /*nIndex*/ 0, /*nLen*/ 
aText.getLength());
+
+    std::vector<sal_GlyphId> aGlyphs2;
+    pGlyph = nullptr;
+    nCurrPos = 0;
+    while (pLayout2->GetNextGlyph(&pGlyph, stPos, nCurrPos))
+        aGlyphs2.push_back(pGlyph->glyphId());
+
-e 
... etc. - the rest is truncated

Reply via email to