Update git submodules
[LibreOffice.git] / vcl / source / accessibility / AccessibleTextAttributeHelper.cxx
blob39a77d2476b423ee7907f274d3645ec399e2958c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <vcl/accessibility/AccessibleTextAttributeHelper.hxx>
22 #include <com/sun/star/accessibility/AccessibleTextType.hpp>
23 #include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
24 #include <com/sun/star/awt/FontSlant.hpp>
25 #include <com/sun/star/awt/FontStrikeout.hpp>
26 #include <com/sun/star/awt/FontUnderline.hpp>
27 #include <com/sun/star/awt/FontWeight.hpp>
28 #include <com/sun/star/style/ParagraphAdjust.hpp>
29 #include <com/sun/star/text/TextMarkupType.hpp>
30 #include <o3tl/any.hxx>
31 #include <tools/color.hxx>
33 namespace
35 OUString lcl_ConvertCharEscapement(sal_Int16 nEscapement)
37 if (nEscapement > 0)
38 return "super";
39 if (nEscapement < 0)
40 return "sub";
42 return "baseline";
45 OUString lcl_ConverCharStrikeout(sal_Int16 nStrikeout)
47 OUString sTextLineThroughStyle;
48 OUString sTextLineThroughText;
49 OUString sTextLineThroughType;
50 OUString sTextLineThroughWidth;
52 switch (nStrikeout)
54 case css::awt::FontStrikeout::BOLD:
55 sTextLineThroughType = "single";
56 sTextLineThroughWidth = "bold";
57 break;
58 case css::awt::FontStrikeout::DONTKNOW:
59 break;
60 case css::awt::FontStrikeout::DOUBLE:
61 sTextLineThroughType = "double";
62 break;
63 case css::awt::FontStrikeout::NONE:
64 sTextLineThroughStyle = "none";
65 break;
66 case css::awt::FontStrikeout::SINGLE:
67 sTextLineThroughType = "single";
68 break;
69 case css::awt::FontStrikeout::SLASH:
70 sTextLineThroughText = u"/"_ustr;
71 break;
72 case css::awt::FontStrikeout::X:
73 sTextLineThroughText = u"X"_ustr;
74 break;
75 default:
76 assert(false && "Unhandled strikeout type");
79 OUString sResult;
80 if (!sTextLineThroughStyle.isEmpty())
81 sResult += u"text-line-through-style:"_ustr + sTextLineThroughStyle + ";";
82 if (!sTextLineThroughText.isEmpty())
83 sResult += u"text-line-through-text:"_ustr + sTextLineThroughText + ";";
84 if (!sTextLineThroughType.isEmpty())
85 sResult += u"text-line-through-type:"_ustr + sTextLineThroughType + ";";
86 if (!sTextLineThroughWidth.isEmpty())
87 sResult += u"text-line-through-width:"_ustr + sTextLineThroughWidth + ";";
89 return sResult;
92 OUString lcl_convertFontWeight(double fontWeight)
94 if (fontWeight == css::awt::FontWeight::THIN || fontWeight == css::awt::FontWeight::ULTRALIGHT)
95 return "100";
96 if (fontWeight == css::awt::FontWeight::LIGHT)
97 return "200";
98 if (fontWeight == css::awt::FontWeight::SEMILIGHT)
99 return "300";
100 if (fontWeight == css::awt::FontWeight::NORMAL)
101 return "normal";
102 if (fontWeight == css::awt::FontWeight::SEMIBOLD)
103 return "500";
104 if (fontWeight == css::awt::FontWeight::BOLD)
105 return "bold";
106 if (fontWeight == css::awt::FontWeight::ULTRABOLD)
107 return "800";
108 if (fontWeight == css::awt::FontWeight::BLACK)
109 return "900";
111 // awt::FontWeight::DONTKNOW || fontWeight == awt::FontWeight::NORMAL
112 return "normal";
115 OUString lcl_ConvertFontSlant(css::awt::FontSlant eFontSlant)
117 switch (eFontSlant)
119 case css::awt::FontSlant::FontSlant_NONE:
120 return "normal";
121 case css::awt::FontSlant::FontSlant_OBLIQUE:
122 case css::awt::FontSlant::FontSlant_REVERSE_OBLIQUE:
123 return "oblique";
124 case css::awt::FontSlant::FontSlant_ITALIC:
125 case css::awt::FontSlant::FontSlant_REVERSE_ITALIC:
126 return "italic";
127 case css::awt::FontSlant::FontSlant_DONTKNOW:
128 case css::awt::FontSlant::FontSlant_MAKE_FIXED_SIZE:
129 default:
130 return "";
134 // s. https://wiki.linuxfoundation.org/accessibility/iaccessible2/textattributes
135 // for values
136 void lcl_ConvertFontUnderline(sal_Int16 nFontUnderline, OUString& rUnderlineStyle,
137 OUString& rUnderlineType, OUString& rUnderlineWidth)
139 rUnderlineStyle = u""_ustr;
140 rUnderlineType = u"single"_ustr;
141 rUnderlineWidth = u"auto"_ustr;
143 switch (nFontUnderline)
145 case css::awt::FontUnderline::BOLD:
146 rUnderlineWidth = u"bold"_ustr;
147 return;
148 case css::awt::FontUnderline::BOLDDASH:
149 rUnderlineWidth = u"bold"_ustr;
150 rUnderlineStyle = u"dash"_ustr;
151 return;
152 case css::awt::FontUnderline::BOLDDASHDOT:
153 rUnderlineWidth = u"bold"_ustr;
154 rUnderlineStyle = u"dot-dash"_ustr;
155 return;
156 case css::awt::FontUnderline::BOLDDASHDOTDOT:
157 rUnderlineWidth = u"bold"_ustr;
158 rUnderlineStyle = u"dot-dot-dash"_ustr;
159 return;
160 case css::awt::FontUnderline::BOLDDOTTED:
161 rUnderlineWidth = u"bold"_ustr;
162 rUnderlineStyle = u"dotted"_ustr;
163 return;
164 case css::awt::FontUnderline::BOLDLONGDASH:
165 rUnderlineWidth = u"bold"_ustr;
166 rUnderlineStyle = u"long-dash"_ustr;
167 return;
168 case css::awt::FontUnderline::BOLDWAVE:
169 rUnderlineWidth = u"bold"_ustr;
170 rUnderlineStyle = u"wave"_ustr;
171 return;
172 case css::awt::FontUnderline::DASH:
173 rUnderlineStyle = u"dash"_ustr;
174 return;
175 case css::awt::FontUnderline::DASHDOT:
176 rUnderlineStyle = u"dot-dash"_ustr;
177 return;
178 case css::awt::FontUnderline::DASHDOTDOT:
179 rUnderlineStyle = u"dot-dot-dash"_ustr;
180 return;
181 case css::awt::FontUnderline::DONTKNOW:
182 rUnderlineWidth = u""_ustr;
183 rUnderlineStyle = u""_ustr;
184 rUnderlineType = u""_ustr;
185 return;
186 case css::awt::FontUnderline::DOTTED:
187 rUnderlineStyle = u"dotted"_ustr;
188 return;
189 case css::awt::FontUnderline::DOUBLE:
190 rUnderlineType = u"double"_ustr;
191 return;
192 case css::awt::FontUnderline::DOUBLEWAVE:
193 rUnderlineStyle = u"wave"_ustr;
194 rUnderlineType = u"double"_ustr;
195 return;
196 case css::awt::FontUnderline::LONGDASH:
197 rUnderlineStyle = u"long-dash"_ustr;
198 return;
199 case css::awt::FontUnderline::NONE:
200 rUnderlineWidth = u"none"_ustr;
201 rUnderlineStyle = u"none"_ustr;
202 rUnderlineType = u"none"_ustr;
203 return;
204 case css::awt::FontUnderline::SINGLE:
205 rUnderlineType = u"single"_ustr;
206 return;
207 case css::awt::FontUnderline::SMALLWAVE:
208 case css::awt::FontUnderline::WAVE:
209 rUnderlineStyle = u"wave"_ustr;
210 return;
211 default:
212 assert(false && "Unhandled font underline type");
216 /** Converts Color to "rgb(r,g,b)" as specified in https://wiki.linuxfoundation.org/accessibility/iaccessible2/textattributes. */
217 OUString lcl_ConvertColor(Color aColor)
219 return u"rgb(" + OUString::number(aColor.GetRed()) + u"\\,"
220 + OUString::number(aColor.GetGreen()) + u"\\," + OUString::number(aColor.GetBlue())
221 + u")";
224 OUString lcl_ConvertParagraphAdjust(css::style::ParagraphAdjust eParaAdjust)
226 switch (eParaAdjust)
228 case css::style::ParagraphAdjust_LEFT:
229 return u"left"_ustr;
230 case css::style::ParagraphAdjust_RIGHT:
231 return u"right"_ustr;
232 case css::style::ParagraphAdjust_BLOCK:
233 case css::style::ParagraphAdjust_STRETCH:
234 return u"justify"_ustr;
235 case css::style::ParagraphAdjust_CENTER:
236 return u"center"_ustr;
237 default:
238 assert(false && "Unhandled ParagraphAdjust value");
239 return u""_ustr;
244 static OUString ConvertUnoToIAccessible2TextAttributes(
245 const css::uno::Sequence<css::beans::PropertyValue>& rUnoAttributes,
246 IA2AttributeType eAttributeType)
248 OUString aRet;
249 for (css::beans::PropertyValue const& prop : rUnoAttributes)
251 OUString sAttribute;
252 OUString sValue;
254 if (eAttributeType & IA2AttributeType::TextAttributes)
256 if (prop.Name == "CharBackColor")
258 sAttribute = "background-color";
259 sValue = lcl_ConvertColor(
260 Color(ColorTransparency, *o3tl::doAccess<sal_Int32>(prop.Value)));
262 else if (prop.Name == "CharColor")
264 sAttribute = "color";
265 sValue = lcl_ConvertColor(
266 Color(ColorTransparency, *o3tl::doAccess<sal_Int32>(prop.Value)));
268 else if (prop.Name == "CharEscapement")
270 sAttribute = "text-position";
271 const sal_Int16 nEscapement = *o3tl::doAccess<sal_Int16>(prop.Value);
272 sValue = lcl_ConvertCharEscapement(nEscapement);
274 else if (prop.Name == "CharFontName")
276 sAttribute = "font-family";
277 sValue = *o3tl::doAccess<OUString>(prop.Value);
279 else if (prop.Name == "CharHeight")
281 sAttribute = "font-size";
282 sValue = OUString::number(*o3tl::doAccess<double>(prop.Value)) + "pt";
284 else if (prop.Name == "CharPosture")
286 sAttribute = "font-style";
287 const css::awt::FontSlant eFontSlant
288 = *o3tl::doAccess<css::awt::FontSlant>(prop.Value);
289 sValue = lcl_ConvertFontSlant(eFontSlant);
291 else if (prop.Name == "CharStrikeout")
293 const sal_Int16 nStrikeout = *o3tl::doAccess<sal_Int16>(prop.Value);
294 aRet += lcl_ConverCharStrikeout(nStrikeout);
296 else if (prop.Name == "CharUnderline")
298 OUString sUnderlineStyle;
299 OUString sUnderlineType;
300 OUString sUnderlineWidth;
301 const sal_Int16 nUnderline = *o3tl::doAccess<sal_Int16>(prop.Value);
302 lcl_ConvertFontUnderline(nUnderline, sUnderlineStyle, sUnderlineType,
303 sUnderlineWidth);
305 // leave 'sAttribute' and 'sName' empty, set all attributes here
306 if (!sUnderlineStyle.isEmpty())
307 aRet += u"text-underline-style:" + sUnderlineStyle + ";";
308 if (!sUnderlineType.isEmpty())
309 aRet += u"text-underline-type:" + sUnderlineType + ";";
310 if (!sUnderlineWidth.isEmpty())
311 aRet += u"text-underline-width:" + sUnderlineWidth + ";";
313 else if (prop.Name == "CharWeight")
315 sAttribute = "font-weight";
316 sValue = lcl_convertFontWeight(*o3tl::doAccess<double>(prop.Value));
320 // so far, "ParaAdjust" is the only UNO text attribute that
321 // maps to an object attribute for IAccessible2 ("text-align")
322 if (sAttribute.isEmpty() && (eAttributeType & IA2AttributeType::ObjectAttributes)
323 && prop.Name == "ParaAdjust")
325 sAttribute = "text-align";
326 const css::style::ParagraphAdjust eParaAdjust
327 = static_cast<css::style::ParagraphAdjust>(*o3tl::doAccess<sal_Int16>(prop.Value));
328 sValue = lcl_ConvertParagraphAdjust(eParaAdjust);
331 if (!sAttribute.isEmpty() && !sValue.isEmpty())
332 aRet += sAttribute + ":" + sValue + ";";
335 return aRet;
338 OUString AccessibleTextAttributeHelper::GetIAccessible2TextAttributes(
339 const css::uno::Reference<css::accessibility::XAccessibleText>& xText,
340 IA2AttributeType eAttributeType, sal_Int32 nOffset, sal_Int32& rStartOffset,
341 sal_Int32& rEndOffset)
343 assert(xText.is());
345 const css::uno::Sequence<css::beans::PropertyValue> attribs
346 = xText->getCharacterAttributes(nOffset, css::uno::Sequence<OUString>());
347 OUString sAttributes = ConvertUnoToIAccessible2TextAttributes(attribs, eAttributeType);
349 css::accessibility::TextSegment aAttributeRun
350 = xText->getTextAtIndex(nOffset, css::accessibility::AccessibleTextType::ATTRIBUTE_RUN);
351 rStartOffset = aAttributeRun.SegmentStart;
352 rEndOffset = aAttributeRun.SegmentEnd;
354 // report spelling error as "invalid:spelling;" IA2 text attribute and
355 // adapt start/end index as necessary
356 css::uno::Reference<css::accessibility::XAccessibleTextMarkup> xTextMarkup(xText,
357 css::uno::UNO_QUERY);
358 if ((eAttributeType & IA2AttributeType::TextAttributes) && xTextMarkup.is())
360 bool bInvalidSpelling = false;
361 const sal_Int32 nMarkupCount(
362 xTextMarkup->getTextMarkupCount(css::text::TextMarkupType::SPELLCHECK));
363 for (sal_Int32 nMarkupIndex = 0; nMarkupIndex < nMarkupCount; ++nMarkupIndex)
365 const css::accessibility::TextSegment aTextSegment
366 = xTextMarkup->getTextMarkup(nMarkupIndex, css::text::TextMarkupType::SPELLCHECK);
367 const sal_Int32 nStartOffsetTextMarkup = aTextSegment.SegmentStart;
368 const sal_Int32 nEndOffsetTextMarkup = aTextSegment.SegmentEnd;
369 if (nStartOffsetTextMarkup <= nOffset)
371 if (nOffset < nEndOffsetTextMarkup)
373 // offset is inside invalid spelling
374 rStartOffset = ::std::max(rStartOffset, nStartOffsetTextMarkup);
375 rEndOffset = ::std::min(rEndOffset, nEndOffsetTextMarkup);
376 bInvalidSpelling = true;
377 break;
379 else
381 rStartOffset = ::std::max(rStartOffset, nEndOffsetTextMarkup);
384 else
386 rEndOffset = ::std::min(rEndOffset, nStartOffsetTextMarkup);
390 if (bInvalidSpelling)
391 sAttributes += u"invalid:spelling;"_ustr;
394 return sAttributes;
397 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */