tdf#145726: Calc: print empty rows that carry a border
- a form with a header row and a hundred empty bordered rows below it printed only the header row. Calc drops a run of 84 or more equally formatted rows below the last cell with content, and everything under it, so the print range ended at the last row with data. The borders were on screen all along, so only printing, the print preview and the PDF export showed it. - the run is measured from the last row with data, so typing changed the result. On a form whose borders end at row 100, text in row 16 left 84 rows below it and the borders stayed away, text in row 17 left 83 and the whole form printed. - ScDocument::IsPrintEmpty prints a page that carries a border and no content, and suppresses one carrying only a colour. A bordered run is therefore kept now, unless it is wider than SC_VISATTR_BORDER_STOP in its own length or in its distance below the data, which is the whole column case i30830 is about. The length stays the same as the sheet is filled in, the distance stops a chain of shorter bordered ranges reaching the sheet end. Neither measure applies to a run under SC_VISATTR_STOP rows, because a run that short was never dropped. - the print range also sets where Ctrl+End lands, how far the desktop scrolls and what range is written out on save, so all three grow with it. In the browser the scroll extent comes from the cell area, so it stays where it was. - a run has to meet one more condition than before to be dropped, so no document prints less than it did, and the ones hitting this bug print more. - the tests say a bordered form is printed, a colour under the content does not pull the print area down, a border too wide or too far down does not either, a frame round a whole column leaves the printout ending at the last row with content, and a form is printed wherever the content ends, including when a font change splits it in two. Signed-off-by: Bayram Çiçek <bayram.cicek@collabora.com> Change-Id: I44d041cdbbcde7bfa69b53035f5bebc8d59f9bdd Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/14112 Tested-by: Jenkins CPCI <releng@collaboraoffice.com> Reviewed-by: Michael Stahl <michael.stahl@collabora.com> (cherry picked from commit 00e5de0a8bd89e5f5c9f7b19fef85331354c4458) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211943 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
committed by
Xisco Fauli
parent
720a6d856e
commit
f2531aab9b
@@ -5943,6 +5943,214 @@ CPPUNIT_TEST_FIXTURE(Test, testAreasWithNotes)
|
||||
m_pDoc->DeleteTab(0);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// A header row, then empty rows that carry a border on every side, down to nLastBorderedRow.
|
||||
// This is the shape of a pre-printed form, where the bordered rows are filled in later.
|
||||
void lcl_makeBorderedForm(ScDocument* pDoc, SCROW nLastBorderedRow)
|
||||
{
|
||||
pDoc->SetString(0, 0, 0, u"Header"_ustr);
|
||||
|
||||
::editeng::SvxBorderLine aLine(nullptr, 50, SvxBorderLineStyle::SOLID);
|
||||
SvxBoxItem aBorderItem(ATTR_BORDER);
|
||||
aBorderItem.SetLine(&aLine, SvxBoxItemLine::LEFT);
|
||||
aBorderItem.SetLine(&aLine, SvxBoxItemLine::RIGHT);
|
||||
aBorderItem.SetLine(&aLine, SvxBoxItemLine::TOP);
|
||||
aBorderItem.SetLine(&aLine, SvxBoxItemLine::BOTTOM);
|
||||
ScPatternAttr aBordered(pDoc->getCellAttributeHelper());
|
||||
aBordered.ItemSetPut(aBorderItem);
|
||||
pDoc->ApplyPatternAreaTab(0, 1, 2, nLastBorderedRow, 0, aBordered);
|
||||
}
|
||||
|
||||
// The pattern of a single border line, for marking one cell or one row.
|
||||
ScPatternAttr lcl_oneLine(ScDocument* pDoc, SvxBoxItemLine eSide)
|
||||
{
|
||||
::editeng::SvxBorderLine aLine(nullptr, 50, SvxBorderLineStyle::SOLID);
|
||||
SvxBoxItem aBorderItem(ATTR_BORDER);
|
||||
aBorderItem.SetLine(&aLine, eSide);
|
||||
ScPatternAttr aPattern(pDoc->getCellAttributeHelper());
|
||||
aPattern.ItemSetPut(aBorderItem);
|
||||
return aPattern;
|
||||
}
|
||||
|
||||
ScPatternAttr lcl_colour(ScDocument* pDoc, const Color& rColor)
|
||||
{
|
||||
ScPatternAttr aPattern(pDoc->getCellAttributeHelper());
|
||||
aPattern.ItemSetPut(SvxBrushItem(rColor, ATTR_BACKGROUND));
|
||||
return aPattern;
|
||||
}
|
||||
|
||||
SCROW lcl_printAreaEndRow(ScDocument* pDoc)
|
||||
{
|
||||
SCCOL nEndCol;
|
||||
SCROW nEndRow;
|
||||
CPPUNIT_ASSERT(pDoc->GetPrintArea(0, nEndCol, nEndRow, false));
|
||||
return nEndRow;
|
||||
}
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testPrintAreaCoversABorderedForm)
|
||||
{
|
||||
// Empty rows carrying a border are printed, so they belong to the print area even though
|
||||
// no cell in them holds anything.
|
||||
m_pDoc->InsertTab(0, u"Form"_ustr);
|
||||
lcl_makeBorderedForm(m_pDoc, 99);
|
||||
|
||||
SCCOL nEndCol;
|
||||
SCROW nEndRow;
|
||||
CPPUNIT_ASSERT(m_pDoc->GetPrintArea(0, nEndCol, nEndRow, false));
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("the printout reaches the last bordered row",
|
||||
static_cast<SCROW>(99), nEndRow);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("the printout reaches the last bordered column",
|
||||
static_cast<SCCOL>(2), nEndCol);
|
||||
m_pDoc->DeleteTab(0);
|
||||
|
||||
// A form of over a thousand rows is still a form.
|
||||
m_pDoc->InsertTab(0, u"LongForm"_ustr);
|
||||
lcl_makeBorderedForm(m_pDoc, 1499);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("the printout reaches the last bordered row",
|
||||
static_cast<SCROW>(1499), lcl_printAreaEndRow(m_pDoc));
|
||||
m_pDoc->DeleteTab(0);
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testPrintAreaIgnoresAColourBelowTheContent)
|
||||
{
|
||||
// A colour below the last cell with content stays out of the print area, wherever it sits
|
||||
// and however far it runs.
|
||||
const SCROW nLast = m_pDoc->MaxRow();
|
||||
|
||||
const std::pair<SCROW, SCROW> aRanges[] = {
|
||||
{ 0, nLast }, // the whole column
|
||||
{ 1, nLast }, // from under the header to the last row
|
||||
{ 10, nLast }, // from below the content to the last row
|
||||
{ 10, nLast - 1 }, // stopping one row short of the last row
|
||||
{ 49999, 49999 }, // one cell far below the content
|
||||
};
|
||||
|
||||
sal_Int32 nSheet = 0;
|
||||
for (const auto& rRange : aRanges)
|
||||
{
|
||||
m_pDoc->InsertTab(0, "Colour" + OUString::number(nSheet++));
|
||||
m_pDoc->SetString(0, 0, 0, u"Header"_ustr);
|
||||
m_pDoc->ApplyPatternAreaTab(0, rRange.first, 0, rRange.second, 0,
|
||||
lcl_colour(m_pDoc, COL_BLUE));
|
||||
|
||||
OString aMessage = "a colour over rows " + OString::number(rRange.first) + " to "
|
||||
+ OString::number(rRange.second);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(aMessage.getStr(), static_cast<SCROW>(0),
|
||||
lcl_printAreaEndRow(m_pDoc));
|
||||
m_pDoc->DeleteTab(0);
|
||||
}
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testPrintAreaIgnoresABorderTooWideForAForm)
|
||||
{
|
||||
// A border far below the content, or one many times wider than a form, stays out of the
|
||||
// print area. The printout ends at the last row with content.
|
||||
const SCROW nLast = m_pDoc->MaxRow();
|
||||
|
||||
const std::pair<SCROW, SCROW> aRanges[] = {
|
||||
{ nLast, nLast }, // the last row of the sheet
|
||||
{ 10, 50000 }, // reaching far below the content
|
||||
};
|
||||
|
||||
sal_Int32 nSheet = 0;
|
||||
for (const auto& rRange : aRanges)
|
||||
{
|
||||
m_pDoc->InsertTab(0, "Border" + OUString::number(nSheet++));
|
||||
m_pDoc->SetString(0, 0, 0, u"Header"_ustr);
|
||||
m_pDoc->ApplyPatternAreaTab(0, rRange.first, 0, rRange.second, 0,
|
||||
lcl_oneLine(m_pDoc, SvxBoxItemLine::BOTTOM));
|
||||
|
||||
OString aMessage = "a border over rows " + OString::number(rRange.first) + " to "
|
||||
+ OString::number(rRange.second);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(aMessage.getStr(), static_cast<SCROW>(0),
|
||||
lcl_printAreaEndRow(m_pDoc));
|
||||
m_pDoc->DeleteTab(0);
|
||||
}
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testPrintAreaIgnoresAFrameRoundAWholeColumn)
|
||||
{
|
||||
// A frame round a whole column keeps the side lines on every row, the top line on the
|
||||
// first row and the bottom line on the last, so it arrives as three ranges and the middle
|
||||
// one covers nearly the sheet. The printout ends at the last row with content.
|
||||
m_pDoc->InsertTab(0, u"Frame"_ustr);
|
||||
m_pDoc->SetString(0, 0, 0, u"Header"_ustr);
|
||||
const SCROW nLast = m_pDoc->MaxRow();
|
||||
|
||||
::editeng::SvxBorderLine aLine(nullptr, 50, SvxBorderLineStyle::SOLID);
|
||||
SvxBoxItem aSides(ATTR_BORDER);
|
||||
aSides.SetLine(&aLine, SvxBoxItemLine::LEFT);
|
||||
aSides.SetLine(&aLine, SvxBoxItemLine::RIGHT);
|
||||
ScPatternAttr aSidesOnly(m_pDoc->getCellAttributeHelper());
|
||||
aSidesOnly.ItemSetPut(aSides);
|
||||
m_pDoc->ApplyPatternAreaTab(0, 0, 0, nLast, 0, aSidesOnly);
|
||||
|
||||
SvxBoxItem aTop(aSides);
|
||||
aTop.SetLine(&aLine, SvxBoxItemLine::TOP);
|
||||
ScPatternAttr aTopRow(m_pDoc->getCellAttributeHelper());
|
||||
aTopRow.ItemSetPut(aTop);
|
||||
m_pDoc->ApplyPatternAreaTab(0, 0, 0, 0, 0, aTopRow);
|
||||
|
||||
SvxBoxItem aBottom(aSides);
|
||||
aBottom.SetLine(&aLine, SvxBoxItemLine::BOTTOM);
|
||||
ScPatternAttr aBottomRow(m_pDoc->getCellAttributeHelper());
|
||||
aBottomRow.ItemSetPut(aBottom);
|
||||
m_pDoc->ApplyPatternAreaTab(0, nLast, 0, nLast, 0, aBottomRow);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("the printout stops at the last row with content",
|
||||
static_cast<SCROW>(0), lcl_printAreaEndRow(m_pDoc));
|
||||
m_pDoc->DeleteTab(0);
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testPrintAreaDoesNotMoveAsTheSheetIsFilledIn)
|
||||
{
|
||||
// A form is printed in full wherever the last cell with content sits, so filling the form
|
||||
// in row by row leaves the printout the same.
|
||||
for (SCROW nContentRow : { SCROW(1), SCROW(13), SCROW(15), SCROW(16), SCROW(17), SCROW(50) })
|
||||
{
|
||||
m_pDoc->InsertTab(0, "Form" + OUString::number(nContentRow));
|
||||
lcl_makeBorderedForm(m_pDoc, 99);
|
||||
m_pDoc->SetString(0, nContentRow, 0, u"filled in"_ustr);
|
||||
|
||||
OString aMessage = "a form, content in row " + OString::number(nContentRow);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(aMessage.getStr(), static_cast<SCROW>(99),
|
||||
lcl_printAreaEndRow(m_pDoc));
|
||||
m_pDoc->DeleteTab(0);
|
||||
}
|
||||
|
||||
// A block too wide to be a form stays out of the printout while the content ends well
|
||||
// above it.
|
||||
for (SCROW nContentRow : { SCROW(0), SCROW(600), SCROW(3000) })
|
||||
{
|
||||
m_pDoc->InsertTab(0, "Wide" + OUString::number(nContentRow));
|
||||
m_pDoc->SetString(0, 0, 0, u"Header"_ustr);
|
||||
if (nContentRow > 0)
|
||||
m_pDoc->SetString(0, nContentRow, 0, u"filled in"_ustr);
|
||||
lcl_makeBorderedForm(m_pDoc, 10499);
|
||||
|
||||
OString aMessage = "a wide block, content in row " + OString::number(nContentRow);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(aMessage.getStr(), nContentRow,
|
||||
lcl_printAreaEndRow(m_pDoc));
|
||||
m_pDoc->DeleteTab(0);
|
||||
}
|
||||
|
||||
// A form too wide for the printout stays out of it wherever the content sits inside it.
|
||||
// Bold over the upper part splits the form into two ranges that still look the same, and
|
||||
// the lower one is short enough on its own to pass for a form.
|
||||
m_pDoc->InsertTab(0, u"Split"_ustr);
|
||||
lcl_makeBorderedForm(m_pDoc, 3000);
|
||||
ScPatternAttr aBold(m_pDoc->getCellAttributeHelper());
|
||||
aBold.ItemSetPut(SvxWeightItem(WEIGHT_BOLD, ATTR_FONT_WEIGHT));
|
||||
m_pDoc->ApplyPatternAreaTab(0, 1, 2, 2500, 0, aBold);
|
||||
m_pDoc->SetString(0, 2600, 0, u"filled in"_ustr);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("a wide form, content in its lower range",
|
||||
static_cast<SCROW>(2600), lcl_printAreaEndRow(m_pDoc));
|
||||
m_pDoc->DeleteTab(0);
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testAnchoredRotatedShape)
|
||||
{
|
||||
m_pDoc->InsertTab(0, u"TestTab"_ustr);
|
||||
|
||||
@@ -1919,6 +1919,12 @@ bool ScAttrArray::GetFirstVisibleAttr( SCROW& rFirstRow ) const
|
||||
|
||||
const SCROW SC_VISATTR_STOP = 84;
|
||||
|
||||
// how far a bordered block reaches into the print area, in its whole length and in its
|
||||
// distance below the last cell with content. The length counts the rows that already hold
|
||||
// content, so it stays the same as the form is filled in. 2000 rows is some forty pages here.
|
||||
|
||||
const SCROW SC_VISATTR_BORDER_STOP = 2000;
|
||||
|
||||
bool ScAttrArray::GetLastVisibleAttr( SCROW& rLastRow, SCROW nLastData, bool bSkipEmpty ) const
|
||||
{
|
||||
if ( mvData.empty() )
|
||||
@@ -1928,8 +1934,8 @@ bool ScAttrArray::GetLastVisibleAttr( SCROW& rLastRow, SCROW nLastData, bool bSk
|
||||
}
|
||||
|
||||
// #i30830# changed behavior:
|
||||
// ignore all attributes starting with the first run of SC_VISATTR_STOP equal rows
|
||||
// below the last content cell
|
||||
// ignore a long run of equally formatted rows below the last content cell, and everything
|
||||
// below it. A bordered run of the size and position a form has is kept instead.
|
||||
|
||||
if ( nLastData == rDocument.MaxRow() )
|
||||
{
|
||||
@@ -1965,7 +1971,30 @@ bool ScAttrArray::GetLastVisibleAttr( SCROW& rLastRow, SCROW nLastData, bool bSk
|
||||
if ( nAttrStartRow <= nLastData )
|
||||
nAttrStartRow = nLastData + 1;
|
||||
SCROW nAttrSize = mvData[nEndPos].nEndRow + 1 - nAttrStartRow;
|
||||
bool bStop = false;
|
||||
if ( nAttrSize >= SC_VISATTR_STOP )
|
||||
{
|
||||
// The length is measured over the whole block, so it holds steady as the
|
||||
// sheet is filled in. The distance keeps the search within
|
||||
// SC_VISATTR_BORDER_STOP rows of the last cell with content.
|
||||
const SvxBoxItem& rBox
|
||||
= mvData[nEndPos].getScPatternAttr()->GetItem( ATTR_BORDER );
|
||||
const bool bBordered = rBox.GetLeft() || rBox.GetRight()
|
||||
|| rBox.GetTop() || rBox.GetBottom();
|
||||
// walk back to the first range of this block, so the length is the same
|
||||
// whichever of its ranges holds the last cell with content
|
||||
SCSIZE nStartPos = nPos;
|
||||
while ( nStartPos > 0
|
||||
&& mvData[nStartPos-1].getScPatternAttr()->IsVisibleEqual(
|
||||
*mvData[nStartPos].getScPatternAttr() ) )
|
||||
--nStartPos;
|
||||
const SCROW nRangeStartRow
|
||||
= ( nStartPos > 0 ) ? ( mvData[nStartPos-1].nEndRow + 1 ) : 0;
|
||||
bStop = !bBordered
|
||||
|| mvData[nEndPos].nEndRow + 1 - nRangeStartRow >= SC_VISATTR_BORDER_STOP
|
||||
|| mvData[nEndPos].nEndRow - nLastData >= SC_VISATTR_BORDER_STOP;
|
||||
}
|
||||
if ( bStop )
|
||||
break; // while, ignore this range and below
|
||||
else if ( mvData[nEndPos].getScPatternAttr()->IsVisible() )
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user