include/vcl/pdfextoutdevdata.hxx         |    5 
 sc/qa/extras/scpdfexport.cxx             |   96 +++++++++
 sc/qa/extras/testdocuments/tdf123870.ods |binary
 sc/source/ui/inc/output.hxx              |   15 +
 sc/source/ui/unoobj/docuno.cxx           |  305 +++++++++++++++++++------------
 sc/source/ui/view/output.cxx             |   76 +++++++
 sc/source/ui/view/output2.cxx            |   68 ++++++
 sc/source/ui/view/printfun.cxx           |   51 +++++
 8 files changed, 495 insertions(+), 121 deletions(-)

New commits:
commit b3c93b16d62e809500005edc749af4b8ad10162c
Author:     Tibor Nagy <tibor.nagy.ext...@allotropia.de>
AuthorDate: Wed Jan 3 11:18:19 2024 +0100
Commit:     Thorsten Behrens <thorsten.behr...@allotropia.de>
CommitDate: Mon Jan 8 21:24:49 2024 +0100

    tdf#123870 sc: fix tagged content for accessible PDF export
    
    Change-Id: Iec7ea2d5acb66d7c5f9241285cf98f83e02c2445
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/161581
    Tested-by: Jenkins
    Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de>

diff --git a/include/vcl/pdfextoutdevdata.hxx b/include/vcl/pdfextoutdevdata.hxx
index 206dfa4adc97..670ff234e2bd 100644
--- a/include/vcl/pdfextoutdevdata.hxx
+++ b/include/vcl/pdfextoutdevdata.hxx
@@ -32,6 +32,7 @@ class Graphic;
 class GDIMetaFile;
 class SdrObject;
 struct SwEnhancedPDFState;
