mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-11 14:41:51 +00:00
Split into objdiff-core / objdiff-gui; update egui to 0.26.2
This commit is contained in:
146
objdiff-gui/src/fonts/matching.rs
Normal file
146
objdiff-gui/src/fonts/matching.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
// font-kit/src/matching.rs
|
||||
//
|
||||
// Copyright © 2018 The Pathfinder Project Developers.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! Determines the closest font matching a description per the CSS Fonts Level 3 specification.
|
||||
|
||||
use float_ord::FloatOrd;
|
||||
use font_kit::{
|
||||
error::SelectionError,
|
||||
properties::{Properties, Stretch, Style, Weight},
|
||||
};
|
||||
|
||||
/// This follows CSS Fonts Level 3 § 5.2 [1].
|
||||
///
|
||||
/// https://drafts.csswg.org/css-fonts-3/#font-style-matching
|
||||
pub fn find_best_match(
|
||||
candidates: &[Properties],
|
||||
query: &Properties,
|
||||
) -> Result<usize, SelectionError> {
|
||||
// Step 4.
|
||||
let mut matching_set: Vec<usize> = (0..candidates.len()).collect();
|
||||
if matching_set.is_empty() {
|
||||
return Err(SelectionError::NotFound);
|
||||
}
|
||||
|
||||
// Step 4a (`font-stretch`).
|
||||
let matching_stretch = if matching_set
|
||||
.iter()
|
||||
.any(|&index| candidates[index].stretch == query.stretch)
|
||||
{
|
||||
// Exact match.
|
||||
query.stretch
|
||||
} else if query.stretch <= Stretch::NORMAL {
|
||||
// Closest width, first checking narrower values and then wider values.
|
||||
match matching_set
|
||||
.iter()
|
||||
.filter(|&&index| candidates[index].stretch < query.stretch)
|
||||
.min_by_key(|&&index| FloatOrd(query.stretch.0 - candidates[index].stretch.0))
|
||||
{
|
||||
Some(&matching_index) => candidates[matching_index].stretch,
|
||||
None => {
|
||||
let matching_index = *matching_set
|
||||
.iter()
|
||||
.min_by_key(|&&index| FloatOrd(candidates[index].stretch.0 - query.stretch.0))
|
||||
.unwrap();
|
||||
candidates[matching_index].stretch
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Closest width, first checking wider values and then narrower values.
|
||||
match matching_set
|
||||
.iter()
|
||||
.filter(|&&index| candidates[index].stretch > query.stretch)
|
||||
.min_by_key(|&&index| FloatOrd(candidates[index].stretch.0 - query.stretch.0))
|
||||
{
|
||||
Some(&matching_index) => candidates[matching_index].stretch,
|
||||
None => {
|
||||
let matching_index = *matching_set
|
||||
.iter()
|
||||
.min_by_key(|&&index| FloatOrd(query.stretch.0 - candidates[index].stretch.0))
|
||||
.unwrap();
|
||||
candidates[matching_index].stretch
|
||||
}
|
||||
}
|
||||
};
|
||||
matching_set.retain(|&index| candidates[index].stretch == matching_stretch);
|
||||
|
||||
// Step 4b (`font-style`).
|
||||
let style_preference = match query.style {
|
||||
Style::Italic => [Style::Italic, Style::Oblique, Style::Normal],
|
||||
Style::Oblique => [Style::Oblique, Style::Italic, Style::Normal],
|
||||
Style::Normal => [Style::Normal, Style::Oblique, Style::Italic],
|
||||
};
|
||||
let matching_style = *style_preference
|
||||
.iter()
|
||||
.find(|&query_style| {
|
||||
matching_set.iter().any(|&index| candidates[index].style == *query_style)
|
||||
})
|
||||
.unwrap();
|
||||
matching_set.retain(|&index| candidates[index].style == matching_style);
|
||||
|
||||
// Step 4c (`font-weight`).
|
||||
//
|
||||
// The spec doesn't say what to do if the weight is between 400 and 500 exclusive, so we
|
||||
// just use 450 as the cutoff.
|
||||
let matching_weight =
|
||||
if matching_set.iter().any(|&index| candidates[index].weight == query.weight) {
|
||||
query.weight
|
||||
} else if query.weight >= Weight(400.0)
|
||||
&& query.weight < Weight(450.0)
|
||||
&& matching_set.iter().any(|&index| candidates[index].weight == Weight(500.0))
|
||||
{
|
||||
// Check 500 first.
|
||||
Weight(500.0)
|
||||
} else if query.weight >= Weight(450.0)
|
||||
&& query.weight <= Weight(500.0)
|
||||
&& matching_set.iter().any(|&index| candidates[index].weight == Weight(400.0))
|
||||
{
|
||||
// Check 400 first.
|
||||
Weight(400.0)
|
||||
} else if query.weight <= Weight(500.0) {
|
||||
// Closest weight, first checking thinner values and then fatter ones.
|
||||
match matching_set
|
||||
.iter()
|
||||
.filter(|&&index| candidates[index].weight <= query.weight)
|
||||
.min_by_key(|&&index| FloatOrd(query.weight.0 - candidates[index].weight.0))
|
||||
{
|
||||
Some(&matching_index) => candidates[matching_index].weight,
|
||||
None => {
|
||||
let matching_index = *matching_set
|
||||
.iter()
|
||||
.min_by_key(|&&index| FloatOrd(candidates[index].weight.0 - query.weight.0))
|
||||
.unwrap();
|
||||
candidates[matching_index].weight
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Closest weight, first checking fatter values and then thinner ones.
|
||||
match matching_set
|
||||
.iter()
|
||||
.filter(|&&index| candidates[index].weight >= query.weight)
|
||||
.min_by_key(|&&index| FloatOrd(candidates[index].weight.0 - query.weight.0))
|
||||
{
|
||||
Some(&matching_index) => candidates[matching_index].weight,
|
||||
None => {
|
||||
let matching_index = *matching_set
|
||||
.iter()
|
||||
.min_by_key(|&&index| FloatOrd(query.weight.0 - candidates[index].weight.0))
|
||||
.unwrap();
|
||||
candidates[matching_index].weight
|
||||
}
|
||||
}
|
||||
};
|
||||
matching_set.retain(|&index| candidates[index].weight == matching_weight);
|
||||
|
||||
// Step 4d concerns `font-size`, but fonts in `font-kit` are unsized, so we ignore that.
|
||||
|
||||
// Return the result.
|
||||
matching_set.into_iter().next().ok_or(SelectionError::NotFound)
|
||||
}
|
||||
104
objdiff-gui/src/fonts/mod.rs
Normal file
104
objdiff-gui/src/fonts/mod.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
pub mod matching;
|
||||
|
||||
use std::{borrow::Cow, fs, sync::Arc};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::fonts::matching::find_best_match;
|
||||
|
||||
pub struct LoadedFontFamily {
|
||||
pub family_name: String,
|
||||
pub fonts: Vec<font_kit::font::Font>,
|
||||
pub handles: Vec<font_kit::handle::Handle>,
|
||||
pub properties: Vec<font_kit::properties::Properties>,
|
||||
pub default_index: usize,
|
||||
}
|
||||
|
||||
pub struct LoadedFont {
|
||||
pub font_name: String,
|
||||
pub font_data: egui::FontData,
|
||||
}
|
||||
|
||||
pub fn load_font_family(
|
||||
source: &font_kit::source::SystemSource,
|
||||
name: &str,
|
||||
) -> Option<LoadedFontFamily> {
|
||||
let family_handle = source.select_family_by_name(name).ok()?;
|
||||
if family_handle.fonts().is_empty() {
|
||||
log::warn!("No fonts found for family '{}'", name);
|
||||
return None;
|
||||
}
|
||||
let handles = family_handle.fonts().to_vec();
|
||||
let mut loaded = Vec::with_capacity(handles.len());
|
||||
for handle in handles.iter() {
|
||||
match font_kit::loaders::default::Font::from_handle(handle) {
|
||||
Ok(font) => loaded.push(font),
|
||||
Err(err) => {
|
||||
log::warn!("Failed to load font '{}': {}", name, err);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
let properties = loaded.iter().map(|f| f.properties()).collect::<Vec<_>>();
|
||||
let default_index =
|
||||
find_best_match(&properties, &font_kit::properties::Properties::new()).unwrap_or(0);
|
||||
let font_family_name =
|
||||
loaded.first().map(|f| f.family_name()).unwrap_or_else(|| name.to_string());
|
||||
Some(LoadedFontFamily {
|
||||
family_name: font_family_name,
|
||||
fonts: loaded,
|
||||
handles,
|
||||
properties,
|
||||
default_index,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_font(handle: &font_kit::handle::Handle) -> Result<LoadedFont> {
|
||||
let loaded = font_kit::loaders::default::Font::from_handle(handle)?;
|
||||
let data = match handle {
|
||||
font_kit::handle::Handle::Memory { bytes, font_index } => egui::FontData {
|
||||
font: Cow::Owned(bytes.to_vec()),
|
||||
index: *font_index,
|
||||
tweak: Default::default(),
|
||||
},
|
||||
font_kit::handle::Handle::Path { path, font_index } => {
|
||||
let vec = fs::read(path).with_context(|| {
|
||||
format!("Failed to load font '{}' (index {})", path.display(), font_index)
|
||||
})?;
|
||||
egui::FontData { font: Cow::Owned(vec), index: *font_index, tweak: Default::default() }
|
||||
}
|
||||
};
|
||||
Ok(LoadedFont { font_name: loaded.full_name(), font_data: data })
|
||||
}
|
||||
|
||||
pub fn load_font_if_needed(
|
||||
ctx: &egui::Context,
|
||||
source: &font_kit::source::SystemSource,
|
||||
font_id: &egui::FontId,
|
||||
base_family: egui::FontFamily,
|
||||
fonts: &mut egui::FontDefinitions,
|
||||
) -> Result<()> {
|
||||
if fonts.families.contains_key(&font_id.family) {
|
||||
return Ok(());
|
||||
}
|
||||
let family_name = match &font_id.family {
|
||||
egui::FontFamily::Proportional | egui::FontFamily::Monospace => return Ok(()),
|
||||
egui::FontFamily::Name(v) => v,
|
||||
};
|
||||
let family = load_font_family(source, family_name)
|
||||
.with_context(|| format!("Failed to load font family '{}'", family_name))?;
|
||||
let default_fonts = fonts.families.get(&base_family).cloned().unwrap_or_default();
|
||||
// FIXME clean up
|
||||
let default_font_ref = family.fonts.get(family.default_index).unwrap();
|
||||
let default_font = family.handles.get(family.default_index).unwrap();
|
||||
let default_font_data = load_font(default_font).unwrap();
|
||||
log::info!("Loaded font family '{}'", family.family_name);
|
||||
fonts.font_data.insert(default_font_ref.full_name(), default_font_data.font_data);
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Name(Arc::from(family.family_name)))
|
||||
.or_insert_with(|| default_fonts)
|
||||
.insert(0, default_font_ref.full_name());
|
||||
ctx.set_fonts(fonts.clone());
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user