summaryrefslogtreecommitdiff
path: root/drawinglayer/source/processor2d
diff options
context:
space:
mode:
authorArmin Le Grand <Armin.Le.Grand@cib.de>2018-12-20 17:31:32 +0100
committerThorsten Behrens <Thorsten.Behrens@CIB.de>2019-02-06 16:46:08 +0100
commit2840352ba56a212d191cc16e08378c87672d7b73 (patch)
treeb61423212519ebc9e2afc0e780aca1661f42403f /drawinglayer/source/processor2d
parentcdfa8b27f28328612b6734533981c1b363ced0a0 (diff)
Enhance tagged PDF export for a11y
The current tagged PDF export does not well support quite some internal structures. This includes all apps (Draw/Impress/Writer/Calc) and some areas. -= AlternativeText ('/Alt'): Only writer currently at least adds Title information, but we also have Description (MS does add) and Name. Target is to add this information when available to content frames. Writer did that by manually adding that tag using PDFExtOutDevData::SetAlternateText, but only used Title so far. To make this work as broad as possible, better add this to primitives. There is already a primitive called ObjectInfoPrimitive2D that encapsulates any content adding Name/Title/Description using GroupPrimitive functionality. Changed Writer to use that way. Draw/Impress already uses it, all apps now use graphic paint using primitives, so we have a natural target to encapsulate. Add support to VclMetafileProcessor2D to interpret it and add - if mpPDFExtOutDevData->GetIsExportTaggedPDF() - that data using a combination of Name/Title/Description and add using mpPDFExtOutDevData->SetAlternateText. This works for Draw/Impress/Writer, but not for Calc because Calc does not create more complex data structures, so SetAlternateText does not work (see PDFWriterImpl::setAlternateText for more infos). -= Area tagged ListContent (use 'L', 'LI', 'LBody' PDF tags): To support this in Draw/Impress, we can also use a similar way to support in primitives. For this I evaluated how to add needed OutlineLevel information to the existing (and already used to write 'P') TextHierarchyParagraphPrimitive2D. Added this and now ready to use in VclMetafileProcessor2D ::processTextHierarchyParagraphPrimitive2D. Added now using the OutlineLevel information at the TextHierarchyParagraphPrimitive2D. Made sure there are fallbacks to unchanged old behaviour when no PDF export or no Tagged-PDF used. Creating now '/L', '/LI' and '/LBody' statements as tagged PDF wants us to do. Exported PDF still works well while additionally a verifier as 'PAC 3' shows the expected and wanted structure. This will work now for any text in Draw/Impress and for Draw-Objects using Lists in Calc. Need to check for direct text in Calc cells and Writer - and guess how big the effort would be for these to make it work there, too. -= Area '/Artifact': Target is to avoid too much ScreenReader hassle when Impress uses Pictures/FillPatterns etc. in Background - what means on MasterPage in Impress. Experimented with different possibilities. Decided to use existing StructureTagPrimitive2D and extend for info if encapsulated data is 'Background' data -> on MasterPage. Can be created in ImplRenderPaintProc in method createRedirectedPrimitive2DSeque as needed by checking for MasterPage member (remember: primitives need to be as independent from model data as possible, never include e.g. a SdrObject reference in any way). Tried different ways to use this in VclMetafileProcessor2D processStructureTagPrimitive2D, see comments there. Current best solution is to just *not* create StuctureTag information for these objects. Change-Id: Ib2a578b02c1256758cda6d15ce37799803d8205c
Diffstat (limited to 'drawinglayer/source/processor2d')
-rw-r--r--drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx152
-rw-r--r--drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx6
-rw-r--r--drawinglayer/source/processor2d/vclprocessor2d.cxx19
-rw-r--r--drawinglayer/source/processor2d/vclprocessor2d.hxx8
4 files changed, 175 insertions, 10 deletions
diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
index c8cb3734a376..ce5c579b5646 100644
--- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
@@ -69,6 +69,9 @@
// for StructureTagPrimitive support in sd's unomodel.cxx
#include <drawinglayer/primitive2d/structuretagprimitive2d.hxx>
+// for support of Title/Description in all apps when embedding pictures
+#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
+
using namespace com::sun::star;
// #112245# definition for maximum allowed point count due to Metafile target.
@@ -559,7 +562,8 @@ namespace drawinglayer
mnSvtGraphicFillCount(0),
mnSvtGraphicStrokeCount(0),
mfCurrentUnifiedTransparence(0.0),
- mpPDFExtOutDevData(dynamic_cast< vcl::PDFExtOutDevData* >(rOutDev.GetExtOutDevData()))
+ mpPDFExtOutDevData(dynamic_cast< vcl::PDFExtOutDevData* >(rOutDev.GetExtOutDevData())),
+ mnCurrentOutlineLevel(-1)
{
OSL_ENSURE(rOutDev.GetConnectMetaFile(), "VclMetafileProcessor2D: Used on OutDev which has no MetaFile Target (!)");
// draw to logic coordinates, do not initialize maCurrentTransformation to viewTransformation
@@ -898,6 +902,11 @@ namespace drawinglayer
RenderEpsPrimitive2D(static_cast< const primitive2d::EpsPrimitive2D& >(rCandidate));
break;
}
+ case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D :
+ {
+ RenderObjectInfoPrimitive2D(static_cast< const primitive2d::ObjectInfoPrimitive2D& >(rCandidate));
+ break;
+ }
default :
{
// process recursively
@@ -991,10 +1000,48 @@ namespace drawinglayer
sal_Int32(ceil(aCropRange.getMaxX())), sal_Int32(ceil(aCropRange.getMaxY())));
}
+ // Create image alternative description from ObjectInfoPrimitive2D info
+ // for PDF export
+ if(mpPDFExtOutDevData->GetIsExportTaggedPDF() && nullptr != getObjectInfoPrimitive2D())
+ {
+ OUString aAlternateDescription(getObjectInfoPrimitive2D()->getName());
+
+ if(!getObjectInfoPrimitive2D()->getTitle().isEmpty())
+ {
+ if(!aAlternateDescription.isEmpty())
+ {
+ aAlternateDescription += " - ";
+ }
+
+ aAlternateDescription += getObjectInfoPrimitive2D()->getTitle();
+ }
+
+ if(!getObjectInfoPrimitive2D()->getDesc().isEmpty())
+ {
+ if(!aAlternateDescription.isEmpty())
+ {
+ aAlternateDescription += " - ";
+ }
+
+ aAlternateDescription += getObjectInfoPrimitive2D()->getDesc();
+ }
+
+ // Use SetAlternateText to set it. This will work as long as some
+ // structure is used (see PDFWriterImpl::setAlternateText and
+ // m_nCurrentStructElement - tagged PDF export works with this in
+ // Draw/Impress/Writer, but not in Calc due to too less structure...)
+ //Z maybe add structure to Calc PDF export, may need some BeginGroup/EndGroup stuff ..?
+ if(!aAlternateDescription.isEmpty())
+ {
+ mpPDFExtOutDevData->SetAlternateText(aAlternateDescription);
+ }
+ }
+
// #i123295# 3rd param is uncropped rect, 4th is cropped. The primitive has the cropped
// object transformation, thus aCurrentRect *is* the clip region while aCropRect is the expanded,
// uncropped region. Thus, correct order is aCropRect, aCurrentRect
- mpPDFExtOutDevData->EndGroup(rGraphicPrimitive.getGraphicObject().GetGraphic(),
+ mpPDFExtOutDevData->EndGroup(
+ rGraphicPrimitive.getGraphicObject().GetGraphic(),
rAttr.GetTransparency(),
aCropRect,
aCurrentRect);
@@ -1193,22 +1240,85 @@ namespace drawinglayer
void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D(const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive)
{
const OString aCommentString("XTEXT_EOP");
+ static bool bSuppressPDFExtOutDevDataSupport(false);
- if(mpPDFExtOutDevData)
+ if(nullptr == mpPDFExtOutDevData || bSuppressPDFExtOutDevDataSupport)
{
- // emulate data handling from ImpEditEngine::Paint
+ // Non-PDF export behaviour (metafile only).
+ // Process recursively and add MetaFile comment.
+ process(rParagraphPrimitive);
+ mpMetaFile->AddAction(new MetaCommentAction(aCommentString));
+ return;
+ }
+
+ if(!mpPDFExtOutDevData->GetIsExportTaggedPDF())
+ {
+ // No Tagged PDF -> Dump as Paragraph
+ // Emulate data handling from old ImpEditEngine::Paint
mpPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::Paragraph );
+
+ // Process recursively and add MetaFile comment
+ process(rParagraphPrimitive);
+ mpMetaFile->AddAction(new MetaCommentAction(aCommentString));
+
+ // Emulate data handling from ImpEditEngine::Paint
+ mpPDFExtOutDevData->EndStructureElement();
+ return;
}
- // process recursively and add MetaFile comment
+ // Create Tagged PDF -> deeper tagged data using StructureElements.
+ // Use OutlineLevel from ParagraphPrimitive, ensure not below -1 what
+ // means 'not active'
+ const sal_Int16 nNewOutlineLevel(std::max(static_cast<sal_Int16>(-1), rParagraphPrimitive.getOutlineLevel()));
+
+ // Do we have a change in OutlineLevel compared to the current one?
+ if(nNewOutlineLevel != mnCurrentOutlineLevel)
+ {
+ if(nNewOutlineLevel > mnCurrentOutlineLevel)
+ {
+ // increase List level
+ for(sal_Int16 a(mnCurrentOutlineLevel); a != nNewOutlineLevel; a++)
+ {
+ mpPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::List );
+ }
+ }
+ else // if(nNewOutlineLevel < mnCurrentOutlineLevel)
+ {
+ // decrease List level
+ for(sal_Int16 a(mnCurrentOutlineLevel); a != nNewOutlineLevel; a--)
+ {
+ mpPDFExtOutDevData->EndStructureElement();
+ }
+ }
+
+ // Remember new current OutlineLevel
+ mnCurrentOutlineLevel = nNewOutlineLevel;
+ }
+
+ const bool bDumpAsListItem(-1 != mnCurrentOutlineLevel);
+
+ if(bDumpAsListItem)
+ {
+ // Dump as ListItem
+ mpPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::ListItem );
+ mpPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::LIBody );
+ }
+ else
+ {
+ // Dump as Paragraph
+ mpPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::Paragraph );
+ }
+
+ // Process recursively and add MetaFile comment
process(rParagraphPrimitive);
mpMetaFile->AddAction(new MetaCommentAction(aCommentString));
- if(mpPDFExtOutDevData)
+ if(bDumpAsListItem)
{
- // emulate data handling from ImpEditEngine::Paint
mpPDFExtOutDevData->EndStructureElement();
}
+
+ mpPDFExtOutDevData->EndStructureElement();
}
void VclMetafileProcessor2D::processTextHierarchyBlockPrimitive2D(const primitive2d::TextHierarchyBlockPrimitive2D& rBlockPrimitive)
@@ -2083,12 +2193,38 @@ namespace drawinglayer
{
// structured tag primitive
const vcl::PDFWriter::StructElement& rTagElement(rStructureTagCandidate.getStructureElement());
- const bool bTagUsed(vcl::PDFWriter::NonStructElement != rTagElement);
+ bool bTagUsed(vcl::PDFWriter::NonStructElement != rTagElement);
+
+ //Z For now, just do not create StructureTag(s) for hidden/background
+ // (Graphic)Objects - see '/Artifact' comments below
+ if(bTagUsed && rStructureTagCandidate.isBackground())
+ {
+ bTagUsed = false;
+ }
if(mpPDFExtOutDevData && bTagUsed)
{
// write start tag
mpPDFExtOutDevData->BeginStructureElement(rTagElement);
+
+ // if(rStructureTagCandidate.isBackground())
+ // {
+ // //Z need to somehow apply '/Artifact' information...
+ // // - Already experimented with adding two BeginStructureElement adding
+ // // a 'vcl::PDFWriter::Artifact' -> not really what e.g. PAC3 expects.
+ // // - May also be adding someting using 'SetStructureAttribute' and
+ // // extending vcl::PDFWriter::StructAttribute...
+ // // - Simple solution for now (see above): Just do not create
+ // // StructureTag(s) for hidden/background (Graphic)Objects. That
+ // // works, shows all content in PDF renderers, but hides in
+ // // TaggedStructure. But: also not visible in PAC3's 'LogicalStructure'
+ // // View where there is a tab for 'Artifacts' - I *guess* a correctly
+ // // tagged '/Artifact' should appear there.
+ // // Unfortunately found no real example - there is https://www.w3.org/TR/WCAG20-TECHS/PDF4.html
+ // // which claims to have an example in
+ // // https://www.w3.org/WAI/WCAG20/Techniques/working-examples/PDF4/decorative-image.docx
+ // // but that file has no '/Artifact' entries at all...
+ // }
}
// process children normally
diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx
index 48e1614ca659..410f332cbd76 100644
--- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx
@@ -169,6 +169,12 @@ namespace drawinglayer
*/
vcl::PDFExtOutDevData* mpPDFExtOutDevData;
+ // Remember the current OutlineLevel. This is used when tagged PDF export
+ // is used to create/write valid structued list entries using PDF statements
+ // like '/L', '/LI', 'LBody' instead of simple '/P' (Paragraph).
+ // The value -1 means 'no OutlineLevel' and values >= 0 express the level.
+ sal_Int16 mnCurrentOutlineLevel;
+
protected:
/* the local processor for BasePrimitive2D-Implementation based primitives,
called from the common process()-implementation
diff --git a/drawinglayer/source/processor2d/vclprocessor2d.cxx b/drawinglayer/source/processor2d/vclprocessor2d.cxx
index c5ae3c970735..64f73b135efc 100644
--- a/drawinglayer/source/processor2d/vclprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclprocessor2d.cxx
@@ -62,6 +62,9 @@
#include <basegfx/polygon/b2dpolygonclipper.hxx>
#include <basegfx/polygon/b2dtrapezoid.hxx>
+// for support of Title/Description in all apps when embedding pictures
+#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
+
using namespace com::sun::star;
namespace
@@ -1201,6 +1204,19 @@ namespace drawinglayer
}
}
+ void VclProcessor2D::RenderObjectInfoPrimitive2D(const primitive2d::ObjectInfoPrimitive2D& rObjectInfoPrimitive2D)
+ {
+ // remember current ObjectInfoPrimitive2D and set new current one (build a stack - push)
+ const primitive2d::ObjectInfoPrimitive2D* pLast(getObjectInfoPrimitive2D());
+ mpObjectInfoPrimitive2D = &rObjectInfoPrimitive2D;
+
+ // process content
+ process(rObjectInfoPrimitive2D.getChildren());
+
+ // restore current ObjectInfoPrimitive2D (pop)
+ mpObjectInfoPrimitive2D = pLast;
+ }
+
void VclProcessor2D::RenderSvgLinearAtomPrimitive2D(const primitive2d::SvgLinearAtomPrimitive2D& rCandidate)
{
const double fDelta(rCandidate.getOffsetB() - rCandidate.getOffsetA());
@@ -1409,7 +1425,8 @@ namespace drawinglayer
maBColorModifierStack(),
maCurrentTransformation(),
maDrawinglayerOpt(),
- mnPolygonStrokePrimitive2D(0)
+ mnPolygonStrokePrimitive2D(0),
+ mpObjectInfoPrimitive2D(nullptr)
{
// set digit language, derived from SvtCTLOptions to have the correct
// number display for arabic/hindi numerals
diff --git a/drawinglayer/source/processor2d/vclprocessor2d.hxx b/drawinglayer/source/processor2d/vclprocessor2d.hxx
index beb6146f1535..84e7c524c091 100644
--- a/drawinglayer/source/processor2d/vclprocessor2d.hxx
+++ b/drawinglayer/source/processor2d/vclprocessor2d.hxx
@@ -51,6 +51,7 @@ namespace drawinglayer { namespace primitive2d {
class ControlPrimitive2D;
class PagePreviewPrimitive2D;
class EpsPrimitive2D;
+ class ObjectInfoPrimitive2D;
class SvgLinearAtomPrimitive2D;
class SvgRadialAtomPrimitive2D;
}}
@@ -86,9 +87,10 @@ namespace drawinglayer
// PolygonStrokePrimitive2D's decompositions (normally only one)
sal_uInt32 mnPolygonStrokePrimitive2D;
+ // currently used ObjectInfoPrimitive2D
+ const primitive2d::ObjectInfoPrimitive2D* mpObjectInfoPrimitive2D;
// common VCL rendering support
-
void RenderTextSimpleOrDecoratedPortionPrimitive2D(const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate);
void RenderPolygonHairlinePrimitive2D(const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased);
void RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate);
@@ -104,6 +106,7 @@ namespace drawinglayer
void RenderPointArrayPrimitive2D(const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate);
void RenderPolygonStrokePrimitive2D(const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate);
void RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D);
+ void RenderObjectInfoPrimitive2D(const primitive2d::ObjectInfoPrimitive2D& rObjectInfoPrimitive2D);
void RenderSvgLinearAtomPrimitive2D(const primitive2d::SvgLinearAtomPrimitive2D& rCandidate);
void RenderSvgRadialAtomPrimitive2D(const primitive2d::SvgRadialAtomPrimitive2D& rCandidate);
@@ -120,6 +123,9 @@ namespace drawinglayer
// access to Drawinglayer configuration options
const SvtOptionsDrawinglayer& getOptionsDrawinglayer() const { return maDrawinglayerOpt; }
+
+ // access to currently used ObjectInfoPrimitive2D
+ const primitive2d::ObjectInfoPrimitive2D* getObjectInfoPrimitive2D() const { return mpObjectInfoPrimitive2D; }
};
} // end of namespace processor2d
} // end of namespace drawinglayer