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