filter/qa/unit/data/fit-to-size-text.fodg   |   35 ++++++++++++++++
 filter/qa/unit/svg.cxx                      |   16 +++++++
 filter/source/svg/svgwriter.cxx             |   29 ++++++++++----
 filter/source/svg/svgwriter.hxx             |    4 -
 sw/inc/IDocumentMarkAccess.hxx              |   14 ++++++
 sw/qa/extras/unowriter/data/bookmark_1.html |    4 +
 sw/qa/extras/unowriter/unowriter.cxx        |   37 +++++++++++++++++
 sw/source/core/doc/docbm.cxx                |   58 +++++++++++++++++++++++++---
 sw/source/core/inc/MarkManager.hxx          |   14 ++++++
 sw/source/core/unocore/unobkm.cxx           |    5 --
 sw/source/filter/basflt/shellio.cxx         |   10 ++++
 11 files changed, 204 insertions(+), 22 deletions(-)

New commits:
commit e2c5108a11431ac83da7eccb3bbeb6a113314dcd
Author:     Mike Kaganski <[email protected]>
AuthorDate: Fri May 30 14:46:22 2025 +0500
Commit:     Mike Kaganski <[email protected]>
CommitDate: Tue Jun 10 20:30:29 2025 +0500

    tdf#166789: use text width from DXArray in MetaActionType::TEXTARRAY
    
    DXArray gives much more reliable data compared to font width, which
    is known to require complex corrections (see tdf#127471 and commit
    3d33e4ce3987ea17e73a72e84f7f0df7af8101a6).
    
    This fix uses tspan's `lengthAdjust` and `textLength` attributes for
    automatic justification, which are already documented as early as in
    SVG 1.0 ( https://www.w3.org/TR/SVG10/text.html#TSpanElement ). This
    works in LibreOffice and in Chrome, but is not supported in Firefox
    yet ( see https://bugzilla.mozilla.org/show_bug.cgi?id=890692 ).
    
    I considered using these attributes on `text` element level, which
    is supported in both browsers - but that breaks text elements with
    multiple tspans. The `transform` attribute can't be used with tspan
    both in Chrome and in Firefox, which creates the same problem, and
    has an additional drawback that tspan offsets need tweaks.
    
    Change-Id: I7d4bbb7542a9d60e53bdf6f6cac7fae0729e6e48
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186033
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <[email protected]>

diff --git a/filter/qa/unit/data/fit-to-size-text.fodg 
b/filter/qa/unit/data/fit-to-size-text.fodg
new file mode 100644
index 000000000000..04e988b48971
--- /dev/null
+++ b/filter/qa/unit/data/fit-to-size-text.fodg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 
office:version="1.4" 
office:mimetype="application/vnd.oasis.opendocument.graphics">
+ <office:font-face-decls>
+  <style:font-face style:name="Liberation Sans" 
svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="roman" 
style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:style style:name="standard" style:family="graphic">
+   <style:graphic-properties draw:stroke="solid" svg:stroke-color="#000000" 
svg:stroke-width="5" draw:fill="none" fo:padding-top="0" fo:padding-bottom="0" 
fo:padding-left="0" fo:padding-right="0"/>
+   <style:text-properties style:font-name="Liberation Sans" 
fo:font-size="18pt" fo:font-variant="normal" fo:language="zxx" 
fo:country="none" fo:font-style="normal" style:letter-kerning="true"/>
+  </style:style>
+ </office:styles>
+ <office:automatic-styles>
+  <style:page-layout style:name="PM0">
+   <style:page-layout-properties fo:margin-top="0" fo:margin-bottom="0" 
fo:margin-left="0" fo:margin-right="0" fo:page-width="8cm" 
fo:page-height="15mm"/>
+  </style:page-layout>
+  <style:style style:name="gr1" style:family="graphic" 
style:parent-style-name="standard">
+   <style:graphic-properties draw:auto-grow-height="false" 
draw:auto-grow-width="false" draw:fit-to-size="true" 
style:shrink-to-fit="false"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Standard" style:page-layout-name="PM0"/>
+ </office:master-styles>
+ <office:body>
+  <office:drawing>
+   <draw:page draw:name="page1" draw:master-page-name="Standard">
+    <draw:frame draw:style-name="gr1" svg:width="7cm" svg:height="5mm" 
svg:x="5mm" svg:y="5mm">
+     <draw:text-box>
+      <text:p>= Foo(Bar).method&lt;templateArg&gt;(arg1, arg2, ...)</text:p>
+     </draw:text-box>
+    </draw:frame>
+   </draw:page>
+  </office:drawing>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/filter/qa/unit/svg.cxx b/filter/qa/unit/svg.cxx
index 617213933ff0..58e66ea19917 100644
--- a/filter/qa/unit/svg.cxx
+++ b/filter/qa/unit/svg.cxx
@@ -20,6 +20,7 @@
 #include <com/sun/star/beans/XPropertySet.hpp>
 
 #include <comphelper/propertyvalue.hxx>
+#include <o3tl/string_view.hxx>
 #include <unotools/streamwrap.hxx>
 #include <unotools/mediadescriptor.hxx>
 #include <tools/stream.hxx>
@@ -312,6 +313,21 @@ CPPUNIT_TEST_FIXTURE(SvgFilterTest, textInImage)
     // - Actual  : 0
 }
 
+CPPUNIT_TEST_FIXTURE(SvgFilterTest, testTdf166789)
+{
+    // A fit-to-size text
+    loadFromFile(u"fit-to-size-text.fodg");
+
+    save(u"impress_svg_Export"_ustr);
+
+    xmlDocUniquePtr pXmlDoc = parseExportedFile();
+
+    // Without the accompanying fix, the text wasn't adjusted to the given 
width
+    OUString length = getXPath(pXmlDoc, 
"//svg:text//svg:tspan[@lengthAdjust='spacingAndGlyphs']",
+                               "textLength");
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(7000, length.toInt32(), 100); // allow ~1.5% 
for rounding errors
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx
index 23c9bc1af23e..5c9249a06aa9 100644
--- a/filter/source/svg/svgwriter.cxx
+++ b/filter/source/svg/svgwriter.cxx
@@ -1578,8 +1578,7 @@ void SVGTextWriter::implWriteEmbeddedBitmaps()
 }
 
 
-void SVGTextWriter::writeTextPortion( const Point& rPos,
-                                      const OUString& rText )
+void SVGTextWriter::writeTextPortion(const Point& rPos, const OUString& rText, 
tools::Long nWidth)
 {
     if( rText.isEmpty() )
         return;
@@ -1658,7 +1657,7 @@ void SVGTextWriter::writeTextPortion( const Point& rPos,
         // to be implemented
     }
 #else
-    implWriteTextPortion( rPos, rText, mpVDev->GetTextColor() );
+    implWriteTextPortion( rPos, rText, mpVDev->GetTextColor(), nWidth );
 #endif
 
     if( bStandAloneTextPortion )
@@ -1668,9 +1667,8 @@ void SVGTextWriter::writeTextPortion( const Point& rPos,
 }
 
 
-void SVGTextWriter::implWriteTextPortion( const Point& rPos,
-                                          const OUString& rText,
-                                          Color aTextColor )
+void SVGTextWriter::implWriteTextPortion(const Point& rPos, const OUString& 
rText, Color aTextColor,
+                                         tools::Long nWidth)
 {
     Point                                   aPos;
     Point                                   aBaseLinePos( rPos );
@@ -1763,6 +1761,18 @@ void SVGTextWriter::implWriteTextPortion( const Point& 
rPos,
 
     addFontAttributes( /* isTexTContainer: */ false );
 
+    tools::Long nTextWidth;
+    if (nWidth)
+    {
+        Size size;
+        implMap(Size(nWidth, 0), size);
+        nTextWidth = size.Width();
+        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"lengthAdjust"_ustr, 
u"spacingAndGlyphs"_ustr);
+        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"textLength"_ustr, 
OUString::number(nTextWidth));
+    }
+    else
+        nTextWidth = mpVDev->GetTextWidth(rText);
+
     if (!maTextOpacity.isEmpty())
     {
         mrExport.AddAttribute(XML_NAMESPACE_NONE, "fill-opacity", 
maTextOpacity);
@@ -1798,7 +1808,7 @@ void SVGTextWriter::implWriteTextPortion( const Point& 
rPos,
         mrExport.GetDocHandler()->characters( rText );
     }
 
-    mnTextWidth += mpVDev->GetTextWidth( rText );
+    mnTextWidth += nTextWidth;
 }
 
 
@@ -3953,7 +3963,10 @@ void SVGActionWriter::ImplWriteActions( const 
GDIMetaFile& rMtf,
                         }
                         else
                         {
-                            maTextWriter.writeTextPortion( pA->GetPoint(), 
aText );
+                            tools::Long nWidth = 0;
+                            if (pA->GetDXArray().size() >= 
o3tl::make_unsigned(aText.getLength()))
+                                nWidth = 
std::round(pA->GetDXArray()[aText.getLength() - 1]);
+                            maTextWriter.writeTextPortion(pA->GetPoint(), 
aText, nWidth);
                         }
                     }
                 }
