tdf#103516 Calc: fit columns to page similarly to Excel

With the page setting "Fit print range(s) to width/height",
"Width in pages" > 0, and no "Height in pages" selected,
apply slightly smaller zoom (~98% of the selected zoom),
when this adjustment results printing fewer pages,
ie. no more pages printed, than MSO.

Note: a very annoying problem was that Calc printed almost
blank extra pages (containing only one or a few table rows)
from documents that Excel printed "correctly".

Change-Id: I527f7084d22fc2ef60780492fbe24791d6ab30ca
Reviewed-on: https://gerrit.libreoffice.org/79063
Tested-by: Jenkins
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sc/qa/extras/scpdfexport.cxx b/sc/qa/extras/scpdfexport.cxx
index 2bc2ed7..4e471e4 100644
--- a/sc/qa/extras/scpdfexport.cxx
+++ b/sc/qa/extras/scpdfexport.cxx
@@ -42,11 +42,11 @@

    // helpers
private:
    std::shared_ptr<utl::TempFile> exportToPdf(uno::Reference<frame::XModel>& xModel,
    std::shared_ptr<utl::TempFile> exportToPDF(uno::Reference<frame::XModel>& xModel,
                                               const ScRange& range);

    static bool hasFontInPdf(const std::shared_ptr<utl::TempFile>& pXPathFile,
                             const char* sFontName, bool& bFound);
    static bool hasTextInPdf(const std::shared_ptr<utl::TempFile>& pPDFFile, const char* sText,
                             bool& bFound);

    void setFont(ScFieldEditEngine& rEE, sal_Int32 nStart, sal_Int32 nEnd,
                 const OUString& rFontName);
@@ -54,9 +54,11 @@
    // unit tests
public:
    void testExportRange_Tdf120161();
    void testExportFitToPage_Tdf103516();

    CPPUNIT_TEST_SUITE(ScPDFExportTest);
    CPPUNIT_TEST(testExportRange_Tdf120161);
    CPPUNIT_TEST(testExportFitToPage_Tdf103516);
    CPPUNIT_TEST_SUITE_END();
};

@@ -109,10 +111,10 @@
    test::BootstrapFixture::tearDown();
}

bool ScPDFExportTest::hasFontInPdf(const std::shared_ptr<utl::TempFile>& pXPathFile,
                                   const char* sFontName, bool& bFound)
bool ScPDFExportTest::hasTextInPdf(const std::shared_ptr<utl::TempFile>& pPDFFile,
                                   const char* sText, bool& bFound)
{
    SvStream* pStream = pXPathFile->GetStream(StreamMode::STD_READ);
    SvStream* pStream = pPDFFile->GetStream(StreamMode::STD_READ);
    CPPUNIT_ASSERT(pStream);

    // get file size
@@ -128,7 +130,7 @@
    if (nRead == nFileSize)
    {
        const std::string haystack(pBuffer, pBuffer + nFileSize);
        const std::string needle(sFontName);
        const std::string needle(sText);
        const std::size_t n = haystack.find(needle);
        bFound = (n != std::string::npos);
    }
@@ -136,11 +138,11 @@

    // close and return the status
    pStream = nullptr;
    pXPathFile->CloseStream();
    pPDFFile->CloseStream();
    return (nRead == nFileSize);
}

