1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 #include <AccessibilityCheck.hxx>
12 #include <AccessibilityIssue.hxx>
13 #include <AccessibilityCheckStrings.hrc>
14 #include <strings.hrc>
15 #include <ndnotxt.hxx>
18 #include <IDocumentDrawModelAccess.hxx>
19 #include <drawdoc.hxx>
20 #include <svx/svdpage.hxx>
21 #include <sortedobjs.hxx>
22 #include <swtable.hxx>
23 #include <com/sun/star/frame/XModel.hpp>
24 #include <com/sun/star/text/XTextContent.hpp>
25 #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
26 #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
27 #include <unoparagraph.hxx>
28 #include <unotools/intlwrapper.hxx>
29 #include <tools/urlobj.hxx>
30 #include <editeng/langitem.hxx>
32 #include <charatr.hxx>
33 #include <svx/xfillit0.hxx>
34 #include <svx/xflclit.hxx>
38 #include <svl/itemiter.hxx>
39 #include <o3tl/string_view.hxx>
40 #include <o3tl/vector_utils.hxx>
41 #include <svx/swframetypes.hxx>
42 #include <fmtanchr.hxx>
43 #include <dcontact.hxx>
44 #include <svx/svdoashp.hxx>
45 #include <svx/sdasitm.hxx>
51 SwTextNode
* lclSearchNextTextNode(SwNode
* pCurrent
)
53 SwTextNode
* pTextNode
= nullptr;
55 auto nIndex
= pCurrent
->GetIndex();
56 auto nCount
= pCurrent
->GetNodes().Count();
58 nIndex
++; // go to next node
60 while (pTextNode
== nullptr && nIndex
< nCount
)
62 auto pNode
= pCurrent
->GetNodes()[nIndex
];
63 if (pNode
->IsTextNode())
64 pTextNode
= pNode
->GetTextNode();
71 std::shared_ptr
<sw::AccessibilityIssue
>
72 lclAddIssue(sfx::AccessibilityIssueCollection
& rIssueCollection
, OUString
const& rText
,
73 sfx::AccessibilityIssueID eIssue
= sfx::AccessibilityIssueID::UNSPECIFIED
)
75 auto pIssue
= std::make_shared
<sw::AccessibilityIssue
>(eIssue
);
76 pIssue
->m_aIssueText
= rText
;
77 rIssueCollection
.getIssues().push_back(pIssue
);
81 class NodeCheck
: public BaseCheck
84 NodeCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
85 : BaseCheck(rIssueCollection
)
89 virtual void check(SwNode
* pCurrent
) = 0;
92 // Check NoTextNodes: Graphic, OLE for alt (title) text
93 class NoTextNodeAltTextCheck
: public NodeCheck
95 void checkNoTextNode(SwNoTextNode
* pNoTextNode
)
100 if (!pNoTextNode
->GetTitle().isEmpty() || !pNoTextNode
->GetDescription().isEmpty())
103 const SwFrameFormat
* pFrameFormat
= pNoTextNode
->GetFlyFormat();
108 = SwResId(STR_NO_ALT
).replaceAll("%OBJECT_NAME%", pFrameFormat
->GetName());
110 if (pNoTextNode
->IsOLENode())
112 auto pIssue
= lclAddIssue(m_rIssueCollection
, sIssueText
,
113 sfx::AccessibilityIssueID::NO_ALT_OLE
);
114 pIssue
->setDoc(pNoTextNode
->GetDoc());
115 pIssue
->setIssueObject(IssueObject::OLE
);
116 pIssue
->setObjectID(pFrameFormat
->GetName());
118 else if (pNoTextNode
->IsGrfNode())
120 const SfxBoolItem
* pIsDecorItem
= pFrameFormat
->GetItemIfSet(RES_DECORATIVE
);
121 if (!(pIsDecorItem
&& pIsDecorItem
->GetValue()))
123 auto pIssue
= lclAddIssue(m_rIssueCollection
, sIssueText
,
124 sfx::AccessibilityIssueID::NO_ALT_GRAPHIC
);
125 pIssue
->setDoc(pNoTextNode
->GetDoc());
126 pIssue
->setIssueObject(IssueObject::GRAPHIC
);
127 pIssue
->setObjectID(pNoTextNode
->GetFlyFormat()->GetName());
128 pIssue
->setNode(pNoTextNode
);
134 NoTextNodeAltTextCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
135 : NodeCheck(rIssueCollection
)
139 void check(SwNode
* pCurrent
) override
141 if (pCurrent
->GetNodeType() & SwNodeType::NoTextMask
)
143 SwNoTextNode
* pNoTextNode
= pCurrent
->GetNoTextNode();
145 checkNoTextNode(pNoTextNode
);
150 // Check Table node if the table is merged and split.
151 class TableNodeMergeSplitCheck
: public NodeCheck
154 void addTableIssue(SwTable
const& rTable
, SwDoc
& rDoc
)
156 const SwTableFormat
* pFormat
= rTable
.GetFrameFormat();
157 OUString sName
= pFormat
->GetName();
158 OUString sIssueText
= SwResId(STR_TABLE_MERGE_SPLIT
).replaceAll("%OBJECT_NAME%", sName
);
159 auto pIssue
= lclAddIssue(m_rIssueCollection
, sIssueText
,
160 sfx::AccessibilityIssueID::TABLE_MERGE_SPLIT
);
161 pIssue
->setDoc(rDoc
);
162 pIssue
->setIssueObject(IssueObject::TABLE
);
163 pIssue
->setObjectID(sName
);
166 void checkTableNode(SwTableNode
* pTableNode
)
171 SwTable
const& rTable
= pTableNode
->GetTable();
172 SwDoc
& rDoc
= pTableNode
->GetDoc();
173 if (rTable
.IsTableComplex())
175 addTableIssue(rTable
, rDoc
);
179 if (rTable
.GetTabLines().size() > 1)
182 size_t nFirstLineSize
= 0;
183 bool bAllColumnsSameSize
= true;
184 bool bCellSpansOverMoreRows
= false;
186 for (SwTableLine
const* pTableLine
: rTable
.GetTabLines())
190 nFirstLineSize
= pTableLine
->GetTabBoxes().size();
194 size_t nLineSize
= pTableLine
->GetTabBoxes().size();
195 if (nFirstLineSize
!= nLineSize
)
197 bAllColumnsSameSize
= false;
202 // Check for row span in each table box (cell)
203 for (SwTableBox
const* pBox
: pTableLine
->GetTabBoxes())
205 if (pBox
->getRowSpan() > 1)
206 bCellSpansOverMoreRows
= true;
209 if (!bAllColumnsSameSize
|| bCellSpansOverMoreRows
)
211 addTableIssue(rTable
, rDoc
);
218 TableNodeMergeSplitCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
219 : NodeCheck(rIssueCollection
)
223 void check(SwNode
* pCurrent
) override
225 if (pCurrent
->GetNodeType() & SwNodeType::Table
)
227 SwTableNode
* pTableNode
= pCurrent
->GetTableNode();
229 checkTableNode(pTableNode
);
234 class TableFormattingCheck
: public NodeCheck
237 void checkTableNode(SwTableNode
* pTableNode
)
242 const SwTable
& rTable
= pTableNode
->GetTable();
243 if (!rTable
.IsTableComplex())
245 size_t nEmptyBoxes
= 0;
246 size_t nBoxCount
= 0;
247 for (const SwTableLine
* pTableLine
: rTable
.GetTabLines())
249 nBoxCount
+= pTableLine
->GetTabBoxes().size();
250 for (const SwTableBox
* pBox
: pTableLine
->GetTabBoxes())
254 // If more than half of the boxes are empty we can assume that it is used for formatting
255 if (nEmptyBoxes
> nBoxCount
/ 2)
257 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_TABLE_FORMATTING
),
258 sfx::AccessibilityIssueID::TABLE_FORMATTING
);
260 pIssue
->setDoc(pTableNode
->GetDoc());
261 pIssue
->setIssueObject(IssueObject::TABLE
);
262 if (const SwTableFormat
* pFormat
= rTable
.GetFrameFormat())
263 pIssue
->setObjectID(pFormat
->GetName());
269 TableFormattingCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
270 : NodeCheck(rIssueCollection
)
274 void check(SwNode
* pCurrent
) override
276 if (pCurrent
->GetNodeType() & SwNodeType::Table
)
278 SwTableNode
* pTableNode
= pCurrent
->GetTableNode();
280 checkTableNode(pTableNode
);
285 class NumberingCheck
: public NodeCheck
288 const std::vector
<std::pair
<OUString
, OUString
>> m_aNumberingCombinations
{
289 { "1.", "2." }, { "(1)", "(2)" }, { "1)", "2)" }, { "a.", "b." }, { "(a)", "(b)" },
290 { "a)", "b)" }, { "A.", "B." }, { "(A)", "(B)" }, { "A)", "B)" }
294 NumberingCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
295 : NodeCheck(rIssueCollection
)
299 void check(SwNode
* pCurrent
) override
301 if (!pCurrent
->IsTextNode())
304 SwTextNode
* pCurrentTextNode
= pCurrent
->GetTextNode();
305 SwTextNode
* pNextTextNode
= lclSearchNextTextNode(pCurrent
);
310 for (auto& rPair
: m_aNumberingCombinations
)
312 if (pCurrentTextNode
->GetText().startsWith(rPair
.first
)
313 && pNextTextNode
->GetText().startsWith(rPair
.second
))
315 OUString sNumbering
= rPair
.first
+ " " + rPair
.second
+ "...";
317 = SwResId(STR_FAKE_NUMBERING
).replaceAll("%NUMBERING%", sNumbering
);
318 auto pIssue
= lclAddIssue(m_rIssueCollection
, sIssueText
,
319 sfx::AccessibilityIssueID::MANUAL_NUMBERING
);
320 pIssue
->setIssueObject(IssueObject::TEXT
);
321 pIssue
->setDoc(pCurrent
->GetDoc());
322 pIssue
->setNode(pCurrent
);
328 class HyperlinkCheck
: public NodeCheck
331 void checkTextRange(uno::Reference
<text::XTextRange
> const& xTextRange
, SwTextNode
* pTextNode
,
334 uno::Reference
<beans::XPropertySet
> xProperties(xTextRange
, uno::UNO_QUERY
);
335 if (!xProperties
->getPropertySetInfo()->hasPropertyByName("HyperLinkURL"))
339 xProperties
->getPropertyValue("HyperLinkURL") >>= sHyperlink
;
340 if (!sHyperlink
.isEmpty())
342 OUString sText
= xTextRange
->getString();
343 INetURLObject
aHyperlink(sHyperlink
);
344 std::shared_ptr
<sw::AccessibilityIssue
> pIssue
;
346 if (aHyperlink
.GetProtocol() != INetProtocol::NotValid
347 && INetURLObject(sText
) == aHyperlink
)
350 = SwResId(STR_HYPERLINK_TEXT_IS_LINK
).replaceFirst("%LINK%", sHyperlink
);
351 pIssue
= lclAddIssue(m_rIssueCollection
, sIssueText
,
352 sfx::AccessibilityIssueID::HYPERLINK_IS_TEXT
);
354 else if (sText
.getLength() <= 5)
356 pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_HYPERLINK_TEXT_IS_SHORT
),
357 sfx::AccessibilityIssueID::HYPERLINK_SHORT
);
362 pIssue
->setIssueObject(IssueObject::TEXT
);
363 pIssue
->setNode(pTextNode
);
364 SwDoc
& rDocument
= pTextNode
->GetDoc();
365 pIssue
->setDoc(rDocument
);
366 pIssue
->setStart(nStart
);
367 pIssue
->setEnd(nStart
+ sText
.getLength());
373 HyperlinkCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
374 : NodeCheck(rIssueCollection
)
378 void check(SwNode
* pCurrent
) override
380 if (!pCurrent
->IsTextNode())
383 SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
384 uno::Reference
<text::XTextContent
> xParagraph
385 = SwXParagraph::CreateXParagraph(pTextNode
->GetDoc(), pTextNode
);
386 if (!xParagraph
.is())
389 uno::Reference
<container::XEnumerationAccess
> xRunEnumAccess(xParagraph
, uno::UNO_QUERY
);
390 uno::Reference
<container::XEnumeration
> xRunEnum
= xRunEnumAccess
->createEnumeration();
391 sal_Int32 nStart
= 0;
392 while (xRunEnum
->hasMoreElements())
394 uno::Reference
<text::XTextRange
> xRun(xRunEnum
->nextElement(), uno::UNO_QUERY
);
397 checkTextRange(xRun
, pTextNode
, nStart
);
398 nStart
+= xRun
->getString().getLength();
404 // Based on https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
405 double calculateRelativeLuminance(Color
const& rColor
)
407 // Convert to BColor which has R, G, B colors components
408 // represented by a floating point number from [0.0, 1.0]
409 const basegfx::BColor aBColor
= rColor
.getBColor();
411 double r
= aBColor
.getRed();
412 double g
= aBColor
.getGreen();
413 double b
= aBColor
.getBlue();
415 // Calculate the values according to the described algorithm
416 r
= (r
<= 0.03928) ? r
/ 12.92 : std::pow((r
+ 0.055) / 1.055, 2.4);
417 g
= (g
<= 0.03928) ? g
/ 12.92 : std::pow((g
+ 0.055) / 1.055, 2.4);
418 b
= (b
<= 0.03928) ? b
/ 12.92 : std::pow((b
+ 0.055) / 1.055, 2.4);
420 return 0.2126 * r
+ 0.7152 * g
+ 0.0722 * b
;
423 // TODO move to common color tools (BColorTools maybe)
424 // Based on https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
425 double calculateContrastRatio(Color
const& rColor1
, Color
const& rColor2
)
427 const double fLuminance1
= calculateRelativeLuminance(rColor1
);
428 const double fLuminance2
= calculateRelativeLuminance(rColor2
);
429 const std::pair
<const double, const double> aMinMax
= std::minmax(fLuminance1
, fLuminance2
);
431 // (L1 + 0.05) / (L2 + 0.05)
432 // L1 is the lighter color (greater luminance value)
433 // L2 is the darker color (smaller luminance value)
434 return (aMinMax
.second
+ 0.05) / (aMinMax
.first
+ 0.05);
437 class TextContrastCheck
: public NodeCheck
440 void checkTextRange(uno::Reference
<text::XTextRange
> const& xTextRange
,
441 uno::Reference
<text::XTextContent
> const& xParagraph
, SwTextNode
* pTextNode
,
442 sal_Int32 nTextStart
)
444 if (xTextRange
->getString().isEmpty())
447 Color
nParaBackColor(COL_AUTO
);
448 uno::Reference
<beans::XPropertySet
> xParagraphProperties(xParagraph
, uno::UNO_QUERY
);
449 if (!(xParagraphProperties
->getPropertyValue("ParaBackColor") >>= nParaBackColor
))
451 SAL_WARN("sw.a11y", "ParaBackColor void");
455 uno::Reference
<beans::XPropertySet
> xProperties(xTextRange
, uno::UNO_QUERY
);
456 if (!xProperties
.is())
460 sal_Int32 nCharColor
= {}; // spurious -Werror=maybe-uninitialized
461 if (!(xProperties
->getPropertyValue("CharColor") >>= nCharColor
))
462 { // not sure this is impossible, can the default be void?
463 SAL_WARN("sw.a11y", "CharColor void");
467 const SwPageDesc
* pPageDescription
= pTextNode
->FindPageDesc();
468 if (!pPageDescription
)
470 const SwFrameFormat
& rPageFormat
= pPageDescription
->GetMaster();
471 const SwAttrSet
& rPageSet
= rPageFormat
.GetAttrSet();
473 const XFillStyleItem
* pXFillStyleItem(
474 rPageSet
.GetItem
<XFillStyleItem
>(XATTR_FILLSTYLE
, false));
475 Color
aPageBackground(COL_AUTO
);
477 if (pXFillStyleItem
&& pXFillStyleItem
->GetValue() == css::drawing::FillStyle_SOLID
)
479 const XFillColorItem
* rXFillColorItem
480 = rPageSet
.GetItem
<XFillColorItem
>(XATTR_FILLCOLOR
, false);
481 aPageBackground
= rXFillColorItem
->GetColorValue();
484 Color
nCharBackColor(COL_AUTO
);
486 if (!(xProperties
->getPropertyValue("CharBackColor") >>= nCharBackColor
))
488 SAL_WARN("sw.a11y", "CharBackColor void");
491 // Determine the background color
492 // Try Character background (highlight)
493 Color
aBackgroundColor(nCharBackColor
);
495 // If not character background color, try paragraph background color
496 if (aBackgroundColor
== COL_AUTO
)
497 aBackgroundColor
= nParaBackColor
;
500 OUString sCharStyleName
;
501 Color
nCharStyleBackColor(COL_AUTO
);
502 if (xProperties
->getPropertyValue("CharStyleName") >>= sCharStyleName
)
506 uno::Reference
<style::XStyleFamiliesSupplier
> xStyleFamiliesSupplier(
507 pTextNode
->GetDoc().GetDocShell()->GetModel(), uno::UNO_QUERY
);
508 uno::Reference
<container::XNameAccess
> xCont
509 = xStyleFamiliesSupplier
->getStyleFamilies();
510 uno::Reference
<container::XNameAccess
> xStyleFamily(
511 xCont
->getByName("CharacterStyles"), uno::UNO_QUERY
);
512 uno::Reference
<beans::XPropertySet
> xInfo(
513 xStyleFamily
->getByName(sCharStyleName
), uno::UNO_QUERY
);
514 xInfo
->getPropertyValue("CharBackColor") >>= nCharStyleBackColor
;
516 catch (const uno::Exception
&)
522 SAL_WARN("sw.a11y", "CharStyleName void");
525 if (aBackgroundColor
!= nCharStyleBackColor
)
528 = lclAddIssue(m_rIssueCollection
, SwResId(STR_TEXT_FORMATTING_CONVEYS_MEANING
),
529 sfx::AccessibilityIssueID::TEXT_FORMATTING
);
530 pIssue
->setIssueObject(IssueObject::TEXT
);
531 pIssue
->setNode(pTextNode
);
532 SwDoc
& rDocument
= pTextNode
->GetDoc();
533 pIssue
->setDoc(rDocument
);
534 pIssue
->setStart(nTextStart
);
535 pIssue
->setEnd(nTextStart
+ xTextRange
->getString().getLength());
539 Color
aForegroundColor(ColorTransparency
, nCharColor
);
540 if (aForegroundColor
== COL_AUTO
)
543 // If not paragraph background color, try page color
544 if (aBackgroundColor
== COL_AUTO
)
545 aBackgroundColor
= aPageBackground
;
547 // If not page color, assume white background color
548 if (aBackgroundColor
== COL_AUTO
)
549 aBackgroundColor
= COL_WHITE
;
551 double fContrastRatio
= calculateContrastRatio(aForegroundColor
, aBackgroundColor
);
552 if (fContrastRatio
< 4.5)
554 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_TEXT_CONTRAST
));
555 pIssue
->setIssueObject(IssueObject::TEXT
);
556 pIssue
->setNode(pTextNode
);
557 pIssue
->setDoc(pTextNode
->GetDoc());
558 pIssue
->setStart(nTextStart
);
559 pIssue
->setEnd(nTextStart
+ xTextRange
->getString().getLength());
564 TextContrastCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
565 : NodeCheck(rIssueCollection
)
569 void check(SwNode
* pCurrent
) override
571 if (!pCurrent
->IsTextNode())
574 SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
575 uno::Reference
<text::XTextContent
> xParagraph
;
576 xParagraph
= SwXParagraph::CreateXParagraph(pTextNode
->GetDoc(), pTextNode
);
577 if (!xParagraph
.is())
580 uno::Reference
<container::XEnumerationAccess
> xRunEnumAccess(xParagraph
, uno::UNO_QUERY
);
581 uno::Reference
<container::XEnumeration
> xRunEnum
= xRunEnumAccess
->createEnumeration();
582 sal_Int32 nStart
= 0;
583 while (xRunEnum
->hasMoreElements())
585 uno::Reference
<text::XTextRange
> xRun(xRunEnum
->nextElement(), uno::UNO_QUERY
);
588 checkTextRange(xRun
, xParagraph
, pTextNode
, nStart
);
589 nStart
+= xRun
->getString().getLength();
595 class TextFormattingCheck
: public NodeCheck
598 TextFormattingCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
599 : NodeCheck(rIssueCollection
)
603 void checkAutoFormat(SwTextNode
* pTextNode
, const SwTextAttr
* pTextAttr
)
605 const SwFormatAutoFormat
& rAutoFormat
= pTextAttr
->GetAutoFormat();
606 SfxItemIter
aItemIter(*rAutoFormat
.GetStyleHandle());
607 const SfxPoolItem
* pItem
= aItemIter
.GetCurItem();
608 std::vector
<OUString
> aFormattings
;
611 OUString sFormattingType
;
612 switch (pItem
->Which())
614 case RES_CHRATR_WEIGHT
:
615 case RES_CHRATR_CJK_WEIGHT
:
616 case RES_CHRATR_CTL_WEIGHT
:
617 sFormattingType
= "Weight";
619 case RES_CHRATR_POSTURE
:
620 case RES_CHRATR_CJK_POSTURE
:
621 case RES_CHRATR_CTL_POSTURE
:
622 sFormattingType
= "Posture";
625 case RES_CHRATR_SHADOWED
:
626 sFormattingType
= "Shadowed";
629 case RES_CHRATR_COLOR
:
630 sFormattingType
= "Font Color";
633 case RES_CHRATR_FONTSIZE
:
634 case RES_CHRATR_CJK_FONTSIZE
:
635 case RES_CHRATR_CTL_FONTSIZE
:
636 sFormattingType
= "Font Size";
639 case RES_CHRATR_FONT
:
640 case RES_CHRATR_CJK_FONT
:
641 case RES_CHRATR_CTL_FONT
:
642 sFormattingType
= "Font";
645 case RES_CHRATR_EMPHASIS_MARK
:
646 sFormattingType
= "Emphasis Mark";
649 case RES_CHRATR_UNDERLINE
:
650 sFormattingType
= "Underline";
653 case RES_CHRATR_OVERLINE
:
654 sFormattingType
= "Overline";
657 case RES_CHRATR_CROSSEDOUT
:
658 sFormattingType
= "Strikethrough";
661 case RES_CHRATR_RELIEF
:
662 sFormattingType
= "Relief";
665 case RES_CHRATR_CONTOUR
:
666 sFormattingType
= "Outline";
671 if (!sFormattingType
.isEmpty())
672 aFormattings
.push_back(sFormattingType
);
673 pItem
= aItemIter
.NextItem();
675 if (aFormattings
.empty())
678 o3tl::remove_duplicates(aFormattings
);
679 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_TEXT_FORMATTING_CONVEYS_MEANING
),
680 sfx::AccessibilityIssueID::TEXT_FORMATTING
);
681 pIssue
->setIssueObject(IssueObject::TEXT
);
682 pIssue
->setNode(pTextNode
);
683 SwDoc
& rDocument
= pTextNode
->GetDoc();
684 pIssue
->setDoc(rDocument
);
685 pIssue
->setStart(pTextAttr
->GetStart());
686 pIssue
->setEnd(pTextAttr
->GetAnyEnd());
689 void check(SwNode
* pCurrent
) override
691 if (!pCurrent
->IsTextNode())
694 SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
695 if (pTextNode
->HasHints())
697 SwpHints
& rHints
= pTextNode
->GetSwpHints();
698 for (size_t i
= 0; i
< rHints
.Count(); ++i
)
700 const SwTextAttr
* pTextAttr
= rHints
.Get(i
);
701 if (pTextAttr
->Which() == RES_TXTATR_AUTOFMT
)
703 checkAutoFormat(pTextNode
, pTextAttr
);
707 else if (pTextNode
->HasSwAttrSet())
709 // Paragraph doesn't have hints but the entire paragraph might have char attributes
710 auto& aSwAttrSet
= pTextNode
->GetSwAttrSet();
711 auto nParagraphLength
= pTextNode
->GetText().getLength();
712 if (nParagraphLength
== 0)
714 if (aSwAttrSet
.GetItem(RES_CHRATR_WEIGHT
, false)
715 || aSwAttrSet
.GetItem(RES_CHRATR_CJK_WEIGHT
, false)
716 || aSwAttrSet
.GetItem(RES_CHRATR_CTL_WEIGHT
, false)
717 || aSwAttrSet
.GetItem(RES_CHRATR_POSTURE
, false)
718 || aSwAttrSet
.GetItem(RES_CHRATR_CJK_POSTURE
, false)
719 || aSwAttrSet
.GetItem(RES_CHRATR_CTL_POSTURE
, false)
720 || aSwAttrSet
.GetItem(RES_CHRATR_SHADOWED
, false)
721 || aSwAttrSet
.GetItem(RES_CHRATR_COLOR
, false)
722 || aSwAttrSet
.GetItem(RES_CHRATR_EMPHASIS_MARK
, false)
723 || aSwAttrSet
.GetItem(RES_CHRATR_UNDERLINE
, false)
724 || aSwAttrSet
.GetItem(RES_CHRATR_OVERLINE
, false)
725 || aSwAttrSet
.GetItem(RES_CHRATR_CROSSEDOUT
, false)
726 || aSwAttrSet
.GetItem(RES_CHRATR_RELIEF
, false)
727 || aSwAttrSet
.GetItem(RES_CHRATR_CONTOUR
, false))
730 = lclAddIssue(m_rIssueCollection
, SwResId(STR_TEXT_FORMATTING_CONVEYS_MEANING
),
731 sfx::AccessibilityIssueID::TEXT_FORMATTING
);
732 pIssue
->setIssueObject(IssueObject::TEXT
);
733 pIssue
->setNode(pTextNode
);
734 SwDoc
& rDocument
= pTextNode
->GetDoc();
735 pIssue
->setDoc(rDocument
);
736 pIssue
->setEnd(nParagraphLength
);
742 class NewlineSpacingCheck
: public NodeCheck
745 static SwTextNode
* getPrevTextNode(SwNode
* pCurrent
)
747 SwTextNode
* pTextNode
= nullptr;
749 auto nIndex
= pCurrent
->GetIndex();
751 nIndex
--; // go to previous node
753 while (pTextNode
== nullptr && nIndex
>= SwNodeOffset(0))
755 auto pNode
= pCurrent
->GetNodes()[nIndex
];
756 if (pNode
->IsTextNode())
757 pTextNode
= pNode
->GetTextNode();
765 NewlineSpacingCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
766 : NodeCheck(rIssueCollection
)
769 void check(SwNode
* pCurrent
) override
771 if (!pCurrent
->IsTextNode())
774 // Don't count empty table box text nodes
775 if (pCurrent
->GetTableBox())
778 SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
779 auto nParagraphLength
= pTextNode
->GetText().getLength();
780 if (nParagraphLength
== 0)
782 SwTextNode
* pPrevTextNode
= getPrevTextNode(pCurrent
);
785 if (pPrevTextNode
->GetText().getLength() == 0)
787 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_AVOID_NEWLINES_SPACE
),
788 sfx::AccessibilityIssueID::TEXT_FORMATTING
);
789 pIssue
->setIssueObject(IssueObject::TEXT
);
790 pIssue
->setNode(pTextNode
);
791 SwDoc
& rDocument
= pTextNode
->GetDoc();
792 pIssue
->setDoc(rDocument
);
797 // Check for excess lines inside this paragraph
798 const OUString
& sParagraphText
= pTextNode
->GetText();
800 for (sal_Int32 i
= 0; i
< nParagraphLength
; i
++)
802 auto aChar
= sParagraphText
[i
];
806 // Looking for 2 newline characters and above as one can be part of the line
807 // break after a sentence
811 = lclAddIssue(m_rIssueCollection
, SwResId(STR_AVOID_NEWLINES_SPACE
),
812 sfx::AccessibilityIssueID::TEXT_FORMATTING
);
813 pIssue
->setIssueObject(IssueObject::TEXT
);
814 pIssue
->setNode(pTextNode
);
815 SwDoc
& rDocument
= pTextNode
->GetDoc();
816 pIssue
->setDoc(rDocument
);
821 // Don't count carriage return as normal character
822 else if (aChar
!= '\r')
831 class SpaceSpacingCheck
: public NodeCheck
834 SpaceSpacingCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
835 : NodeCheck(rIssueCollection
)
838 void check(SwNode
* pCurrent
) override
840 if (!pCurrent
->IsTextNode())
842 SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
843 auto nParagraphLength
= pTextNode
->GetText().getLength();
844 const OUString
& sParagraphText
= pTextNode
->GetText();
845 sal_Int32 nSpaceCount
= 0;
846 sal_Int32 nSpaceStart
= 0;
847 sal_Int32 nTabCount
= 0;
848 bool bNonSpaceFound
= false;
849 bool bPreviousWasChar
= false;
850 for (sal_Int32 i
= 0; i
< nParagraphLength
; i
++)
852 switch (sParagraphText
[i
])
859 if (nSpaceCount
== 2)
866 if (bPreviousWasChar
)
869 bPreviousWasChar
= false;
872 auto pIssue
= lclAddIssue(m_rIssueCollection
,
873 SwResId(STR_AVOID_TABS_FORMATTING
),
874 sfx::AccessibilityIssueID::TEXT_FORMATTING
);
875 pIssue
->setIssueObject(IssueObject::TEXT
);
876 pIssue
->setNode(pTextNode
);
877 SwDoc
& rDocument
= pTextNode
->GetDoc();
878 pIssue
->setDoc(rDocument
);
880 pIssue
->setEnd(nParagraphLength
);
887 if (nSpaceCount
>= 2)
890 = lclAddIssue(m_rIssueCollection
, SwResId(STR_AVOID_SPACES_SPACE
),
891 sfx::AccessibilityIssueID::TEXT_FORMATTING
);
892 pIssue
->setIssueObject(IssueObject::TEXT
);
893 pIssue
->setNode(pTextNode
);
894 SwDoc
& rDocument
= pTextNode
->GetDoc();
895 pIssue
->setDoc(rDocument
);
896 pIssue
->setStart(nSpaceStart
);
897 pIssue
->setEnd(nSpaceStart
+ nSpaceCount
- 1);
899 bNonSpaceFound
= true;
900 bPreviousWasChar
= true;
909 class FakeFootnoteCheck
: public NodeCheck
912 void checkAutoFormat(SwTextNode
* pTextNode
, const SwTextAttr
* pTextAttr
)
914 const SwFormatAutoFormat
& rAutoFormat
= pTextAttr
->GetAutoFormat();
915 SfxItemIter
aItemIter(*rAutoFormat
.GetStyleHandle());
916 const SfxPoolItem
* pItem
= aItemIter
.GetCurItem();
919 if (pItem
->Which() == RES_CHRATR_ESCAPEMENT
)
921 auto pEscapementItem
= static_cast<const SvxEscapementItem
*>(pItem
);
922 if (pEscapementItem
->GetEscapement() == SvxEscapement::Superscript
923 && pTextAttr
->GetStart() == 0 && pTextAttr
->GetAnyEnd() == 1)
925 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_AVOID_FAKE_FOOTNOTES
),
926 sfx::AccessibilityIssueID::FAKE_FOOTNOTE
);
927 pIssue
->setIssueObject(IssueObject::TEXT
);
928 pIssue
->setNode(pTextNode
);
929 SwDoc
& rDocument
= pTextNode
->GetDoc();
930 pIssue
->setDoc(rDocument
);
932 pIssue
->setEnd(pTextNode
->GetText().getLength());
936 pItem
= aItemIter
.NextItem();
941 FakeFootnoteCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
942 : NodeCheck(rIssueCollection
)
945 void check(SwNode
* pCurrent
) override
947 if (!pCurrent
->IsTextNode())
949 SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
950 if (pTextNode
->GetText().getLength() == 0)
953 if (pTextNode
->GetText()[0] == '*')
955 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_AVOID_FAKE_FOOTNOTES
),
956 sfx::AccessibilityIssueID::FAKE_FOOTNOTE
);
957 pIssue
->setIssueObject(IssueObject::TEXT
);
958 pIssue
->setNode(pTextNode
);
959 SwDoc
& rDocument
= pTextNode
->GetDoc();
960 pIssue
->setDoc(rDocument
);
962 pIssue
->setEnd(pTextNode
->GetText().getLength());
964 else if (pTextNode
->HasHints())
966 SwpHints
& rHints
= pTextNode
->GetSwpHints();
967 for (size_t i
= 0; i
< rHints
.Count(); ++i
)
969 const SwTextAttr
* pTextAttr
= rHints
.Get(i
);
970 if (pTextAttr
->Which() == RES_TXTATR_AUTOFMT
)
972 checkAutoFormat(pTextNode
, pTextAttr
);
979 class FakeCaptionCheck
: public NodeCheck
982 FakeCaptionCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
983 : NodeCheck(rIssueCollection
)
986 void check(SwNode
* pCurrent
) override
988 if (!pCurrent
->IsTextNode())
991 SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
992 const OUString
& sText
= pTextNode
->GetText();
994 if (sText
.getLength() == 0)
997 // Check if it's a real caption
998 const SwNode
* aStartFly
= pCurrent
->FindFlyStartNode();
1000 && aStartFly
->GetFlyFormat()->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR
)
1003 auto aIter
= SwIterator
<SwTextFrame
, SwTextNode
, sw::IteratorMode::UnwrapMulti
>(*pTextNode
);
1005 for (auto aTextFrame
= aIter
.First(); aTextFrame
; aTextFrame
= aIter
.Next())
1007 auto aObjects
= aTextFrame
->GetDrawObjs();
1009 nCount
+= aObjects
->size();
1015 // Check that there's exactly 1 image anchored in this node
1019 sText
.convertToString(&sTemp
, RTL_TEXTENCODING_ASCII_US
, 0);
1020 if (sText
.startsWith(SwResId(STR_POOLCOLL_LABEL
))
1021 || sText
.startsWith(SwResId(STR_POOLCOLL_LABEL_ABB
))
1022 || sText
.startsWith(SwResId(STR_POOLCOLL_LABEL_TABLE
))
1023 || sText
.startsWith(SwResId(STR_POOLCOLL_LABEL_FRAME
))
1024 || sText
.startsWith(SwResId(STR_POOLCOLL_LABEL_DRAWING
))
1025 || sText
.startsWith(SwResId(STR_POOLCOLL_LABEL_FIGURE
)))
1027 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_AVOID_FAKE_CAPTIONS
),
1028 sfx::AccessibilityIssueID::FAKE_CAPTION
);
1029 pIssue
->setIssueObject(IssueObject::TEXT
);
1030 pIssue
->setNode(pTextNode
);
1031 SwDoc
& rDocument
= pTextNode
->GetDoc();
1032 pIssue
->setDoc(rDocument
);
1033 pIssue
->setStart(0);
1034 pIssue
->setEnd(sText
.getLength());
1040 class BlinkingTextCheck
: public NodeCheck
1043 void checkTextRange(uno::Reference
<text::XTextRange
> const& xTextRange
, SwTextNode
* pTextNode
,
1046 uno::Reference
<beans::XPropertySet
> xProperties(xTextRange
, uno::UNO_QUERY
);
1047 if (xProperties
.is() && xProperties
->getPropertySetInfo()->hasPropertyByName("CharFlash"))
1049 bool bBlinking
= false;
1050 xProperties
->getPropertyValue("CharFlash") >>= bBlinking
;
1054 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_TEXT_BLINKING
));
1055 pIssue
->setIssueObject(IssueObject::TEXT
);
1056 pIssue
->setNode(pTextNode
);
1057 pIssue
->setDoc(pTextNode
->GetDoc());
1058 pIssue
->setStart(nStart
);
1059 pIssue
->setEnd(nStart
+ xTextRange
->getString().getLength());
1065 BlinkingTextCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1066 : NodeCheck(rIssueCollection
)
1070 void check(SwNode
* pCurrent
) override
1072 if (!pCurrent
->IsTextNode())
1075 SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
1076 uno::Reference
<text::XTextContent
> xParagraph
;
1077 xParagraph
= SwXParagraph::CreateXParagraph(pTextNode
->GetDoc(), pTextNode
);
1078 if (!xParagraph
.is())
1081 uno::Reference
<container::XEnumerationAccess
> xRunEnumAccess(xParagraph
, uno::UNO_QUERY
);
1082 uno::Reference
<container::XEnumeration
> xRunEnum
= xRunEnumAccess
->createEnumeration();
1083 sal_Int32 nStart
= 0;
1084 while (xRunEnum
->hasMoreElements())
1086 uno::Reference
<text::XTextRange
> xRun(xRunEnum
->nextElement(), uno::UNO_QUERY
);
1089 checkTextRange(xRun
, pTextNode
, nStart
);
1090 nStart
+= xRun
->getString().getLength();
1096 class HeaderCheck
: public NodeCheck
1099 int m_nPreviousLevel
;
1102 HeaderCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1103 : NodeCheck(rIssueCollection
)
1104 , m_nPreviousLevel(0)
1108 void check(SwNode
* pCurrent
) override
1110 if (!pCurrent
->IsTextNode())
1113 SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
1114 SwTextFormatColl
* pCollection
= pTextNode
->GetTextColl();
1115 if (!pCollection
->IsAssignedToListLevelOfOutlineStyle())
1118 int nLevel
= pCollection
->GetAssignedOutlineStyleLevel();
1119 assert(nLevel
>= 0);
1120 if (nLevel
> m_nPreviousLevel
&& std::abs(nLevel
- m_nPreviousLevel
) > 1)
1122 lclAddIssue(m_rIssueCollection
, SwResId(STR_HEADINGS_NOT_IN_ORDER
));
1124 m_nPreviousLevel
= nLevel
;
1128 // ISO 142891-1 : 7.14
1129 class NonInteractiveFormCheck
: public NodeCheck
1132 NonInteractiveFormCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1133 : NodeCheck(rIssueCollection
)
1137 void check(SwNode
* pCurrent
) override
1139 if (!pCurrent
->IsTextNode())
1142 SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
1143 const auto& text
= pTextNode
->GetText();
1145 // Series of tests to detect if there are fake forms in the text.
1146 bool bCheck
= text
.indexOf("___") == -1; // Repeated underscores.
1149 bCheck
= text
.indexOf("....") == -1; // Repeated dots.
1152 bCheck
= text
.indexOf(u
"……") == -1; // Repeated ellipsis.
1155 bCheck
= text
.indexOf(u
"….") == -1; // A dot after an ellipsis.
1158 bCheck
= text
.indexOf(u
".…") == -1; // An ellipsis after a dot.
1160 // Checking if all the tests are passed successfully. If not, adding a warning.
1163 sal_Int32 nStart
= 0;
1164 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_NON_INTERACTIVE_FORMS
));
1165 pIssue
->setIssueObject(IssueObject::TEXT
);
1166 pIssue
->setNode(pTextNode
);
1167 pIssue
->setDoc(pTextNode
->GetDoc());
1168 pIssue
->setStart(nStart
);
1169 pIssue
->setEnd(nStart
+ text
.getLength());
1174 /// Check for floating text frames, as it causes problems with reading order.
1175 class FloatingTextCheck
: public NodeCheck
1178 FloatingTextCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1179 : NodeCheck(rIssueCollection
)
1183 void check(SwNode
* pCurrent
) override
1185 // if node is a text-node and if it has text, we proceed. Otherwise - return.
1186 const SwTextNode
* textNode
= pCurrent
->GetTextNode();
1187 if (!textNode
|| textNode
->GetText().isEmpty())
1190 // If a node is in fly and if it is not anchored as char, throw warning.
1191 const SwNode
* startFly
= pCurrent
->FindFlyStartNode();
1193 && startFly
->GetFlyFormat()->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR
)
1195 SwNodeIndex
aCurrentIdx(*pCurrent
);
1196 SwNodeIndex
aIdx(*startFly
);
1197 SwNode
* pFirstTextNode
= &aIdx
.GetNode();
1198 SwNodeOffset nEnd
= startFly
->EndOfSectionIndex();
1201 if (pFirstTextNode
->IsContentNode() && pFirstTextNode
->IsTextNode())
1203 if (aIdx
== aCurrentIdx
)
1205 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_FLOATING_TEXT
));
1206 pIssue
->setIssueObject(IssueObject::TEXTFRAME
);
1207 pIssue
->setObjectID(startFly
->GetFlyFormat()->GetName());
1208 pIssue
->setDoc(pCurrent
->GetDoc());
1209 pIssue
->setNode(pCurrent
);
1214 pFirstTextNode
= &aIdx
.GetNode();
1220 /// Heading paragraphs (with outline levels > 0) are not allowed in tables
1221 class TableHeadingCheck
: public NodeCheck
1224 // Boolean indicating if heading-in-table warning is already triggered.
1228 TableHeadingCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1229 : NodeCheck(rIssueCollection
)
1230 , m_bPrevPassed(true)
1234 void check(SwNode
* pCurrent
) override
1239 const SwTextNode
* textNode
= pCurrent
->GetTextNode();
1241 if (textNode
&& textNode
->GetAttrOutlineLevel() != 0)
1243 const SwTableNode
* parentTable
= pCurrent
->FindTableNode();
1247 m_bPrevPassed
= false;
1248 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_HEADING_IN_TABLE
));
1249 pIssue
->setIssueObject(IssueObject::TEXT
);
1250 pIssue
->setDoc(pCurrent
->GetDoc());
1251 pIssue
->setNode(pCurrent
);
1257 /// Checking if headings are ordered correctly.
1258 class HeadingOrderCheck
: public NodeCheck
1261 HeadingOrderCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1262 : NodeCheck(rIssueCollection
)
1266 void check(SwNode
* pCurrent
) override
1268 const SwTextNode
* pTextNode
= pCurrent
->GetTextNode();
1272 // If outline level stands for heading level...
1273 const int currentLevel
= pTextNode
->GetAttrOutlineLevel();
1277 // ... and if is bigger than previous by more than 1, warn.
1278 if (currentLevel
- m_prevLevel
> 1)
1280 // Preparing and posting a warning.
1281 OUString resultString
;
1284 resultString
= SwResId(STR_HEADING_START
);
1288 resultString
= SwResId(STR_HEADING_ORDER
);
1290 = resultString
.replaceAll("%LEVEL_PREV%", OUString::number(m_prevLevel
));
1293 = resultString
.replaceAll("%LEVEL_CURRENT%", OUString::number(currentLevel
));
1294 auto pIssue
= lclAddIssue(m_rIssueCollection
, resultString
);
1295 pIssue
->setIssueObject(IssueObject::TEXT
);
1296 pIssue
->setDoc(pCurrent
->GetDoc());
1297 pIssue
->setNode(pCurrent
);
1300 // Updating previous level.
1301 m_prevLevel
= currentLevel
;
1305 // Previous heading level to compare with.
1306 int m_prevLevel
= 0;
1309 class DocumentCheck
: public BaseCheck
1312 DocumentCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1313 : BaseCheck(rIssueCollection
)
1317 virtual void check(SwDoc
* pDoc
) = 0;
1320 // Check default language
1321 class DocumentDefaultLanguageCheck
: public DocumentCheck
1324 DocumentDefaultLanguageCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1325 : DocumentCheck(rIssueCollection
)
1329 void check(SwDoc
* pDoc
) override
1331 // TODO maybe - also check RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CTL_LANGUAGE if CJK or CTL are enabled
1332 const SvxLanguageItem
& rLang
= pDoc
->GetDefault(RES_CHRATR_LANGUAGE
);
1333 LanguageType eLanguage
= rLang
.GetLanguage();
1334 if (eLanguage
== LANGUAGE_NONE
)
1336 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_DOCUMENT_DEFAULT_LANGUAGE
),
1337 sfx::AccessibilityIssueID::DOCUMENT_LANGUAGE
);
1338 pIssue
->setIssueObject(IssueObject::LANGUAGE_NOT_SET
);
1339 pIssue
->setObjectID(OUString());
1340 pIssue
->setDoc(*pDoc
);
1344 for (SwTextFormatColl
* pTextFormatCollection
: *pDoc
->GetTextFormatColls())
1346 const SwAttrSet
& rAttrSet
= pTextFormatCollection
->GetAttrSet();
1347 if (rAttrSet
.GetLanguage(false).GetLanguage() == LANGUAGE_NONE
)
1349 OUString sName
= pTextFormatCollection
->GetName();
1351 = SwResId(STR_STYLE_NO_LANGUAGE
).replaceAll("%STYLE_NAME%", sName
);
1353 auto pIssue
= lclAddIssue(m_rIssueCollection
, sIssueText
,
1354 sfx::AccessibilityIssueID::STYLE_LANGUAGE
);
1355 pIssue
->setIssueObject(IssueObject::LANGUAGE_NOT_SET
);
1356 pIssue
->setObjectID(sName
);
1357 pIssue
->setDoc(*pDoc
);
1364 class DocumentTitleCheck
: public DocumentCheck
1367 DocumentTitleCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1368 : DocumentCheck(rIssueCollection
)
1372 void check(SwDoc
* pDoc
) override
1374 SwDocShell
* pShell
= pDoc
->GetDocShell();
1378 const uno::Reference
<document::XDocumentPropertiesSupplier
> xDPS(pShell
->GetModel(),
1379 uno::UNO_QUERY_THROW
);
1380 const uno::Reference
<document::XDocumentProperties
> xDocumentProperties(
1381 xDPS
->getDocumentProperties());
1382 OUString sTitle
= xDocumentProperties
->getTitle();
1383 if (o3tl::trim(sTitle
).empty())
1385 auto pIssue
= lclAddIssue(m_rIssueCollection
, SwResId(STR_DOCUMENT_TITLE
),
1386 sfx::AccessibilityIssueID::DOCUMENT_TITLE
);
1387 pIssue
->setDoc(*pDoc
);
1388 pIssue
->setIssueObject(IssueObject::DOCUMENT_TITLE
);
1393 class FootnoteEndnoteCheck
: public DocumentCheck
1396 FootnoteEndnoteCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1397 : DocumentCheck(rIssueCollection
)
1401 void check(SwDoc
* pDoc
) override
1403 for (SwTextFootnote
* pTextFootnote
: pDoc
->GetFootnoteIdxs())
1405 SwFormatFootnote
const& rFootnote
= pTextFootnote
->GetFootnote();
1406 auto pIssue
= lclAddIssue(m_rIssueCollection
, rFootnote
.IsEndNote()
1407 ? SwResId(STR_AVOID_ENDNOTES
)
1408 : SwResId(STR_AVOID_FOOTNOTES
));
1409 pIssue
->setDoc(*pDoc
);
1410 pIssue
->setIssueObject(IssueObject::FOOTENDNOTE
);
1411 pIssue
->setTextFootnote(pTextFootnote
);
1416 class BackgroundImageCheck
: public DocumentCheck
1419 BackgroundImageCheck(sfx::AccessibilityIssueCollection
& rIssueCollection
)
1420 : DocumentCheck(rIssueCollection
)
1423 void check(SwDoc
* pDoc
) override
1425 uno::Reference
<lang::XComponent
> xDoc
= pDoc
->GetDocShell()->GetBaseModel();
1426 uno::Reference
<style::XStyleFamiliesSupplier
> xStyleFamiliesSupplier(xDoc
, uno::UNO_QUERY
);
1427 if (!xStyleFamiliesSupplier
.is())
1429 uno::Reference
<container::XNameAccess
> xStyleFamilies
1430 = xStyleFamiliesSupplier
->getStyleFamilies();
1431 uno::Reference
<container::XNameAccess
> xStyleFamily(xStyleFamilies
->getByName("PageStyles"),
1433 if (!xStyleFamily
.is())
1435 const uno::Sequence
<OUString
>& xStyleFamilyNames
= xStyleFamily
->getElementNames();
1436 for (const OUString
& rStyleFamilyName
: xStyleFamilyNames
)
1438 uno::Reference
<beans::XPropertySet
> xPropertySet(
1439 xStyleFamily
->getByName(rStyleFamilyName
), uno::UNO_QUERY
);
1440 if (!xPropertySet
.is())
1442 auto aFillStyleContainer
= xPropertySet
->getPropertyValue("FillStyle");
1443 if (aFillStyleContainer
.has
<drawing::FillStyle
>())
1445 drawing::FillStyle aFillStyle
= aFillStyleContainer
.get
<drawing::FillStyle
>();
1446 if (aFillStyle
== drawing::FillStyle_BITMAP
)
1449 = lclAddIssue(m_rIssueCollection
, SwResId(STR_AVOID_BACKGROUND_IMAGES
),
1450 sfx::AccessibilityIssueID::DOCUMENT_BACKGROUND
);
1452 pIssue
->setDoc(*pDoc
);
1453 pIssue
->setIssueObject(IssueObject::DOCUMENT_BACKGROUND
);
1460 } // end anonymous namespace
1462 // Check Shapes, TextBox
1463 void AccessibilityCheck::checkObject(SwNode
* pCurrent
, SdrObject
* pObject
)
1468 // Check for fontworks.
1469 if (SdrObjCustomShape
* pCustomShape
= dynamic_cast<SdrObjCustomShape
*>(pObject
))
1471 const SdrCustomShapeGeometryItem
& rGeometryItem
1472 = pCustomShape
->GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY
);
1474 if (const uno::Any
* pAny
= rGeometryItem
.GetPropertyValueByName("Type"))
1475 if (pAny
->get
<OUString
>().startsWith("fontwork-"))
1476 lclAddIssue(m_aIssueCollection
, SwResId(STR_FONTWORKS
));
1479 // Checking if there is floating Writer text draw object and if so, throwing a warning.
1480 // (Floating objects with text create problems with reading order)
1481 if (pObject
->HasText()
1482 && FindFrameFormat(pObject
)->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR
)
1484 auto pIssue
= lclAddIssue(m_aIssueCollection
, SwResId(STR_FLOATING_TEXT
));
1485 pIssue
->setIssueObject(IssueObject::TEXTFRAME
);
1486 pIssue
->setObjectID(pObject
->GetName());
1487 pIssue
->setDoc(*m_pDoc
);
1489 pIssue
->setNode(pCurrent
);
1492 const SdrObjKind nObjId
= pObject
->GetObjIdentifier();
1493 const SdrInventor nInv
= pObject
->GetObjInventor();
1495 if (nObjId
== SdrObjKind::CustomShape
|| nObjId
== SdrObjKind::Text
1496 || nObjId
== SdrObjKind::Media
|| nObjId
== SdrObjKind::Group
1497 || nObjId
== SdrObjKind::Graphic
|| nInv
== SdrInventor::FmForm
)
1499 if (pObject
->GetTitle().isEmpty() && pObject
->GetDescription().isEmpty())
1501 OUString sName
= pObject
->GetName();
1502 OUString sIssueText
= SwResId(STR_NO_ALT
).replaceAll("%OBJECT_NAME%", sName
);
1503 auto pIssue
= lclAddIssue(m_aIssueCollection
, sIssueText
,
1504 sfx::AccessibilityIssueID::NO_ALT_SHAPE
);
1505 // Set FORM Issue for Form objects because of the design mode
1506 if (nInv
== SdrInventor::FmForm
)
1507 pIssue
->setIssueObject(IssueObject::FORM
);
1509 pIssue
->setIssueObject(IssueObject::SHAPE
);
1511 pIssue
->setObjectID(pObject
->GetName());
1512 pIssue
->setDoc(*m_pDoc
);
1514 pIssue
->setNode(pCurrent
);
1519 void AccessibilityCheck::init()
1521 if (m_aDocumentChecks
.empty())
1523 m_aDocumentChecks
.emplace_back(new DocumentDefaultLanguageCheck(m_aIssueCollection
));
1524 m_aDocumentChecks
.emplace_back(new DocumentTitleCheck(m_aIssueCollection
));
1525 m_aDocumentChecks
.emplace_back(new FootnoteEndnoteCheck(m_aIssueCollection
));
1526 m_aDocumentChecks
.emplace_back(new BackgroundImageCheck(m_aIssueCollection
));
1529 if (m_aNodeChecks
.empty())
1531 m_aNodeChecks
.emplace_back(new NoTextNodeAltTextCheck(m_aIssueCollection
));
1532 m_aNodeChecks
.emplace_back(new TableNodeMergeSplitCheck(m_aIssueCollection
));
1533 m_aNodeChecks
.emplace_back(new TableFormattingCheck(m_aIssueCollection
));
1534 m_aNodeChecks
.emplace_back(new NumberingCheck(m_aIssueCollection
));
1535 m_aNodeChecks
.emplace_back(new HyperlinkCheck(m_aIssueCollection
));
1536 m_aNodeChecks
.emplace_back(new TextContrastCheck(m_aIssueCollection
));
1537 m_aNodeChecks
.emplace_back(new BlinkingTextCheck(m_aIssueCollection
));
1538 m_aNodeChecks
.emplace_back(new HeaderCheck(m_aIssueCollection
));
1539 m_aNodeChecks
.emplace_back(new TextFormattingCheck(m_aIssueCollection
));
1540 m_aNodeChecks
.emplace_back(new NonInteractiveFormCheck(m_aIssueCollection
));
1541 m_aNodeChecks
.emplace_back(new FloatingTextCheck(m_aIssueCollection
));
1542 m_aNodeChecks
.emplace_back(new TableHeadingCheck(m_aIssueCollection
));
1543 m_aNodeChecks
.emplace_back(new HeadingOrderCheck(m_aIssueCollection
));
1544 m_aNodeChecks
.emplace_back(new NewlineSpacingCheck(m_aIssueCollection
));
1545 m_aNodeChecks
.emplace_back(new SpaceSpacingCheck(m_aIssueCollection
));
1546 m_aNodeChecks
.emplace_back(new FakeFootnoteCheck(m_aIssueCollection
));
1547 m_aNodeChecks
.emplace_back(new FakeCaptionCheck(m_aIssueCollection
));
1551 void AccessibilityCheck::checkNode(SwNode
* pNode
)
1553 if (m_pDoc
== nullptr || pNode
== nullptr)
1558 for (std::shared_ptr
<BaseCheck
>& rpNodeCheck
: m_aNodeChecks
)
1560 auto pNodeCheck
= dynamic_cast<NodeCheck
*>(rpNodeCheck
.get());
1562 pNodeCheck
->check(pNode
);
1566 void AccessibilityCheck::checkDocumentProperties()
1568 if (m_pDoc
== nullptr)
1573 for (std::shared_ptr
<BaseCheck
>& rpDocumentCheck
: m_aDocumentChecks
)
1575 auto pDocumentCheck
= dynamic_cast<DocumentCheck
*>(rpDocumentCheck
.get());
1577 pDocumentCheck
->check(m_pDoc
);
1581 void AccessibilityCheck::check()
1583 if (m_pDoc
== nullptr)
1588 checkDocumentProperties();
1590 auto const& pNodes
= m_pDoc
->GetNodes();
1591 SwNode
* pNode
= nullptr;
1592 for (SwNodeOffset
n(0); n
< pNodes
.Count(); ++n
)
1597 for (std::shared_ptr
<BaseCheck
>& rpNodeCheck
: m_aNodeChecks
)
1599 auto pNodeCheck
= dynamic_cast<NodeCheck
*>(rpNodeCheck
.get());
1601 pNodeCheck
->check(pNode
);
1604 for (SwFrameFormat
* const& pFrameFormat
: pNode
->GetAnchoredFlys())
1606 SdrObject
* pObject
= pFrameFormat
->FindSdrObject();
1608 checkObject(pNode
, pObject
);
1614 } // end sw namespace
1616 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */