|
4 | 4 | # Copyright (C) 2008-2024 NV Access Limited, Joseph Lee, Babbage B.V., Davy Kager, Bram Duvigneau, |
5 | 5 | # Leonard de Ruijter, Burman's Computer and Education Ltd., Julien Cochuyt |
6 | 6 |
|
| 7 | +from enum import StrEnum |
7 | 8 | import itertools |
8 | 9 | import typing |
9 | 10 | from typing import ( |
|
14 | 15 | Generator, |
15 | 16 | Iterable, |
16 | 17 | List, |
| 18 | + NamedTuple, |
17 | 19 | Optional, |
18 | 20 | Set, |
19 | 21 | Tuple, |
|
46 | 48 | ReportTableHeaders, |
47 | 49 | OutputMode, |
48 | 50 | ) |
49 | | -from config.featureFlagEnums import ReviewRoutingMovesSystemCaretFlag |
| 51 | +from config.featureFlagEnums import ReviewRoutingMovesSystemCaretFlag, FontFormattingBrailleModeFlag |
50 | 52 | from logHandler import log |
51 | 53 | import controlTypes |
52 | 54 | import api |
|
327 | 329 | #: Unicode braille indicator at the end of untranslated braille input. |
328 | 330 | INPUT_END_IND = " ⣹" |
329 | 331 |
|
| 332 | + |
| 333 | +class FormatTagDelimiter(StrEnum): |
| 334 | + """Delimiters for the start and end of format tags. |
| 335 | +
|
| 336 | + As these are shapes, they should be provided in unicode braille. |
| 337 | + """ |
| 338 | + |
| 339 | + START = "⣋" |
| 340 | + END = "⣙" |
| 341 | + |
| 342 | + |
330 | 343 | # used to separate chunks of text when programmatically joined |
331 | 344 | TEXT_SEPARATOR = " " |
332 | 345 |
|
|
380 | 393 | BLUETOOTH_PORT = ("bluetooth", _("Bluetooth")) |
381 | 394 |
|
382 | 395 |
|
| 396 | +class FormattingMarker(NamedTuple): |
| 397 | + """A pair of braille symbols that indicate the start and end of a particular type of font formatting. |
| 398 | +
|
| 399 | + As these are shapes, they should be provided in unicode braille. |
| 400 | + """ |
| 401 | + |
| 402 | + start: str |
| 403 | + end: str |
| 404 | + |
| 405 | + |
| 406 | +fontAttributeFormattingMarkers: dict[str, FormattingMarker] = { |
| 407 | + "bold": FormattingMarker( |
| 408 | + # Translators: Brailled at the start of bold text. |
| 409 | + # This is the English letter "b" in braille. |
| 410 | + start=pgettext("braille formatting symbol", "⠃"), |
| 411 | + # Translators: Brailled at the end of bold text. |
| 412 | + # This is the English letter "b" plus dot 7 in braille. |
| 413 | + end=pgettext("braille formatting symbol", "⡃"), |
| 414 | + ), |
| 415 | + "italic": FormattingMarker( |
| 416 | + # Translators: Brailled at the start of italic text. |
| 417 | + # This is the English letter "i" in braille. |
| 418 | + start=pgettext("braille formatting symbol", "⠊"), |
| 419 | + # Translators: Brailled at the end of italic text. |
| 420 | + # This is the English letter "i" plus dot 7 in braille. |
| 421 | + end=pgettext("braille formatting symbol", "⡊"), |
| 422 | + ), |
| 423 | + "underline": FormattingMarker( |
| 424 | + # Translators: Brailled at the start of underlined text. |
| 425 | + # This is the English letter "u" in braille. |
| 426 | + start=pgettext("braille formatting symbol", "⠥"), |
| 427 | + # Translators: Brailled at the end of underlined text. |
| 428 | + # This is the English letter "u" plus dot 7 in braille. |
| 429 | + end=pgettext("braille formatting symbol", "⡥"), |
| 430 | + ), |
| 431 | + "strikethrough": FormattingMarker( |
| 432 | + # Translators: Brailled at the start of strikethrough text. |
| 433 | + # This is the English letter "s" in braille. |
| 434 | + start=pgettext("braille formatting symbol", "⠎"), |
| 435 | + # Translators: Brailled at the end of strikethrough text. |
| 436 | + # This is the English letter "s" plus dot 7 in braille. |
| 437 | + end=pgettext("braille formatting symbol", "⡎"), |
| 438 | + ), |
| 439 | +} |
| 440 | + |
| 441 | + |
383 | 442 | def NVDAObjectHasUsefulText(obj: "NVDAObject") -> bool: |
384 | 443 | """Does obj contain useful text to display in braille |
385 | 444 |
|
@@ -1124,11 +1183,64 @@ def getFormatFieldBraille(field, fieldCache, isAtStart, formatConfig): |
1124 | 1183 | # Translators: brailled when text contains a bookmark |
1125 | 1184 | text = _("bkmk") |
1126 | 1185 | textList.append(text) |
| 1186 | + |
| 1187 | + if ( |
| 1188 | + config.conf["braille"]["fontFormattingDisplay"].calculated() == FontFormattingBrailleModeFlag.TAGS |
| 1189 | + and (formattingTags := _getFormattingTags(field, fieldCache, formatConfig)) is not None |
| 1190 | + ): |
| 1191 | + textList.append(formattingTags) |
| 1192 | + |
1127 | 1193 | fieldCache.clear() |
1128 | 1194 | fieldCache.update(field) |
1129 | 1195 | return TEXT_SEPARATOR.join([x for x in textList if x]) |
1130 | 1196 |
|
1131 | 1197 |
|
| 1198 | +def _getFormattingTags( |
| 1199 | + field: dict[str, str], |
| 1200 | + fieldCache: dict[str, str], |
| 1201 | + formatConfig: dict[str, bool], |
| 1202 | +) -> str | None: |
| 1203 | + """Get the formatting tags for the given field and cache. |
| 1204 | +
|
| 1205 | + Formatting tags are calculated according to the preferences passed in formatConfig. |
| 1206 | +
|
| 1207 | + :param field: The format current field. |
| 1208 | + :param fieldCache: The previous format field. |
| 1209 | + :param formatConfig: The user's formatting preferences. |
| 1210 | + :return: The braille formatting tag as a string, or None if no pertinant formatting is applied. |
| 1211 | + """ |
| 1212 | + textList: list[str] = [] |
| 1213 | + if formatConfig["fontAttributeReporting"] & OutputMode.BRAILLE: |
| 1214 | + # Only calculate font attribute tags if the user has enabled font attribute reporting in braille. |
| 1215 | + for fontAttribute, formattingMarker in fontAttributeFormattingMarkers.items(): |
| 1216 | + _appendFormattingMarker(fontAttribute, formattingMarker, textList, field, fieldCache) |
| 1217 | + if len(textList) > 0: |
| 1218 | + return f"{FormatTagDelimiter.START}{''.join(textList)}{FormatTagDelimiter.END}" |
| 1219 | + |
| 1220 | + |
| 1221 | +def _appendFormattingMarker( |
| 1222 | + attribute: str, |
| 1223 | + marker: FormattingMarker, |
| 1224 | + textList: list[str], |
| 1225 | + field: dict[str, str], |
| 1226 | + fieldCache: dict[str, str], |
| 1227 | +) -> None: |
| 1228 | + """Append a formatting marker to the text list if the attribute has changed. |
| 1229 | +
|
| 1230 | + :param attribute: The attribute to check. |
| 1231 | + :param marker: The formatting marker to use. |
| 1232 | + :param textList: The list of marker strings to append to. |
| 1233 | + :param field: The current format field. |
| 1234 | + :param fieldCache: The previous format field. |
| 1235 | + """ |
| 1236 | + newVal = field.get(attribute, False) |
| 1237 | + oldVal = fieldCache.get(attribute, False) if fieldCache is not None else False |
| 1238 | + if newVal and not oldVal: |
| 1239 | + textList.append(marker.start) |
| 1240 | + elif oldVal and not newVal: |
| 1241 | + textList.append(marker.end) |
| 1242 | + |
| 1243 | + |
1132 | 1244 | class TextInfoRegion(Region): |
1133 | 1245 | pendingCaretUpdate = False #: True if the cursor should be updated for this region on the display |
1134 | 1246 | allowPageTurns = True #: True if a page turn should be tried when a TextInfo cannot move anymore and the object supports page turns. |
@@ -1174,7 +1286,13 @@ def _setCursor(self, info: textInfos.TextInfo): |
1174 | 1286 |
|
1175 | 1287 | def _getTypeformFromFormatField(self, field, formatConfig): |
1176 | 1288 | typeform = louis.plain_text |
1177 | | - if not (formatConfig["fontAttributeReporting"] & OutputMode.BRAILLE): |
| 1289 | + if not ( |
| 1290 | + (formatConfig["fontAttributeReporting"] & OutputMode.BRAILLE) |
| 1291 | + and ( |
| 1292 | + config.conf["braille"]["fontFormattingDisplay"].calculated() |
| 1293 | + == FontFormattingBrailleModeFlag.LIBLOUIS |
| 1294 | + ) |
| 1295 | + ): |
1178 | 1296 | return typeform |
1179 | 1297 | if field.get("bold", False): |
1180 | 1298 | typeform |= louis.bold |
|
0 commit comments