Skip to content

Commit 504374c

Browse files
authored
Merge 507d723 into 3f82ab6
2 parents 3f82ab6 + 507d723 commit 504374c

6 files changed

Lines changed: 190 additions & 4 deletions

File tree

source/braille.py

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# Copyright (C) 2008-2024 NV Access Limited, Joseph Lee, Babbage B.V., Davy Kager, Bram Duvigneau,
55
# Leonard de Ruijter, Burman's Computer and Education Ltd., Julien Cochuyt
66

7+
from enum import StrEnum
78
import itertools
89
import typing
910
from typing import (
@@ -14,6 +15,7 @@
1415
Generator,
1516
Iterable,
1617
List,
18+
NamedTuple,
1719
Optional,
1820
Set,
1921
Tuple,
@@ -46,7 +48,7 @@
4648
ReportTableHeaders,
4749
OutputMode,
4850
)
49-
from config.featureFlagEnums import ReviewRoutingMovesSystemCaretFlag
51+
from config.featureFlagEnums import ReviewRoutingMovesSystemCaretFlag, FontFormattingBrailleModeFlag
5052
from logHandler import log
5153
import controlTypes
5254
import api
@@ -327,6 +329,17 @@
327329
#: Unicode braille indicator at the end of untranslated braille input.
328330
INPUT_END_IND = " ⣹"
329331

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+
330343
# used to separate chunks of text when programmatically joined
331344
TEXT_SEPARATOR = " "
332345

@@ -380,6 +393,52 @@
380393
BLUETOOTH_PORT = ("bluetooth", _("Bluetooth"))
381394

