drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx | 19 + include/vcl/pdfwriter.hxx | 4 vcl/qa/cppunit/pdfexport/data/formcontrol.fodt | 192 +++++++++++++ vcl/qa/cppunit/pdfexport/pdfexport.cxx | 132 ++++++++ vcl/source/gdi/pdfwriter_impl.cxx | 72 ++-- 5 files changed, 387 insertions(+), 32 deletions(-)
New commits: commit 2a87a59a52ae26db5106f7d1f4346225d032b550 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Mon Mar 27 12:51:24 2023 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Apr 4 15:01:15 2023 +0200 tdf#152234 vcl,drawinglayer: PDF/UA export: produce Role for form controls veraPDF complains: Specification: ISO 14289-1:2014, Clause: 7.18.4, Test number: 2 If the Form element omits a Role attribute (Table 348), it shall have only one child: an object reference (14.7.4.3) identifying the widget annotation per ISO 32000-1:2008, 14.8.4.5, Table 340. LO forms produce both page content in an MCID and an /Annot, so Role is needed. Change-Id: Ic231931a7c35d8da37ca76e02d97501edb43347c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/149626 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx index 56675ff113ef..58396ba0c73b 100644 --- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx @@ -1158,6 +1158,25 @@ void VclMetafileProcessor2D::processControlPrimitive2D( mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Form); OUString const& rAltText(rControlPrimitive.GetAltText()); + vcl::PDFWriter::StructAttributeValue role; + switch (pPDFControl->Type) + { + case vcl::PDFWriter::PushButton: + role = vcl::PDFWriter::Pb; + break; + case vcl::PDFWriter::RadioButton: + role = vcl::PDFWriter::Rb; + break; + case vcl::PDFWriter::CheckBox: + role = vcl::PDFWriter::Cb; + break; + default: // there is a paucity of roles, tv is the catch-all one + role = vcl::PDFWriter::Tv; + break; + } + // ISO 14289-1:2014, Clause: 7.18.4 + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Role, role); + // ISO 14289-1:2014, Clause: 7.18.1 if (!rAltText.isEmpty()) { mpPDFExtOutDevData->SetAlternateText(rAltText); diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx index b16e7120b398..0d959f9add34 100644 --- a/include/vcl/pdfwriter.hxx +++ b/include/vcl/pdfwriter.hxx @@ -141,7 +141,7 @@ public: Placement, WritingMode, SpaceBefore, SpaceAfter, StartIndent, EndIndent, TextIndent, TextAlign, Width, Height, BlockAlign, InlineAlign, LineHeight, BaselineShift, TextDecorationType, ListNumbering, - RowSpan, ColSpan, Scope, + RowSpan, ColSpan, Scope, Role, // link destination is an artificial attribute that sets // the link annotation ID of a Link element @@ -180,6 +180,8 @@ public: Underline, Overline, LineThrough, // Scope Row, Column, Both, + // Role + Rb, Cb, Pb, Tv, // ListNumbering Disc, Circle, Square, Decimal, UpperRoman, LowerRoman, UpperAlpha, LowerAlpha }; diff --git a/vcl/qa/cppunit/pdfexport/data/formcontrol.fodt b/vcl/qa/cppunit/pdfexport/data/formcontrol.fodt new file mode 100644 index 000000000000..f6ec84585217 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/formcontrol.fodt @@ -0,0 +1,192 @@ +<?xml version='1.0' encoding='UTF-8'?> +<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:c alcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext: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:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns: meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:meta><meta:creation-date>2023-04-03T17:28:30.031698782</meta:creation-date><dc:date>2023-04-03T17:48:01.654889994</dc:date><meta:editing-duration>PT19M34S</meta:editing-duration><meta:editing-cycles>3</meta:editing-cycles><meta:generator>LibreOfficeDev/7.6.0.0.alpha0$Linux_X86_64 LibreOffice_project/581bc338cb60e9511c2f870acfbb7ec3593a582d</meta:generator><dc:title>dummy</dc:title><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="0" meta:word-count="0" meta:character-count="0" meta:non-whitespace-character-count="0"/></office:meta> + <office:font-face-decls> + <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/> + <style:font-face style:name="Lohit Devanagari1" svg:font-family="'Lohit Devanagari'" style:font-family-generic="system" style:font-pitch="variable"/> + <style:font-face style:name="Source Han Serif CN" svg:font-family="'Source Han Serif CN'" style:font-family-generic="system" style:font-pitch="variable"/> + </office:font-face-decls> + <office:styles> + <style:default-style style:family="graphic"> + <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:flow-with-text="false"/> + <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false"> + <style:tab-stops/> + </style:paragraph-properties> + <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Source Han Serif CN" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/> + </style:default-style> + <style:default-style style:family="paragraph"> + <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/> + <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Source Han Serif CN" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/> + </style:default-style> + <style:default-style style:family="table"> + <style:table-properties table:border-model="collapsing"/> + </style:default-style> + <style:default-style style:family="table-row"> + <style:table-row-properties fo:keep-together="auto"/> + </style:default-style> + <style:style style:name="Standard" style:family="paragraph" style:class="text"/> + <text:outline-style style:name="Outline"> + <text:outline-level-style text:level="1" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="2" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="3" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="4" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="5" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="6" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="7" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="8" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="9" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="10" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + </text:outline-style> + <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/> + <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/> + <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/> + <loext:theme loext:name="Office"> + <loext:color-table loext:name="LibreOffice"> + <loext:color loext:name="dk1" loext:color="#000000"/> + <loext:color loext:name="lt1" loext:color="#ffffff"/> + <loext:color loext:name="dk2" loext:color="#000000"/> + <loext:color loext:name="lt2" loext:color="#ffffff"/> + <loext:color loext:name="accent1" loext:color="#18a303"/> + <loext:color loext:name="accent2" loext:color="#0369a3"/> + <loext:color loext:name="accent3" loext:color="#a33e03"/> + <loext:color loext:name="accent4" loext:color="#8e03a3"/> + <loext:color loext:name="accent5" loext:color="#c99c00"/> + <loext:color loext:name="accent6" loext:color="#c9211e"/> + <loext:color loext:name="hlink" loext:color="#0000ee"/> + <loext:color loext:name="folHlink" loext:color="#551a8b"/> + </loext:color-table> + </loext:theme> + </office:styles> + <office:automatic-styles> + <style:style style:name="P1" style:family="paragraph"> + <style:paragraph-properties fo:text-align="start"/> + </style:style> + <style:style style:name="gr1" style:family="graphic"> + <style:graphic-properties draw:textarea-vertical-align="middle" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph"/> + </style:style> + <style:page-layout style:name="pm1"> + <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm"> + <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + <style:page-layout style:name="pm2" style:page-usage="left"> + <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm"> + <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + <style:page-layout style:name="pm3" style:page-usage="right"> + <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm"> + <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + <style:page-layout style:name="pm4"> + <style:page-layout-properties fo:page-width="22.901cm" fo:page-height="11.4cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm"> + <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + <style:page-layout style:name="pm5"> + <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="1cm" fo:margin-bottom="1cm" fo:margin-left="2cm" fo:margin-right="1cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm"> + <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + <style:page-layout style:name="pm6"> + <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm"> + <style:footnote-sep style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + <style:page-layout style:name="pm7"> + <style:page-layout-properties fo:page-width="29.7cm" fo:page-height="21.001cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm"> + <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + </office:automatic-styles> + <office:master-styles> + <style:master-page style:name="Standard" style:page-layout-name="pm1"/> + </office:master-styles> + <office:body> + <office:text> + <office:forms form:automatic-focus="false" form:apply-design-mode="false"> + <form:form form:name="Form" form:apply-filter="true" form:command-type="table" form:control-implementation="ooo:com.sun.star.form.component.Form" office:target-frame=""> + <form:properties> + <form:property form:property-name="PropertyChangeNotificationEnabled" office:value-type="boolean" office:boolean-value="true"/> + <form:property form:property-name="TargetURL" office:value-type="string" office:string-value=""/> + </form:properties> + <form:checkbox form:name="Check Box 1" form:control-implementation="ooo:com.sun.star.form.component.CheckBox" xml:id="control1" form:id="control1" form:label="Check Box" form:input-required="false" form:image-position="center"> + <form:properties> + <form:property form:property-name="ControlTypeinMSO" office:value-type="float" office:value="0"/> + <form:property form:property-name="DefaultControl" office:value-type="string" office:string-value="com.sun.star.form.control.CheckBox"/> + <form:property form:property-name="HelpText" office:value-type="string" office:string-value="helpful text"/> + <form:property form:property-name="ObjIDinMSO" office:value-type="float" office:value="65535"/> + <form:property form:property-name="SecondaryRefValue" office:value-type="string" office:string-value=""/> + </form:properties> + </form:checkbox> + </form:form> + </office:forms> + <text:sequence-decls> + <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/> + <text:sequence-decl text:display-outline-level="0" text:name="Table"/> + <text:sequence-decl text:display-outline-level="0" text:name="Text"/> + <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/> + <text:sequence-decl text:display-outline-level="0" text:name="Figure"/> + </text:sequence-decls> + <text:p text:style-name="Standard"><draw:control text:anchor-type="paragraph" draw:z-index="0" draw:name="Control 1" draw:style-name="gr1" draw:text-style-name="P1" svg:width="3.003cm" svg:height="1.04cm" svg:x="1.381cm" svg:y="0.651cm" draw:control="control1"> + <svg:title>textuelle alternative</svg:title> + <svg:desc>a box to check</svg:desc> + </draw:control></text:p> + </office:text> + </office:body> +</office:document> \ No newline at end of file diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx index 448ba4ba24ed..4f24545a0829 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx @@ -3703,6 +3703,138 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testMediaShapeAnnot) CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef); } +CPPUNIT_TEST_FIXTURE(PdfExportTest, testFormControlAnnot) +{ + aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); + + // Enable PDF/UA + uno::Sequence<beans::PropertyValue> aFilterData( + comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } })); + aMediaDescriptor["FilterData"] <<= aFilterData; + + saveAsPDF(u"formcontrol.fodt"); + + 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()); + + auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots")); + CPPUNIT_ASSERT(pAnnots); + + // There should be one annotation + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size()); + auto pAnnotReference + = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]); + CPPUNIT_ASSERT(pAnnotReference); + // check /Annot + vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject(); + CPPUNIT_ASSERT(pAnnot); + CPPUNIT_ASSERT_EQUAL( + OString("Annot"), + static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"))->GetValue()); + CPPUNIT_ASSERT_EQUAL( + OString("Widget"), + static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"))->GetValue()); + auto pT = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pAnnot->Lookup("T")); + CPPUNIT_ASSERT(pT); + CPPUNIT_ASSERT_EQUAL(OString("Check Box 1"), pT->GetValue()); + auto pTU = dynamic_cast<vcl::filter::PDFHexStringElement*>(pAnnot->Lookup("TU")); + CPPUNIT_ASSERT(pTU); + CPPUNIT_ASSERT_EQUAL(OUString("helpful text"), + ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pTU)); + + auto pStructParent + = dynamic_cast<vcl::filter::PDFNumberElement*>(pAnnot->Lookup("StructParent")); + CPPUNIT_ASSERT(pStructParent); + + vcl::filter::PDFReferenceElement* pStructElemRef(nullptr); + + // check ParentTree to find StructElem + auto nRoots(0); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get()); + if (!pObject1) + continue; + auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type")); + if (pType1 && pType1->GetValue() == "StructTreeRoot") + { + ++nRoots; + auto pParentTree + = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject1->Lookup("ParentTree")); + CPPUNIT_ASSERT(pParentTree); + auto pNumTree = pParentTree->LookupObject(); + CPPUNIT_ASSERT(pNumTree); + auto pNums = dynamic_cast<vcl::filter::PDFArrayElement*>(pNumTree->Lookup("Nums")); + CPPUNIT_ASSERT(pNums); + auto nFound(0); + for (size_t i = 0; i < pNums->GetElements().size(); i += 2) + { + auto pI = dynamic_cast<vcl::filter::PDFNumberElement*>(pNums->GetElement(i)); + if (pI->GetValue() == pStructParent->GetValue()) + { + ++nFound; + CPPUNIT_ASSERT(i < pNums->GetElements().size() - 1); + pStructElemRef + = dynamic_cast<vcl::filter::PDFReferenceElement*>(pNums->GetElement(i + 1)); + CPPUNIT_ASSERT(pStructElemRef); + } + } + CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nFound)>(1), nFound); + } + } + CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRoots)>(1), nRoots); + + // check /StructElem + CPPUNIT_ASSERT(pStructElemRef); + auto pStructElem(pStructElemRef->LookupObject()); + CPPUNIT_ASSERT(pStructElem); + + auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("Type")); + CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType->GetValue()); + auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("S")); + CPPUNIT_ASSERT_EQUAL(OString("Form"), pS->GetValue()); + auto pAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(pStructElem->Lookup("Alt")); + CPPUNIT_ASSERT_EQUAL(OUString("textuelle alternative - a box to check"), + ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAlt)); + auto pA = dynamic_cast<vcl::filter::PDFReferenceElement*>(pStructElem->Lookup("A")); + CPPUNIT_ASSERT(pA); + auto pAObj = pA->LookupObject(); + auto pO = dynamic_cast<vcl::filter::PDFNameElement*>(pAObj->Lookup("O")); + CPPUNIT_ASSERT(pO); + CPPUNIT_ASSERT_EQUAL(OString("PrintField"), pO->GetValue()); + auto pRole = dynamic_cast<vcl::filter::PDFNameElement*>(pAObj->Lookup("Role")); + CPPUNIT_ASSERT(pRole); + CPPUNIT_ASSERT_EQUAL(OString("Cb"), pRole->GetValue()); + auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pStructElem->Lookup("K")); + auto nMCID(0); + auto nRef(0); + for (size_t i = 0; i < pKids->GetElements().size(); ++i) + { + auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i)); + auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids->GetElement(i)); + if (pNum) + { + ++nMCID; + } + if (pRef) + { + ++nRef; + auto pObjR = pRef->LookupObject(); + auto pOType = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->Lookup("Type")); + CPPUNIT_ASSERT_EQUAL(OString("OBJR"), pOType->GetValue()); + auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObjR->Lookup("Obj")); + CPPUNIT_ASSERT_EQUAL(pAnnot, pAnnotRef->LookupObject()); + } + } + CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID); + CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef); +} + CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142129) { loadFromURL(u"master.odm"); diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 242172313940..6415ac76e90e 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -1859,6 +1859,7 @@ const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr ) aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan"; aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan"; aAttributeStrings[ PDFWriter::Scope ] = "Scope"; + aAttributeStrings[ PDFWriter::Role ] = "Role"; aAttributeStrings[ PDFWriter::Type ] = "Type"; aAttributeStrings[ PDFWriter::Subtype ] = "Subtype"; aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation"; @@ -1907,6 +1908,10 @@ const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue aValueStrings[ PDFWriter::Header ] = "Header"; aValueStrings[ PDFWriter::Footer ] = "Footer"; aValueStrings[ PDFWriter::Watermark ] = "Watermark"; + aValueStrings[ PDFWriter::Rb ] = "Rb"; + aValueStrings[ PDFWriter::Cb ] = "Cb"; + aValueStrings[ PDFWriter::Pb ] = "Pb"; + aValueStrings[ PDFWriter::Tv ] = "Tv"; aValueStrings[ PDFWriter::Disc ] = "Disc"; aValueStrings[ PDFWriter::Circle ] = "Circle"; aValueStrings[ PDFWriter::Square ] = "Square"; @@ -1977,10 +1982,15 @@ OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) { // create layout, list and table attribute sets OStringBuffer aLayout(256), aList(64), aTable(64); + OStringBuffer aPrintField; for (auto const& attribute : i_rEle.m_aAttributes) { if( attribute.first == PDFWriter::ListNumbering ) appendStructureAttributeLine( attribute.first, attribute.second, aList, true ); + else if (attribute.first == PDFWriter::Role) + { + appendStructureAttributeLine(attribute.first, attribute.second, aPrintField, true); + } else if( attribute.first == PDFWriter::RowSpan || attribute.first == PDFWriter::ColSpan || attribute.first == PDFWriter::Scope) @@ -2028,47 +2038,37 @@ OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) } std::vector< sal_Int32 > aAttribObjects; - if( !aLayout.isEmpty() ) + auto const WriteAttrs = [&](char const*const pName, OStringBuffer & rBuf) { aAttribObjects.push_back( createObject() ); if (updateObject( aAttribObjects.back() )) { - OString aObj = - OString::number(aAttribObjects.back()) - + " 0 obj\n" - "<</O/Layout\n"; - aLayout.append( ">>\nendobj\n\n" ); - writeBuffer( aObj ); - writeBuffer( aLayout ); - } + OStringBuffer aObj( 64 ); + aObj.append( aAttribObjects.back() ); + aObj.append( " 0 obj\n" + "<</O"); + aObj.append(pName); + aObj.append("\n"); + rBuf.append(">>\nendobj\n\n"); + writeBuffer(aObj); + writeBuffer(rBuf); + } + }; + if( !aLayout.isEmpty() ) + { + WriteAttrs("/Layout", aLayout); } if( !aList.isEmpty() ) { - aAttribObjects.push_back( createObject() ); - if (updateObject( aAttribObjects.back() )) - { - OString aObj = - OString::number( aAttribObjects.back() ) - + " 0 obj\n" - "<</O/List\n"; - aList.append( ">>\nendobj\n\n" ); - writeBuffer( aObj ); - writeBuffer( aList ); - } + WriteAttrs("/List", aList); + } + if (!aPrintField.isEmpty()) + { + WriteAttrs("/PrintField", aPrintField); } if( !aTable.isEmpty() ) { - aAttribObjects.push_back( createObject() ); - if (updateObject( aAttribObjects.back() )) - { - OString aObj = - OString::number( aAttribObjects.back() ) - + " 0 obj\n" - "<</O/Table\n"; - aTable.append( ">>\nendobj\n\n" ); - writeBuffer( aObj ); - writeBuffer( aTable ); - } + WriteAttrs("/Table", aTable); } OStringBuffer aRet( 64 ); @@ -11170,6 +11170,16 @@ bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr } } break; + case PDFWriter::Role: + if (eVal == PDFWriter::Rb || eVal == PDFWriter::Cb || eVal == PDFWriter::Pb || eVal == PDFWriter::Tv) + { + if (eType == PDFWriter::Form + && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) + { + bInsert = true; + } + } + break; case PDFWriter::ListNumbering: if( eVal == PDFWriter::NONE || eVal == PDFWriter::Disc ||