adobeAcrobat.py
8.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#braille.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2008-2014 NV Access Limited
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
import api
import controlTypes
import eventHandler
import winUser
from . import IAccessible, getNVDAObjectFromEvent
from NVDAObjects import NVDAObjectTextInfo
from NVDAObjects.behaviors import EditableText
from comtypes import GUID, COMError, IServiceProvider
from comtypes.gen.AcrobatAccessLib import IAccID, IGetPDDomNode, IPDDomElement
from logHandler import log
SID_AccID = GUID("{449D454B-1F46-497e-B2B6-3357AED9912B}")
SID_GetPDDomNode = GUID("{C0A1D5E9-1142-4cf3-B607-82FC3B96A4DF}")
stdNamesToRoles = {
# Part? Art?
"Sect": controlTypes.ROLE_SECTION,
"Div": controlTypes.ROLE_SECTION,
"BlockQuote": controlTypes.ROLE_BLOCKQUOTE,
"Caption": controlTypes.ROLE_CAPTION,
# Toc? Toci? Index? Nonstruct? Private?
# Table, TR, TH, TD covered by IAccessible
"L": controlTypes.ROLE_LIST,
"LI": controlTypes.ROLE_LISTITEM,
"Lbl": controlTypes.ROLE_LABEL,
# LBody
"P": controlTypes.ROLE_PARAGRAPH,
"H": controlTypes.ROLE_HEADING,
# H1 to H6 handled separately
# Span, Quote, Note, Reference, BibEntry, Code, Figure
"Formula": controlTypes.ROLE_MATH,
"Form": controlTypes.ROLE_FORM,
}
def normalizeStdName(stdName):
if "H1" <= stdName <= "H6":
return controlTypes.ROLE_HEADING, stdName[1]
try:
return stdNamesToRoles[stdName], None
except KeyError:
pass
raise LookupError
class AcrobatNode(IAccessible):
def initOverlayClass(self):
try:
serv = self.IAccessibleObject.QueryInterface(IServiceProvider)
except COMError:
log.debugWarning("Could not get IServiceProvider")
return
if self.event_objectID > 0:
self.accID = self.event_objectID
elif self.event_childID > 0:
self.accID = self.event_childID
else:
try:
self.accID = serv.QueryService(SID_AccID, IAccID).get_accID()
except COMError:
self.accID = None
# Get the IPDDomNode.
try:
self.pdDomNode = serv.QueryService(SID_GetPDDomNode, IGetPDDomNode).get_PDDomNode(self.IAccessibleChildID)
except COMError:
self.pdDomNode = None
if self.pdDomNode:
# If this node has IPDDomElement, query to that.
try:
self.pdDomNode = self.pdDomNode.QueryInterface(IPDDomElement)
except COMError:
pass
def _get_role(self):
try:
return normalizeStdName(self.pdDomNode.GetStdName())[0]
except (AttributeError, LookupError, COMError):
pass
role = super(AcrobatNode, self).role
if role == controlTypes.ROLE_PANE:
# Pane doesn't make sense for nodes in a document.
role = controlTypes.ROLE_TEXTFRAME
return role
def scrollIntoView(self):
try:
self.pdDomNode.ScrollTo()
except (AttributeError, COMError):
log.debugWarning("IPDDomNode::ScrollTo failed", exc_info=True)
def _isEqual(self, other):
if self.windowHandle == other.windowHandle and self.accID and other.accID:
return self.accID == other.accID
return super(AcrobatNode, self)._isEqual(other)
def _getNodeMathMl(self, node):
tag = node.GetTagName()
yield "<%s" % tag
# Output relevant attributes.
if tag == "mfenced":
for attr in "open", "close", "separators":
val = node.GetAttribute(attr, "XML-1.00")
if val:
yield ' %s="%s"' % (attr, val)
yield ">"
val = node.GetValue()
if val:
yield val
else:
for childNum in xrange(node.GetChildCount()):
try:
subNode = node.GetChild(childNum).QueryInterface(IPDDomElement)
except COMError:
continue
for sub in self._getNodeMathMl(subNode):
yield sub
yield "</%s>" % tag
def _get_mathMl(self):
# There could be other stuff before the math element. Ug.
for childNum in xrange(self.pdDomNode.GetChildCount()):
try:
child = self.pdDomNode.GetChild(childNum).QueryInterface(IPDDomElement)
except COMError:
continue
if child.GetTagName() == "math":
return "".join(self._getNodeMathMl(child))
raise LookupError
class RootNode(AcrobatNode):
shouldAllowIAccessibleFocusEvent = True
def event_valueChange(self):
# Acrobat has indicated that a page has died and been replaced by a new one.
if not self.isInForeground:
# If this isn't in the foreground, it doesn't matter,
# as focus will be fired on the correct object when it is in the foreground again.
return
# The new page has the same event params, so we must bypass NVDA's IAccessible caching.
obj = getNVDAObjectFromEvent(self.windowHandle, winUser.OBJID_CLIENT, 0)
if not obj:
return
eventHandler.queueEvent("gainFocus",obj)
class Document(RootNode):
def _get_treeInterceptorClass(self):
import virtualBuffers.adobeAcrobat
return virtualBuffers.adobeAcrobat.AdobeAcrobat
def _get_shouldAllowIAccessibleFocusEvent(self):
# HACK: #1659: When moving the focus, Acrobat sometimes fires focus on the document before firing it on the real focus;
# e.g. when tabbing through a multi-page form.
# This causes extraneous verbosity.
# Therefore, if already focused inside this document, only allow focus on the document if it has no active descendant.
if api.getFocusObject().windowHandle == self.windowHandle:
try:
return self.IAccessibleObject.accFocus in (None, 0)
except COMError:
pass
return super(Document, self).shouldAllowIAccessibleFocusEvent
class RootTextNode(RootNode):
"""The message text node that appears instead of the document when the document is not available.
"""
def _get_parent(self):
#hack: This code should be taken out once the crash is fixed in Adobe Reader X.
#If going parent on a root text node after saying ok to the accessibility options (untagged) and before the processing document dialog appears, Reader X will crash.
return api.getDesktopObject()
class AcrobatTextInfo(NVDAObjectTextInfo):
def _getStoryText(self):
return self.obj.value or ""
def _getCaretOffset(self):
caret = getNVDAObjectFromEvent(self.obj.windowHandle, winUser.OBJID_CARET, 0)
if not caret:
raise RuntimeError("No caret")
try:
return int(caret.description)
except (ValueError, TypeError):
raise RuntimeError("Bad caret index")
class EditableTextNode(EditableText, AcrobatNode):
TextInfo = AcrobatTextInfo
def event_valueChange(self):
pass
class AcrobatSDIWindowClient(IAccessible):
def initOverlayClass(self):
if self.name or not self.parent:
return
# HACK: There are three client objects, one with a name and two without.
# The unnamed objects (probably manufactured by Acrobat) have broken next and previous relationships.
# The unnamed objects' parent/grandparent is the named object, but when descending into the named object, the unnamed objects are skipped.
# Given the brokenness of the unnamed objects, just skip them completely and use the parent/grandparent when they are encountered.
try:
acc = self.IAccessibleObject.accParent
if not acc.accName(0):
acc = acc.accParent
except COMError:
return
self.IAccessibleObject = acc
self.invalidateCache()
class BadFocusStates(AcrobatNode):
"""An object which reports focus states when it shouldn't.
"""
def _get_states(self):
states = super(BadFocusStates, self).states
states.difference_update({controlTypes.STATE_FOCUSABLE, controlTypes.STATE_FOCUSED})
return states
def findExtraOverlayClasses(obj, clsList):
"""Determine the most appropriate class(es) for Acrobat objects.
This works similarly to L{NVDAObjects.NVDAObject.findOverlayClasses} except that it never calls any other findOverlayClasses method.
"""
role = obj.role
states = obj.states
if role == controlTypes.ROLE_DOCUMENT or (role == controlTypes.ROLE_PAGE and controlTypes.STATE_READONLY in states):
clsList.append(Document)
elif obj.event_childID == 0 and obj.event_objectID == winUser.OBJID_CLIENT:
# Other root node.
if role == controlTypes.ROLE_EDITABLETEXT:
clsList.append(RootTextNode)
else:
clsList.append(RootNode)
elif role == controlTypes.ROLE_EDITABLETEXT:
if {controlTypes.STATE_READONLY, controlTypes.STATE_FOCUSABLE, controlTypes.STATE_LINKED} <= states:
# HACK: Acrobat sets focus states on text nodes beneath links,
# making them appear as read only editable text fields.
clsList.append(BadFocusStates)
elif controlTypes.STATE_FOCUSABLE in states:
clsList.append(EditableTextNode)
clsList.append(AcrobatNode)