+struct ScEnhancedPDFState;
 
 namespace vcl
 {
@@ -98,6 +99,7 @@ class VCL_DLLPUBLIC PDFExtOutDevData final : public 
ExtOutDevData
     ::std::map<SdrObject const*, ::std::vector<sal_Int32>> m_ScreenAnnotations;
 
     SwEnhancedPDFState * m_pSwPDFState = nullptr;
+    ScEnhancedPDFState * m_pScPDFState = nullptr;
 
 public:
 
@@ -159,6 +161,9 @@ public:
     SwEnhancedPDFState * GetSwPDFState() { return m_pSwPDFState; }
     void SetSwPDFState(SwEnhancedPDFState *const pSwPDFState) { m_pSwPDFState 
= pSwPDFState; }
 
+    ScEnhancedPDFState* GetScPDFState() { return m_pScPDFState; }
+    void SetScPDFState(ScEnhancedPDFState* const pScPDFState) { m_pScPDFState 
= pScPDFState; }
+
     const Graphic& GetCurrentGraphic() const;
 
     /** Start a new group of render output
diff --git a/sc/qa/extras/scpdfexport.cxx b/sc/qa/extras/scpdfexport.cxx
index bcca563ec9b3..bdb4eb3e44a0 100644
--- a/sc/qa/extras/scpdfexport.cxx
+++ b/sc/qa/extras/scpdfexport.cxx
@@ -34,6 +34,9 @@
 #if USE_TLS_NSS
 #include <nss.h>
 #endif
+#include <vcl/filter/pdfdocument.hxx>
+#include <tools/zcodec.hxx>
+#include <o3tl/string_view.hxx>
 
 using namespace css::lang;
 using namespace ::com::sun::star;
@@ -62,6 +65,7 @@ public:
     void testExportFitToPage_Tdf103516();
     void testUnoCommands_Tdf120161();
     void testTdf64703_hiddenPageBreak();
+    void testTdf123870();
     void testTdf143978();
     void testTdf120190();
     void testTdf84012();
@@ -73,6 +77,7 @@ public:
     CPPUNIT_TEST(testExportFitToPage_Tdf103516);
     CPPUNIT_TEST(testUnoCommands_Tdf120161);
     CPPUNIT_TEST(testTdf64703_hiddenPageBreak);
+    CPPUNIT_TEST(testTdf123870);
     CPPUNIT_TEST(testTdf143978);
     CPPUNIT_TEST(testTdf120190);
     CPPUNIT_TEST(testTdf84012);
@@ -149,7 +154,8 @@ void ScPDFExportTest::exportToPDF(const 
uno::Reference<frame::XModel>& xModel, c
     css::uno::Sequence<css::beans::PropertyValue> aFilterData{
         comphelper::makePropertyValue("Selection", xCellRange),
         comphelper::makePropertyValue("Printing", sal_Int32(2)),
-        comphelper::makePropertyValue("ViewPDFAfterExport", true)
+        comphelper::makePropertyValue("ViewPDFAfterExport", true),
+        comphelper::makePropertyValue("PDFUACompliance", true)
     };
 
     // init set of params for storeToURL() call
@@ -388,6 +394,94 @@ void ScPDFExportTest::testTdf64703_hiddenPageBreak()
     }
 }
 
+void ScPDFExportTest::testTdf123870()
+{
+    loadFromURL(u"tdf123870.ods");
+    uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
+
+    // A1:G4
+    ScRange range1(0, 0, 0, 6, 4, 0);
+    exportToPDF(xModel, range1);
+
+    vcl::filter::PDFDocument aDocument;
+    SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+    CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+    // The document has one page.
+    std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+    vcl::filter::PDFObjectElement* pContents = 
aPages[0]->LookupObject("Contents"_ostr);
+    CPPUNIT_ASSERT(pContents);
+    vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+    CPPUNIT_ASSERT(pStream);
+    SvMemoryStream& rObjectStream = pStream->GetMemory();
+    // Uncompress it.
+    SvMemoryStream aUncompressed;
+    ZCodec aZCodec;
+    aZCodec.BeginCompression();
+    rObjectStream.Seek(0);
+    aZCodec.Decompress(rObjectStream, aUncompressed);
+    CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+    auto pStart = static_cast<const char*>(aUncompressed.GetData());
+    const char* const pEnd = pStart + aUncompressed.GetSize();
+
+    enum
+    {
+        Default,
+        Artifact,
+        Tagged
+    } state
+        = Default;
+
+    auto nLine(0);
+    auto nTagged(0);
+    auto nArtifacts(0);
+    while (true)
+    {
+        ++nLine;
+        auto const pLine = ::std::find(pStart, pEnd, '
');
+        if (pLine == pEnd)
+        {
+            break;
+        }
+        std::string_view const line(pStart, pLine - pStart);
+        pStart = pLine + 1;
+        if (!line.empty() && line[0] != '%')
+        {
+            ::std::cerr << nLine << ": " << line << "
 ";
+            if (o3tl::ends_with(line, "/Artifact BMC"))
+            {
+                CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, 
state);
+                state = Artifact;
+                ++nArtifacts;
+            }
+            else if ((o3tl::starts_with(line, "/P<</MCID") && 
o3tl::ends_with(line, ">>BDC"))
+                     || (o3tl::starts_with(line, "/Figure<</MCID")
+                         && o3tl::ends_with(line, ">>BDC")))
+            {
+                CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, 
state);
+                state = Tagged;
+                ++nTagged;
+            }
+            else if (line == "EMC")
+            {
+                CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default);
+                state = Default;
+            }
+            else if (nLine > 1) // first line is expected "0.1 w"
+            {
+                CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state 
!= Default);
+            }
+        }
+    }
+    // text in cell + 1 shape
+    CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nTagged)>(9), nTagged);
+    // header, footer, background color, color scale, shadow, cell border
+    CPPUNIT_ASSERT(nArtifacts >= 6);
+}
+
 void ScPDFExportTest::testTdf143978()
 {
     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
diff --git a/sc/qa/extras/testdocuments/tdf123870.ods 
b/sc/qa/extras/testdocuments/tdf123870.ods
new file mode 100644
index 000000000000..bccf80662509
Binary files /dev/null and b/sc/qa/extras/testdocuments/tdf123870.ods differ
diff --git a/sc/source/ui/inc/output.hxx b/sc/source/ui/inc/output.hxx
index e4763767b7f5..b94fbb1ec7ac 100644
--- a/sc/source/ui/inc/output.hxx
+++ b/sc/source/ui/inc/output.hxx
@@ -26,6 +26,7 @@
 #include <tools/gen.hxx>
 #include <editeng/svxenum.hxx>
 #include <vcl/outdev.hxx>
+#include <vcl/pdfwriter.hxx>
 #include <tools/degree.hxx>
 #include <o3tl/deleter.hxx>
 #include <optional>
@@ -61,6 +62,17 @@ enum ScOutputType { OUTTYPE_WINDOW, OUTTYPE_PRINTER };
 class ClearableClipRegion;
 typedef std::unique_ptr<ClearableClipRegion, 
o3tl::default_delete<ClearableClipRegion>> ClearableClipRegionPtr;
 
+typedef std::map<SCROW, sal_Int32> TableRowIdMap;
+typedef std::map<std::pair<SCROW, SCCOL>, sal_Int32> TableDataIdMap;
+struct ScEnhancedPDFState
+{
+    sal_Int32 m_WorksheetId = -1;
+    sal_Int32 m_TableId = -1;
+    TableRowIdMap m_TableRowMap;
+    TableDataIdMap m_TableDataMap;
+    ScEnhancedPDFState(){};
+};
+
 /// Describes reference mark to be drawn, position & size in TWIPs
 struct ReferenceMark {
     tools::Long nX;
@@ -342,6 +354,9 @@ public:
 
     void    SetSnapPixel();
 
+    bool    ReopenPDFStructureElement(vcl::PDFWriter::StructElement aType, 
SCROW nRow = -1,
+                                      SCCOL nCol = -1);
+
     void    DrawGrid(vcl::RenderContext& rRenderContext, bool bGrid, bool 
bPage, bool bMergeCover = false);
     void    DrawStrings( bool bPixelToLogic = false );
 
diff --git a/sc/source/ui/unoobj/docuno.cxx b/sc/source/ui/unoobj/docuno.cxx
index e1a70b742517..747600e9af63 100644
--- a/sc/source/ui/unoobj/docuno.cxx
+++ b/sc/source/ui/unoobj/docuno.cxx
@@ -131,6 +131,7 @@
 #include <table.hxx>
 #include <appoptio.hxx>
 #include <formulaopt.hxx>
+#include <output.hxx>
 
 #include <strings.hrc>
 
@@ -2111,6 +2112,156 @@ uno::Sequence<beans::PropertyValue> SAL_CALL 
ScModelObj::getRenderer( sal_Int32
     return aSequence;
 }
 
+static void lcl_PDFExportHelper(const OutputDevice* pDev, const OUString& 
rTabName, bool bIsFirstPage)
+{
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(pDev->GetExtOutDevData());
+    if (pPDF)
+    {
+        css::lang::Locale const 
docLocale(Application::GetSettings().GetLanguageTag().getLocale());
+        pPDF->SetDocumentLocale(docLocale);
+
+        // first page of a sheet: add outline item for the sheet name
+
+        if (pPDF->GetIsExportBookmarks())
+        {
+            // the sheet starts at the top of the page
+            tools::Rectangle aArea(pDev->PixelToLogic(tools::Rectangle(0, 0, 
0, 0)));
+            sal_Int32 nDestID = pPDF->CreateDest(aArea);
+            // top-level
+            pPDF->CreateOutlineItem(-1/*nParent*/, rTabName, nDestID);
+        }
+        // #i56629# add the named destination stuff
+        if (pPDF->GetIsExportNamedDestinations())
+        {
+            tools::Rectangle aArea(pDev->PixelToLogic(tools::Rectangle(0, 0, 
0, 0)));
+            //need the PDF page number here
+            pPDF->CreateNamedDest(rTabName, aArea);
+        }
+
+        if (pPDF->GetIsExportTaggedPDF())
+        {
+            if (bIsFirstPage)
+                pPDF->WrapBeginStructureElement(vcl::PDFWriter::Document, 
"Workbook");
+            else
+            {   // if there is a new worksheet(not first), delete and add new 
ScPDFState
+                assert(pPDF->GetScPDFState());
+                delete pPDF->GetScPDFState();
+                pPDF->SetScPDFState(nullptr);
+            }
+
+            assert(pPDF->GetScPDFState() == nullptr);
+            pPDF->SetScPDFState(new ScEnhancedPDFState());
+        }
+    }
+}
+
+static void lcl_PDFExportBookmarkHelper(OutputDevice* pDev, ScDocument& rDoc,
+                                        const 
std::unique_ptr<ScPrintFuncCache>& pPrintFuncCache,
+                                        const ScMarkData& rMark, sal_Int32 
nTab)
+{
+    //  resolve the hyperlinks for PDF export
+
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(pDev->GetExtOutDevData());
+    if (!pPDF || pPDF->GetBookmarks().empty())
+        return;
+
+    //  iterate over the hyperlinks that were output for this page
+
+    std::vector<vcl::PDFExtOutDevBookmarkEntry>& rBookmarks = 
pPDF->GetBookmarks();
+    for (const auto& rBookmark : rBookmarks)
+    {
+        OUString aBookmark = rBookmark.aBookmark;
+        if (aBookmark.toChar() == '#')
+        {
+            //  try to resolve internal link
+
+            OUString aTarget(aBookmark.copy(1));
+
+            ScRange aTargetRange;
+            tools::Rectangle aTargetRect; // 1/100th mm
+            bool bIsSheet = false;
+            bool bValid = lcl_ParseTarget(aTarget, aTargetRange, aTargetRect, 
bIsSheet, rDoc, nTab);
+
+            if (bValid)
+            {
+                sal_Int32 nPage = -1;
+                tools::Rectangle aArea;
+                if (bIsSheet)
+                {
+                    //  Get first page for sheet (if nothing from that sheet 
is printed,
+                    //  this page can show a different sheet)
+                    nPage = 
pPrintFuncCache->GetTabStart(aTargetRange.aStart.Tab());
+                    aArea = pDev->PixelToLogic(tools::Rectangle(0, 0, 0, 0));
+                }
+                else
+                {
+                    pPrintFuncCache->InitLocations(rMark, pDev); // does 
nothing if already initialized
+
+                    ScPrintPageLocation aLocation;
+                    if (pPrintFuncCache->FindLocation(aTargetRange.aStart, 
aLocation))
+                    {
+                        nPage = aLocation.nPage;
+
+                        // get the rectangle of the page's cell range in 
1/100th mm
+                        ScRange aLocRange = aLocation.aCellRange;
+                        tools::Rectangle aLocationMM = rDoc.GetMMRect(
+                            aLocRange.aStart.Col(), aLocRange.aStart.Row(), 
aLocRange.aEnd.Col(),
+                            aLocRange.aEnd.Row(), aLocRange.aStart.Tab());
+                        tools::Rectangle aLocationPixel = aLocation.aRectangle;
+
+                        // Scale and move the target rectangle from 
aLocationMM to aLocationPixel,
+                        // to get the target rectangle in pixels.
+                        assert(aLocationPixel.GetWidth() != 0 && 
aLocationPixel.GetHeight() != 0);
+
+                        Fraction aScaleX(aLocationPixel.GetWidth(), 
aLocationMM.GetWidth());
+                        Fraction aScaleY(aLocationPixel.GetHeight(), 
aLocationMM.GetHeight());
+
+                        tools::Long nX1
+                            = aLocationPixel.Left()
+                            + static_cast<tools::Long>(
+                                Fraction(aTargetRect.Left() - 
aLocationMM.Left(), 1) * aScaleX);
+                        tools::Long nX2
+                            = aLocationPixel.Left()
+                            + static_cast<tools::Long>(
+                                Fraction(aTargetRect.Right() - 
aLocationMM.Left(), 1) * aScaleX);
+                        tools::Long nY1
+                            = aLocationPixel.Top()
+                            + static_cast<tools::Long>(
+                                Fraction(aTargetRect.Top() - 
aLocationMM.Top(), 1) * aScaleY);
+                        tools::Long nY2
+                            = aLocationPixel.Top()
+                            + static_cast<tools::Long>(
+                                Fraction(aTargetRect.Bottom() - 
aLocationMM.Top(), 1) * aScaleY);
+
+                        if (nX1 > aLocationPixel.Right())
+                            nX1 = aLocationPixel.Right();
+                        if (nX2 > aLocationPixel.Right())
+                            nX2 = aLocationPixel.Right();
+                        if (nY1 > aLocationPixel.Bottom())
+                            nY1 = aLocationPixel.Bottom();
+                        if (nY2 > aLocationPixel.Bottom())
+                            nY2 = aLocationPixel.Bottom();
+
+                        // The link target area is interpreted using the 
device's MapMode at
+                        // the time of the CreateDest call, so PixelToLogic 
can be used here,
+                        // regardless of the MapMode that is actually selected.
+                        aArea = pDev->PixelToLogic(tools::Rectangle(nX1, nY1, 
nX2, nY2));
+                    }
+                }
+
+                if (nPage >= 0)
+                    pPDF->SetLinkDest(rBookmark.nLinkId, 
pPDF->CreateDest(aArea, nPage));
+            }
+        }
+        else
+        {
+            //  external link, use as-is
+            pPDF->SetLinkURL(rBookmark.nLinkId, aBookmark);
+        }
+    }
+    rBookmarks.clear();
+}
+
 void SAL_CALL ScModelObj::render( sal_Int32 nSelRenderer, const uno::Any& 
aSelection,
                                     const uno::Sequence<beans::PropertyValue>& 
rOptions )
 {
@@ -2126,6 +2277,8 @@ void SAL_CALL ScModelObj::render( sal_Int32 nSelRenderer, 
const uno::Any& aSelec
     OUString aPagesStr;
     bool bRenderToGraphic = false;
     bool bSinglePageSheets = false;
+    bool bIsFirstPage = false;
+    bool bIsLastPage = false;
     if ( !FillRenderMarkData( aSelection, rOptions, aMark, aStatus, aPagesStr, 
bRenderToGraphic ) )
         throw lang::IllegalArgumentException();
 
@@ -2140,7 +2293,14 @@ void SAL_CALL ScModelObj::render( sal_Int32 
nSelRenderer, const uno::Any& aSelec
         if ( rValue.Name == "SinglePageSheets" )
         {
             rValue.Value >>= bSinglePageSheets;
-            break;
+        }
+        else if (rValue.Name == "IsFirstPage")
+        {
+            rValue.Value >>= bIsFirstPage;
+        }
+        else if (rValue.Name == "IsLastPage")
+        {
+            rValue.Value >>= bIsLastPage;
         }
     }
 
@@ -2198,8 +2358,23 @@ void SAL_CALL ScModelObj::render( sal_Int32 
nSelRenderer, const uno::Any& aSelec
             rDoc.SetVisibleTab(nVisTab);
         }
 
+        OUString aTabName;
+        rDoc.GetName(nVisTab, aTabName);
+        lcl_PDFExportHelper(pDev, aTabName, bIsFirstPage);
+
         pDocShell->DoDraw(pDev, Point(0,0), Size(aPageSize.Width, 
aPageSize.Height), JobSetup());
 
+        vcl::PDFExtOutDevData* pPDFData = 
dynamic_cast<vcl::PDFExtOutDevData*>(pDev->GetExtOutDevData());
+        if (pPDFData && pPDFData->GetIsExportTaggedPDF() && bIsLastPage)
+        {
+            pPDFData->EndStructureElement();  // Workbook
+            assert(pPDFData->GetScPDFState());
+            delete pPDFData->GetScPDFState();
+            pPDFData->SetScPDFState(nullptr);
+        }
+
+        lcl_PDFExportBookmarkHelper(pDev, rDoc, pPrintFuncCache, aMark, 
nVisTab);
+
         return;
     }
     else if ( aMark.IsMarked() )
@@ -2304,131 +2479,31 @@ void SAL_CALL ScModelObj::render( sal_Int32 
nSelRenderer, const uno::Any& aSelec
     tools::Long nDisplayStart = pPrintFuncCache->GetDisplayStart( nTab );
     tools::Long nTabStart = pPrintFuncCache->GetTabStart( nTab );
 
-    vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* 
>(pDev->GetExtOutDevData() );
-    if ( nRenderer == nTabStart )
+    if ( nRenderer == nTabStart || bIsFirstPage )
     {
-        if (pPDFData)
-        {
-            css::lang::Locale const 
docLocale(Application::GetSettings().GetLanguageTag().getLocale());
-            pPDFData->SetDocumentLocale(docLocale);
-        }
-
-        // first page of a sheet: add outline item for the sheet name
-
-        if ( pPDFData && pPDFData->GetIsExportBookmarks() )
-        {
-            // the sheet starts at the top of the page
-            tools::Rectangle aArea( pDev->PixelToLogic( tools::Rectangle( 
0,0,0,0 ) ) );
-            sal_Int32 nDestID = pPDFData->CreateDest( aArea );
-            OUString aTabName;
-            rDoc.GetName( nTab, aTabName );
-            // top-level
-            pPDFData->CreateOutlineItem( -1/*nParent*/, aTabName, nDestID );
-        }
-        // #i56629# add the named destination stuff
-        if( pPDFData && pPDFData->GetIsExportNamedDestinations() )
-        {
-            tools::Rectangle aArea( pDev->PixelToLogic( tools::Rectangle( 
0,0,0,0 ) ) );
-            OUString aTabName;
-            rDoc.GetName( nTab, aTabName );
-            //need the PDF page number here
-            pPDFData->CreateNamedDest( aTabName, aArea );
-        }
+        OUString aTabName;
+        rDoc.GetName(nTab, aTabName);
+        lcl_PDFExportHelper(pDev, aTabName, bIsFirstPage);
     }
 
     (void)pPrintFunc->DoPrint( aPage, nTabStart, nDisplayStart, true, nullptr 
);
 
+    vcl::PDFExtOutDevData* pPDFData = 
dynamic_cast<vcl::PDFExtOutDevData*>(pDev->GetExtOutDevData());
+    if (pPDFData && pPDFData->GetIsExportTaggedPDF() && bIsLastPage)
+    {
+        pPDFData->EndStructureElement();  // Workbook
+        assert(pPDFData->GetScPDFState());
+        delete pPDFData->GetScPDFState();
+        pPDFData->SetScPDFState(nullptr);
+    }
+
     if (!m_pPrintState)
     {
         m_pPrintState.reset(new ScPrintState());
         pPrintFunc->GetPrintState(*m_pPrintState, true);
     }
 
-    //  resolve the hyperlinks for PDF export
-
-    if ( !pPDFData || pPDFData->GetBookmarks().empty() )
-        return;
-
-    //  iterate over the hyperlinks that were output for this page
-
-    std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = 
pPDFData->GetBookmarks();
-    for ( const auto& rBookmark : rBookmarks )
-    {
-        OUString aBookmark = rBookmark.aBookmark;
-        if ( aBookmark.toChar() == '#' )
-        {
-            //  try to resolve internal link
-
-            OUString aTarget( aBookmark.copy( 1 ) );
-
-            ScRange aTargetRange;
-            tools::Rectangle aTargetRect;      // 1/100th mm
-            bool bIsSheet = false;
-            bool bValid = lcl_ParseTarget( aTarget, aTargetRange, aTargetRect, 
bIsSheet, rDoc, nTab );
-
-            if ( bValid )
-            {
-                sal_Int32 nPage = -1;
-                tools::Rectangle aArea;
-                if ( bIsSheet )
-                {
-                    //  Get first page for sheet (if nothing from that sheet 
is printed,
-                    //  this page can show a different sheet)
-                    nPage = pPrintFuncCache->GetTabStart( 
aTargetRange.aStart.Tab() );
-                    aArea = pDev->PixelToLogic( tools::Rectangle( 0,0,0,0 ) );
-                }
-                else
-                {
-                    pPrintFuncCache->InitLocations( aMark, pDev );      // 
does nothing if already initialized
-
-                    ScPrintPageLocation aLocation;
-                    if ( pPrintFuncCache->FindLocation( aTargetRange.aStart, 
aLocation ) )
-                    {
-                        nPage = aLocation.nPage;
-
-                        // get the rectangle of the page's cell range in 
1/100th mm
-                        ScRange aLocRange = aLocation.aCellRange;
-                        tools::Rectangle aLocationMM = rDoc.GetMMRect(
-                                   aLocRange.aStart.Col(), 
aLocRange.aStart.Row(),
-                                   aLocRange.aEnd.Col(),   
aLocRange.aEnd.Row(),
-                                   aLocRange.aStart.Tab() );
-                        tools::Rectangle aLocationPixel = aLocation.aRectangle;
-
-                        // Scale and move the target rectangle from 
aLocationMM to aLocationPixel,
-                        // to get the target rectangle in pixels.
-                        assert(aLocationPixel.GetWidth() != 0 && 
aLocationPixel.GetHeight() != 0);
-
-                        Fraction aScaleX( aLocationPixel.GetWidth(), 
aLocationMM.GetWidth() );
-                        Fraction aScaleY( aLocationPixel.GetHeight(), 
aLocationMM.GetHeight() );
-
-                        tools::Long nX1 = aLocationPixel.Left() + 
static_cast<tools::Long>( Fraction( aTargetRect.Left() - aLocationMM.Left(), 1 
) * aScaleX );
-                        tools::Long nX2 = aLocationPixel.Left() + 
static_cast<tools::Long>( Fraction( aTargetRect.Right() - aLocationMM.Left(), 1 
) * aScaleX );
-                        tools::Long nY1 = aLocationPixel.Top() + 
static_cast<tools::Long>( Fraction( aTargetRect.Top() - aLocationMM.Top(), 1 ) 
* aScaleY );
-                        tools::Long nY2 = aLocationPixel.Top() + 
static_cast<tools::Long>( Fraction( aTargetRect.Bottom() - aLocationMM.Top(), 1 
) * aScaleY );
-
-                        if ( nX1 > aLocationPixel.Right() ) nX1 = 
aLocationPixel.Right();
-                        if ( nX2 > aLocationPixel.Right() ) nX2 = 
aLocationPixel.Right();
-                        if ( nY1 > aLocationPixel.Bottom() ) nY1 = 
aLocationPixel.Bottom();
-                        if ( nY2 > aLocationPixel.Bottom() ) nY2 = 
aLocationPixel.Bottom();
-
-                        // The link target area is interpreted using the 
device's MapMode at
-                        // the time of the CreateDest call, so PixelToLogic 
can be used here,
-                        // regardless of the MapMode that is actually selected.
-                        aArea = pDev->PixelToLogic( tools::Rectangle( nX1, 
nY1, nX2, nY2 ) );
-                    }
-                }
-
-                if ( nPage >= 0 )
-                    pPDFData->SetLinkDest( rBookmark.nLinkId, 
pPDFData->CreateDest( aArea, nPage ) );
-            }
-        }
-        else
-        {
-            //  external link, use as-is
-            pPDFData->SetLinkURL( rBookmark.nLinkId, aBookmark );
-        }
-    }
-    rBookmarks.clear();
+    lcl_PDFExportBookmarkHelper(pDev, rDoc, pPrintFuncCache, aMark, nTab);
 }
 
 // XLinkTargetSupplier
diff --git a/sc/source/ui/view/output.cxx b/sc/source/ui/view/output.cxx
index be482d70d03e..23f3cecb539f 100644
--- a/sc/source/ui/view/output.cxx
+++ b/sc/source/ui/view/output.cxx
@@ -297,6 +297,56 @@ void ScOutputData::SetSyntaxMode( bool bNewMode )
     }
 }
 