diff --git a/filter/source/svg/svgwriter.hxx b/filter/source/svg/svgwriter.hxx
index 85c931fb1763..4bf5d8c5e6d1 100644
--- a/filter/source/svg/svgwriter.hxx
+++ b/filter/source/svg/svgwriter.hxx
@@ -264,9 +264,9 @@ class SVGTextWriter final
     template< typename MetaBitmapActionType >
     void writeBitmapPlaceholder( const MetaBitmapActionType* pAction );
     void implWriteEmbeddedBitmaps();
-    void writeTextPortion( const Point& rPos, const OUString& rText );
+    void writeTextPortion(const Point& rPos, const OUString& rText, 
tools::Long nWidth = 0);
     void implWriteTextPortion( const Point& rPos, const OUString& rText,
-                               Color aTextColor );
+                               Color aTextColor, tools::Long nWidth );
 
     void setVirtualDevice( VirtualDevice* pVDev, MapMode& rTargetMapMode )
     {
commit db014a93ef7ac46033d6bb9be2ad473d23efaa7e
Author:     Mike Kaganski <[email protected]>
AuthorDate: Thu May 15 17:45:55 2025 +0500
Commit:     Mike Kaganski <[email protected]>
CommitDate: Tue Jun 10 20:30:28 2025 +0500

    Related: tdf#165918 Avoid renaming pre-existing bookmarks
    
    Thanks Michael Stahl, for pointing this:
    
    > what happens when you first insert a bookmark, then insert a file that
    > contains a bookmark with the same name at a place in the document before
    > the pre-existing one - the SwHistoryBookmark uses the name of the bookmark
    > to find it, and this loop would rename the pre-exsiting bookmark, so on
    > undo of insert bookmark it can't be found?
    
    ( https://gerrit.libreoffice.org/c/core/+/185340/comment/274a3fba_aa363535/ 
)
    
    This change avoids this problem by preparing a set of already existing names
    at the start of the performance mode, and only checking the marks that were
    added while in the performance mode.
    
    Change-Id: Id4dc1809871fd4a998396d091f2f920ecd71b7d9
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185363
    Tested-by: Jenkins
    Reviewed-by: Michael Stahl <[email protected]>
    Reviewed-by: Mike Kaganski <[email protected]>

diff --git a/sw/qa/extras/unowriter/data/bookmark_1.html 
b/sw/qa/extras/unowriter/data/bookmark_1.html
new file mode 100644
index 000000000000..225817dc6729
--- /dev/null
+++ b/sw/qa/extras/unowriter/data/bookmark_1.html
@@ -0,0 +1,4 @@
+<html>
+<body><a name="Bookmark 1">abc</a>
+</body>
+</html>
\ No newline at end of file
diff --git a/sw/qa/extras/unowriter/unowriter.cxx 
b/sw/qa/extras/unowriter/unowriter.cxx
index 565d1b6d9433..c02233e3382c 100644
--- a/sw/qa/extras/unowriter/unowriter.cxx
+++ b/sw/qa/extras/unowriter/unowriter.cxx
@@ -16,6 +16,7 @@
 #include <com/sun/star/frame/XDispatchProviderInterception.hpp>
 #include <com/sun/star/frame/XDispatchProviderInterceptor.hpp>
 #include <com/sun/star/table/XCellRange.hpp>
+#include <com/sun/star/text/ControlCharacter.hpp>
 #include <com/sun/star/text/TextContentAnchorType.hpp>
 #include <com/sun/star/text/AutoTextContainer.hpp>
 #include <com/sun/star/text/VertOrientation.hpp>
@@ -1353,6 +1354,42 @@ CPPUNIT_TEST_FIXTURE(SwUnoWriter, testTdf164885)
     CPPUNIT_ASSERT_EQUAL(u".uno:Open"_ustr, 
interceptor->pDispatch->sLastCommand);
 }
 