382395

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+
383442
def NVDAObjectHasUsefulText(obj: "NVDAObject") -> bool:
384443
"""Does obj contain useful text to display in braille
385444
@@ -1124,11 +1183,64 @@ def getFormatFieldBraille(field, fieldCache, isAtStart, formatConfig):
11241183
# Translators: brailled when text contains a bookmark
11251184
text = _("bkmk")
11261185
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+
11271193
fieldCache.clear()
11281194
fieldCache.update(field)
11291195
return TEXT_SEPARATOR.join([x for x in textList if x])
11301196

11311197

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+
11321244
class TextInfoRegion(Region):
11331245
pendingCaretUpdate = False #: True if the cursor should be updated for this region on the display
11341246
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):
11741286

11751287
def _getTypeformFromFormatField(self, field, formatConfig):
11761288
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+
):
11781296
return typeform
11791297
if field.get("bold", False):
11801298
typeform |= louis.bold

source/config/configSpec.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
interruptSpeechWhileScrolling = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="enabled")
9090
showSelection = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="enabled")
9191
reportLiveRegions = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="enabled")
92+
fontFormattingDisplay = featureFlag(optionsEnum="FontFormattingBrailleModeFlag", behaviorOfDefault="LIBLOUIS")
9293
[[auto]]
9394
excludedDisplays = string_list(default=list())
9495
@@ -206,7 +207,7 @@
206207
reportFontName = boolean(default=false)
207208
reportFontSize = boolean(default=false)
208209
# Deprecated in 2025.1
209-
reportFontAttributes = boolean(default=false)
210+
reportFontAttributes = boolean(default=false)
210211
# 0: Off, 1: Speech, 2: Braille, 3: Speech and Braille
211212
fontAttributeReporting = integer(0, 3, default=0)
212213
reportRevisions = boolean(default=true)

source/config/featureFlagEnums.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,23 @@ def _displayStringLabels(self):
122122
NOTIFICATIONS = enum.auto()
123123

124124

125+
class FontFormattingBrailleModeFlag(DisplayStringEnum):
126+
"""Enumeration containing the possible ways to display formatting changes in braille."""
127+
128+
DEFAULT = enum.auto()
129+
LIBLOUIS = enum.auto()
130+
TAGS = enum.auto()
131+
132+
@property
133+
def _displayStringLabels(self) -> dict["FontFormattingBrailleModeFlag", str]:
134+
return {
135+
# Translators: Label for a way of outputting formatting in braille.
136+
FontFormattingBrailleModeFlag.LIBLOUIS: _("Liblouis"),
137+
# Translators: Label for a way of outputting formatting in braille.
138+
FontFormattingBrailleModeFlag.TAGS: _("Tags"),
139+
}
140+
141+
125142
def getAvailableEnums() -> typing.Generator[typing.Tuple[str, FlagValueEnum], None, None]:
126143
for name, value in globals().items():
127144
if (

source/gui/settingsDialogs.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4395,6 +4395,18 @@ def makeSettings(self, settingsSizer):
43954395
)
43964396
)
43974397
self.bindHelpEvent("BrailleSettingsShowSelection", self.brailleShowSelectionCombo)
4398+
4399+
self.formattingDisplayCombo: nvdaControls.FeatureFlagCombo = (
4400+
followCursorGroupHelper.addLabeledControl(
4401+
# Translators: This is a label for a combo-box in the Braille settings panel.
4402+
labelText=_("Formatting &display"),
4403+
wxCtrlClass=nvdaControls.FeatureFlagCombo,
4404+
keyPath=("braille", "fontFormattingDisplay"),
4405+
conf=config.conf,
4406+
)
4407+
)
4408+
self.bindHelpEvent("BrailleFormattingDisplay", self.formattingDisplayCombo)
4409+
43984410
self.followCursorGroupBox.Enable(
43994411
list(braille.BrailleMode)[self.brailleModes.GetSelection()] is braille.BrailleMode.FOLLOW_CURSORS,
44004412
)
@@ -4466,6 +4478,7 @@ def onSave(self):
44664478
]
44674479
self.brailleInterruptSpeechCombo.saveCurrentValueToConf()
44684480
self.brailleShowSelectionCombo.saveCurrentValueToConf()
4481+
self.formattingDisplayCombo.saveCurrentValueToConf()
44694482

44704483
def onShowCursorChange(self, evt):
44714484
self.cursorBlinkCheckBox.Enable(evt.IsChecked())

user_docs/en/changes.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
* Enhanced Microsoft Word comment command: press twice to present comment content in browsable message. (#16800, @Cary-Rowen)
1010
* Enhanced Microsoft Excel notes command: press twice to present notes content in browsable message. (#16878, @Cary-Rowen)
1111
* NVDA can now be configured to report font attributes in speech and braille separately. (#16755)
12-
12+
* It is now possible to change the way NVDA displays certain text formatting attributes in braille.
13+
The available options are:
14+
* Liblouis (default): Uses formatting markers defined in the selected braille table.
15+
* Tags: Uses start and end tags to denote where certain font attributes begin and end. (#16864)
1316

1417
### Bug Fixes
1518

user_docs/en/userGuide.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,6 +2163,40 @@ This means that you do not have to scroll the display at the end of each line ev
21632163
This may allow for more fluent reading of large amounts of text.
21642164
It is disabled by default.
21652165

2166+
##### Formatting display {#BrailleFormattingDisplay}
2167+
2168+
| . {.hideHeaderRow} |.|
2169+
|---|---|
2170+
| Options | Default (Liblouis), Liblouis, Tags |
2171+
| Default | Liblouis |
2172+
2173+
This setting determines how NVDA will display text formatting in braille.
2174+
This option only has an effect if NVDA is set to [display font attributes in braille](#DocumentFormattingFontAttributes).
2175+
The following options are supported:
2176+
2177+
| Option | Behaviour |
2178+
|---|---|
2179+
| Liblouis | Use native Braille formatting. Note that this option will only indicate bold, italic and underlined text, and only if the selected braille table supports indicating these attributes. |
2180+
| [Tags](#BrailleFormattingDisplayTags) | Use tags that describe how and where text formatting changes. |
2181+
2182+
###### Tags {#BrailleFormattingDisplayTags}
2183+
2184+
When "Formatting display" is set to "Tags", a formatting tag is displayed in braille when a change in formatting is detected.
2185+
These tags start with ⣋ and end with ⣙.
2186+
A formatting tag will contain one or more symbols which describe the text formatting.
2187+
The following symbols are defined:
2188+
2189+
| Symbol | Meaning |
2190+
|---|---|
2191+
| ⠃ ("b") | Start bold |
2192+
| ⡃ ("b" with dot 7) | End bold |
2193+
| ⠊ ("i") | Start italic |
2194+
| ⡊ ("i" with dot 7) | End italic |
2195+
| ⠥ ("u") | Start underline |
2196+
| ⡥ ("u" with dot 7) | End underline |
2197+
| ⠎ ("s")| Start strikethrough |
2198+
| ⡎ ("s" with dot 7) | End strikethrough |
2199+
21662200
##### Avoid splitting words when possible {#BrailleSettingsWordWrap}
21672201

21682202
If this is enabled, a word which is too large to fit at the end of the braille display will not be split.

0 commit comments

Comments
 (0)