+bool ScOutputData::ReopenPDFStructureElement(vcl::PDFWriter::StructElement 
aType, SCROW nRow, SCCOL nCol)
+{
+    bool bReopenTag = false;
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(mpDev->GetExtOutDevData());
+    if (pPDF)
+    {
+        if (aType == vcl::PDFWriter::Part) // Worksheet
+        {
+            if (pPDF->GetScPDFState()->m_WorksheetId != -1)
+            {
+                sal_Int32 nId = pPDF->GetScPDFState()->m_WorksheetId;
+                pPDF->BeginStructureElement(nId);
+                bReopenTag = true;
+            }
+        }
+        else if (aType == vcl::PDFWriter::Table)
+        {
+            if (pPDF->GetScPDFState()->m_TableId != -1)
+            {
+                sal_Int32 nId = pPDF->GetScPDFState()->m_TableId;
+                pPDF->BeginStructureElement(nId);
+                bReopenTag = true;
+            }
+        }
+        else if (aType == vcl::PDFWriter::TableRow)
+        {
+            const auto aIter = pPDF->GetScPDFState()->m_TableRowMap.find(nRow);
+            if (aIter != pPDF->GetScPDFState()->m_TableRowMap.end() && nRow == 
aIter->first)
+            {
+                sal_Int32 nId = (*aIter).second;
+                pPDF->BeginStructureElement(nId);
+                bReopenTag = true;
+            }
+        }
+        else if (aType == vcl::PDFWriter::TableData)
+        {
+            const std::pair<SCROW, SCCOL> keyToFind = std::make_pair(nRow, 
nCol);
+            const auto aIter = 
pPDF->GetScPDFState()->m_TableDataMap.find(keyToFind);
+            if (aIter != pPDF->GetScPDFState()->m_TableDataMap.end() && 
keyToFind == aIter->first)
+            {
+                sal_Int32 nId = (*aIter).second;
+                pPDF->BeginStructureElement(nId);
+                bReopenTag = true;
+            }
+        }
+    }
+
+    return bReopenTag;
+}
+
 void ScOutputData::DrawGrid(vcl::RenderContext& rRenderContext, bool bGrid, 
bool bPage, bool bMergeCover)
 {
     // bMergeCover : Draw lines in sheet bgcolor to cover lok client grid 
lines in merged cell areas.
@@ -1025,6 +1075,9 @@ void drawCells(vcl::RenderContext& rRenderContext, 
std::optional<Color> const &
 
 void ScOutputData::DrawBackground(vcl::RenderContext& rRenderContext)
 {
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(mpDev->GetExtOutDevData());
+    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();
+
     Size aOnePixel = rRenderContext.PixelToLogic(Size(1,1));
     tools::Long nOneXLogic = aOnePixel.Width();
     tools::Long nOneYLogic = aOnePixel.Height();
@@ -1074,6 +1127,9 @@ void ScOutputData::DrawBackground(vcl::RenderContext& 
rRenderContext)
             }
             else
             {
+                if (bTaggedPDF)
+                    
pPDF->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement);
+
                 // scan for rows with the same background:
                 SCSIZE nSkip = 0;
                 while ( nArrY+nSkip+2<nArrCount &&
@@ -1181,6 +1237,9 @@ void ScOutputData::DrawBackground(vcl::RenderContext& 
rRenderContext)
                 drawCells(rRenderContext, std::optional<Color>(), nullptr, 
pOldColor, pOldBackground, aRect, nPosXLogic, nLayoutSign, nOneXLogic, 
nOneYLogic, nullptr, pOldDataBarInfo, nullptr, pOldIconSetInfo, 
mpDoc->GetIconSetBitmapMap());
 
                 nArrY += nSkip;
+
+                if (bTaggedPDF)
+                    pPDF->EndStructureElement();
             }
         }
         nPosY += nRowHeight;
@@ -1194,6 +1253,9 @@ void ScOutputData::DrawShadow()
 
 void ScOutputData::DrawExtraShadow(bool bLeft, bool bTop, bool bRight, bool 
bBottom)
 {
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(mpDev->GetExtOutDevData());
+    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();
+
     mpDev->SetLineColor();
 
     const StyleSettings& rStyleSettings = 
Application::GetSettings().GetStyleSettings();
@@ -1235,6 +1297,9 @@ void ScOutputData::DrawExtraShadow(bool bLeft, bool bTop, 
bool bRight, bool bBot
                             pThisRowInfo->cellInfo(nCol).pHShadowOrigin;
                     if ( pAttr && !bSkipX )
                     {
+                        if (bTaggedPDF)
+                            
pPDF->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement);
+
                         ScShadowPart ePart = nPass ?
                                 pThisRowInfo->cellInfo(nCol).eVShadowPart :
                                 pThisRowInfo->cellInfo(nCol).eHShadowPart;
@@ -1322,6 +1387,9 @@ void ScOutputData::DrawExtraShadow(bool bLeft, bool bTop, 
bool bRight, bool bBot
                             //! merge rectangles?
                             mpDev->SetFillColor( bCellContrast ? 
aAutoTextColor : pAttr->GetColor() );
                             mpDev->DrawRect( aRect );
+
+                            if (bTaggedPDF)
+                                pPDF->EndStructureElement();
                         }
                     }
                 }
@@ -1390,6 +1458,11 @@ static tools::Long lclGetSnappedY( const OutputDevice& 
rDev, tools::Long nPosY,
 
 void ScOutputData::DrawFrame(vcl::RenderContext& rRenderContext)
 {
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(mpDev->GetExtOutDevData());
+    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();
+    if (bTaggedPDF)
+        pPDF->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement);
+
     DrawModeFlags nOldDrawMode = rRenderContext.GetDrawMode();
 
     Color aSingleColor;
@@ -1510,6 +1583,9 @@ void ScOutputData::DrawFrame(vcl::RenderContext& 
rRenderContext)
     pProcessor.reset();
 
     rRenderContext.SetDrawMode(nOldDrawMode);
+
+    if (bTaggedPDF)
+        pPDF->EndStructureElement();
 }
 
 void ScOutputData::DrawRotatedFrame(vcl::RenderContext& rRenderContext)
diff --git a/sc/source/ui/view/output2.cxx b/sc/source/ui/view/output2.cxx
index d5e87a66d3d0..f9e4749377d7 100644
--- a/sc/source/ui/view/output2.cxx
+++ b/sc/source/ui/view/output2.cxx
@@ -1482,6 +1482,20 @@ void ScOutputData::DrawStrings( bool bPixelToLogic )
 
 void ScOutputData::LayoutStrings(bool bPixelToLogic)
 {
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(mpDev->GetExtOutDevData());
+    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();
+    if (bTaggedPDF)
+    {
+        bool bReopenTag = ReopenPDFStructureElement(vcl::PDFWriter::Table);
+        if (!bReopenTag)
+        {
+            sal_Int32 nId = pPDF->EnsureStructureElement(nullptr);
+            pPDF->InitStructureElement(nId, vcl::PDFWriter::Table, "Table");
+            pPDF->BeginStructureElement(nId);
+            pPDF->GetScPDFState()->m_TableId = nId;
+        }
+    }
+
     bool bOrigIsInLayoutStrings = mpDoc->IsInLayoutStrings();
     mpDoc->SetLayoutStrings(true);
     OSL_ENSURE( mpDev == mpRefDevice ||
@@ -1502,8 +1516,6 @@ void ScOutputData::LayoutStrings(bool bPixelToLogic)
     // for the entire formula group, which could be large.
     mpDoc->InterpretCellsIfNeeded( ScRange( nX1, nY1, nTab, nX2, nY2, nTab ));
 
-    vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* 
>(mpDev->GetExtOutDevData() );
-
     ScDrawStringsVars aVars( this, bPixelToLogic );
 
     bool bProgress = false;
@@ -1521,7 +1533,7 @@ void ScOutputData::LayoutStrings(bool bPixelToLogic)
     }
 
     SCCOL nLoopStartX = nX1;
-    if ( nX1 > 0 )
+    if ( nX1 > 0  && !bTaggedPDF )
         --nLoopStartX;          // start before nX1 for rest of long text to 
the left
 
     // variables for GetOutputArea
@@ -1546,11 +1558,31 @@ void ScOutputData::LayoutStrings(bool bPixelToLogic)
         SCROW nY = pThisRowInfo->nRowNo;
         if (pThisRowInfo->bChanged)
         {
+            if (bTaggedPDF)
+            {
+                bool bReopenTag = false;
+                if (nLoopStartX != 0)
+                {
+                    bReopenTag
+                        = ReopenPDFStructureElement(vcl::PDFWriter::TableRow, 
nY);
+                }
+                if (!bReopenTag)
+                {
+                    sal_Int32 nId = pPDF->EnsureStructureElement(nullptr);
+                    pPDF->InitStructureElement(nId, vcl::PDFWriter::TableRow, 
"TR");
+                    pPDF->BeginStructureElement(nId);
+                    pPDF->GetScPDFState()->m_TableRowMap.emplace(nY, nId);
+                }
+            }
+
             tools::Long nPosX = nInitPosX;
             if ( nLoopStartX < nX1 )
                 nPosX -= pRowInfo[0].basicCellInfo(nLoopStartX).nWidth * 
nLayoutSign;
             for (SCCOL nX=nLoopStartX; nX<=nX2; nX++)
             {
+                if (bTaggedPDF)
+                    pPDF->WrapBeginStructureElement(vcl::PDFWriter::TableData, 
"TD");
+
                 bool bMergeEmpty = false;
                 const ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX);
                 bool bEmpty = nX < nX1 || 
pThisRowInfo->basicCellInfo(nX).bEmptyCellText;
@@ -1880,6 +1912,13 @@ void ScOutputData::LayoutStrings(bool bPixelToLogic)
                     RowInfo* pMarkRowInfo = ( nCellY == nY ) ? pThisRowInfo : 
&pRowInfo[0];
                     pMarkRowInfo->basicCellInfo(nMarkX).bEditEngine = true;
                     bDoCell = false;    // don't draw here
+
+                    // Mark the tagged "TD" structure element to be drawn in 
DrawEdit
+                    if (bTaggedPDF)
+                    {
+                        sal_Int32 nId = pPDF->GetCurrentStructureElement();
+                        pPDF->GetScPDFState()->m_TableDataMap[{nY, nX}] = nId;
+                    }
                 }
                 if ( bDoCell )
                 {
@@ -2088,6 +2127,9 @@ void ScOutputData::LayoutStrings(bool bPixelToLogic)
                         const OUString& aString = aVars.GetString();
                         if (!aString.isEmpty())
                         {
+                            if (bTaggedPDF)
+                                
pPDF->WrapBeginStructureElement(vcl::PDFWriter::Paragraph, "P");
+
                             // If the string is clipped, make it shorter for
                             // better performance since drawing by HarfBuzz is
                             // quite expensive especially for long string.
@@ -2154,6 +2196,8 @@ void ScOutputData::LayoutStrings(bool bPixelToLogic)
                                 mpDev->DrawText(aDrawTextPos, aShort, 0, -1, 
nullptr, nullptr,
                                     aVars.GetLayoutGlyphs(aShort));
                             }
+                            if (bTaggedPDF)
+                                pPDF->EndStructureElement();
                         }
 
                         if ( bHClip || bVClip )
@@ -2165,7 +2209,7 @@ void ScOutputData::LayoutStrings(bool bPixelToLogic)
                         }
 
                         // PDF: whole-cell hyperlink from formula?
-                        bool bHasURL = pPDFData && aCell.getType() == 
CELLTYPE_FORMULA && aCell.getFormula()->IsHyperLinkCell();
+                        bool bHasURL = pPDF && aCell.getType() == 
CELLTYPE_FORMULA && aCell.getFormula()->IsHyperLinkCell();
                         if (bHasURL)
                         {
                             tools::Rectangle aURLRect( aURLStart, 
aVars.GetTextSize() );
@@ -2174,10 +2218,17 @@ void ScOutputData::LayoutStrings(bool bPixelToLogic)
                     }
                 }
                 nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign;