+CPPUNIT_TEST_FIXTURE(SwUnoWriter, testMarkWithPreexistingNameInsertion)
+{
+    createSwDoc();
+
+    // Add a second paragraph, and create a bookmark there, with a specific 
name
+    auto xTextDocument = mxComponent.queryThrow<text::XTextDocument>();
+    auto xText = xTextDocument->getText();
+    xText->insertControlCharacter(xText->getEnd(), 
text::ControlCharacter::PARAGRAPH_BREAK, false);
+
+    auto xFac = mxComponent.queryThrow<lang::XMultiServiceFactory>();
+    auto xMark = xFac->createInstance(u"com.sun.star.text.Bookmark"_ustr);
+    auto xNamed = xMark.queryThrow<container::XNamed>();
+    xNamed->setName(u"Bookmark 1"_ustr);
+    xText->insertTextContent(xText->getEnd(), 
xMark.queryThrow<text::XTextContent>(), false);
+
+    // Insert the content of a file, which has a bookmark with the same name, 
before existing one
+    dispatchCommand(
+        mxComponent, u".uno:InsertDoc"_ustr,
+        { comphelper::makePropertyValue(u"Name"_ustr, 
createFileURL(u"bookmark_1.html")) });
+
+    // The pre-existing bookmark's name must not change
+    // Before the fix, this would fail with "Actual  : Bookmark 1 Copy 1"
+    CPPUNIT_ASSERT_EQUAL(u"Bookmark 1"_ustr, xNamed->getName());
+
+    auto xSupplier = mxComponent.queryThrow<text::XBookmarksSupplier>();
+    auto xBookmarks = xSupplier->getBookmarks();
+    auto names = xBookmarks->getElementNames();
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), names.getLength());
+    // names[1] is the pre-existing bookmark
+    CPPUNIT_ASSERT_EQUAL(u"Bookmark 1"_ustr, names[1]);
+    // names[0] is the bookmark coming from the inserted file
+    OUString rest;
+    CPPUNIT_ASSERT(names[0].startsWith("Bookmark 1", &rest));
+    CPPUNIT_ASSERT(!rest.isEmpty()); // should be " Copy 1"
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/doc/docbm.cxx b/sw/source/core/doc/docbm.cxx
index d51e76725e1c..57729c703cfd 100644
--- a/sw/source/core/doc/docbm.cxx
+++ b/sw/source/core/doc/docbm.cxx
@@ -565,19 +565,37 @@ namespace sw::mark
         , m_pLastActiveFieldmark(nullptr)
     { }
 
