win_wrap/uia/pattern/
text.rs

1/*
2 * Copyright (c) 2024. The RigelA open source project team and
3 * its contributors reserve all rights.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and limitations under the License.
12 */
13
14use crate::uia::{
15    element::UiAutomationElement,
16    pattern::{PatternCreator, PatternError},
17};
18use windows::{
19    core::BSTR,
20    Win32::{
21        Foundation::POINT,
22        UI::Accessibility::{
23            IUIAutomationTextPattern, IUIAutomationTextPattern2, IUIAutomationTextRange,
24            IUIAutomationTextRangeArray, TextPatternRangeEndpoint_End,
25            TextPatternRangeEndpoint_Start, TextUnit_Character, TextUnit_Document, TextUnit_Format,
26            TextUnit_Line, TextUnit_Page, TextUnit_Paragraph, TextUnit_Word, UIA_TextPattern2Id,
27            UIA_TextPatternId, UIA_PATTERN_ID,
28        },
29    },
30};
31use windows_core::Interface;
32
33/**
34提供对包含文本的控件的访问。
35*/
36pub struct UiAutomationTextPattern(IUIAutomationTextPattern);
37
38impl TryFrom<IUIAutomationTextPattern> for UiAutomationTextPattern {
39    type Error = PatternError;
40
41    fn try_from(value: IUIAutomationTextPattern) -> Result<Self, Self::Error> {
42        Ok(Self(value))
43    }
44}
45
46impl TryFrom<IUIAutomationTextPattern2> for UiAutomationTextPattern {
47    type Error = PatternError;
48
49    fn try_from(value: IUIAutomationTextPattern2) -> Result<Self, Self::Error> {
50        Ok(Self(value.cast().map_err(|e| -> PatternError {
51            format!("Can't convert type. ({})", e).into()
52        })?))
53    }
54}
55
56impl PatternCreator<IUIAutomationTextPattern> for UiAutomationTextPattern {
57    const PATTERN: UIA_PATTERN_ID = UIA_TextPatternId;
58}
59
60/// <https://learn.microsoft.com/en-us/windows/win32/api/uiautomationclient/nn-uiautomationclient-iuiautomationtextpattern>
61impl UiAutomationTextPattern {
62    /**
63    查询包含文档正文的文本范围。
64    此属性是只读的。
65    某些辅助文本(如页眉、脚注或批注)可能不包括在内。
66    */
67    pub fn document_range(&self) -> UiAutomationTextRange {
68        unsafe { UiAutomationTextRange::obtain(&self.0.DocumentRange().unwrap()) }
69    }
70
71    /**
72    查询文本范围的集合,该集合表示基于文本的控件中当前选定的文本。
73    如果控件支持选择多个不连续的文本范围,则 ranges 集合将为每个选定的范围接收一个文本范围。
74    如果控件仅包含所选文本的单个范围,则 ranges 集合将接收单个文本范围。
75    如果控件包含文本插入点,但未选择任何文本,则 ranges 集合将在文本插入点的位置接收一个退化(空)文本区域。
76    如果控件不包含文本插入点或不支持文本选择,则范围设置为 NULL。
77    使用 supported_text_selection 属性测试控件是否支持文本选择。
78    */
79    pub fn get_selection(&self) -> Vec<UiAutomationTextRange> {
80        if let Ok(array) = unsafe { self.0.GetSelection() } {
81            return array.to_vec();
82        }
83        vec![]
84    }
85
86    /**
87    查询一个值,该值指定控件支持的文本选择的类型。
88    此属性是只读的。
89    */
90    pub fn supported_text_selection(&self) -> SupportedTextSelection {
91        match unsafe { self.0.SupportedTextSelection() }.unwrap().0 {
92            1 => SupportedTextSelection::Single,
93            2 => SupportedTextSelection::Multiple,
94            _ => SupportedTextSelection::None,
95        }
96    }
97
98    /**
99    从基于文本的控件中检索不相交的文本范围的数组,其中每个文本区域表示可见文本的连续范围。
100    */
101    pub fn get_visible_ranges(&self) -> Vec<UiAutomationTextRange> {
102        unsafe {
103            if let Ok(array) = self.0.GetVisibleRanges() {
104                return array.to_vec();
105            }
106        }
107        vec![]
108    }
109
110    /**
111    查询包含子元素(如图像、超链接、Microsoft Excel 电子表格或其他嵌入对象)的文本区域。
112    如果区域中没有包含子元素的文本,则返回退化(空)区域。
113    `child` 要包含在文本范围中的子元素,可以是与 UiAutomationTextPattern 关联的元素的子参数,也可以是 UiAutomationTextRange 的子元素数组的子参数。
114    */
115    pub fn range_from_child(&self, child: &UiAutomationElement) -> Option<UiAutomationTextRange> {
116        if let Ok(c) = unsafe { self.0.RangeFromChild(child.get_raw()) } {
117            return Some(UiAutomationTextRange::obtain(&c));
118        }
119        None
120    }
121
122    /**
123    查询最接近指定屏幕坐标的退化(空)文本范围。
124    如果屏幕坐标位于图像、超链接、Microsoft Excel 电子表格或其他嵌入对象的坐标内,则返回换行子对象的文本区域。
125    由于不会忽略隐藏文本,因此此方法从最接近指定坐标的可见文本中检索退化范围。
126    Windows Internet Explorer 9 中 range_from_point 的实现不会返回预期的结果。相反,客户应该:
127    1. 调用get_visible_ranges方法以检索可见文本范围的数组。
128    2. 对于数组中的每个文本范围,调用get_bounding_rectangles以检索边界矩形。
129    3. 检查边界矩形以查找占据特定屏幕坐标的文本范围。
130    */
131    pub fn range_from_point(&self, x: i32, y: i32) -> Option<UiAutomationTextRange> {
132        if let Ok(x) = unsafe { self.0.RangeFromPoint(POINT { x, y }) } {
133            return Some(UiAutomationTextRange::obtain(&x));
134        }
135        None
136    }
137}
138
139/// 扩展 UiAutomationTextPattern。
140pub struct UiAutomationTextPattern2(IUIAutomationTextPattern2, UiAutomationTextPattern);
141
142impl TryFrom<IUIAutomationTextPattern2> for UiAutomationTextPattern2 {
143    type Error = PatternError;
144
145    fn try_from(value: IUIAutomationTextPattern2) -> Result<Self, Self::Error> {
146        let p = value.clone().try_into()?;
147        Ok(Self(value, p))
148    }
149}
150
151impl PatternCreator<IUIAutomationTextPattern2> for UiAutomationTextPattern2 {
152    const PATTERN: UIA_PATTERN_ID = UIA_TextPattern2Id;
153}
154
155/// <https://learn.microsoft.com/en-us/windows/win32/api/uiautomationclient/nn-uiautomationclient-iuiautomationtextpattern2>
156impl UiAutomationTextPattern2 {
157    /**
158    查询属于基于文本的控件的插入符号位置的零长度文本范围。
159    此方法检索一个文本区域,客户端可以使用该文本区域查找属于基于文本的控件的插入符号的边界矩形,或查找插入符号附近的文本。
160    返回(is_active,range)。
161    `is_active` 如果包含插入符号的基于文本的控件具有键盘焦点,则为 true,否则为 false。如果 is_active 为 false,则属于基于文本的控件的插入符号可能与系统插入符号位于同一位置。
162    `range` 接收一个文本范围,该范围表示属于基于文本的控件的插入符号的当前位置。
163    */
164    pub fn get_caret_range(&self) -> Option<(bool, UiAutomationTextRange)> {
165        unsafe {
166            let mut active = std::mem::zeroed();
167            if let Ok(range) = self.0.GetCaretRange(&mut active) {
168                return Some((active.as_bool(), UiAutomationTextRange::obtain(&range)));
169            }
170            None
171        }
172    }
173
174    /**
175    查询包含文本的文本范围,该文本是与指定批注元素关联的批注的目标。
176    `annotation` 要检索其目标文本的批注元素。此元素是实现文档的 UiAutomationTextPattern2 的元素的同级元素。
177    */
178    pub fn range_from_annotation(&self, annotation: &UiAutomationElement) -> UiAutomationTextRange {
179        let range = unsafe { self.0.RangeFromAnnotation(annotation.get_raw()).unwrap() };
180
181        UiAutomationTextRange::obtain(&range)
182    }
183}
184
185unsafe impl Send for UiAutomationTextPattern2 {}
186
187unsafe impl Sync for UiAutomationTextPattern2 {}
188
189impl std::ops::Deref for UiAutomationTextPattern2 {
190    type Target = UiAutomationTextPattern;
191
192    fn deref(&self) -> &Self::Target {
193        &self.1
194    }
195}
196
197trait TextRangeArray {
198    fn to_vec(&self) -> Vec<UiAutomationTextRange>;
199}
200
201impl TextRangeArray for IUIAutomationTextRangeArray {
202    fn to_vec(&self) -> Vec<UiAutomationTextRange> {
203        let mut v = vec![];
204        unsafe {
205            for i in 0..self.Length().unwrap() {
206                if let Ok(item) = self.GetElement(i) {
207                    v.push(UiAutomationTextRange::obtain(&item));
208                }
209            }
210        }
211        v
212    }
213}
214
215/**
216提供对支持 IUIAutomationTextPattern 接口的容器中连续文本范围的访问。
217客户端应用程序可以使用 IUIAutomationTextRange 接口从文本范围中选择、比较和查询嵌入对象。
218该接口使用两个端点来分隔文本范围的开始和结束位置。
219文本的不相交范围由 IUIAutomationTextRangeArray 接口表示。
220*/
221#[derive(Clone, Debug)]
222pub struct UiAutomationTextRange(IUIAutomationTextRange);
223
224/// <https://learn.microsoft.com/en-us/windows/win32/api/uiautomationclient/nn-uiautomationclient-iuiautomationtextrange>
225impl UiAutomationTextRange {
226    /**
227    获取一个实例。
228    */
229    pub(crate) fn obtain(range: &IUIAutomationTextRange) -> Self {
230        Self(range.clone())
231    }
232
233    /**
234    查询一个值,该值指定此文本区域是否与另一个文本区域具有相同的端点。
235    `range` 指向要与此范围进行比较的文本范围。
236    */
237    pub fn compare(&self, range: &UiAutomationTextRange) -> bool {
238        unsafe { self.0.Compare(&range.0) }
239            .unwrap_or(false.into())
240            .as_bool()
241    }
242
243    /**
244    将文本区域添加到支持所选文本的多个不相交范围的控件中的选定文本范围的集合中。
245    */
246    pub fn add_to_selection(&self) {
247        unsafe { self.0.AddToSelection() }.unwrap_or(())
248    }
249
250    /**
251    查询一个值,该值指定此文本区域的起始或结束端点是否与另一个文本区域的起始或结束端点相同。
252    `src_start_endpoint` 源范围的端点类型,如果是true表示使用起始点,false表示使用结束点。
253    `range` 要比较的文本区域。
254    `target_start_endpoint` 目标范围的端点类型,如果是true表示使用起始点,false表示使用结束点。
255    */
256    pub fn compare_endpoints(
257        &self,
258        src_start_endpoint: bool,
259        range: &UiAutomationTextRange,
260        target_start_endpoint: bool,
261    ) -> i32 {
262        let src_endpoint = if src_start_endpoint {
263            TextPatternRangeEndpoint_Start
264        } else {
265            TextPatternRangeEndpoint_End
266        };
267        let target_endpoint = if target_start_endpoint {
268            TextPatternRangeEndpoint_Start
269        } else {
270            TextPatternRangeEndpoint_End
271        };
272        unsafe {
273            self.0
274                .CompareEndpoints(src_endpoint, &range.0, target_endpoint)
275        }
276        .unwrap_or(0)
277    }
278
279    /**
280    按指定的文本单位规范化文本范围。
281    客户端应用程序(如屏幕阅读器)使用此方法检索插入点或插入符号位置处存在的完整单词、句子或段落。
282    尽管名称如此,但 expand_to_enclosing_unit 方法不一定扩展文本范围。
283    相反,它通过移动终结点来“规范化”文本范围,使该范围包含指定的文本单元。
284    如果范围小于指定单位,则扩大范围,如果范围长于指定单位,则缩短范围。
285    如果范围已经是指定单位的确切数量,则保持不变。
286    如果控件不支持指定的文本单位,则 expand_to_enclosing_unit 默认为支持的下一个最大文本单元。
287    从最小单位到最大单位的顺序如下: 字符、格式、词、行、段、页、文档。
288    expand_to_enclosing_unit 同时支持可见文本和隐藏文本。
289    Format,作为单位值,定位文本范围的边界,以根据范围内文本的共享文本属性(或格式)来扩展或移动范围。但是,文本单元不会在嵌入对象(如图像或超链接)的边界上移动或扩展文本范围。
290    有关详细信息,请参阅 UI 自动化文本单元或文本内容的 UI 自动化支持。
291    `text_unit` 文本单位,例如行或段落。
292    */
293    pub fn expand_to_enclosing_unit(&self, text_unit: TextUnit) {
294        let unit = match text_unit {
295            TextUnit::Character => TextUnit_Character,
296            TextUnit::Format => TextUnit_Format,
297            TextUnit::Word => TextUnit_Word,
298            TextUnit::Line => TextUnit_Line,
299            TextUnit::Paragraph => TextUnit_Paragraph,
300            TextUnit::Page => TextUnit_Page,
301            TextUnit::Document => TextUnit_Document,
302        };
303        unsafe { self.0.ExpandToEnclosingUnit(unit) }.unwrap_or(())
304    }
305
306    /**
307    返回文本范围的纯文本。
308    `max_length` 要返回的字符串的最大长度,如果不需要限制,则为 -1。
309    */
310    pub fn get_text(&self, max_length: i32) -> String {
311        unsafe { self.0.GetText(max_length) }
312            .unwrap_or(BSTR::new())
313            .to_string()
314    }
315}
316
317unsafe impl Sync for UiAutomationTextRange {}
318
319unsafe impl Send for UiAutomationTextRange {}
320
321/*
322包含指定用于导航的文本单位的值。
323*/
324pub enum TextUnit {
325    /// 字符
326    Character,
327    /// 格式
328    Format,
329    /// 单词
330    Word,
331    /// 行
332    Line,
333    /// 段落
334    Paragraph,
335    /// 页面
336    Page,
337    /// 文档
338    Document,
339}
340
341pub enum SupportedTextSelection {
342    /// 不支持文本选择。
343    None,
344    /// 支持单个连续文本选择。
345    Single,
346    /// 支持多个不相交的文本选择。
347    Multiple,
348}