+                if (bTaggedPDF)
+                    pPDF->EndStructureElement();
             }
+            if (bTaggedPDF)
+                pPDF->EndStructureElement();
         }
         nPosY += pRowInfo[nArrY].nHeight;
     }
+    if (bTaggedPDF)
+        pPDF->EndStructureElement();
+
     if ( bProgress )
         ScProgress::DeleteInterpretProgress();
 }
@@ -4384,6 +4435,9 @@ void ScOutputData::DrawEditAsianVertical(DrawEditParam& 
rParam)
 
 void ScOutputData::DrawEdit(bool bPixelToLogic)
 {
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(mpDev->GetExtOutDevData());
+    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();
+
     InitOutputEditEngine();
 
     bool bHyphenatorSet = false;
@@ -4425,6 +4479,10 @@ void ScOutputData::DrawEdit(bool bPixelToLogic)
                 {
                     SCROW nY = pThisRowInfo->nRowNo;
 
+                    bool bReopenTag = false;
+                    if (bTaggedPDF)
+                        bReopenTag = 
ReopenPDFStructureElement(vcl::PDFWriter::TableData, nY, nX);
+
                     SCCOL nCellX = nX;                  // position where the 
cell really starts
                     SCROW nCellY = nY;
                     bool bDoCell = false;
@@ -4557,6 +4615,8 @@ void ScOutputData::DrawEdit(bool bPixelToLogic)
                         pOldPreviewFontSet = aParam.mpOldPreviewFontSet;
                         bHyphenatorSet = aParam.mbHyphenatorSet;
                     }
+                    if (bReopenTag)
+                        pPDF->EndStructureElement();
                 }
                 nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign;
             }