-    void MarkManager::disableUniqueNameChecks() { m_bCheckUniqueNames = false; 
}
+    // In the mode where the names are not checked, we need to avoid a case 
where there was a
+    // bookmark, and a file is inserted at an earlier point, with the 
same-name bookmark, causing
+    // a rename of the pre-existing bookmark. m_aUsedNames and 
m_vUncheckedNameMarks are used for
+    // that. m_aUsedNames is pre-populated with the existing names (at the 
moment when the mode
+    // started); and m_vUncheckedNameMarks stores only the marks needing the 
checks.
+
+    void MarkManager::disableUniqueNameChecks()
+    {
+        if (!m_bCheckUniqueNames)
+            return; // nested call
+        m_bCheckUniqueNames = false;
+        assert(m_aUsedNames.empty());
+
+        // Populate the pre-existing already deduplicated names
+        for (auto& pMark : m_vAllMarks)
+            m_aUsedNames.insert(pMark->GetName());
+    }
     void MarkManager::enableUniqueNameChecks()
     {
         if (m_bCheckUniqueNames)
             return;
-        // Make sure that all names are unique
-        std::unordered_set<OUString> usedNames;
+        // Make sure that all previously unchecked names are unique
         for (auto& pMark : m_vAllMarks)
         {
-            assert(pMark);
-            pMark->SetName(getUniqueMarkName(pMark->GetName(), 
[&usedNames](const OUString& n)
-                                             { return 
usedNames.insert(n).second; }));
+            if (!m_vUncheckedNameMarks.contains(pMark))
+                continue; // mark was added and removed while in the 
performance mode
+            pMark->SetName(getUniqueMarkName(pMark->GetName(), [this](const 
OUString& n)
+                                             { return 
m_aUsedNames.insert(n).second; }));
         }
+        m_aUsedNames.clear();
+        m_vUncheckedNameMarks.clear();
         m_bCheckUniqueNames = true;
     }
 
