def _getFormatFieldAtRange(self, range, formatConfig): formatField = textInfos.FormatField() if formatConfig["reportFontName"]: try: fontNameValue = range.GetAttributeValue( UIAHandler.UIA_FontNameAttributeId) except COMError: fontNameValue = UIAHandler.handler.reservedNotSupportedValue if fontNameValue != UIAHandler.handler.reservedNotSupportedValue: formatField["font-name"] = fontNameValue if formatConfig["reportFontSize"]: try: fontSizeValue = range.GetAttributeValue( UIAHandler.UIA_FontSizeAttributeId) except COMError: fontSizeValue = UIAHandler.handler.reservedNotSupportedValue if fontSizeValue != UIAHandler.handler.reservedNotSupportedValue: formatField['font-size'] = "%g pt" % float(fontSizeValue) if formatConfig["reportHeadings"]: try: styleIDValue = range.GetAttributeValue( UIAHandler.UIA_StyleIdAttributeId) except COMError: styleIDValue = UIAHandler.handler.reservedNotSupportedValue if UIAHandler.StyleId_Heading1 <= styleIDValue <= UIAHandler.StyleId_Heading9: formatField["heading-level"] = ( styleIDValue - UIAHandler.StyleId_Heading1) + 1 return textInfos.FieldCommand("formatChange", formatField)
def _startElementHandler(self, tagName, attrs): if tagName == 'unich': data = attrs.get('value', None) if data is not None: try: data = chr(int(data)) except ValueError: data = textUtils.REPLACEMENT_CHAR self._CharacterDataHandler( data, processBufferedSurrogates=isLowSurrogate(data)) return elif tagName == 'control': newAttrs = textInfos.ControlField(attrs) self._commandList.append( textInfos.FieldCommand("controlStart", newAttrs)) elif tagName == 'text': newAttrs = textInfos.FormatField(attrs) self._commandList.append( textInfos.FieldCommand("formatChange", newAttrs)) else: raise ValueError("Unknown tag name: %s" % tagName) # Normalise attributes common to both field types. try: newAttrs["_startOfNode"] = newAttrs["_startOfNode"] == "1" except KeyError: pass try: newAttrs["_endOfNode"] = newAttrs["_endOfNode"] == "1" except KeyError: pass
def getFormatField(textInfo, formatConfig): formatField = textInfos.FormatField() for field in textInfo.getTextWithFields(formatConfig): if isinstance(field, textInfos.FieldCommand) and\ isinstance(field.field, textInfos.FormatField): formatField.update(field.field) return formatField
def _getFormatFieldAndOffsets(self, offset, formatConfig, calculateOffsets=True): formatField = textInfos.FormatField() fontObj = self.obj.excelCellObject.font if formatConfig['reportFontName']: formatField['font-name'] = fontObj.name if formatConfig['reportFontSize']: formatField['font-size'] = str(fontObj.size) if formatConfig['reportFontAttributes']: formatField['bold'] = fontObj.bold formatField['italic'] = fontObj.italic formatField['underline'] = fontObj.underline if formatConfig['reportColor']: try: formatField['color'] = colors.RGB.fromCOLORREF( int(fontObj.color)) except COMError: pass try: formatField['background-color'] = colors.RGB.fromCOLORREF( int(self.obj.excelCellObject.interior.color)) except COMError: pass return formatField, (self._startOffset, self._endOffset)
def getParagraphStyle(self, info): formatField = textInfos.FormatField() formatConfig = config.conf['documentFormatting'] for field in info.getTextWithFields(formatConfig): #if isinstance(field,textInfos.FieldCommand): and isinstance(field.field,textInfos.FormatField): try: formatField.update(field.field) except: pass result = [ formatField.get(fieldName, None) for fieldName in self.styleFields ] return tuple(result)
def getTextWithFields(self, formatConfig=None): commands = [] if self.isCollapsed: return commands if not formatConfig: formatConfig = config.conf["documentFormatting"] left, top = self._consoleCoordFromOffset(self._startOffset) right, bottom = self._consoleCoordFromOffset(self._endOffset - 1) rect = wincon.SMALL_RECT(left, top, right, bottom) if bottom - top > 0: #offsets span multiple lines rect.Left = 0 rect.Right = self.consoleScreenBufferInfo.dwSize.x - 1 length = self.consoleScreenBufferInfo.dwSize.x * (bottom - top + 1) else: length = self._endOffset - self._startOffset buf = wincon.ReadConsoleOutput(consoleOutputHandle, length, rect) if bottom - top > 0: buf = buf[left:len(buf) - (self.consoleScreenBufferInfo.dwSize.x - right) + 1] lastAttr = None lastText = [] boundEnd = self._startOffset for i, c in enumerate(buf): if self._startOffset + i == boundEnd: field, (boundStart, boundEnd) = self._getFormatFieldAndOffsets( boundEnd, formatConfig) if lastText: commands.append("".join(lastText)) lastText = [] commands.append(textInfos.FieldCommand("formatChange", field)) if not c.Attributes == lastAttr: formatField = textInfos.FormatField() if formatConfig['reportColor']: formatField["color"] = CONSOLE_COLORS_TO_RGB[c.Attributes & 0x0f] formatField["background-color"] = CONSOLE_COLORS_TO_RGB[ (c.Attributes >> 4) & 0x0f] if formatConfig[ 'reportFontAttributes'] and c.Attributes & COMMON_LVB_UNDERSCORE: formatField['underline'] = True if formatField: if lastText: commands.append("".join(lastText)) lastText = [] command = textInfos.FieldCommand("formatChange", formatField) commands.append(command) lastAttr = c.Attributes lastText.append(c.Char) commands.append("".join(lastText)) return commands
def _getFormatFieldAndOffsets(self,offset,formatConfig,calculateOffsets=True): """Retrieve the formatting information for a given offset and the offsets spanned by that field. Subclasses must override this if support for text formatting is desired. The base implementation associates text with line numbers if possible. """ formatField=textInfos.FormatField() startOffset,endOffset=self._startOffset,self._endOffset if formatConfig["reportLineNumber"]: if calculateOffsets: startOffset,endOffset=self._getLineOffsets(offset) lineNum=self._getLineNumFromOffset(offset) if lineNum is not None: formatField["line-number"]=lineNum+1 return formatField,(startOffset,endOffset)
def reportFocus(self): # #4878: Excel specific code for speaking format changes on the focused object. info=self.makeTextInfo(textInfos.POSITION_FIRST) info.expand(textInfos.UNIT_CHARACTER) formatField=textInfos.FormatField() formatConfig=config.conf['documentFormatting'] for field in info.getTextWithFields(formatConfig): if isinstance(field,textInfos.FieldCommand) and isinstance(field.field,textInfos.FormatField): formatField.update(field.field) if not hasattr(self.parent,'_formatFieldSpeechCache'): self.parent._formatFieldSpeechCache={} text=speech.getFormatFieldSpeech(formatField,attrsCache=self.parent._formatFieldSpeechCache,formatConfig=formatConfig) if formatField else None if text: speech.speakText(text) super(ExcelCell,self).reportFocus()
def _getFormatFieldsAndText(self, tempRange, formatConfig): if not self.allowGetFormatFieldsAndTextOnDegenerateUIARanges and tempRange.compareEndpoints( UIAHandler.TextPatternRangeEndpoint_Start, tempRange, UIAHandler.TextPatternRangeEndpoint_End) == 0: return formatField = self._getFormatFieldAtRange(tempRange, formatConfig) if formatConfig["reportSpellingErrors"]: try: annotationTypes = tempRange.GetAttributeValue( UIAHandler.UIA_AnnotationTypesAttributeId) except COMError: annotationTypes = UIAHandler.handler.reservedNotSupportedValue if annotationTypes == UIAHandler.AnnotationType_SpellingError: formatField.field["invalid-spelling"] = True yield formatField yield tempRange.GetText(-1) elif annotationTypes == UIAHandler.handler.ReservedMixedAttributeValue: for r in self._iterUIARangeByUnit(tempRange, UIAHandler.TextUnit_Word): text = r.GetText(-1) if not text: continue r.MoveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_End, r, UIAHandler.TextPatternRangeEndpoint_Start) r.ExpandToEnclosingUnit(UIAHandler.TextUnit_Character) try: annotationTypes = r.GetAttributeValue( UIAHandler.UIA_AnnotationTypesAttributeId) except COMError: annotationTypes = UIAHandler.handler.reservedNotSupportedValue newField = textInfos.FormatField() newField.update(formatField.field) if annotationTypes == UIAHandler.AnnotationType_SpellingError: newField["invalid-spelling"] = True yield textInfos.FieldCommand("formatChange", newField) yield text else: yield formatField yield tempRange.GetText(-1) else: yield formatField yield tempRange.GetText(-1)
def _hasBackground(self,colors,ti=None) : cfg = { "detectFormatAfterCursor":False, "reportFontName":False,"reportFontSize":False,"reportFontAttributes":False,"reportColor":True,"reportRevisions":False, "reportStyle":False,"reportAlignment":False,"reportSpellingErrors":False, "reportPage":False,"reportLineNumber":False,"reportTables":False, "reportLinks":False,"reportHeadings":False,"reportLists":False, "reportBlockQuotes":False,"reportComments":False, } retval = dict((color,False) for color in colors) if not ti : ti = self.makeTextInfo(textInfos.POSITION_SELECTION) ti._endOffset = ti._startOffset ti.collapse() ti.expand(textInfos.UNIT_CHARACTER) formatField=textInfos.FormatField() for field in ti.getTextWithFields(cfg): if isinstance(field,textInfos.FieldCommand) and isinstance(field.field,textInfos.FormatField): if 'background-color' in field.field : formatField.update(field.field) rgb = formatField['background-color'] if rgb in retval : retval[rgb] = True return retval
def _getFormatFieldAndOffsets(self,offset,formatConfig,calculateOffsets=True): formatField=textInfos.FormatField() fontObj=self.obj.excelCellObject.font if formatConfig['reportAlignment']: value=alignmentLabels.get(self.obj.excelCellObject.horizontalAlignment) if value: formatField['text-align']=value value=alignmentLabels.get(self.obj.excelCellObject.verticalAlignment) if value: formatField['vertical-align']=value if formatConfig['reportFontName']: formatField['font-name']=fontObj.name if formatConfig['reportFontSize']: formatField['font-size']=str(fontObj.size) if formatConfig['reportFontAttributes']: formatField['bold']=fontObj.bold formatField['italic']=fontObj.italic underline=fontObj.underline formatField['underline']=False if underline is None or underline==xlUnderlineStyleNone else True if formatConfig['reportStyle']: try: styleName=self.obj.excelCellObject.style.nameLocal except COMError: styleName=None if styleName: formatField['style']=styleName if formatConfig['reportColor']: try: formatField['color']=colors.RGB.fromCOLORREF(int(fontObj.color)) except COMError: pass try: formatField['background-color']=colors.RGB.fromCOLORREF(int(self.obj.excelCellObject.interior.color)) except COMError: pass return formatField,(self._startOffset,self._endOffset)
def _getFormatFieldAndOffsets(self,offset,formatConfig,calculateOffsets=True): obj = self.obj try: startOffset,endOffset,attribsString=obj.IAccessibleTextObject.attributes(offset) except COMError: log.debugWarning("could not get attributes",exc_info=True) return textInfos.FormatField(),(self._startOffset,self._endOffset) formatField=textInfos.FormatField() if not attribsString and offset>0: try: attribsString=obj.IAccessibleTextObject.attributes(offset-1)[2] except COMError: pass if attribsString: formatField.update(IAccessibleHandler.splitIA2Attribs(attribsString)) try: escapement = int(formatField["CharEscapement"]) if escapement < 0: textPos = "sub" elif escapement > 0: textPos = "super" else: textPos = "baseline" formatField["text-position"] = textPos except KeyError: pass try: formatField["font-name"] = formatField["CharFontName"] except KeyError: pass try: formatField["font-size"] = "%spt" % formatField["CharHeight"] except KeyError: pass try: formatField["italic"] = formatField["CharPosture"] == "2" except KeyError: pass try: formatField["strikethrough"] = formatField["CharStrikeout"] == "1" except KeyError: pass try: underline = formatField["CharUnderline"] if underline == "10": # Symphony doesn't provide for semantic communication of spelling errors, so we have to rely on the WAVE underline type. formatField["invalid-spelling"] = True else: formatField["underline"] = underline != "0" except KeyError: pass try: formatField["bold"] = float(formatField["CharWeight"]) > 100 except KeyError: pass try: color=formatField.pop('CharColor') except KeyError: color=None if color: formatField['color']=colors.RGB.fromString(color) try: backgroundColor=formatField.pop('CharBackColor') except KeyError: backgroundColor=None if backgroundColor: formatField['background-color']=colors.RGB.fromString(backgroundColor) # optimisation: Assume a hyperlink occupies a full attribute run. try: if obj.IAccessibleTextObject.QueryInterface(IAccessibleHandler.IAccessibleHypertext).hyperlinkIndex(offset) != -1: formatField["link"] = True except COMError: pass if offset == 0: # Only include the list item prefix on the first line of the paragraph. numbering = formatField.get("Numbering") if numbering: formatField["line-prefix"] = numbering.get("NumberingPrefix") or numbering.get("BulletChar") if obj.hasFocus: # Symphony exposes some information for the caret position as attributes on the document object. # optimisation: Use the tree interceptor to get the document. try: docAttribs = obj.treeInterceptor.rootNVDAObject.IA2Attributes except AttributeError: # No tree interceptor, so we can't efficiently fetch this info. pass else: try: formatField["page-number"] = docAttribs["page-number"] except KeyError: pass try: formatField["line-number"] = docAttribs["line-number"] except KeyError: pass return formatField,(startOffset,endOffset)
def getTextWithFields(self, formatConfig=None): if self.isCollapsed: # #7652: We cannot fetch fields on collapsed ranges otherwise we end up with repeating controlFields in braille (such as list list list). return [] fields = super(WordDocumentTextInfo, self).getTextWithFields(formatConfig=formatConfig) if len(fields) == 0: # Nothing to do... was probably a collapsed range. return fields # Sometimes embedded objects and graphics In MS Word can cause a controlStart then a controlEnd with no actual formatChange / text in the middle. # SpeakTextInfo always expects that the first lot of controlStarts will always contain some text. # Therefore ensure that the first lot of controlStarts does contain some text by inserting a blank formatChange and empty string in this case. for index in range(len(fields)): field = fields[index] if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": continue elif isinstance( field, textInfos.FieldCommand) and field.command == "controlEnd": formatChange = textInfos.FieldCommand("formatChange", textInfos.FormatField()) fields.insert(index, formatChange) fields.insert(index + 1, "") break ##7971: Microsoft Word exposes list bullets as part of the actual text. # This then confuses NVDA's braille cursor routing as it expects that there is a one-to-one mapping between characters in the text string and unit character moves. # Therefore, detect when at the start of a list, and strip the bullet from the text string, placing it in the text's formatField as line-prefix. listItemStarted = False lastFormatField = None for index in range(len(fields)): field = fields[index] if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": if field.field.get( 'role' ) == controlTypes.ROLE_LISTITEM and field.field.get( '_startOfNode'): # We are in the start of a list item. listItemStarted = True elif isinstance(field, textInfos.FieldCommand ) and field.command == "formatChange": # This is the most recent formatField we have seen. lastFormatField = field.field elif listItemStarted and isinstance(field, str): # This is the first text string within the list. # Remove the text up to the first space, and store it as line-prefix which NVDA will appropriately speak/braille as a bullet. try: spaceIndex = field.index(' ') except ValueError: log.debugWarning("No space found in this text string") break prefix = field[0:spaceIndex] fields[index] = field[spaceIndex + 1:] lastFormatField['line-prefix'] = prefix # Let speech know that line-prefix is safe to be spoken always, as it will only be exposed on the very first formatField on the list item. lastFormatField['line-prefix_speakAlways'] = True break else: # Not a controlStart, formatChange or text string. Nothing to do. break # Fill in page number attributes where NVDA expects try: page = fields[0].field['page-number'] except KeyError: page = None if page is not None: for field in fields: if isinstance(field, textInfos.FieldCommand) and isinstance( field.field, textInfos.FormatField): field.field['page-number'] = page # MS Word can sometimes return a higher ancestor in its textRange's children. # E.g. a table inside a table header. # This does not cause a loop, but does cause information to be doubled # Detect these duplicates and remove them from the generated fields. seenStarts = set() pendingRemoves = [] index = 0 for index, field in enumerate(fields): if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": runtimeID = field.field['runtimeID'] if not runtimeID: continue if runtimeID in seenStarts: pendingRemoves.append(field.field) else: seenStarts.add(runtimeID) elif seenStarts: seenStarts.clear() index = 0 while index < len(fields): field = fields[index] if isinstance(field, textInfos.FieldCommand) and any( x is field.field for x in pendingRemoves): del fields[index] else: index += 1 return fields
def getTextWithFields( # noqa: C901 self, formatConfig: Optional[Dict] = None ) -> textInfos.TextInfo.TextWithFieldsT: fields = None # #11043: when a non-collapsed text range is positioned within a blank table cell # MS Word does not return the table cell as an enclosing element, # Thus NVDa thinks the range is not inside the cell. # This can be detected by asking for the first 2 characters of the range's text, # Which will either be an empty string, or the single end-of-row mark. # Anything else means it is not on an empty table cell, # or the range really does span more than the cell itself. # If this situation is detected, # copy and collapse the range, and fetch the content from that instead, # As a collapsed range on an empty cell does correctly return the table cell as its first enclosing element. if not self.isCollapsed: rawText = self._rangeObj.GetText(2) if not rawText or rawText == END_OF_ROW_MARK: r = self.copy() r.end = r.start fields = super(WordDocumentTextInfo, r).getTextWithFields(formatConfig=formatConfig) if fields is None: fields = super().getTextWithFields(formatConfig=formatConfig) if len(fields) == 0: # Nothing to do... was probably a collapsed range. return fields # MS Word tries to produce speakable math content within equations. # However, using mathPlayer with the exposed mathml property on the equation is much nicer. # But, we therefore need to remove the inner math content if reading by line if not formatConfig or not formatConfig.get('extraDetail'): # We really only want to remove content if we can guarantee that mathPlayer is available. mathPres.ensureInit() if mathPres.speechProvider or mathPres.brailleProvider: curLevel = 0 mathLevel = None mathStartIndex = None mathEndIndex = None for index in range(len(fields)): field = fields[index] if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": curLevel += 1 if mathLevel is None and field.field.get('mathml'): mathLevel = curLevel mathStartIndex = index elif isinstance(field, textInfos.FieldCommand ) and field.command == "controlEnd": if curLevel == mathLevel: mathEndIndex = index curLevel -= 1 if mathEndIndex is not None: del fields[mathStartIndex + 1:mathEndIndex] # Sometimes embedded objects and graphics In MS Word can cause a controlStart then a controlEnd with no actual formatChange / text in the middle. # SpeakTextInfo always expects that the first lot of controlStarts will always contain some text. # Therefore ensure that the first lot of controlStarts does contain some text by inserting a blank formatChange and empty string in this case. for index in range(len(fields)): field = fields[index] if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": continue elif isinstance( field, textInfos.FieldCommand) and field.command == "controlEnd": formatChange = textInfos.FieldCommand("formatChange", textInfos.FormatField()) fields.insert(index, formatChange) fields.insert(index + 1, "") break ##7971: Microsoft Word exposes list bullets as part of the actual text. # This then confuses NVDA's braille cursor routing as it expects that there is a one-to-one mapping between characters in the text string and unit character moves. # Therefore, detect when at the start of a list, and strip the bullet from the text string, placing it in the text's formatField as line-prefix. listItemStarted = False lastFormatField = None for index in range(len(fields)): field = fields[index] if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": if field.field.get( 'role' ) == controlTypes.Role.LISTITEM and field.field.get( '_startOfNode'): # We are in the start of a list item. listItemStarted = True elif isinstance(field, textInfos.FieldCommand ) and field.command == "formatChange": # This is the most recent formatField we have seen. lastFormatField = field.field elif listItemStarted and isinstance(field, str): # This is the first text string within the list. # Remove the text up to the first space, and store it as line-prefix which NVDA will appropriately speak/braille as a bullet. try: spaceIndex = field.index(' ') except ValueError: log.debugWarning("No space found in this text string") break prefix = field[0:spaceIndex] fields[index] = field[spaceIndex + 1:] lastFormatField['line-prefix'] = prefix # Let speech know that line-prefix is safe to be spoken always, as it will only be exposed on the very first formatField on the list item. lastFormatField['line-prefix_speakAlways'] = True break else: # Not a controlStart, formatChange or text string. Nothing to do. break # Fill in page number attributes where NVDA expects try: page = fields[0].field['page-number'] except KeyError: page = None if page is not None: for field in fields: if isinstance(field, textInfos.FieldCommand) and isinstance( field.field, textInfos.FormatField): field.field['page-number'] = page # MS Word can sometimes return a higher ancestor in its textRange's children. # E.g. a table inside a table header. # This does not cause a loop, but does cause information to be doubled # Detect these duplicates and remove them from the generated fields. seenStarts = set() pendingRemoves = [] index = 0 for index, field in enumerate(fields): if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": runtimeID = field.field['runtimeID'] if not runtimeID: continue if runtimeID in seenStarts: pendingRemoves.append(field.field) else: seenStarts.add(runtimeID) elif seenStarts: seenStarts.clear() index = 0 while index < len(fields): field = fields[index] if isinstance(field, textInfos.FieldCommand) and any( x is field.field for x in pendingRemoves): del fields[index] else: index += 1 return fields