core_text/
run.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10use core_foundation::base::{CFIndex, CFRange, CFType, CFTypeID, TCFType};
11use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
12use core_foundation::string::CFString;
13use core_foundation::{declare_TCFType, impl_CFTypeDescription, impl_TCFType};
14use core_graphics::base::CGFloat;
15use core_graphics::font::CGGlyph;
16use core_graphics::geometry::CGPoint;
17use std::borrow::Cow;
18use std::os::raw::c_void;
19use std::slice;
20
21use crate::line::TypographicBounds;
22
23#[repr(C)]
24pub struct __CTRun(c_void);
25
26pub type CTRunRef = *const __CTRun;
27
28declare_TCFType! {
29    CTRun, CTRunRef
30}
31impl_TCFType!(CTRun, CTRunRef, CTRunGetTypeID);
32impl_CFTypeDescription!(CTRun);
33
34impl CTRun {
35    pub fn attributes(&self) -> Option<CFDictionary<CFString, CFType>> {
36        unsafe {
37            let attrs = CTRunGetAttributes(self.0);
38            if attrs.is_null() {
39                return None;
40            }
41            Some(TCFType::wrap_under_get_rule(attrs))
42        }
43    }
44    pub fn glyph_count(&self) -> CFIndex {
45        unsafe { CTRunGetGlyphCount(self.0) }
46    }
47
48    pub fn glyphs(&self) -> Cow<[CGGlyph]> {
49        unsafe {
50            // CTRunGetGlyphsPtr can return null under some not understood circumstances.
51            // If it does the Apple documentation tells us to allocate our own buffer and call
52            // CTRunGetGlyphs
53            let count = CTRunGetGlyphCount(self.0);
54            let glyphs_ptr = CTRunGetGlyphsPtr(self.0);
55            if !glyphs_ptr.is_null() {
56                Cow::from(slice::from_raw_parts(glyphs_ptr, count as usize))
57            } else {
58                let mut vec = Vec::with_capacity(count as usize);
59                // "If the length of the range is set to 0, then the copy operation will continue
60                // from the start index of the range to the end of the run"
61                CTRunGetGlyphs(self.0, CFRange::init(0, 0), vec.as_mut_ptr());
62                vec.set_len(count as usize);
63                Cow::from(vec)
64            }
65        }
66    }
67
68    pub fn positions(&self) -> Cow<[CGPoint]> {
69        unsafe {
70            // CTRunGetPositionsPtr can return null under some not understood circumstances.
71            // If it does the Apple documentation tells us to allocate our own buffer and call
72            // CTRunGetPositions
73            let count = CTRunGetGlyphCount(self.0);
74            let positions_ptr = CTRunGetPositionsPtr(self.0);
75            if !positions_ptr.is_null() {
76                Cow::from(slice::from_raw_parts(positions_ptr, count as usize))
77            } else {
78                let mut vec = Vec::with_capacity(count as usize);
79                // "If the length of the range is set to 0, then the copy operation will continue
80                // from the start index of the range to the end of the run"
81                CTRunGetPositions(self.0, CFRange::init(0, 0), vec.as_mut_ptr());
82                vec.set_len(count as usize);
83                Cow::from(vec)
84            }
85        }
86    }
87
88    pub fn get_typographic_bounds(&self) -> TypographicBounds {
89        let mut ascent = 0.0;
90        let mut descent = 0.0;
91        let mut leading = 0.0;
92        unsafe {
93            // The portion of the run to calculate the typographic bounds for. By setting this to 0,
94            // CoreText will measure the bounds from start to end, see https://developer.apple.com/documentation/coretext/1510569-ctrungettypographicbounds?language=objc.
95            let range = CFRange {
96                location: 0,
97                length: 0,
98            };
99
100            let width = CTRunGetTypographicBounds(
101                self.as_concrete_TypeRef(),
102                range,
103                &mut ascent,
104                &mut descent,
105                &mut leading,
106            );
107            TypographicBounds {
108                width,
109                ascent,
110                descent,
111                leading,
112            }
113        }
114    }
115
116    pub fn string_indices(&self) -> Cow<[CFIndex]> {
117        unsafe {
118            // CTRunGetStringIndicesPtr can return null under some not understood circumstances.
119            // If it does the Apple documentation tells us to allocate our own buffer and call
120            // CTRunGetStringIndices
121            let count = CTRunGetGlyphCount(self.0);
122            let indices_ptr = CTRunGetStringIndicesPtr(self.0);
123            if !indices_ptr.is_null() {
124                Cow::from(slice::from_raw_parts(indices_ptr, count as usize))
125            } else {
126                let mut vec = Vec::with_capacity(count as usize);
127                // "If the length of the range is set to 0, then the copy operation will continue
128                // from the start index of the range to the end of the run"
129                CTRunGetStringIndices(self.0, CFRange::init(0, 0), vec.as_mut_ptr());
130                vec.set_len(count as usize);
131                Cow::from(vec)
132            }
133        }
134    }
135}
136
137#[test]
138fn create_runs() {
139    use crate::font;
140    use crate::line::*;
141    use crate::string_attributes::*;
142    use core_foundation::attributed_string::CFMutableAttributedString;
143    let mut string = CFMutableAttributedString::new();
144    string.replace_str(&CFString::new("Food"), CFRange::init(0, 0));
145    let len = string.char_len();
146    unsafe {
147        string.set_attribute(
148            CFRange::init(0, len),
149            kCTFontAttributeName,
150            &font::new_from_name("Helvetica", 16.).unwrap(),
151        );
152    }
153    let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
154    let runs = line.glyph_runs();
155    assert_eq!(runs.len(), 1);
156    for run in runs.iter() {
157        assert_eq!(run.glyph_count(), 4);
158        let font = run
159            .attributes()
160            .unwrap()
161            .get(CFString::new("NSFont"))
162            .downcast::<font::CTFont>()
163            .unwrap();
164        assert_eq!(font.pt_size(), 16.);
165
166        let positions = run.positions();
167        assert_eq!(positions.len(), 4);
168        assert!(positions[0].x < positions[1].x);
169
170        let glyphs = run.glyphs();
171        assert_eq!(glyphs.len(), 4);
172        assert_ne!(glyphs[0], glyphs[1]);
173        assert_eq!(glyphs[1], glyphs[2]);
174
175        let indices = run.string_indices();
176        assert_eq!(indices.as_ref(), &[0, 1, 2, 3]);
177    }
178}
179
180#[cfg_attr(feature = "link", link(name = "CoreText", kind = "framework"))]
181extern "C" {
182    fn CTRunGetTypeID() -> CFTypeID;
183    fn CTRunGetAttributes(run: CTRunRef) -> CFDictionaryRef;
184    fn CTRunGetGlyphCount(run: CTRunRef) -> CFIndex;
185    fn CTRunGetPositionsPtr(run: CTRunRef) -> *const CGPoint;
186    fn CTRunGetPositions(run: CTRunRef, range: CFRange, buffer: *const CGPoint);
187    fn CTRunGetStringIndicesPtr(run: CTRunRef) -> *const CFIndex;
188    fn CTRunGetStringIndices(run: CTRunRef, range: CFRange, buffer: *const CFIndex);
189    fn CTRunGetGlyphsPtr(run: CTRunRef) -> *const CGGlyph;
190    fn CTRunGetGlyphs(run: CTRunRef, range: CFRange, buffer: *const CGGlyph);
191    fn CTRunGetTypographicBounds(
192        line: CTRunRef,
193        range: CFRange,
194        ascent: *mut CGFloat,
195        descent: *mut CGFloat,
196        leading: *mut CGFloat,
197    ) -> CGFloat;
198}