@@ -697,10 +715,19 @@ namespace sw::mark
             pMark->Swap();
 
         // for performance reasons, we trust UnoMarks to have a (generated) 
unique name
-        if (eType != IDocumentMarkAccess::MarkType::UNO_BOOKMARK && 
m_bCheckUniqueNames)
-            pMark->SetName(getUniqueMarkName(
-                pMark->GetName(), [this](const OUString& n)
-                { return lcl_FindMarkByName(n, m_vAllMarks.begin(), 
m_vAllMarks.end()) == m_vAllMarks.end(); }));
+        if (eType != IDocumentMarkAccess::MarkType::UNO_BOOKMARK)
+        {
+            if (m_bCheckUniqueNames)
+            {
+                pMark->SetName(getUniqueMarkName(
+                    pMark->GetName(), [this](const OUString& n)
+                    { return lcl_FindMarkByName(n, m_vAllMarks.begin(), 
m_vAllMarks.end()) == m_vAllMarks.end(); }));
+            }
+            else
+            {
+                m_vUncheckedNameMarks.insert(pMark.get());
+            }
+        }
 
         // insert any dummy chars before inserting into sorted vectors
         pMark->InitDoc(m_rDoc, eMode, pSepPos);
@@ -1411,6 +1438,7 @@ namespace sw::mark
         m_vFieldmarks.clear();
         m_vBookmarks.clear();
         m_vAnnotationMarks.clear();
+        m_vUncheckedNameMarks.clear();
         for (const auto & p : m_vAllMarks)
             delete p;
         m_vAllMarks.clear();
diff --git a/sw/source/core/inc/MarkManager.hxx 
b/sw/source/core/inc/MarkManager.hxx
index 2f8c3aecff65..bb03ee530666 100644
--- a/sw/source/core/inc/MarkManager.hxx
+++ b/sw/source/core/inc/MarkManager.hxx
@@ -152,6 +152,11 @@ namespace sw::mark {
             // container for all marks, this container owns the objects it 
points to
             container_t m_vAllMarks;
 
+            // container for all marks with possibly duplicating names 
(m_bCheckUniqueNames mode)
+            std::unordered_set<sw::mark::MarkBase*> m_vUncheckedNameMarks;
+            // container for deduplicating names (m_bCheckUniqueNames mode)
+            std::unordered_set<OUString> m_aUsedNames;
+
             // additional container for bookmarks
             container_t m_vBookmarks;
             // additional container for fieldmarks
diff --git a/sw/source/filter/basflt/shellio.cxx 
b/sw/source/filter/basflt/shellio.cxx
index 98ac9340618b..48e494b33b0d 100644
--- a/sw/source/filter/basflt/shellio.cxx
+++ b/sw/source/filter/basflt/shellio.cxx
@@ -18,6 +18,7 @@
  */
 
 #include <hintids.hxx>
+#include <comphelper/scopeguard.hxx>
 #include <osl/diagnose.h>
 #include <tools/date.hxx>
 #include <tools/time.hxx>
@@ -201,13 +202,14 @@ ErrCodeMsg SwReader::Read( const Reader& rOptions )
 
         mxDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
 
-        // Preformance mode: import all bookmarks names as defined in the 
document
-        mxDoc->getIDocumentMarkAccess()->disableUniqueNameChecks();
-
-        nError = po->Read( *mxDoc, msBaseURL, *pPam, maFileName );
+        {
+            // Preformance mode: import all bookmarks names as defined in the 
document
+            mxDoc->getIDocumentMarkAccess()->disableUniqueNameChecks();
+            comphelper::ScopeGuard perfModeGuard(
+                [this]() { 
mxDoc->getIDocumentMarkAccess()->enableUniqueNameChecks(); });
 
-        // End performance mode: now make sure that all names are unique
-        mxDoc->getIDocumentMarkAccess()->enableUniqueNameChecks();
+            nError = po->Read(*mxDoc, msBaseURL, *pPam, maFileName);
+        }
 
         // an ODF document may contain redline mode in settings.xml; save it!
         ePostReadRedlineFlags = 
mxDoc->getIDocumentRedlineAccess().GetRedlineFlags();
commit b6feb8dd390d6bc1015069abf1e4d583c82d8cfb
Author:     Mike Kaganski <[email protected]>
AuthorDate: Thu May 15 09:55:49 2025 +0500
Commit:     Mike Kaganski <[email protected]>
CommitDate: Tue Jun 10 20:30:28 2025 +0500

    tdf#165918: speed up mark names uniqueness check
    
    MarkManager::makeMark call can be quite expensive, when it needs to
    ensure that the passed name is unique. It needs to iterate over all
    the existing marks in m_vAllMarks, checking their names. At import
    time, this creates quadratic complexity, even when the passed names
    are already all unique.
    
    This change introduces a dedicated performance mode, when names are
    not deduplicated in load time. When the mode is ended, an optimized
    loop is performed, using a set of names for much faster uniqueness
    check. Depending on the number of bookmarks, I saw the improvement
    of the test documents loading times up to 40% (like 190 s -> 115 s;
    of course, realistic documents would have more modest improvements).
    
    Change-Id: I4d624ea6cd7268e7bcfdefecac6d3f1bb58edc28
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185340
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <[email protected]>

diff --git a/sw/inc/IDocumentMarkAccess.hxx b/sw/inc/IDocumentMarkAccess.hxx
index bc8f2e1c03d6..2fdaf39fbbb6 100644
--- a/sw/inc/IDocumentMarkAccess.hxx
+++ b/sw/inc/IDocumentMarkAccess.hxx
@@ -146,6 +146,20 @@ class IDocumentMarkAccess
             const SwPaM& rPaM,
             const OUString& rName ) = 0;
 
