android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / core / access / AccessibilityCheck.cxx
blob0837353949dae945fe59043723da2c25965656b0
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
9 */
11 #include <AccessibilityCheck.hxx>
12 #include <AccessibilityIssue.hxx>
13 #include <AccessibilityCheckStrings.hrc>
14 #include <strings.hrc>
15 #include <ndnotxt.hxx>
16 #include <ndtxt.hxx>
17 #include <docsh.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>
31 #include <calbck.hxx>
32 #include <charatr.hxx>
33 #include <svx/xfillit0.hxx>
34 #include <svx/xflclit.hxx>
35 #include <ftnidx.hxx>
36 #include <txtftn.hxx>
37 #include <txtfrm.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>
47 namespace sw
49 namespace
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();
65 nIndex++;
68 return pTextNode;
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);
78 return pIssue;
81 class NodeCheck : public BaseCheck
83 public:
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)
97 if (!pNoTextNode)
98 return;
100 if (!pNoTextNode->GetTitle().isEmpty() || !pNoTextNode->GetDescription().isEmpty())
101 return;
103 const SwFrameFormat* pFrameFormat = pNoTextNode->GetFlyFormat();
104 if (!pFrameFormat)
105 return;
107 OUString sIssueText
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);
133 public:
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();
144 if (pNoTextNode)
145 checkNoTextNode(pNoTextNode);
150 // Check Table node if the table is merged and split.
151 class TableNodeMergeSplitCheck : public NodeCheck
153 private:
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)
168 if (!pTableNode)
169 return;
171 SwTable const& rTable = pTableNode->GetTable();
172 SwDoc& rDoc = pTableNode->GetDoc();
173 if (rTable.IsTableComplex())
175 addTableIssue(rTable, rDoc);
177 else
179 if (rTable.GetTabLines().size() > 1)
181 int i = 0;
182 size_t nFirstLineSize = 0;
183 bool bAllColumnsSameSize = true;
184 bool bCellSpansOverMoreRows = false;
186 for (SwTableLine const* pTableLine : rTable.GetTabLines())
188 if (i == 0)
190 nFirstLineSize = pTableLine->GetTabBoxes().size();
192 else
194 size_t nLineSize = pTableLine->GetTabBoxes().size();
195 if (nFirstLineSize != nLineSize)
197 bAllColumnsSameSize = false;
200 i++;
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);
217 public:
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();
228 if (pTableNode)
229 checkTableNode(pTableNode);
234 class TableFormattingCheck : public NodeCheck
236 private:
237 void checkTableNode(SwTableNode* pTableNode)
239 if (!pTableNode)
240 return;
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())
251 if (pBox->IsEmpty())
252 ++nEmptyBoxes;
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());
268 public:
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();
279 if (pTableNode)
280 checkTableNode(pTableNode);
285 class NumberingCheck : public NodeCheck
287 private:
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)" }
293 public:
294 NumberingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
295 : NodeCheck(rIssueCollection)
299 void check(SwNode* pCurrent) override
301 if (!pCurrent->IsTextNode())
302 return;
304 SwTextNode* pCurrentTextNode = pCurrent->GetTextNode();
305 SwTextNode* pNextTextNode = lclSearchNextTextNode(pCurrent);
307 if (!pNextTextNode)
308 return;
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 + "...";
316 OUString sIssueText
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
330 private:
331 void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange, SwTextNode* pTextNode,
332 sal_Int32 nStart)
334 uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY);
335 if (!xProperties->getPropertySetInfo()->hasPropertyByName("HyperLinkURL"))
336 return;
338 OUString sHyperlink;
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)
349 OUString sIssueText
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);
360 if (pIssue)
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());
372 public:
373 HyperlinkCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
374 : NodeCheck(rIssueCollection)
378 void check(SwNode* pCurrent) override
380 if (!pCurrent->IsTextNode())
381 return;
383 SwTextNode* pTextNode = pCurrent->GetTextNode();
384 uno::Reference<text::XTextContent> xParagraph
385 = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode);
386 if (!xParagraph.is())
387 return;
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);
395 if (xRun.is())
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
439 private:
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())
445 return;
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");
452 return;
455 uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY);
456 if (!xProperties.is())
457 return;
459 // Foreground color
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");
464 return;
467 const SwPageDesc* pPageDescription = pTextNode->FindPageDesc();
468 if (!pPageDescription)
469 return;
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");
489 return;
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;
498 else
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&)
520 else
522 SAL_WARN("sw.a11y", "CharStyleName void");
525 if (aBackgroundColor != nCharStyleBackColor)
527 auto pIssue
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)
541 return;
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());
563 public:
564 TextContrastCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
565 : NodeCheck(rIssueCollection)
569 void check(SwNode* pCurrent) override
571 if (!pCurrent->IsTextNode())
572 return;
574 SwTextNode* pTextNode = pCurrent->GetTextNode();
575 uno::Reference<text::XTextContent> xParagraph;
576 xParagraph = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode);
577 if (!xParagraph.is())
578 return;
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);
586 if (xRun.is())
588 checkTextRange(xRun, xParagraph, pTextNode, nStart);
589 nStart += xRun->getString().getLength();
595 class TextFormattingCheck : public NodeCheck
597 public:
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;
609 while (pItem)
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";
618 break;
619 case RES_CHRATR_POSTURE:
620 case RES_CHRATR_CJK_POSTURE:
621 case RES_CHRATR_CTL_POSTURE:
622 sFormattingType = "Posture";
623 break;
625 case RES_CHRATR_SHADOWED:
626 sFormattingType = "Shadowed";
627 break;
629 case RES_CHRATR_COLOR:
630 sFormattingType = "Font Color";
631 break;
633 case RES_CHRATR_FONTSIZE:
634 case RES_CHRATR_CJK_FONTSIZE:
635 case RES_CHRATR_CTL_FONTSIZE:
636 sFormattingType = "Font Size";
637 break;
639 case RES_CHRATR_FONT:
640 case RES_CHRATR_CJK_FONT:
641 case RES_CHRATR_CTL_FONT:
642 sFormattingType = "Font";
643 break;
645 case RES_CHRATR_EMPHASIS_MARK:
646 sFormattingType = "Emphasis Mark";
647 break;
649 case RES_CHRATR_UNDERLINE:
650 sFormattingType = "Underline";
651 break;
653 case RES_CHRATR_OVERLINE:
654 sFormattingType = "Overline";
655 break;
657 case RES_CHRATR_CROSSEDOUT:
658 sFormattingType = "Strikethrough";
659 break;
661 case RES_CHRATR_RELIEF:
662 sFormattingType = "Relief";
663 break;
665 case RES_CHRATR_CONTOUR:
666 sFormattingType = "Outline";
667 break;
668 default:
669 break;
671 if (!sFormattingType.isEmpty())
672 aFormattings.push_back(sFormattingType);
673 pItem = aItemIter.NextItem();
675 if (aFormattings.empty())
676 return;
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())
692 return;
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)
713 return;
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))
729 auto pIssue
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
744 private:
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();
758 nIndex--;
761 return pTextNode;
764 public:
765 NewlineSpacingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
766 : NodeCheck(rIssueCollection)
769 void check(SwNode* pCurrent) override
771 if (!pCurrent->IsTextNode())
772 return;
774 // Don't count empty table box text nodes
775 if (pCurrent->GetTableBox())
776 return;
778 SwTextNode* pTextNode = pCurrent->GetTextNode();
779 auto nParagraphLength = pTextNode->GetText().getLength();
780 if (nParagraphLength == 0)
782 SwTextNode* pPrevTextNode = getPrevTextNode(pCurrent);
783 if (!pPrevTextNode)
784 return;
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);
795 else
797 // Check for excess lines inside this paragraph
798 const OUString& sParagraphText = pTextNode->GetText();
799 int nLineCount = 0;
800 for (sal_Int32 i = 0; i < nParagraphLength; i++)
802 auto aChar = sParagraphText[i];
803 if (aChar == '\n')
805 nLineCount++;
806 // Looking for 2 newline characters and above as one can be part of the line
807 // break after a sentence
808 if (nLineCount > 2)
810 auto pIssue
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);
817 pIssue->setStart(i);
818 pIssue->setEnd(i);
821 // Don't count carriage return as normal character
822 else if (aChar != '\r')
824 nLineCount = 0;
831 class SpaceSpacingCheck : public NodeCheck
833 public:
834 SpaceSpacingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
835 : NodeCheck(rIssueCollection)
838 void check(SwNode* pCurrent) override
840 if (!pCurrent->IsTextNode())
841 return;
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])
854 case ' ':
856 if (bNonSpaceFound)
858 nSpaceCount++;
859 if (nSpaceCount == 2)
860 nSpaceStart = i;
862 break;
864 case '\t':
866 if (bPreviousWasChar)
868 ++nTabCount;
869 bPreviousWasChar = false;
870 if (nTabCount == 2)
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);
879 pIssue->setStart(0);
880 pIssue->setEnd(nParagraphLength);
883 break;
885 default:
887 if (nSpaceCount >= 2)
889 auto pIssue
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;
901 nSpaceCount = 0;
902 break;
909 class FakeFootnoteCheck : public NodeCheck
911 private:
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();
917 while (pItem)
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);
931 pIssue->setStart(0);
932 pIssue->setEnd(pTextNode->GetText().getLength());
933 break;
936 pItem = aItemIter.NextItem();
940 public:
941 FakeFootnoteCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
942 : NodeCheck(rIssueCollection)
945 void check(SwNode* pCurrent) override
947 if (!pCurrent->IsTextNode())
948 return;
949 SwTextNode* pTextNode = pCurrent->GetTextNode();
950 if (pTextNode->GetText().getLength() == 0)
951 return;
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);
961 pIssue->setStart(0);
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
981 public:
982 FakeCaptionCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
983 : NodeCheck(rIssueCollection)
986 void check(SwNode* pCurrent) override
988 if (!pCurrent->IsTextNode())
989 return;
991 SwTextNode* pTextNode = pCurrent->GetTextNode();
992 const OUString& sText = pTextNode->GetText();
994 if (sText.getLength() == 0)
995 return;
997 // Check if it's a real caption
998 const SwNode* aStartFly = pCurrent->FindFlyStartNode();
999 if (aStartFly
1000 && aStartFly->GetFlyFormat()->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
1001 return;
1003 auto aIter = SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti>(*pTextNode);
1004 auto nCount = 0;
1005 for (auto aTextFrame = aIter.First(); aTextFrame; aTextFrame = aIter.Next())
1007 auto aObjects = aTextFrame->GetDrawObjs();
1008 if (aObjects)
1009 nCount += aObjects->size();
1011 if (nCount > 1)
1012 return;
1015 // Check that there's exactly 1 image anchored in this node
1016 if (nCount == 1)
1018 OString sTemp;
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
1042 private:
1043 void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange, SwTextNode* pTextNode,
1044 sal_Int32 nStart)
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;
1052 if (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());
1064 public:
1065 BlinkingTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1066 : NodeCheck(rIssueCollection)
1070 void check(SwNode* pCurrent) override
1072 if (!pCurrent->IsTextNode())
1073 return;
1075 SwTextNode* pTextNode = pCurrent->GetTextNode();
1076 uno::Reference<text::XTextContent> xParagraph;
1077 xParagraph = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode);
1078 if (!xParagraph.is())
1079 return;
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);
1087 if (xRun.is())
1089 checkTextRange(xRun, pTextNode, nStart);
1090 nStart += xRun->getString().getLength();
1096 class HeaderCheck : public NodeCheck
1098 private:
1099 int m_nPreviousLevel;
1101 public:
1102 HeaderCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1103 : NodeCheck(rIssueCollection)
1104 , m_nPreviousLevel(0)
1108 void check(SwNode* pCurrent) override
1110 if (!pCurrent->IsTextNode())
1111 return;
1113 SwTextNode* pTextNode = pCurrent->GetTextNode();
1114 SwTextFormatColl* pCollection = pTextNode->GetTextColl();
1115 if (!pCollection->IsAssignedToListLevelOfOutlineStyle())
1116 return;
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
1131 public:
1132 NonInteractiveFormCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1133 : NodeCheck(rIssueCollection)
1137 void check(SwNode* pCurrent) override
1139 if (!pCurrent->IsTextNode())
1140 return;
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.
1148 if (bCheck)
1149 bCheck = text.indexOf("....") == -1; // Repeated dots.
1151 if (bCheck)
1152 bCheck = text.indexOf(u"……") == -1; // Repeated ellipsis.
1154 if (bCheck)
1155 bCheck = text.indexOf(u"….") == -1; // A dot after an ellipsis.
1157 if (bCheck)
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.
1161 if (!bCheck)
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
1177 public:
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())
1188 return;
1190 // If a node is in fly and if it is not anchored as char, throw warning.
1191 const SwNode* startFly = pCurrent->FindFlyStartNode();
1192 if (startFly
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();
1199 while (aIdx < nEnd)
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);
1211 break;
1213 ++aIdx;
1214 pFirstTextNode = &aIdx.GetNode();
1220 /// Heading paragraphs (with outline levels > 0) are not allowed in tables
1221 class TableHeadingCheck : public NodeCheck
1223 private:
1224 // Boolean indicating if heading-in-table warning is already triggered.
1225 bool m_bPrevPassed;
1227 public:
1228 TableHeadingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1229 : NodeCheck(rIssueCollection)
1230 , m_bPrevPassed(true)
1234 void check(SwNode* pCurrent) override
1236 if (!m_bPrevPassed)
1237 return;
1239 const SwTextNode* textNode = pCurrent->GetTextNode();
1241 if (textNode && textNode->GetAttrOutlineLevel() != 0)
1243 const SwTableNode* parentTable = pCurrent->FindTableNode();
1245 if (parentTable)
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
1260 public:
1261 HeadingOrderCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1262 : NodeCheck(rIssueCollection)
1266 void check(SwNode* pCurrent) override
1268 const SwTextNode* pTextNode = pCurrent->GetTextNode();
1269 if (!pTextNode)
1270 return;
1272 // If outline level stands for heading level...
1273 const int currentLevel = pTextNode->GetAttrOutlineLevel();
1274 if (!currentLevel)
1275 return;
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;
1282 if (!m_prevLevel)
1284 resultString = SwResId(STR_HEADING_START);
1286 else
1288 resultString = SwResId(STR_HEADING_ORDER);
1289 resultString
1290 = resultString.replaceAll("%LEVEL_PREV%", OUString::number(m_prevLevel));
1292 resultString
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;
1304 private:
1305 // Previous heading level to compare with.
1306 int m_prevLevel = 0;
1309 class DocumentCheck : public BaseCheck
1311 public:
1312 DocumentCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1313 : BaseCheck(rIssueCollection)
1317 virtual void check(SwDoc* pDoc) = 0;
1320 // Check default language
1321 class DocumentDefaultLanguageCheck : public DocumentCheck
1323 public:
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);
1342 else
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();
1350 OUString sIssueText
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
1366 public:
1367 DocumentTitleCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1368 : DocumentCheck(rIssueCollection)
1372 void check(SwDoc* pDoc) override
1374 SwDocShell* pShell = pDoc->GetDocShell();
1375 if (!pShell)
1376 return;
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
1395 public:
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
1418 public:
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())
1428 return;
1429 uno::Reference<container::XNameAccess> xStyleFamilies
1430 = xStyleFamiliesSupplier->getStyleFamilies();
1431 uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"),
1432 uno::UNO_QUERY);
1433 if (!xStyleFamily.is())
1434 return;
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())
1441 continue;
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)
1448 auto pIssue
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)
1465 if (!pObject)
1466 return;
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);
1488 if (pCurrent)
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);
1508 else
1509 pIssue->setIssueObject(IssueObject::SHAPE);
1511 pIssue->setObjectID(pObject->GetName());
1512 pIssue->setDoc(*m_pDoc);
1513 if (pCurrent)
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)
1554 return;
1556 init();
1558 for (std::shared_ptr<BaseCheck>& rpNodeCheck : m_aNodeChecks)
1560 auto pNodeCheck = dynamic_cast<NodeCheck*>(rpNodeCheck.get());
1561 if (pNodeCheck)
1562 pNodeCheck->check(pNode);
1566 void AccessibilityCheck::checkDocumentProperties()
1568 if (m_pDoc == nullptr)
1569 return;
1571 init();
1573 for (std::shared_ptr<BaseCheck>& rpDocumentCheck : m_aDocumentChecks)
1575 auto pDocumentCheck = dynamic_cast<DocumentCheck*>(rpDocumentCheck.get());
1576 if (pDocumentCheck)
1577 pDocumentCheck->check(m_pDoc);
1581 void AccessibilityCheck::check()
1583 if (m_pDoc == nullptr)
1584 return;
1586 init();
1588 checkDocumentProperties();
1590 auto const& pNodes = m_pDoc->GetNodes();
1591 SwNode* pNode = nullptr;
1592 for (SwNodeOffset n(0); n < pNodes.Count(); ++n)
1594 pNode = pNodes[n];
1595 if (pNode)
1597 for (std::shared_ptr<BaseCheck>& rpNodeCheck : m_aNodeChecks)
1599 auto pNodeCheck = dynamic_cast<NodeCheck*>(rpNodeCheck.get());
1600 if (pNodeCheck)
1601 pNodeCheck->check(pNode);
1604 for (SwFrameFormat* const& pFrameFormat : pNode->GetAnchoredFlys())
1606 SdrObject* pObject = pFrameFormat->FindSdrObject();
1607 if (pObject)
1608 checkObject(pNode, pObject);
1614 } // end sw namespace
1616 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */