font_kit/loaders/
core_text.rs

1// font-kit/src/loaders/core_text.rs
2//
3// Copyright © 2018 The Pathfinder Project Developers.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! A loader that uses Apple's Core Text API to load and rasterize fonts.
12
13use byteorder::{BigEndian, ReadBytesExt};
14use core_graphics::base::{kCGImageAlphaPremultipliedLast, CGFloat};
15use core_graphics::color_space::CGColorSpace;
16use core_graphics::context::{CGContext, CGTextDrawingMode};
17use core_graphics::font::{CGFont, CGGlyph};
18use core_graphics::geometry::{CGAffineTransform, CGPoint, CGRect, CGSize};
19use core_graphics::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CG_ZERO_POINT, CG_ZERO_SIZE};
20use core_graphics::path::CGPathElementType;
21use core_text;
22use core_text::font::CTFont;
23use core_text::font_descriptor::kCTFontDefaultOrientation;
24use core_text::font_descriptor::{SymbolicTraitAccessors, TraitAccessors};
25use log::warn;
26use pathfinder_geometry::line_segment::LineSegment2F;
27use pathfinder_geometry::rect::{RectF, RectI};
28use pathfinder_geometry::transform2d::Transform2F;
29use pathfinder_geometry::vector::Vector2F;
30use pathfinder_simd::default::F32x4;
31use std::cmp::Ordering;
32use std::f32;
33use std::fmt::{self, Debug, Formatter};
34use std::fs::File;
35use std::io::{Seek, SeekFrom};
36use std::ops::Deref;
37use std::path::Path;
38use std::sync::Arc;
39
40use crate::canvas::{Canvas, Format, RasterizationOptions};
41use crate::error::{FontLoadingError, GlyphLoadingError};
42use crate::file_type::FileType;
43use crate::handle::Handle;
44use crate::hinting::HintingOptions;
45use crate::loader::{FallbackResult, Loader};
46use crate::metrics::Metrics;
47use crate::outline::OutlineSink;
48use crate::properties::{Properties, Stretch, Style, Weight};
49use crate::utils;
50
51const TTC_TAG: [u8; 4] = [b't', b't', b'c', b'f'];
52const OTTO_TAG: [u8; 4] = [b'O', b'T', b'T', b'O'];
53const OTTO_HEX: u32 = 0x4f54544f; // 'OTTO'
54const TRUE_HEX: u32 = 0x74727565; // 'true'
55const TYP1_HEX: u32 = 0x74797031; // 'typ1'
56const SFNT_HEX: u32 = 0x73666e74; // 'sfnt'
57
58#[allow(non_upper_case_globals)]
59const kCGImageAlphaOnly: u32 = 7;
60
61pub(crate) static FONT_WEIGHT_MAPPING: [f32; 9] = [-0.7, -0.5, -0.23, 0.0, 0.2, 0.3, 0.4, 0.6, 0.8];
62
63/// Core Text's representation of a font.
64pub type NativeFont = CTFont;
65
66/// A loader that uses Apple's Core Text API to load and rasterize fonts.
67#[derive(Clone)]
68pub struct Font {
69    core_text_font: CTFont,
70    font_data: FontData,
71}
72
73impl Font {
74    /// Loads a font from raw font data (the contents of a `.ttf`/`.otf`/etc. file).
75    ///
76    /// If the data represents a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index
77    /// of the font to load from it. If the data represents a single font, pass 0 for `font_index`.
78    pub fn from_bytes(
79        mut font_data: Arc<Vec<u8>>,
80        font_index: u32,
81    ) -> Result<Font, FontLoadingError> {
82        // Sadly, there's no API to load OpenType collections on macOS, I don't believe…
83        // If not otf/ttf or otc/ttc, we unpack it as data fork font.
84        if !font_is_single_otf(&*font_data) && !font_is_collection(&*font_data) {
85            let mut new_font_data = (*font_data).clone();
86            unpack_data_fork_font(&mut new_font_data)?;
87            font_data = Arc::new(new_font_data);
88        } else if font_is_collection(&*font_data) {
89            let mut new_font_data = (*font_data).clone();
90            unpack_otc_font(&mut new_font_data, font_index)?;
91            font_data = Arc::new(new_font_data);
92        }
93
94        let core_text_font = match core_text::font::new_from_buffer(&*font_data) {
95            Ok(ct_font) => ct_font,
96            Err(_) => return Err(FontLoadingError::Parse),
97        };
98
99        Ok(Font {
100            core_text_font,
101            font_data: FontData::Memory(font_data),
102        })
103    }
104
105    /// Loads a font from a `.ttf`/`.otf`/etc. file.
106    ///
107    /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the
108    /// font to load from it. If the file represents a single font, pass 0 for `font_index`.
109    pub fn from_file(file: &mut File, font_index: u32) -> Result<Font, FontLoadingError> {
110        file.seek(SeekFrom::Start(0))?;
111        let font_data = Arc::new(utils::slurp_file(file).map_err(FontLoadingError::Io)?);
112        Font::from_bytes(font_data, font_index)
113    }
114
115    /// Loads a font from the path to a `.ttf`/`.otf`/etc. file.
116    ///
117    /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the
118    /// font to load from it. If the file represents a single font, pass 0 for `font_index`.
119    #[inline]
120    pub fn from_path<P: AsRef<Path>>(path: P, font_index: u32) -> Result<Font, FontLoadingError> {
121        <Font as Loader>::from_path(path, font_index)
122    }
123
124    /// Creates a font from a native API handle.
125    pub unsafe fn from_native_font(core_text_font: NativeFont) -> Font {
126        Font::from_core_text_font(core_text_font)
127    }
128
129    unsafe fn from_core_text_font(core_text_font: NativeFont) -> Font {
130        let mut font_data = FontData::Unavailable;
131        match core_text_font.url() {
132            None => warn!("No URL found for Core Text font!"),
133            Some(url) => match url.to_path() {
134                Some(path) => match File::open(path) {
135                    Ok(ref mut file) => match utils::slurp_file(file) {
136                        Ok(data) => font_data = FontData::Memory(Arc::new(data)),
137                        Err(_) => warn!("Couldn't read file data for Core Text font!"),
138                    },
139                    Err(_) => warn!("Could not open file for Core Text font!"),
140                },
141                None => warn!("Could not convert URL from Core Text font to path!"),
142            },
143        }
144
145        Font {
146            core_text_font,
147            font_data,
148        }
149    }
150
151    /// Creates a font from a Core Graphics font handle.
152    ///
153    /// This function is only available on the Core Text backend.
154    pub fn from_core_graphics_font(core_graphics_font: CGFont) -> Font {
155        unsafe {
156            Font::from_core_text_font(core_text::font::new_from_CGFont(&core_graphics_font, 16.0))
157        }
158    }
159
160    /// Loads the font pointed to by a handle.
161    #[inline]
162    pub fn from_handle(handle: &Handle) -> Result<Self, FontLoadingError> {
163        <Self as Loader>::from_handle(handle)
164    }
165
166    /// Determines whether a file represents a supported font, and if so, what type of font it is.
167    pub fn analyze_bytes(font_data: Arc<Vec<u8>>) -> Result<FileType, FontLoadingError> {
168        if let Ok(font_count) = read_number_of_fonts_from_otc_header(&font_data) {
169            return Ok(FileType::Collection(font_count));
170        }
171        match core_text::font::new_from_buffer(&*font_data) {
172            Ok(_) => Ok(FileType::Single),
173            Err(_) => Err(FontLoadingError::Parse),
174        }
175    }
176
177    /// Determines whether a file represents a supported font, and if so, what type of font it is.
178    pub fn analyze_file(file: &mut File) -> Result<FileType, FontLoadingError> {
179        file.seek(SeekFrom::Start(0))?;
180
181        let font_data = Arc::new(utils::slurp_file(file).map_err(FontLoadingError::Io)?);
182        if let Ok(font_count) = read_number_of_fonts_from_otc_header(&font_data) {
183            return Ok(FileType::Collection(font_count));
184        }
185
186        match core_text::font::new_from_buffer(&*font_data) {
187            Ok(_) => Ok(FileType::Single),
188            Err(_) => Err(FontLoadingError::Parse),
189        }
190    }
191
192    /// Determines whether a path points to a supported font, and if so, what type of font it is.
193    #[inline]
194    pub fn analyze_path<P: AsRef<Path>>(path: P) -> Result<FileType, FontLoadingError> {
195        <Self as Loader>::analyze_path(path)
196    }
197
198    /// Returns the wrapped native font handle.
199    #[inline]
200    pub fn native_font(&self) -> NativeFont {
201        self.core_text_font.clone()
202    }
203
204    /// Returns the PostScript name of the font. This should be globally unique.
205    #[inline]
206    pub fn postscript_name(&self) -> Option<String> {
207        Some(self.core_text_font.postscript_name())
208    }
209
210    /// Returns the full name of the font (also known as "display name" on macOS).
211    #[inline]
212    pub fn full_name(&self) -> String {
213        self.core_text_font.display_name()
214    }
215
216    /// Returns the name of the font family.
217    #[inline]
218    pub fn family_name(&self) -> String {
219        self.core_text_font.family_name()
220    }
221
222    /// Returns the name of the font style, according to Core Text.
223    ///
224    /// NB: This function is only available on the Core Text backend.
225    #[inline]
226    pub fn style_name(&self) -> String {
227        self.core_text_font.style_name()
228    }
229
230    /// Returns true if and only if the font is monospace (fixed-width).
231    #[inline]
232    pub fn is_monospace(&self) -> bool {
233        self.core_text_font.symbolic_traits().is_monospace()
234    }
235
236    /// Returns the values of various font properties, corresponding to those defined in CSS.
237    pub fn properties(&self) -> Properties {
238        let symbolic_traits = self.core_text_font.symbolic_traits();
239        let all_traits = self.core_text_font.all_traits();
240
241        let style = if symbolic_traits.is_italic() {
242            Style::Italic
243        } else if all_traits.normalized_slant() > 0.0 {
244            Style::Oblique
245        } else {
246            Style::Normal
247        };
248
249        let weight = core_text_to_css_font_weight(all_traits.normalized_weight() as f32);
250        let stretch = core_text_width_to_css_stretchiness(all_traits.normalized_width() as f32);
251
252        Properties {
253            style,
254            weight,
255            stretch,
256        }
257    }
258
259    /// Returns the number of glyphs in the font.
260    ///
261    /// Glyph IDs range from 0 inclusive to this value exclusive.
262    pub fn glyph_count(&self) -> u32 {
263        self.core_text_font.glyph_count() as u32
264    }
265
266    /// Returns the usual glyph ID for a Unicode character.
267    ///
268    /// Be careful with this function; typographically correct character-to-glyph mapping must be
269    /// done using a *shaper* such as HarfBuzz. This function is only useful for best-effort simple
270    /// use cases like "what does character X look like on its own".
271    pub fn glyph_for_char(&self, character: char) -> Option<u32> {
272        unsafe {
273            let (mut dest, mut src) = ([0, 0], [0, 0]);
274            let src = character.encode_utf16(&mut src);
275            self.core_text_font
276                .get_glyphs_for_characters(src.as_ptr(), dest.as_mut_ptr(), 2);
277
278            let id = dest[0] as u32;
279            if id != 0 {
280                Some(id)
281            } else {
282                None
283            }
284        }
285    }
286
287    /// Returns the glyph ID for the specified glyph name.
288    #[inline]
289    pub fn glyph_by_name(&self, name: &str) -> Option<u32> {
290        let code = self.core_text_font.get_glyph_with_name(name);
291
292        Some(u32::from(code))
293    }
294
295    /// Sends the vector path for a glyph to a path builder.
296    ///
297    /// If `hinting_mode` is not None, this function performs grid-fitting as requested before
298    /// sending the hinding outlines to the builder.
299    ///
300    /// TODO(pcwalton): What should we do for bitmap glyphs?
301    pub fn outline<S>(
302        &self,
303        glyph_id: u32,
304        _: HintingOptions,
305        sink: &mut S,
306    ) -> Result<(), GlyphLoadingError>
307    where
308        S: OutlineSink,
309    {
310        let path = match self
311            .core_text_font
312            .create_path_for_glyph(glyph_id as u16, &CG_AFFINE_TRANSFORM_IDENTITY)
313        {
314            Ok(path) => path,
315            Err(_) => {
316                // This will happen if the path is empty (rdar://42832439). To distinguish this
317                // case from the case in which the glyph does not exist, call another API.
318                drop(self.typographic_bounds(glyph_id)?);
319                return Ok(());
320            }
321        };
322
323        let units_per_point = self.units_per_point() as f32;
324        path.apply(&|element| {
325            let points = element.points();
326            match element.element_type {
327                CGPathElementType::MoveToPoint => {
328                    sink.move_to(points[0].to_vector() * units_per_point)
329                }
330                CGPathElementType::AddLineToPoint => {
331                    sink.line_to(points[0].to_vector() * units_per_point)
332                }
333                CGPathElementType::AddQuadCurveToPoint => sink.quadratic_curve_to(
334                    points[0].to_vector() * units_per_point,
335                    points[1].to_vector() * units_per_point,
336                ),
337                CGPathElementType::AddCurveToPoint => {
338                    let ctrl = LineSegment2F::new(points[0].to_vector(), points[1].to_vector())
339                        * units_per_point;
340                    sink.cubic_curve_to(ctrl, points[2].to_vector() * units_per_point)
341                }
342                CGPathElementType::CloseSubpath => sink.close(),
343            }
344        });
345        Ok(())
346    }
347
348    /// Returns the boundaries of a glyph in font units.
349    pub fn typographic_bounds(&self, glyph_id: u32) -> Result<RectF, GlyphLoadingError> {
350        let rect = self
351            .core_text_font
352            .get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph_id as u16]);
353        let rect = RectF::new(
354            Vector2F::new(rect.origin.x as f32, rect.origin.y as f32),
355            Vector2F::new(rect.size.width as f32, rect.size.height as f32),
356        );
357        Ok(rect * self.units_per_point() as f32)
358    }
359
360    /// Returns the distance from the origin of the glyph with the given ID to the next, in font
361    /// units.
362    pub fn advance(&self, glyph_id: u32) -> Result<Vector2F, GlyphLoadingError> {
363        // FIXME(pcwalton): Apple's docs don't say what happens when the glyph is out of range!
364        unsafe {
365            let (glyph_id, mut advance) = (glyph_id as u16, CG_ZERO_SIZE);
366            self.core_text_font.get_advances_for_glyphs(
367                kCTFontDefaultOrientation,
368                &glyph_id,
369                &mut advance,
370                1,
371            );
372            let advance = Vector2F::new(advance.width as f32, advance.height as f32);
373            Ok(advance * self.units_per_point() as f32)
374        }
375    }
376
377    /// Returns the amount that the given glyph should be displaced from the origin.
378    pub fn origin(&self, glyph_id: u32) -> Result<Vector2F, GlyphLoadingError> {
379        unsafe {
380            // FIXME(pcwalton): Apple's docs don't say what happens when the glyph is out of range!
381            let (glyph_id, mut translation) = (glyph_id as u16, CG_ZERO_SIZE);
382            self.core_text_font.get_vertical_translations_for_glyphs(
383                kCTFontDefaultOrientation,
384                &glyph_id,
385                &mut translation,
386                1,
387            );
388            let translation = Vector2F::new(translation.width as f32, translation.height as f32);
389            Ok(translation * self.units_per_point() as f32)
390        }
391    }
392
393    /// Retrieves various metrics that apply to the entire font.
394    pub fn metrics(&self) -> Metrics {
395        let units_per_em = self.core_text_font.units_per_em();
396        let units_per_point = (units_per_em as f64) / self.core_text_font.pt_size();
397
398        let bounding_box = self.core_text_font.bounding_box();
399        let bounding_box = RectF::new(
400            Vector2F::new(bounding_box.origin.x as f32, bounding_box.origin.y as f32),
401            Vector2F::new(
402                bounding_box.size.width as f32,
403                bounding_box.size.height as f32,
404            ),
405        );
406        let bounding_box = bounding_box * units_per_point as f32;
407
408        Metrics {
409            units_per_em,
410            ascent: (self.core_text_font.ascent() * units_per_point) as f32,
411            descent: (-self.core_text_font.descent() * units_per_point) as f32,
412            line_gap: (self.core_text_font.leading() * units_per_point) as f32,
413            underline_position: (self.core_text_font.underline_position() * units_per_point) as f32,
414            underline_thickness: (self.core_text_font.underline_thickness() * units_per_point)
415                as f32,
416            cap_height: (self.core_text_font.cap_height() * units_per_point) as f32,
417            x_height: (self.core_text_font.x_height() * units_per_point) as f32,
418            bounding_box,
419        }
420    }
421
422    /// Returns a handle to this font, if possible.
423    ///
424    /// This is useful if you want to open the font with a different loader.
425    #[inline]
426    pub fn handle(&self) -> Option<Handle> {
427        <Self as Loader>::handle(self)
428    }
429
430    /// Attempts to return the raw font data (contents of the font file).
431    ///
432    /// If this font is a member of a collection, this function returns the data for the entire
433    /// collection.
434    pub fn copy_font_data(&self) -> Option<Arc<Vec<u8>>> {
435        match self.font_data {
436            FontData::Unavailable => None,
437            FontData::Memory(ref memory) => Some((*memory).clone()),
438        }
439    }
440
441    /// Returns the pixel boundaries that the glyph will take up when rendered using this loader's
442    /// rasterizer at the given size and transform.
443    #[inline]
444    pub fn raster_bounds(
445        &self,
446        glyph_id: u32,
447        point_size: f32,
448        transform: Transform2F,
449        hinting_options: HintingOptions,
450        rasterization_options: RasterizationOptions,
451    ) -> Result<RectI, GlyphLoadingError> {
452        <Self as Loader>::raster_bounds(
453            self,
454            glyph_id,
455            point_size,
456            transform,
457            hinting_options,
458            rasterization_options,
459        )
460    }
461
462    /// Rasterizes a glyph to a canvas with the given size and origin.
463    ///
464    /// Format conversion will be performed if the canvas format does not match the rasterization
465    /// options. For example, if bilevel (black and white) rendering is requested to an RGBA
466    /// surface, this function will automatically convert the 1-bit raster image to the 32-bit
467    /// format of the canvas. Note that this may result in a performance penalty, depending on the
468    /// loader.
469    ///
470    /// If `hinting_options` is not None, the requested grid fitting is performed.
471    ///
472    /// TODO(pcwalton): This is woefully incomplete. See WebRender's code for a more complete
473    /// implementation.
474    pub fn rasterize_glyph(
475        &self,
476        canvas: &mut Canvas,
477        glyph_id: u32,
478        point_size: f32,
479        transform: Transform2F,
480        hinting_options: HintingOptions,
481        rasterization_options: RasterizationOptions,
482    ) -> Result<(), GlyphLoadingError> {
483        if canvas.size.x() == 0 || canvas.size.y() == 0 {
484            return Ok(());
485        }
486
487        let (cg_color_space, cg_image_format) =
488            match format_to_cg_color_space_and_image_format(canvas.format) {
489                None => {
490                    // Core Graphics doesn't support the requested image format. Allocate a
491                    // temporary canvas, then perform color conversion.
492                    //
493                    // FIXME(pcwalton): Could improve this by only allocating a canvas with a tight
494                    // bounding rect and blitting only that part.
495                    let mut temp_canvas = Canvas::new(canvas.size, Format::Rgba32);
496                    self.rasterize_glyph(
497                        &mut temp_canvas,
498                        glyph_id,
499                        point_size,
500                        transform,
501                        hinting_options,
502                        rasterization_options,
503                    )?;
504                    canvas.blit_from_canvas(&temp_canvas);
505                    return Ok(());
506                }
507                Some(cg_color_space_and_format) => cg_color_space_and_format,
508            };
509
510        let core_graphics_context = CGContext::create_bitmap_context(
511            Some(canvas.pixels.as_mut_ptr() as *mut _),
512            canvas.size.x() as usize,
513            canvas.size.y() as usize,
514            canvas.format.bits_per_component() as usize,
515            canvas.stride,
516            &cg_color_space,
517            cg_image_format,
518        );
519
520        match canvas.format {
521            Format::Rgba32 | Format::Rgb24 => {
522                core_graphics_context.set_rgb_fill_color(0.0, 0.0, 0.0, 0.0);
523            }
524            Format::A8 => core_graphics_context.set_gray_fill_color(0.0, 0.0),
525        }
526
527        let core_graphics_size = CGSize::new(canvas.size.x() as f64, canvas.size.y() as f64);
528        core_graphics_context.fill_rect(CGRect::new(&CG_ZERO_POINT, &core_graphics_size));
529
530        match rasterization_options {
531            RasterizationOptions::Bilevel => {
532                core_graphics_context.set_allows_font_smoothing(false);
533                core_graphics_context.set_should_smooth_fonts(false);
534                core_graphics_context.set_should_antialias(false);
535            }
536            RasterizationOptions::GrayscaleAa | RasterizationOptions::SubpixelAa => {
537                // FIXME(pcwalton): These shouldn't be handled the same!
538                core_graphics_context.set_allows_font_smoothing(true);
539                core_graphics_context.set_should_smooth_fonts(true);
540                core_graphics_context.set_should_antialias(true);
541            }
542        }
543
544        match canvas.format {
545            Format::Rgba32 | Format::Rgb24 => {
546                core_graphics_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
547            }
548            Format::A8 => core_graphics_context.set_gray_fill_color(1.0, 1.0),
549        }
550
551        // CoreGraphics origin is in the bottom left. This makes behavior consistent.
552        core_graphics_context.translate(0.0, canvas.size.y() as CGFloat);
553        core_graphics_context.set_font(&self.core_text_font.copy_to_CGFont());
554        core_graphics_context.set_font_size(point_size as CGFloat);
555        core_graphics_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
556        let matrix = transform.matrix.0 * F32x4::new(1.0, -1.0, -1.0, 1.0);
557        core_graphics_context.set_text_matrix(&CGAffineTransform {
558            a: matrix.x() as CGFloat,
559            b: matrix.y() as CGFloat,
560            c: matrix.z() as CGFloat,
561            d: matrix.w() as CGFloat,
562            tx: transform.vector.x() as CGFloat,
563            ty: -transform.vector.y() as CGFloat,
564        });
565        let origin = CGPoint::new(0.0, 0.0);
566        core_graphics_context.show_glyphs_at_positions(&[glyph_id as CGGlyph], &[origin]);
567
568        Ok(())
569    }
570
571    /// Returns true if and only if the font loader can perform hinting in the requested way.
572    ///
573    /// Some APIs support only rasterizing glyphs with hinting, not retrieving hinted outlines. If
574    /// `for_rasterization` is false, this function returns true if and only if the loader supports
575    /// retrieval of hinted *outlines*. If `for_rasterization` is true, this function returns true
576    /// if and only if the loader supports *rasterizing* hinted glyphs.
577    #[inline]
578    pub fn supports_hinting_options(&self, hinting_options: HintingOptions, _: bool) -> bool {
579        match hinting_options {
580            HintingOptions::None => true,
581            HintingOptions::Vertical(..)
582            | HintingOptions::VerticalSubpixel(..)
583            | HintingOptions::Full(..) => false,
584        }
585    }
586
587    /// Get font fallback results for the given text and locale.
588    ///
589    /// Note: this is currently just a stub implementation, a proper implementation
590    /// would use CTFontCopyDefaultCascadeListForLanguages.
591    fn get_fallbacks(&self, text: &str, _locale: &str) -> FallbackResult<Font> {
592        warn!("unsupported");
593        FallbackResult {
594            fonts: Vec::new(),
595            valid_len: text.len(),
596        }
597    }
598
599    #[inline]
600    fn units_per_point(&self) -> f64 {
601        (self.core_text_font.units_per_em() as f64) / self.core_text_font.pt_size()
602    }
603
604    /// Returns the raw contents of the OpenType table with the given tag.
605    ///
606    /// Tags are four-character codes. A list of tags can be found in the [OpenType specification].
607    ///
608    /// [OpenType specification]: https://docs.microsoft.com/en-us/typography/opentype/spec/
609    #[inline]
610    pub fn load_font_table(&self, table_tag: u32) -> Option<Box<[u8]>> {
611        self.core_text_font
612            .get_font_table(table_tag)
613            .map(|data| data.bytes().into())
614    }
615}
616
617impl Loader for Font {
618    type NativeFont = NativeFont;
619
620    #[inline]
621    fn from_bytes(font_data: Arc<Vec<u8>>, font_index: u32) -> Result<Self, FontLoadingError> {
622        Font::from_bytes(font_data, font_index)
623    }
624
625    #[inline]
626    fn from_file(file: &mut File, font_index: u32) -> Result<Font, FontLoadingError> {
627        Font::from_file(file, font_index)
628    }
629
630    #[inline]
631    unsafe fn from_native_font(native_font: Self::NativeFont) -> Self {
632        Font::from_native_font(native_font)
633    }
634
635    #[inline]
636    fn analyze_bytes(font_data: Arc<Vec<u8>>) -> Result<FileType, FontLoadingError> {
637        Font::analyze_bytes(font_data)
638    }
639
640    #[inline]
641    fn analyze_file(file: &mut File) -> Result<FileType, FontLoadingError> {
642        Font::analyze_file(file)
643    }
644
645    #[inline]
646    fn native_font(&self) -> Self::NativeFont {
647        self.native_font()
648    }
649
650    #[inline]
651    fn postscript_name(&self) -> Option<String> {
652        self.postscript_name()
653    }
654
655    #[inline]
656    fn full_name(&self) -> String {
657        self.full_name()
658    }
659
660    #[inline]
661    fn family_name(&self) -> String {
662        self.family_name()
663    }
664
665    #[inline]
666    fn is_monospace(&self) -> bool {
667        self.is_monospace()
668    }
669
670    #[inline]
671    fn properties(&self) -> Properties {
672        self.properties()
673    }
674
675    #[inline]
676    fn glyph_for_char(&self, character: char) -> Option<u32> {
677        self.glyph_for_char(character)
678    }
679
680    #[inline]
681    fn glyph_by_name(&self, name: &str) -> Option<u32> {
682        self.glyph_by_name(name)
683    }
684
685    #[inline]
686    fn glyph_count(&self) -> u32 {
687        self.glyph_count()
688    }
689
690    #[inline]
691    fn outline<S>(
692        &self,
693        glyph_id: u32,
694        hinting_mode: HintingOptions,
695        sink: &mut S,
696    ) -> Result<(), GlyphLoadingError>
697    where
698        S: OutlineSink,
699    {
700        self.outline(glyph_id, hinting_mode, sink)
701    }
702
703    #[inline]
704    fn typographic_bounds(&self, glyph_id: u32) -> Result<RectF, GlyphLoadingError> {
705        self.typographic_bounds(glyph_id)
706    }
707
708    #[inline]
709    fn advance(&self, glyph_id: u32) -> Result<Vector2F, GlyphLoadingError> {
710        self.advance(glyph_id)
711    }
712
713    #[inline]
714    fn origin(&self, glyph_id: u32) -> Result<Vector2F, GlyphLoadingError> {
715        self.origin(glyph_id)
716    }
717
718    #[inline]
719    fn metrics(&self) -> Metrics {
720        self.metrics()
721    }
722
723    #[inline]
724    fn copy_font_data(&self) -> Option<Arc<Vec<u8>>> {
725        self.copy_font_data()
726    }
727
728    #[inline]
729    fn supports_hinting_options(
730        &self,
731        hinting_options: HintingOptions,
732        for_rasterization: bool,
733    ) -> bool {
734        self.supports_hinting_options(hinting_options, for_rasterization)
735    }
736
737    #[inline]
738    fn rasterize_glyph(
739        &self,
740        canvas: &mut Canvas,
741        glyph_id: u32,
742        point_size: f32,
743        transform: Transform2F,
744        hinting_options: HintingOptions,
745        rasterization_options: RasterizationOptions,
746    ) -> Result<(), GlyphLoadingError> {
747        self.rasterize_glyph(
748            canvas,
749            glyph_id,
750            point_size,
751            transform,
752            hinting_options,
753            rasterization_options,
754        )
755    }
756
757    #[inline]
758    fn get_fallbacks(&self, text: &str, locale: &str) -> FallbackResult<Self> {
759        self.get_fallbacks(text, locale)
760    }
761
762    #[inline]
763    fn load_font_table(&self, table_tag: u32) -> Option<Box<[u8]>> {
764        self.load_font_table(table_tag)
765    }
766}
767
768impl Debug for Font {
769    fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
770        self.full_name().fmt(fmt)
771    }
772}
773
774#[derive(Clone)]
775enum FontData {
776    Unavailable,
777    Memory(Arc<Vec<u8>>),
778}
779
780impl Deref for FontData {
781    type Target = [u8];
782    fn deref(&self) -> &[u8] {
783        match *self {
784            FontData::Unavailable => panic!("Font data unavailable!"),
785            FontData::Memory(ref data) => &***data,
786        }
787    }
788}
789
790trait CGPointExt {
791    fn to_vector(&self) -> Vector2F;
792}
793
794impl CGPointExt for CGPoint {
795    #[inline]
796    fn to_vector(&self) -> Vector2F {
797        Vector2F::new(self.x as f32, self.y as f32)
798    }
799}
800
801fn core_text_to_css_font_weight(core_text_weight: f32) -> Weight {
802    let index = piecewise_linear_find_index(core_text_weight, &FONT_WEIGHT_MAPPING);
803
804    Weight(index * 100.0 + 100.0)
805}
806
807fn core_text_width_to_css_stretchiness(core_text_width: f32) -> Stretch {
808    Stretch(piecewise_linear_lookup(
809        (core_text_width + 1.0) * 4.0,
810        &Stretch::MAPPING,
811    ))
812}
813
814fn font_is_collection(header: &[u8]) -> bool {
815    header.len() >= 4 && header[0..4] == TTC_TAG
816}
817
818fn read_number_of_fonts_from_otc_header(header: &[u8]) -> Result<u32, FontLoadingError> {
819    if !font_is_collection(header) {
820        return Err(FontLoadingError::UnknownFormat);
821    }
822    Ok((&header[8..]).read_u32::<BigEndian>()?)
823}
824
825fn get_slice_from_start(slice: &[u8], start: usize) -> Result<&[u8], FontLoadingError> {
826    slice.get(start..).ok_or(FontLoadingError::Parse)
827}
828
829// Unpacks an OTC font "in-place".
830fn unpack_otc_font(data: &mut [u8], font_index: u32) -> Result<(), FontLoadingError> {
831    if font_index >= read_number_of_fonts_from_otc_header(data)? {
832        return Err(FontLoadingError::NoSuchFontInCollection);
833    }
834
835    let offset_table_pos_pos = 12 + 4 * font_index as usize;
836
837    let offset_table_pos =
838        get_slice_from_start(&data, offset_table_pos_pos)?.read_u32::<BigEndian>()? as usize;
839    debug_assert!(utils::SFNT_VERSIONS
840        .iter()
841        .any(|version| { data[offset_table_pos..(offset_table_pos + 4)] == *version }));
842    let num_tables = get_slice_from_start(&data, offset_table_pos + 4)?.read_u16::<BigEndian>()?;
843
844    // Must copy forward in order to avoid problems with overlapping memory.
845    let offset_table_and_table_record_size = 12 + (num_tables as usize) * 16;
846    for offset in 0..offset_table_and_table_record_size {
847        data[offset] = data[offset_table_pos + offset]
848    }
849
850    Ok(())
851}
852
853// NB: This assumes little-endian, but that's true for all extant Apple hardware.
854fn format_to_cg_color_space_and_image_format(format: Format) -> Option<(CGColorSpace, u32)> {
855    match format {
856        Format::Rgb24 => {
857            // Unsupported by Core Graphics.
858            None
859        }
860        Format::Rgba32 => Some((
861            CGColorSpace::create_device_rgb(),
862            kCGImageAlphaPremultipliedLast,
863        )),
864        Format::A8 => Some((CGColorSpace::create_device_gray(), kCGImageAlphaOnly)),
865    }
866}
867
868fn font_is_single_otf(header: &[u8]) -> bool {
869    header.len() >= 4
870        && ((&header[..4]).read_u32::<BigEndian>().unwrap() == 0x00010000
871            || header[..4] == OTTO_TAG)
872}
873
874/// https://developer.apple.com/library/archive/documentation/mac/pdf/MoreMacintoshToolbox.pdf#page=151
875fn unpack_data_fork_font(data: &mut [u8]) -> Result<(), FontLoadingError> {
876    let data_offset = (&data[..]).read_u32::<BigEndian>()? as usize;
877    let map_offset = get_slice_from_start(&data, 4)?.read_u32::<BigEndian>()? as usize;
878    let num_types =
879        get_slice_from_start(&data, map_offset + 28)?.read_u16::<BigEndian>()? as usize + 1;
880
881    let mut font_data_offset = 0;
882    let mut font_data_len = 0;
883
884    let type_list_offset = get_slice_from_start(&data, map_offset + 24)?.read_u16::<BigEndian>()?
885        as usize
886        + map_offset;
887    for i in 0..num_types {
888        let res_type =
889            get_slice_from_start(&data, map_offset + 30 + i * 8)?.read_u32::<BigEndian>()?;
890
891        if res_type == SFNT_HEX {
892            let ref_list_offset = get_slice_from_start(&data, map_offset + 30 + i * 8 + 6)?
893                .read_u16::<BigEndian>()? as usize;
894            let res_data_offset =
895                get_slice_from_start(&data, type_list_offset + ref_list_offset + 5)?
896                    .read_u24::<BigEndian>()? as usize;
897            font_data_len = get_slice_from_start(&data, data_offset + res_data_offset)?
898                .read_u32::<BigEndian>()? as usize;
899            font_data_offset = data_offset + res_data_offset + 4;
900            let sfnt_version =
901                get_slice_from_start(&data, font_data_offset)?.read_u32::<BigEndian>()?;
902
903            // TrueType outline, 'OTTO', 'true', 'typ1'
904            if sfnt_version == 0x00010000
905                || sfnt_version == OTTO_HEX
906                || sfnt_version == TRUE_HEX
907                || sfnt_version == TYP1_HEX
908            {
909                break;
910            }
911        }
912    }
913
914    if font_data_len == 0 {
915        return Err(FontLoadingError::Parse);
916    }
917
918    for offset in 0..font_data_len {
919        data[offset] = data[font_data_offset + offset];
920    }
921
922    Ok(())
923}
924
925#[cfg(test)]
926mod test {
927    use super::Font;
928    use crate::properties::{Stretch, Weight};
929
930    #[cfg(feature = "source")]
931    use crate::source::SystemSource;
932
933    static TEST_FONT_POSTSCRIPT_NAME: &'static str = "ArialMT";
934
935    #[cfg(feature = "source")]
936    #[test]
937    fn test_from_core_graphics_font() {
938        let font0 = SystemSource::new()
939            .select_by_postscript_name(TEST_FONT_POSTSCRIPT_NAME)
940            .unwrap()
941            .load()
942            .unwrap();
943        let core_text_font = font0.native_font();
944        let core_graphics_font = core_text_font.copy_to_CGFont();
945        let font1 = Font::from_core_graphics_font(core_graphics_font);
946        assert_eq!(font1.postscript_name().unwrap(), TEST_FONT_POSTSCRIPT_NAME);
947    }
948
949    #[test]
950    fn test_core_text_to_css_font_weight() {
951        // Exact matches
952        assert_eq!(super::core_text_to_css_font_weight(-0.7), Weight(100.0));
953        assert_eq!(super::core_text_to_css_font_weight(0.0), Weight(400.0));
954        assert_eq!(super::core_text_to_css_font_weight(0.4), Weight(700.0));
955        assert_eq!(super::core_text_to_css_font_weight(0.8), Weight(900.0));
956
957        // Linear interpolation
958        assert_eq!(super::core_text_to_css_font_weight(0.1), Weight(450.0));
959    }
960
961    #[test]
962    fn test_core_text_to_css_font_stretch() {
963        // Exact matches
964        assert_eq!(
965            super::core_text_width_to_css_stretchiness(0.0),
966            Stretch(1.0)
967        );
968        assert_eq!(
969            super::core_text_width_to_css_stretchiness(-1.0),
970            Stretch(0.5)
971        );
972        assert_eq!(
973            super::core_text_width_to_css_stretchiness(1.0),
974            Stretch(2.0)
975        );
976
977        // Linear interpolation
978        assert_eq!(
979            super::core_text_width_to_css_stretchiness(0.85),
980            Stretch(1.7)
981        );
982    }
983}
984
985pub(crate) fn piecewise_linear_lookup(index: f32, mapping: &[f32]) -> f32 {
986    let lower_value = mapping[f32::floor(index) as usize];
987    let upper_value = mapping[f32::ceil(index) as usize];
988    utils::lerp(lower_value, upper_value, f32::fract(index))
989}
990
991pub(crate) fn piecewise_linear_find_index(query_value: f32, mapping: &[f32]) -> f32 {
992    let upper_index = match mapping
993        .binary_search_by(|value| value.partial_cmp(&query_value).unwrap_or(Ordering::Less))
994    {
995        Ok(index) => return index as f32,
996        Err(upper_index) => upper_index,
997    };
998    if upper_index == 0 || upper_index >= mapping.len() {
999        return upper_index as f32;
1000    }
1001    let lower_index = upper_index - 1;
1002    let (upper_value, lower_value) = (mapping[upper_index], mapping[lower_index]);
1003    let t = (query_value - lower_value) / (upper_value - lower_value);
1004    lower_index as f32 + t
1005}