+        /** A performance optimization mode
+
+           Creating and inserting a lot of marks, the checks in makeMark if 
name is unique may
+           become a bottleneck, because there we have to iterate over all 
marks, checking their
+           names, which creates a quadratic complexity. This may e.g. slow 
down loading documents
+           with thousands of bookmarks.
+
+           When the check is disabled using disableUniqueNameChecks, duplicate 
names are allowed.
+           When the check is eventually enabled using enableUniqueNameChecks, 
one pass over all
+           marks is performed, and all duplicated names are made unique.
+        */
+        virtual void disableUniqueNameChecks() = 0;
+        virtual void enableUniqueNameChecks() = 0;
+
         /** Returns a mark in the document for a paragraph.
             If there is none, a mark will be created.
 
diff --git a/sw/source/core/doc/docbm.cxx b/sw/source/core/doc/docbm.cxx
index 730da32625d4..d51e76725e1c 100644
--- a/sw/source/core/doc/docbm.cxx
+++ b/sw/source/core/doc/docbm.cxx
@@ -565,6 +565,22 @@ namespace sw::mark
         , m_pLastActiveFieldmark(nullptr)
     { }
 
+    void MarkManager::disableUniqueNameChecks() { m_bCheckUniqueNames = false; 
}
+    void MarkManager::enableUniqueNameChecks()
+    {
+        if (m_bCheckUniqueNames)
+            return;
+        // Make sure that all names are unique
+        std::unordered_set<OUString> usedNames;
+        for (auto& pMark : m_vAllMarks)
+        {
+            assert(pMark);
+            pMark->SetName(getUniqueMarkName(pMark->GetName(), 
[&usedNames](const OUString& n)
+                                             { return 
usedNames.insert(n).second; }));
+        }
+        m_bCheckUniqueNames = true;
+    }
+
     ::sw::mark::IMark* MarkManager::makeMark(const SwPaM& rPaM,
         const OUString& rName,
         const IDocumentMarkAccess::MarkType eType,
@@ -681,8 +697,10 @@ namespace sw::mark
             pMark->Swap();
 
         // for performance reasons, we trust UnoMarks to have a (generated) 
unique name
-        if ( eType != IDocumentMarkAccess::MarkType::UNO_BOOKMARK )
-            pMark->SetName( getUniqueMarkName( pMark->GetName() ) );
+        if (eType != IDocumentMarkAccess::MarkType::UNO_BOOKMARK && 
m_bCheckUniqueNames)
+            pMark->SetName(getUniqueMarkName(
+                pMark->GetName(), [this](const OUString& n)
+                { return lcl_FindMarkByName(n, m_vAllMarks.begin(), 
m_vAllMarks.end()) == m_vAllMarks.end(); }));
 
         // insert any dummy chars before inserting into sorted vectors
         pMark->InitDoc(m_rDoc, eMode, pSepPos);
@@ -1800,7 +1818,9 @@ namespace sw::mark
         }
     }
 
-    OUString MarkManager::getUniqueMarkName(const OUString& rName) const
+    template <class IsNameUniqueFunc>
+        requires std::is_invocable_r_v<bool, IsNameUniqueFunc, const OUString&>
+    OUString MarkManager::getUniqueMarkName(const OUString& rName, 
IsNameUniqueFunc f) const
     {
         OSL_ENSURE(rName.getLength(),
             "<MarkManager::getUniqueMarkName(..)> - a name should be 
proposed");
@@ -1812,7 +1832,7 @@ namespace sw::mark
             return newName;
         }
 
-        if (lcl_FindMarkByName(rName, m_vAllMarks.begin(), m_vAllMarks.end()) 
== m_vAllMarks.end())
+        if (f(rName))
         {
             return rName;
         }
@@ -1830,7 +1850,7 @@ namespace sw::mark
         {
             sTmp = aPrefix + OUString::number(nCnt);
             nCnt++;
-            if (lcl_FindMarkByName(sTmp, m_vAllMarks.begin(), 
m_vAllMarks.end()) == m_vAllMarks.end())
+            if (f(sTmp))
             {
                 break;
             }
diff --git a/sw/source/core/inc/MarkManager.hxx 
b/sw/source/core/inc/MarkManager.hxx
index dd7eb9f6f18b..2f8c3aecff65 100644
--- a/sw/source/core/inc/MarkManager.hxx
+++ b/sw/source/core/inc/MarkManager.hxx
@@ -58,6 +58,9 @@ namespace sw::mark {
                 const SwPaM& rPaM,
                 const OUString& rName ) override;
 
+            virtual void disableUniqueNameChecks() override;
+            virtual void enableUniqueNameChecks() override;
+
             virtual void repositionMark(::sw::mark::IMark* io_pMark, const 
SwPaM& rPaM) override;
             virtual bool renameMark(::sw::mark::IMark* io_pMark, const 
OUString& rNewName) override;
             virtual void correctMarksAbsolute(const SwNode& rOldNode, const 
SwPosition& rNewPos, const sal_Int32 nOffset) override;
@@ -139,7 +142,9 @@ namespace sw::mark {
             MarkManager& operator=(MarkManager const&) = delete;
 
             // make names
-            OUString getUniqueMarkName(const OUString& rName) const;
+            template <class IsNameUniqueFunc>
+                requires std::is_invocable_r_v<bool, IsNameUniqueFunc, const 
OUString&>
+            OUString getUniqueMarkName(const OUString& rName, IsNameUniqueFunc 
f) const;
 
             void sortSubsetMarks();
             void sortMarks();
@@ -160,6 +165,8 @@ namespace sw::mark {
             SwDoc& m_rDoc;
 
             sw::mark::FieldmarkWithDropDownButton* m_pLastActiveFieldmark;
+
+            bool m_bCheckUniqueNames = true;
     };
 
 }
diff --git a/sw/source/core/unocore/unobkm.cxx 
b/sw/source/core/unocore/unobkm.cxx
index 81818a13d03a..e7cfdf8e3843 100644
--- a/sw/source/core/unocore/unobkm.cxx
+++ b/sw/source/core/unocore/unobkm.cxx
@@ -168,11 +168,6 @@ rtl::Reference<SwXBookmark> SwXBookmark::CreateXBookmark(
     }
     if (!xBookmark.is())
     {
-        OSL_ENSURE(!pBookmark ||
-            dynamic_cast< ::sw::mark::IBookmark* >(pBookmark) ||
-            IDocumentMarkAccess::GetType(*pBookmark) == 
IDocumentMarkAccess::MarkType::ANNOTATIONMARK,
-            "<SwXBookmark::GetObject(..)>"
-            "SwXBookmark requested for non-bookmark mark and non-annotation 
mark.");
         SwXBookmark *const pXBookmark =
             pBookmark ? new SwXBookmark(&rDoc) : new SwXBookmark;
         xBookmark.set(pXBookmark);
diff --git a/sw/source/filter/basflt/shellio.cxx 
b/sw/source/filter/basflt/shellio.cxx
index a092b8e61a9a..98ac9340618b 100644
--- a/sw/source/filter/basflt/shellio.cxx
+++ b/sw/source/filter/basflt/shellio.cxx
@@ -201,8 +201,14 @@ ErrCodeMsg SwReader::Read( const Reader& rOptions )
 
         mxDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
 
+        // Preformance mode: import all bookmarks names as defined in the 
document
+        mxDoc->getIDocumentMarkAccess()->disableUniqueNameChecks();
+
         nError = po->Read( *mxDoc, msBaseURL, *pPam, maFileName );
 
+        // End performance mode: now make sure that all names are unique
+        mxDoc->getIDocumentMarkAccess()->enableUniqueNameChecks();
+
         // an ODF document may contain redline mode in settings.xml; save it!
         ePostReadRedlineFlags = 
mxDoc->getIDocumentRedlineAccess().GetRedlineFlags();
 

Reply via email to