std::shared_ptr<utl::TempFile> ScPDFExportTest::exportToPdf(uno::Reference<frame::XModel>& xModel,
std::shared_ptr<utl::TempFile> ScPDFExportTest::exportToPDF(uno::Reference<frame::XModel>& xModel,
                                                            const ScRange& range)
{
    // create temp file name
@@ -249,27 +251,109 @@
    // A1:G1
    {
        ScRange range1(0, 0, 0, 6, 0, 0);
        std::shared_ptr<utl::TempFile> pXPathFile = exportToPdf(xModel, range1);
        std::shared_ptr<utl::TempFile> pPDFFile = exportToPDF(xModel, range1);
        bool bFound = false;
        CPPUNIT_ASSERT(hasFontInPdf(pXPathFile, "DejaVuSans", bFound));
        CPPUNIT_ASSERT(hasTextInPdf(pPDFFile, "DejaVuSans", bFound));
        CPPUNIT_ASSERT_EQUAL(false, bFound);
    }

    // G1:H1
    {
        ScRange range1(6, 0, 0, 7, 0, 0);
        std::shared_ptr<utl::TempFile> pXPathFile = exportToPdf(xModel, range1);
        std::shared_ptr<utl::TempFile> pPDFFile = exportToPDF(xModel, range1);
        bool bFound = false;
        CPPUNIT_ASSERT(hasFontInPdf(pXPathFile, "DejaVuSans", bFound));
        CPPUNIT_ASSERT(hasTextInPdf(pPDFFile, "DejaVuSans", bFound));
        CPPUNIT_ASSERT_EQUAL(true, bFound);
    }

    // H1:I1
    {
        ScRange range1(7, 0, 0, 8, 0, 0);
        std::shared_ptr<utl::TempFile> pXPathFile = exportToPdf(xModel, range1);
        std::shared_ptr<utl::TempFile> pPDFFile = exportToPDF(xModel, range1);
        bool bFound = false;
        CPPUNIT_ASSERT(hasFontInPdf(pXPathFile, "DejaVuSans", bFound));
        CPPUNIT_ASSERT(hasTextInPdf(pPDFFile, "DejaVuSans", bFound));
        CPPUNIT_ASSERT_EQUAL(true, bFound);
    }
}

void ScPDFExportTest::testExportFitToPage_Tdf103516()
{
    // create test document
    uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
    uno::Reference<sheet::XSpreadsheetDocument> xDoc(xModel, uno::UNO_QUERY_THROW);
    uno::Reference<sheet::XSpreadsheets> xSheets(xDoc->getSheets(), UNO_SET_THROW);
    uno::Reference<container::XIndexAccess> xIndex(xSheets, uno::UNO_QUERY_THROW);
    xSheets->insertNewByName("First Sheet", 0);
    uno::Reference<sheet::XSpreadsheet> rSheet(xIndex->getByIndex(0), UNO_QUERY_THROW);

    // 2. Setup data
    {
        SfxObjectShell* pFoundShell = SfxObjectShell::GetShellFromComponent(mxComponent);
        CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pFoundShell);
        ScDocShellRef xDocSh = dynamic_cast<ScDocShell*>(pFoundShell);
        CPPUNIT_ASSERT(xDocSh.get() != nullptr);

        // put some content into the table
        ScDocument& rDoc = xDocSh->GetDocument();
        for (unsigned int r = 0; r < 80; ++r)
            for (unsigned int c = 0; c < 12; ++c)
                rDoc.SetValue(ScAddress(c, r, 0), (r + 1) * (c + 1));
    }

    // A1:G50: 2-page export
    {
        ScRange range1(0, 0, 0, 6, 49, 0);
        std::shared_ptr<utl::TempFile> pPDFFile = exportToPDF(xModel, range1);
        bool bFound = false;
        CPPUNIT_ASSERT(hasTextInPdf(pPDFFile, "/Count 2>>", bFound));
        CPPUNIT_ASSERT_EQUAL(true, bFound);
    }

    // A1:L80: 4-page export
    {
        ScRange range1(0, 0, 0, 11, 79, 0);
        std::shared_ptr<utl::TempFile> pPDFFile = exportToPDF(xModel, range1);
        bool bFound = false;
        CPPUNIT_ASSERT(hasTextInPdf(pPDFFile, "/Count 4>>", bFound));
        CPPUNIT_ASSERT_EQUAL(true, bFound);
    }

    // set fit to page: width=1 page, height=0 (automatic)
    uno::Reference<style::XStyleFamiliesSupplier> xStyleFamSupp(xDoc, UNO_QUERY_THROW);
    uno::Reference<container::XNameAccess> xStyleFamiliesNames(xStyleFamSupp->getStyleFamilies(),
                                                               UNO_SET_THROW);
    uno::Reference<container::XNameAccess> xPageStyles(xStyleFamiliesNames->getByName("PageStyles"),
                                                       UNO_QUERY_THROW);
    uno::Any aDefaultStyle = xPageStyles->getByName("Default");
    uno::Reference<beans::XPropertySet> xProp(aDefaultStyle, UNO_QUERY_THROW);

    uno::Any aScaleX, aScaleY;
    sal_Int16 nScale;
    aScaleX <<= static_cast<sal_Int16>(1);
    xProp->setPropertyValue("ScaleToPagesX", aScaleX);
    aScaleX = xProp->getPropertyValue("ScaleToPagesX");
    aScaleX >>= nScale;
    CPPUNIT_ASSERT_EQUAL(sal_Int16(1), nScale);

    aScaleY = xProp->getPropertyValue("ScaleToPagesY");
    aScaleY >>= nScale;
    CPPUNIT_ASSERT_EQUAL(sal_Int16(0), nScale);

    // A1:G50 with fit to page width=1: slightly smaller zoom results only 1-page export
    {
        ScRange range1(0, 0, 0, 6, 49, 0);
        std::shared_ptr<utl::TempFile> pPDFFile = exportToPDF(xModel, range1);
        bool bFound = false;
        CPPUNIT_ASSERT(hasTextInPdf(pPDFFile, "/Count 1>>", bFound));
        CPPUNIT_ASSERT_EQUAL(true, bFound);
    }

    // A1:L80 with fit to page width=1: slightly smaller zoom results only 1-page export
    {
        ScRange range1(0, 0, 0, 11, 79, 0);
        std::shared_ptr<utl::TempFile> pPDFFile = exportToPDF(xModel, range1);
        bool bFound = false;
        CPPUNIT_ASSERT(hasTextInPdf(pPDFFile, "/Count 1>>", bFound));
        CPPUNIT_ASSERT_EQUAL(true, bFound);
    }
}
diff --git a/sc/source/ui/view/printfun.cxx b/sc/source/ui/view/printfun.cxx
index 71f29e3..352012b 100644
--- a/sc/source/ui/view/printfun.cxx
+++ b/sc/source/ui/view/printfun.cxx
@@ -2898,6 +2898,24 @@
                nZoom = (nLastFitZoom + nZoom) / 2;
            }
        }
        // tdf#103516 remove the almost blank page(s) for better
        // interoperability by using slightly smaller zoom
        if (nW > 0 && nH == 0 && m_aRanges.m_nPagesY > 1)
        {
            sal_uInt32 nLastPagesY = m_aRanges.m_nPagesY;
            nLastFitZoom = nZoom;
            nZoom *= 0.98;
            if (nZoom < nLastFitZoom)
            {
                CalcPages();
                // same page count with smaller zoom: use the original zoom
                if (m_aRanges.m_nPagesY == nLastPagesY)
                {
                    nZoom = nLastFitZoom;
                    CalcPages();
                }
            }
        }
    }
    else if (aTableParam.bScaleAll)
    {