tdf#168638 sc: tag a database range's labels as TH
Mark a table with Data - Define Range and the option "Contains column labels", and export with File - Export as PDF and Tagged PDF on. Every cell of the range comes out the same: <</Type/StructElem/S/TD /P 7 0 R /Pg 1 0 R /K[9 0 R ]>> so nothing in the file marks the row that names the columns. The label cells are TH now: <</Type/StructElem/S/TH /P 7 0 R /Pg 1 0 R /A <</O/Table/Scope/Column>> /K[9 0 R ]>> A range sorted left to right holds its labels in the first column and gets /Scope/Row there. A range Calc defines itself for a sort or an AutoFilter counts the same, on the header row it guessed. Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com> Change-Id: I5f4d3ec709f330604d518e1bf0c89c1655b18816 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12657 Reviewed-by: Miklos Vajna <vmiklos@collabora.com> Tested-by: Jenkins CPCI <releng@collaboraoffice.com> (cherry picked from commit 8a524317421379ba257fcfba04f63f06da7cdcc1) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211174 Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org> Tested-by: Jenkins
This commit is contained in:
committed by
Xisco Fauli
parent
fe55fbaa27
commit
cfbbd638ab
@@ -225,6 +225,7 @@ public:
|
||||
void MoveTo(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
|
||||
SCCOL nUpdateCol = -1);
|
||||
void SetByRow(bool bByR) { bByRow = bByR; }
|
||||
bool IsByRow() const { return bByRow; }
|
||||
bool HasHeader() const { return bHasHeader; }
|
||||
void SetHeader(bool bHasH) { bHasHeader = bHasH; }
|
||||
bool HasTotals() const { return bHasTotals; }
|
||||
|
||||
@@ -1097,6 +1097,75 @@ CPPUNIT_TEST_FIXTURE(ScPDFExportTest, testPageRangeSkipsSheetStart)
|
||||
CPPUNIT_ASSERT_EQUAL(2, nOutlineItems);
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(ScPDFExportTest, testDatabaseRangeHeader)
|
||||
{
|
||||
loadFromFile(u"db-range-header.fods");
|
||||
exportWholeDocumentToPDF({ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true) });
|
||||
|
||||
vcl::filter::PDFDocument aDocument;
|
||||
CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ)));
|
||||
|
||||
std::vector<vcl::filter::PDFObjectElement*> aTables;
|
||||
for (const auto& rDocElement : aDocument.GetElements())
|
||||
{
|
||||
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
|
||||
if (!pObject)
|
||||
continue;
|
||||
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
|
||||
if (pType && pType->GetValue() == "Table")
|
||||
aTables.push_back(pObject);
|
||||
}
|
||||
// the sheet fits one page, so every row it has is in this one table
|
||||
CPPUNIT_ASSERT_EQUAL(size_t(1), aTables.size());
|
||||
|
||||
vcl::filter::PDFObjectElement* pTable = aTables.front();
|
||||
|
||||
OStringBuffer aCells;
|
||||
auto pRows = dynamic_cast<vcl::filter::PDFArrayElement*>(pTable->Lookup("K"_ostr));
|
||||
CPPUNIT_ASSERT(pRows);
|
||||
for (auto* pRowElement : pRows->GetElements())
|
||||
{
|
||||
auto pRow = dynamic_cast<vcl::filter::PDFReferenceElement*>(pRowElement);
|
||||
CPPUNIT_ASSERT(pRow);
|
||||
CPPUNIT_ASSERT(pRow->LookupObject());
|
||||
auto pCells
|
||||
= dynamic_cast<vcl::filter::PDFArrayElement*>(pRow->LookupObject()->Lookup("K"_ostr));
|
||||
CPPUNIT_ASSERT(pCells);
|
||||
for (auto* pCellElement : pCells->GetElements())
|
||||
{
|
||||
auto pCell = dynamic_cast<vcl::filter::PDFReferenceElement*>(pCellElement);
|
||||
CPPUNIT_ASSERT(pCell);
|
||||
CPPUNIT_ASSERT(pCell->LookupObject());
|
||||
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(
|
||||
pCell->LookupObject()->Lookup("S"_ostr));
|
||||
CPPUNIT_ASSERT(pType);
|
||||
// the scope a header cell announces, and nothing for a data cell
|
||||
OString aScope;
|
||||
if (vcl::filter::PDFElement* pElement = pCell->LookupObject()->Lookup("A"_ostr))
|
||||
{
|
||||
// one owner dictionary, which several attribute groups would turn into an array
|
||||
auto pAttributes = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pElement);
|
||||
CPPUNIT_ASSERT(pAttributes);
|
||||
auto pValue = dynamic_cast<vcl::filter::PDFNameElement*>(
|
||||
pAttributes->LookupElement("Scope"_ostr));
|
||||
CPPUNIT_ASSERT(pValue);
|
||||
aScope = "/" + pValue->GetValue();
|
||||
}
|
||||
aCells.append(pType->GetValue() + aScope + " ");
|
||||
}
|
||||
aCells.append("| ");
|
||||
}
|
||||
|
||||
// Without the fix every cell was a TD, so this read
|
||||
// "TD TD | TD TD | TD TD | TD | TD TD | TD TD | TD TD | TD TD | ".
|
||||
//
|
||||
// the labels of the first range and of the one kept by column, and nothing in the range
|
||||
// defined without labels or outside every range
|
||||
CPPUNIT_ASSERT_EQUAL("TH/Column TH/Column | TD TD | TD TD | TD | TD TD | TD TD | "
|
||||
"TH/Row TD | TH/Row TD | "_ostr,
|
||||
aCells.makeStringAndClear());
|
||||
}
|
||||
|
||||
// just needs to not crash on export to pdf
|
||||
CPPUNIT_TEST_FIXTURE(ScPDFExportTest, testForcepoint97)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office: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:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
|
||||
<office:font-face-decls>
|
||||
<style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/>
|
||||
</office:font-face-decls>
|
||||
<office:styles>
|
||||
<style:default-style style:family="table-cell">
|
||||
<style:text-properties style:font-name="Liberation Sans" fo:font-size="10pt" fo:language="en" fo:country="US"/>
|
||||
</style:default-style>
|
||||
</office:styles>
|
||||
<office:automatic-styles>
|
||||
<style:style style:name="T1" style:family="text">
|
||||
<style:text-properties fo:font-weight="bold"/>
|
||||
</style:style>
|
||||
</office:automatic-styles>
|
||||
<office:body>
|
||||
<office:spreadsheet>
|
||||
<table:table table:name="Parts">
|
||||
<table:table-column table:number-columns-repeated="2"/>
|
||||
<table:table-row>
|
||||
<table:table-cell office:value-type="string"><text:p>Na<text:span text:style-name="T1">me</text:span></text:p></table:table-cell>
|
||||
<table:table-cell office:value-type="string"><text:p>Quantity</text:p></table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell office:value-type="string"><text:p>Bolt</text:p></table:table-cell>
|
||||
<table:table-cell office:value-type="float" office:value="12"><text:p>12</text:p></table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell office:value-type="string"><text:p>Nut</text:p></table:table-cell>
|
||||
<table:table-cell office:value-type="float" office:value="34"><text:p>34</text:p></table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:number-columns-repeated="2"/>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell office:value-type="string"><text:p>Outside</text:p></table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:number-columns-repeated="2"/>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell office:value-type="string"><text:p>Plain</text:p></table:table-cell>
|
||||
<table:table-cell office:value-type="string"><text:p>Text</text:p></table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell office:value-type="string"><text:p>Screw</text:p></table:table-cell>
|
||||
<table:table-cell office:value-type="float" office:value="56"><text:p>56</text:p></table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:number-columns-repeated="2"/>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell office:value-type="string"><text:p>Width</text:p></table:table-cell>
|
||||
<table:table-cell office:value-type="float" office:value="78"><text:p>78</text:p></table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell office:value-type="string"><text:p>Height</text:p></table:table-cell>
|
||||
<table:table-cell office:value-type="float" office:value="90"><text:p>90</text:p></table:table-cell>
|
||||
</table:table-row>
|
||||
</table:table>
|
||||
<table:database-ranges>
|
||||
<table:database-range table:name="PartList" table:target-range-address="Parts.A1:Parts.B3" table:contains-header="true"/>
|
||||
<table:database-range table:name="NoLabels" table:target-range-address="Parts.A7:Parts.B8" table:contains-header="false"/>
|
||||
<table:database-range table:name="ByColumn" table:target-range-address="Parts.A10:Parts.B11" table:contains-header="true" table:orientation="column"/>
|
||||
</table:database-ranges>
|
||||
</office:spreadsheet>
|
||||
</office:body>
|
||||
</office:document>
|
||||
@@ -89,7 +89,7 @@ struct ScEnhancedPDFState
|
||||
TableDataIdMap m_TableDataMap;
|
||||
/// the Worksheet element of every sheet drawn so far
|
||||
std::unordered_map<SCTAB, sal_Int32> m_WorksheetIds;
|
||||
/// the TD element at every grid position drawn so far, unlike m_TableDataMap kept for the
|
||||
/// the cell element at every grid position drawn so far, unlike m_TableDataMap kept for the
|
||||
/// whole export; the grid position, not the cell the content came from, is what shares a
|
||||
/// page with the destination that names it
|
||||
std::unordered_map<ScAddress, sal_Int32> m_CellIds;
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
#include <spellcheckcontext.hxx>
|
||||
#include <scopetools.hxx>
|
||||
#include <tabvwsh.hxx>
|
||||
#include <dbdata.hxx>
|
||||
|
||||
#include <com/sun/star/i18n/DirectionProperty.hpp>
|
||||
#include <comphelper/scopeguard.hxx>
|
||||
@@ -1487,6 +1488,40 @@ static SvxCellHorJustify getAlignmentFromContext( SvxCellHorJustify eInHorJust,
|
||||
return eHorJustContext;
|
||||
}
|
||||
|
||||
/// what a database range's labels head, where this position holds one of them
|
||||
static vcl::pdf::PDFWriter::StructAttributeValue
|
||||
lcl_GetLabelScope(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, SCROW nRow)
|
||||
{
|
||||
const ScDBData* pDBData(rDoc.GetDBAtCursor(nCol, nRow, nTab, ScDBDataPortion::AREA));
|
||||
if (!pDBData || !pDBData->HasHeader())
|
||||
return vcl::pdf::PDFWriter::Invalid;
|
||||
|
||||
ScRange aRange;
|
||||
pDBData->GetArea(aRange);
|
||||
// a range the user works left to right keeps its labels in the first column
|
||||
if (!pDBData->IsByRow())
|
||||
return nCol == aRange.aStart.Col() ? vcl::pdf::PDFWriter::Row
|
||||
: vcl::pdf::PDFWriter::Invalid;
|
||||
|
||||
return nRow == aRange.aStart.Row() ? vcl::pdf::PDFWriter::Column
|
||||
: vcl::pdf::PDFWriter::Invalid;
|
||||
}
|
||||
|
||||
/// the element for a cell, a TH where a database range says the cell holds one of its labels
|
||||
static sal_Int32 lcl_OpenCellElement(vcl::PDFExtOutDevData& rPDF, const ScDocument& rDoc,
|
||||
SCTAB nTab, SCCOL nCol, SCROW nRow)
|
||||
{
|
||||
const vcl::pdf::PDFWriter::StructAttributeValue eScope(
|
||||
lcl_GetLabelScope(rDoc, nTab, nCol, nRow));
|
||||
if (eScope == vcl::pdf::PDFWriter::Invalid)
|
||||
return rPDF.WrapBeginStructureElement(vcl::pdf::StructElement::TableData, u"TD"_ustr);
|
||||
|
||||
const sal_Int32 nId(
|
||||
rPDF.WrapBeginStructureElement(vcl::pdf::StructElement::TableHeader, u"TH"_ustr));
|
||||
rPDF.SetStructureAttribute(vcl::pdf::PDFWriter::Scope, eScope);
|
||||
return nId;
|
||||
}
|
||||
|
||||
void ScOutputData::DrawStrings( bool bPixelToLogic )
|
||||
{
|
||||
LayoutStrings(bPixelToLogic);
|
||||
@@ -1963,7 +1998,7 @@ void ScOutputData::LayoutStringsImpl(bool const bPixelToLogic, RowInfo* const pT
|
||||
pThisRowInfo->basicCellInfo(nMarkX).bEditEngine = true;
|
||||
bDoCell = false; // don't draw here
|
||||
|
||||
// Mark the tagged "TD" structure element to be drawn in DrawEdit
|
||||
// Mark the tagged cell structure element to be drawn in DrawEdit
|
||||
if (bTaggedPDF)
|
||||
{
|
||||
if (bReopenRowTag)
|
||||
@@ -1978,10 +2013,7 @@ void ScOutputData::LayoutStringsImpl(bool const bPixelToLogic, RowInfo* const pT
|
||||
bReopenRowTag = true;
|
||||
}
|
||||
|
||||
pPDF->WrapBeginStructureElement(vcl::pdf::StructElement::TableData,
|
||||
u"TD"_ustr);
|
||||
|
||||
sal_Int32 nId = pPDF->GetCurrentStructureElement();
|
||||
const sal_Int32 nId(lcl_OpenCellElement(*pPDF, *mpDoc, mnTab, nX, nY));
|
||||
pPDF->GetScPDFState()->m_TableDataMap[{ nY, nX }] = nId;
|
||||
// the first page it is drawn on, which is the one a destination for it names
|
||||
pPDF->GetScPDFState()->m_CellIds.emplace(ScAddress(nX, nY, mnTab), nId);
|
||||
@@ -2215,8 +2247,7 @@ void ScOutputData::LayoutStringsImpl(bool const bPixelToLogic, RowInfo* const pT
|
||||
// what a destination naming this cell has to reach
|
||||
pPDF->GetScPDFState()->m_CellIds.emplace(
|
||||
ScAddress(nX, nY, mnTab),
|
||||
pPDF->WrapBeginStructureElement(vcl::pdf::StructElement::TableData,
|
||||
u"TD"_ustr));
|
||||
lcl_OpenCellElement(*pPDF, *mpDoc, mnTab, nX, nY));
|
||||
pPDF->WrapBeginStructureElement(vcl::pdf::StructElement::Paragraph,
|
||||
u"P"_ustr);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user