diff --git a/sc/source/ui/view/printfun.cxx b/sc/source/ui/view/printfun.cxx
index db81b8692849..f5f92b7f6876 100644
--- a/sc/source/ui/view/printfun.cxx
+++ b/sc/source/ui/view/printfun.cxx
@@ -29,6 +29,7 @@
 #include <svtools/colorcfg.hxx>
 #include <editeng/editstat.hxx>
 #include <svx/fmview.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
 #include <editeng/frmdiritem.hxx>
 #include <editeng/lrspitem.hxx>
 #include <editeng/paperinf.hxx>
@@ -576,6 +577,20 @@ void ScPrintFunc::DrawToDev(ScDocument& rDoc, 
OutputDevice* pDev, double /* nPri
     aOutputData.SetShowNullValues(bNullVal);
     aOutputData.SetShowFormulas(bFormula);
 
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(pDev->GetExtOutDevData());
+    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();
+    if (bTaggedPDF)
+    {
+        bool bReopen = 
aOutputData.ReopenPDFStructureElement(vcl::PDFWriter::Part);
+        if (!bReopen)
+        {
+            sal_Int32 nId = pPDF->EnsureStructureElement(nullptr);
+            pPDF->InitStructureElement(nId, vcl::PDFWriter::Part, "Worksheet");
+            pPDF->BeginStructureElement(nId);
+            pPDF->GetScPDFState()->m_WorksheetId = nId;
+        }
+    }
+
     ScDrawLayer* pModel = rDoc.GetDrawLayer();
     std::unique_ptr<FmFormView> pDrawView;
 
@@ -651,6 +666,10 @@ void ScPrintFunc::DrawToDev(ScDocument& rDoc, 
OutputDevice* pDev, double /* nPri
 
     // #i72502#
     aOutputData.PrintDrawingLayer(SC_LAYER_FRONT, aMMOffset);
+
+    if (bTaggedPDF)
+        pPDF->EndStructureElement();
+
     aOutputData.PrintDrawingLayer(SC_LAYER_INTERN, aMMOffset);
     aOutputData.PostPrintDrawingLayer(aMMOffset); // #i74768#
 }
@@ -1622,6 +1641,20 @@ void ScPrintFunc::PrintArea( SCCOL nX1, SCROW nY1, SCCOL 
nX2, SCROW nY2,
     ScOutputData aOutputData( pDev, OUTTYPE_PRINTER, aTabInfo, &rDoc, 
nPrintTab,
                                 nScrX, nScrY, nX1, nY1, nX2, nY2, nScaleX, 
nScaleY );
 
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(pDev->GetExtOutDevData());
+    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();
+    if (bTaggedPDF)
+    {
+        bool bReopen = 
aOutputData.ReopenPDFStructureElement(vcl::PDFWriter::Part);
+        if (!bReopen)
+        {
+            sal_Int32 nId = pPDF->EnsureStructureElement(nullptr);
+            pPDF->InitStructureElement(nId, vcl::PDFWriter::Part, "Worksheet");
+            pPDF->BeginStructureElement(nId);
+            pPDF->GetScPDFState()->m_WorksheetId = nId;
+        }
+    }
+
     aOutputData.SetDrawView( pDrawView );
 
     // test if all paint parts are hidden, then a paint is not necessary at all
@@ -1690,9 +1723,17 @@ void ScPrintFunc::PrintArea( SCCOL nX1, SCROW nY1, SCCOL 
nX2, SCROW nY2,
         aOutputData.PrintDrawingLayer(SC_LAYER_FRONT, aMMOffset);
     }
 
+    if (bTaggedPDF)
+    {
+        aOutputData.PrintDrawingLayer(SC_LAYER_CONTROLS, aMMOffset);
+        pPDF->EndStructureElement();
+    }
+
     // #i72502#
     aOutputData.PrintDrawingLayer(SC_LAYER_INTERN, aMMOffset);
-    aOutputData.PostPrintDrawingLayer(aMMOffset); // #i74768#
+
+    if (!bTaggedPDF)
+        aOutputData.PostPrintDrawingLayer(aMMOffset); // #i74768#
 }
 
 bool ScPrintFunc::IsMirror( tools::Long nPageNo )          // Mirror margins?
@@ -1763,6 +1804,11 @@ void ScPrintFunc::MakeEditEngine()
 void ScPrintFunc::PrintHF( tools::Long nPageNo, bool bHeader, tools::Long 
nStartY,
                             bool bDoPrint, ScPreviewLocationData* 
pLocationData )
 {
+    vcl::PDFExtOutDevData* pPDF = 
dynamic_cast<vcl::PDFExtOutDevData*>(pDev->GetExtOutDevData());
+    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();
+    if (bTaggedPDF)
+        pPDF->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement);
+
     const ScPrintHFParam& rParam = bHeader ? aHdr : aFtr;
 
     pDev->SetMapMode( aTwipMode );          // Head-/Footlines in Twips
@@ -1896,6 +1942,9 @@ void ScPrintFunc::PrintHF( tools::Long nPageNo, bool 
bHeader, tools::Long nStart
         tools::Rectangle aHeaderRect( aBorderStart, aBorderSize );
         pLocationData->AddHeaderFooter( aHeaderRect, bHeader, bLeft );
     }
+
+    if (bTaggedPDF)
+        pPDF->EndStructureElement();
 }
 
 tools::Long ScPrintFunc::DoNotes( tools::Long nNoteStart, bool bDoPrint, 
ScPreviewLocationData* pLocationData )

Reply via email to