use byteorder::{BigEndian, ReadBytesExt};
use core_graphics::base::{kCGImageAlphaPremultipliedLast, CGFloat};
use core_graphics::color_space::CGColorSpace;
use core_graphics::context::{CGContext, CGTextDrawingMode};
use core_graphics::data_provider::CGDataProvider;
use core_graphics::font::{CGFont, CGGlyph};
use core_graphics::geometry::{CGAffineTransform, CGPoint, CGRect, CGSize};
use core_graphics::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CG_ZERO_POINT, CG_ZERO_SIZE};
use core_graphics::path::CGPathElementType;
use core_text;
use core_text::font::CTFont;
use core_text::font_descriptor::kCTFontDefaultOrientation;
use core_text::font_descriptor::{SymbolicTraitAccessors, TraitAccessors};
use log::warn;
use pathfinder_geometry::line_segment::LineSegment2F;
use pathfinder_geometry::rect::{RectF, RectI};
use pathfinder_geometry::transform2d::Transform2F;
use pathfinder_geometry::vector::Vector2F;
use pathfinder_simd::default::F32x4;
use std::cmp::Ordering;
use std::f32;
use std::fmt::{self, Debug, Formatter};
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc;
use crate::canvas::{Canvas, Format, RasterizationOptions};
use crate::error::{FontLoadingError, GlyphLoadingError};
use crate::file_type::FileType;
use crate::handle::Handle;
use crate::hinting::HintingOptions;
use crate::loader::{FallbackResult, Loader};
use crate::metrics::Metrics;
use crate::outline::OutlineSink;
use crate::properties::{Properties, Stretch, Style, Weight};
use crate::utils;
const TTC_TAG: [u8; 4] = [b't', b't', b'c', b'f'];
#[allow(non_upper_case_globals)]
const kCGImageAlphaOnly: u32 = 7;
pub(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];
pub type NativeFont = CTFont;
#[derive(Clone)]
pub struct Font {
core_text_font: CTFont,
font_data: FontData,
}
impl Font {
pub fn from_bytes(
mut font_data: Arc<Vec<u8>>,
font_index: u32,
) -> Result<Font, FontLoadingError> {
if font_is_collection(&**font_data) {
let mut new_font_data = (*font_data).clone();
unpack_otc_font(&mut new_font_data, font_index)?;
font_data = Arc::new(new_font_data);
}
let data_provider = CGDataProvider::from_buffer(font_data.clone());
let core_graphics_font =
CGFont::from_data_provider(data_provider).map_err(|_| FontLoadingError::Parse)?;
let core_text_font = core_text::font::new_from_CGFont(&core_graphics_font, 16.0);
Ok(Font {
core_text_font,
font_data: FontData::Memory(font_data),
})
}
pub fn from_file(file: &mut File, font_index: u32) -> Result<Font, FontLoadingError> {
file.seek(SeekFrom::Start(0))?;
let font_data = Arc::new(utils::slurp_file(file).map_err(FontLoadingError::Io)?);
Font::from_bytes(font_data, font_index)
}
#[inline]
pub fn from_path<P: AsRef<Path>>(path: P, font_index: u32) -> Result<Font, FontLoadingError> {
<Font as Loader>::from_path(path, font_index)
}
pub unsafe fn from_native_font(core_text_font: NativeFont) -> Font {
Font::from_core_text_font(core_text_font)
}
unsafe fn from_core_text_font(core_text_font: NativeFont) -> Font {
let mut font_data = FontData::Unavailable;
match core_text_font.url() {
None => warn!("No URL found for Core Text font!"),
Some(url) => match url.to_path() {
Some(path) => match File::open(path) {
Ok(ref mut file) => match utils::slurp_file(file) {
Ok(data) => font_data = FontData::Memory(Arc::new(data)),
Err(_) => warn!("Couldn't read file data for Core Text font!"),
},
Err(_) => warn!("Could not open file for Core Text font!"),
},
None => warn!("Could not convert URL from Core Text font to path!"),
},
}
Font {
core_text_font,
font_data,
}
}
pub fn from_core_graphics_font(core_graphics_font: CGFont) -> Font {
unsafe {
Font::from_core_text_font(core_text::font::new_from_CGFont(&core_graphics_font, 16.0))
}
}
#[inline]
pub fn from_handle(handle: &Handle) -> Result<Self, FontLoadingError> {
<Self as Loader>::from_handle(handle)
}
pub fn analyze_bytes(font_data: Arc<Vec<u8>>) -> Result<FileType, FontLoadingError> {
if let Ok(font_count) = read_number_of_fonts_from_otc_header(&font_data) {
return Ok(FileType::Collection(font_count));
}
let data_provider = CGDataProvider::from_buffer(font_data);
match CGFont::from_data_provider(data_provider) {
Ok(_) => Ok(FileType::Single),
Err(_) => Err(FontLoadingError::Parse),
}
}
pub fn analyze_file(file: &mut File) -> Result<FileType, FontLoadingError> {
file.seek(SeekFrom::Start(0))?;
let font_data = Arc::new(utils::slurp_file(file).map_err(FontLoadingError::Io)?);
if let Ok(font_count) = read_number_of_fonts_from_otc_header(&font_data) {
return Ok(FileType::Collection(font_count));
}
let data_provider = CGDataProvider::from_buffer(font_data.clone());
match CGFont::from_data_provider(data_provider) {
Ok(_) => Ok(FileType::Single),
Err(_) => Err(FontLoadingError::Parse),
}
}
#[inline]
pub fn analyze_path<P: AsRef<Path>>(path: P) -> Result<FileType, FontLoadingError> {
<Self as Loader>::analyze_path(path)
}
#[inline]
pub fn native_font(&self) -> NativeFont {
self.core_text_font.clone()
}
#[inline]
pub fn postscript_name(&self) -> Option<String> {
Some(self.core_text_font.postscript_name())
}
#[inline]
pub fn full_name(&self) -> String {
self.core_text_font.display_name()
}
#[inline]
pub fn family_name(&self) -> String {
self.core_text_font.family_name()
}
#[inline]
pub fn style_name(&self) -> String {
self.core_text_font.style_name()
}
#[inline]
pub fn is_monospace(&self) -> bool {
self.core_text_font.symbolic_traits().is_monospace()
}
pub fn properties(&self) -> Properties {
let symbolic_traits = self.core_text_font.symbolic_traits();
let all_traits = self.core_text_font.all_traits();
let style = if symbolic_traits.is_italic() {
Style::Italic
} else if all_traits.normalized_slant() > 0.0 {
Style::Oblique
} else {
Style::Normal
};
let weight = core_text_to_css_font_weight(all_traits.normalized_weight() as f32);
let stretch = core_text_width_to_css_stretchiness(all_traits.normalized_width() as f32);
Properties {
style,
weight,
stretch,
}
}
pub fn glyph_count(&self) -> u32 {
self.core_text_font.glyph_count() as u32
}
pub fn glyph_for_char(&self, character: char) -> Option<u32> {
unsafe {
let (mut dest, mut src) = ([0, 0], [0, 0]);
let src = character.encode_utf16(&mut src);
self.core_text_font
.get_glyphs_for_characters(src.as_ptr(), dest.as_mut_ptr(), 2);
let id = dest[0] as u32;
if id != 0 {
Some(id)
} else {
None
}
}
}
#[inline]
pub fn glyph_by_name(&self, name: &str) -> Option<u32> {
let code = self.core_text_font.get_glyph_with_name(name);
Some(u32::from(code))
}
pub fn outline<S>(
&self,
glyph_id: u32,
_: HintingOptions,
sink: &mut S,
) -> Result<(), GlyphLoadingError>
where
S: OutlineSink,
{
let path = match self
.core_text_font
.create_path_for_glyph(glyph_id as u16, &CG_AFFINE_TRANSFORM_IDENTITY)
{
Ok(path) => path,
Err(_) => {
drop(self.typographic_bounds(glyph_id)?);
return Ok(());
}
};
let units_per_point = self.units_per_point() as f32;
path.apply(&|element| {
let points = element.points();
match element.element_type {
CGPathElementType::MoveToPoint => {
sink.move_to(points[0].to_vector() * units_per_point)
}
CGPathElementType::AddLineToPoint => {
sink.line_to(points[0].to_vector() * units_per_point)
}
CGPathElementType::AddQuadCurveToPoint => sink.quadratic_curve_to(
points[0].to_vector() * units_per_point,
points[1].to_vector() * units_per_point,
),
CGPathElementType::AddCurveToPoint => {
let ctrl = LineSegment2F::new(points[0].to_vector(), points[1].to_vector())
* units_per_point;
sink.cubic_curve_to(ctrl, points[2].to_vector() * units_per_point)
}
CGPathElementType::CloseSubpath => sink.close(),
}
});
Ok(())
}
pub fn typographic_bounds(&self, glyph_id: u32) -> Result<RectF, GlyphLoadingError> {
let rect = self
.core_text_font
.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph_id as u16]);
let rect = RectF::new(
Vector2F::new(rect.origin.x as f32, rect.origin.y as f32),
Vector2F::new(rect.size.width as f32, rect.size.height as f32),
);
Ok(rect * self.units_per_point() as f32)
}
pub fn advance(&self, glyph_id: u32) -> Result<Vector2F, GlyphLoadingError> {
unsafe {
let (glyph_id, mut advance) = (glyph_id as u16, CG_ZERO_SIZE);
self.core_text_font.get_advances_for_glyphs(
kCTFontDefaultOrientation,
&glyph_id,
&mut advance,
1,
);
let advance = Vector2F::new(advance.width as f32, advance.height as f32);
Ok(advance * self.units_per_point() as f32)
}
}
pub fn origin(&self, glyph_id: u32) -> Result<Vector2F, GlyphLoadingError> {
unsafe {
let (glyph_id, mut translation) = (glyph_id as u16, CG_ZERO_SIZE);
self.core_text_font.get_vertical_translations_for_glyphs(
kCTFontDefaultOrientation,
&glyph_id,
&mut translation,
1,
);
let translation = Vector2F::new(translation.width as f32, translation.height as f32);
Ok(translation * self.units_per_point() as f32)
}
}
pub fn metrics(&self) -> Metrics {
let units_per_em = self.core_text_font.units_per_em();
let units_per_point = (units_per_em as f64) / self.core_text_font.pt_size();
let bounding_box = self.core_text_font.bounding_box();
let bounding_box = RectF::new(
Vector2F::new(bounding_box.origin.x as f32, bounding_box.origin.y as f32),
Vector2F::new(
bounding_box.size.width as f32,
bounding_box.size.height as f32,
),
);
let bounding_box = bounding_box * units_per_point as f32;
Metrics {
units_per_em,
ascent: (self.core_text_font.ascent() * units_per_point) as f32,
descent: (-self.core_text_font.descent() * units_per_point) as f32,
line_gap: (self.core_text_font.leading() * units_per_point) as f32,
underline_position: (self.core_text_font.underline_position() * units_per_point) as f32,
underline_thickness: (self.core_text_font.underline_thickness() * units_per_point)
as f32,
cap_height: (self.core_text_font.cap_height() * units_per_point) as f32,
x_height: (self.core_text_font.x_height() * units_per_point) as f32,
bounding_box,
}
}
#[inline]
pub fn handle(&self) -> Option<Handle> {
<Self as Loader>::handle(self)
}
pub fn copy_font_data(&self) -> Option<Arc<Vec<u8>>> {
match self.font_data {
FontData::Unavailable => None,
FontData::Memory(ref memory) => Some((*memory).clone()),
}
}
#[inline]
pub fn raster_bounds(
&self,
glyph_id: u32,
point_size: f32,
transform: Transform2F,
hinting_options: HintingOptions,
rasterization_options: RasterizationOptions,
) -> Result<RectI, GlyphLoadingError> {
<Self as Loader>::raster_bounds(
self,
glyph_id,
point_size,
transform,
hinting_options,
rasterization_options,
)
}
pub fn rasterize_glyph(
&self,
canvas: &mut Canvas,
glyph_id: u32,
point_size: f32,
transform: Transform2F,
hinting_options: HintingOptions,
rasterization_options: RasterizationOptions,
) -> Result<(), GlyphLoadingError> {
if canvas.size.x() == 0 || canvas.size.y() == 0 {
return Ok(());
}
let (cg_color_space, cg_image_format) =
match format_to_cg_color_space_and_image_format(canvas.format) {
None => {
let mut temp_canvas = Canvas::new(canvas.size, Format::Rgba32);
self.rasterize_glyph(
&mut temp_canvas,
glyph_id,
point_size,
transform,
hinting_options,
rasterization_options,
)?;
canvas.blit_from_canvas(&temp_canvas);
return Ok(());
}
Some(cg_color_space_and_format) => cg_color_space_and_format,
};
let core_graphics_context = CGContext::create_bitmap_context(
Some(canvas.pixels.as_mut_ptr() as *mut _),
canvas.size.x() as usize,
canvas.size.y() as usize,
canvas.format.bits_per_component() as usize,
canvas.stride,
&cg_color_space,
cg_image_format,
);
match canvas.format {
Format::Rgba32 | Format::Rgb24 => {
core_graphics_context.set_rgb_fill_color(0.0, 0.0, 0.0, 0.0);
}
Format::A8 => core_graphics_context.set_gray_fill_color(0.0, 0.0),
}
let core_graphics_size = CGSize::new(canvas.size.x() as f64, canvas.size.y() as f64);
core_graphics_context.fill_rect(CGRect::new(&CG_ZERO_POINT, &core_graphics_size));
match rasterization_options {
RasterizationOptions::Bilevel => {
core_graphics_context.set_allows_font_smoothing(false);
core_graphics_context.set_should_smooth_fonts(false);
core_graphics_context.set_should_antialias(false);
}
RasterizationOptions::GrayscaleAa | RasterizationOptions::SubpixelAa => {
core_graphics_context.set_allows_font_smoothing(true);
core_graphics_context.set_should_smooth_fonts(true);
core_graphics_context.set_should_antialias(true);
}
}
match canvas.format {
Format::Rgba32 | Format::Rgb24 => {
core_graphics_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
}
Format::A8 => core_graphics_context.set_gray_fill_color(1.0, 1.0),
}
core_graphics_context.translate(0.0, canvas.size.y() as CGFloat);
core_graphics_context.set_font(&self.core_text_font.copy_to_CGFont());
core_graphics_context.set_font_size(point_size as CGFloat);
core_graphics_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
let matrix = transform.matrix.0 * F32x4::new(1.0, -1.0, -1.0, 1.0);
core_graphics_context.set_text_matrix(&CGAffineTransform {
a: matrix.x() as CGFloat,
b: matrix.y() as CGFloat,
c: matrix.z() as CGFloat,
d: matrix.w() as CGFloat,
tx: transform.vector.x() as CGFloat,
ty: -transform.vector.y() as CGFloat,
});
let origin = CGPoint::new(0.0, 0.0);
core_graphics_context.show_glyphs_at_positions(&[glyph_id as CGGlyph], &[origin]);
Ok(())
}
#[inline]
pub fn supports_hinting_options(&self, hinting_options: HintingOptions, _: bool) -> bool {
match hinting_options {
HintingOptions::None => true,
HintingOptions::Vertical(..)
| HintingOptions::VerticalSubpixel(..)
| HintingOptions::Full(..) => false,
}
}
fn get_fallbacks(&self, text: &str, _locale: &str) -> FallbackResult<Font> {
warn!("unsupported");
FallbackResult {
fonts: Vec::new(),
valid_len: text.len(),
}
}
#[inline]
fn units_per_point(&self) -> f64 {
(self.core_text_font.units_per_em() as f64) / self.core_text_font.pt_size()
}
#[inline]
pub fn load_font_table(&self, table_tag: u32) -> Option<Box<[u8]>> {
self.core_text_font
.get_font_table(table_tag)
.map(|data| data.bytes().into())
}
}
impl Loader for Font {
type NativeFont = NativeFont;
#[inline]
fn from_bytes(font_data: Arc<Vec<u8>>, font_index: u32) -> Result<Self, FontLoadingError> {
Font::from_bytes(font_data, font_index)
}
#[inline]
fn from_file(file: &mut File, font_index: u32) -> Result<Font, FontLoadingError> {
Font::from_file(file, font_index)
}
#[inline]
unsafe fn from_native_font(native_font: Self::NativeFont) -> Self {
Font::from_native_font(native_font)
}
#[inline]
fn analyze_bytes(font_data: Arc<Vec<u8>>) -> Result<FileType, FontLoadingError> {
Font::analyze_bytes(font_data)
}
#[inline]
fn analyze_file(file: &mut File) -> Result<FileType, FontLoadingError> {
Font::analyze_file(file)
}
#[inline]
fn native_font(&self) -> Self::NativeFont {
self.native_font()
}
#[inline]
fn postscript_name(&self) -> Option<String> {
self.postscript_name()
}
#[inline]
fn full_name(&self) -> String {
self.full_name()
}
#[inline]
fn family_name(&self) -> String {
self.family_name()
}
#[inline]
fn is_monospace(&self) -> bool {
self.is_monospace()
}
#[inline]
fn properties(&self) -> Properties {
self.properties()
}
#[inline]
fn glyph_for_char(&self, character: char) -> Option<u32> {
self.glyph_for_char(character)
}
#[inline]
fn glyph_by_name(&self, name: &str) -> Option<u32> {
self.glyph_by_name(name)
}
#[inline]
fn glyph_count(&self) -> u32 {
self.glyph_count()
}
#[inline]
fn outline<S>(
&self,
glyph_id: u32,
hinting_mode: HintingOptions,
sink: &mut S,
) -> Result<(), GlyphLoadingError>
where
S: OutlineSink,
{
self.outline(glyph_id, hinting_mode, sink)
}
#[inline]
fn typographic_bounds(&self, glyph_id: u32) -> Result<RectF, GlyphLoadingError> {
self.typographic_bounds(glyph_id)
}
#[inline]
fn advance(&self, glyph_id: u32) -> Result<Vector2F, GlyphLoadingError> {
self.advance(glyph_id)
}
#[inline]
fn origin(&self, glyph_id: u32) -> Result<Vector2F, GlyphLoadingError> {
self.origin(glyph_id)
}
#[inline]
fn metrics(&self) -> Metrics {
self.metrics()
}
#[inline]
fn copy_font_data(&self) -> Option<Arc<Vec<u8>>> {
self.copy_font_data()
}
#[inline]
fn supports_hinting_options(
&self,
hinting_options: HintingOptions,
for_rasterization: bool,
) -> bool {
self.supports_hinting_options(hinting_options, for_rasterization)
}
#[inline]
fn rasterize_glyph(
&self,
canvas: &mut Canvas,
glyph_id: u32,
point_size: f32,
transform: Transform2F,
hinting_options: HintingOptions,
rasterization_options: RasterizationOptions,
) -> Result<(), GlyphLoadingError> {
self.rasterize_glyph(
canvas,
glyph_id,
point_size,
transform,
hinting_options,
rasterization_options,
)
}
#[inline]
fn get_fallbacks(&self, text: &str, locale: &str) -> FallbackResult<Self> {
self.get_fallbacks(text, locale)
}
#[inline]
fn load_font_table(&self, table_tag: u32) -> Option<Box<[u8]>> {
self.load_font_table(table_tag)
}
}
impl Debug for Font {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
self.full_name().fmt(fmt)
}
}
#[derive(Clone)]
enum FontData {
Unavailable,
Memory(Arc<Vec<u8>>),
}
impl Deref for FontData {
type Target = [u8];
fn deref(&self) -> &[u8] {
match *self {
FontData::Unavailable => panic!("Font data unavailable!"),
FontData::Memory(ref data) => &***data,
}
}
}
trait CGPointExt {
fn to_vector(&self) -> Vector2F;
}
impl CGPointExt for CGPoint {
#[inline]
fn to_vector(&self) -> Vector2F {
Vector2F::new(self.x as f32, self.y as f32)
}
}
fn core_text_to_css_font_weight(core_text_weight: f32) -> Weight {
let index = piecewise_linear_find_index(core_text_weight, &FONT_WEIGHT_MAPPING);
Weight(index * 100.0 + 100.0)
}
fn core_text_width_to_css_stretchiness(core_text_width: f32) -> Stretch {
Stretch(piecewise_linear_lookup(
(core_text_width + 1.0) * 4.0,
&Stretch::MAPPING,
))
}
fn font_is_collection(header: &[u8]) -> bool {
header.len() >= 4 && header[0..4] == TTC_TAG
}
fn read_number_of_fonts_from_otc_header(header: &[u8]) -> Result<u32, FontLoadingError> {
if !font_is_collection(header) {
return Err(FontLoadingError::UnknownFormat);
}
Ok((&header[8..]).read_u32::<BigEndian>()?)
}
fn unpack_otc_font(data: &mut [u8], font_index: u32) -> Result<(), FontLoadingError> {
if font_index >= read_number_of_fonts_from_otc_header(data)? {
return Err(FontLoadingError::NoSuchFontInCollection);
}
let offset_table_pos_pos = 12 + 4 * font_index as usize;
let offset_table_pos = (&data[offset_table_pos_pos..]).read_u32::<BigEndian>()? as usize;
debug_assert!(utils::SFNT_VERSIONS
.iter()
.any(|version| { data[offset_table_pos..(offset_table_pos + 4)] == *version }));
let num_tables = (&data[(offset_table_pos + 4)..]).read_u16::<BigEndian>()?;
let offset_table_and_table_record_size = 12 + (num_tables as usize) * 16;
for offset in 0..offset_table_and_table_record_size {
data[offset] = data[offset_table_pos + offset]
}
Ok(())
}
fn format_to_cg_color_space_and_image_format(format: Format) -> Option<(CGColorSpace, u32)> {
match format {
Format::Rgb24 => {
None
}
Format::Rgba32 => Some((
CGColorSpace::create_device_rgb(),
kCGImageAlphaPremultipliedLast,
)),
Format::A8 => Some((CGColorSpace::create_device_gray(), kCGImageAlphaOnly)),
}
}
#[cfg(test)]
mod test {
use super::Font;
use crate::properties::{Stretch, Weight};
#[cfg(feature = "source")]
use crate::source::SystemSource;
static TEST_FONT_POSTSCRIPT_NAME: &'static str = "ArialMT";
#[cfg(feature = "source")]
#[test]
fn test_from_core_graphics_font() {
let font0 = SystemSource::new()
.select_by_postscript_name(TEST_FONT_POSTSCRIPT_NAME)
.unwrap()
.load()
.unwrap();
let core_text_font = font0.native_font();
let core_graphics_font = core_text_font.copy_to_CGFont();
let font1 = Font::from_core_graphics_font(core_graphics_font);
assert_eq!(font1.postscript_name().unwrap(), TEST_FONT_POSTSCRIPT_NAME);
}
#[test]
fn test_core_text_to_css_font_weight() {
assert_eq!(super::core_text_to_css_font_weight(-0.7), Weight(100.0));
assert_eq!(super::core_text_to_css_font_weight(0.0), Weight(400.0));
assert_eq!(super::core_text_to_css_font_weight(0.4), Weight(700.0));
assert_eq!(super::core_text_to_css_font_weight(0.8), Weight(900.0));
assert_eq!(super::core_text_to_css_font_weight(0.1), Weight(450.0));
}
#[test]
fn test_core_text_to_css_font_stretch() {
assert_eq!(
super::core_text_width_to_css_stretchiness(0.0),
Stretch(1.0)
);
assert_eq!(
super::core_text_width_to_css_stretchiness(-1.0),
Stretch(0.5)
);
assert_eq!(
super::core_text_width_to_css_stretchiness(1.0),
Stretch(2.0)
);
assert_eq!(
super::core_text_width_to_css_stretchiness(0.85),
Stretch(1.7)
);
}
}
pub(crate) fn piecewise_linear_lookup(index: f32, mapping: &[f32]) -> f32 {
let lower_value = mapping[f32::floor(index) as usize];
let upper_value = mapping[f32::ceil(index) as usize];
utils::lerp(lower_value, upper_value, f32::fract(index))
}
pub(crate) fn piecewise_linear_find_index(query_value: f32, mapping: &[f32]) -> f32 {
let upper_index = match mapping
.binary_search_by(|value| value.partial_cmp(&query_value).unwrap_or(Ordering::Less))
{
Ok(index) => return index as f32,
Err(upper_index) => upper_index,
};
if upper_index == 0 {
return upper_index as f32;
}
let lower_index = upper_index - 1;
let (upper_value, lower_value) = (mapping[upper_index], mapping[lower_index]);
let t = (query_value - lower_value) / (upper_value - lower_value);
lower_index as f32 + t
}