381 lines
9.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Utilities for Typst.
pub mod fat;
#[macro_use]
mod macros;
mod bitset;
mod deferred;
mod duration;
mod hash;
mod pico;
mod round;
mod scalar;
pub use self::bitset::{BitSet, SmallBitSet};
pub use self::deferred::Deferred;
pub use self::duration::format_duration;
pub use self::hash::{LazyHash, ManuallyHash};
pub use self::pico::{PicoStr, ResolvedPicoStr};
pub use self::round::{round_int_with_precision, round_with_precision};
pub use self::scalar::Scalar;
#[doc(hidden)]
pub use once_cell;
use std::fmt::{Debug, Formatter};
use std::hash::Hash;
use std::iter::{Chain, Flatten, Rev};
use std::num::NonZeroUsize;
use std::ops::{Add, Deref, Div, Mul, Neg, Sub};
use std::sync::Arc;
use siphasher::sip128::{Hasher128, SipHasher13};
use unicode_math_class::MathClass;
/// Turn a closure into a struct implementing [`Debug`].
pub fn debug<F>(f: F) -> impl Debug
where
F: Fn(&mut Formatter) -> std::fmt::Result,
{
struct Wrapper<F>(F);
impl<F> Debug for Wrapper<F>
where
F: Fn(&mut Formatter) -> std::fmt::Result,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0(f)
}
}
Wrapper(f)
}
/// Calculate a 128-bit siphash of a value.
pub fn hash128<T: Hash + ?Sized>(value: &T) -> u128 {
let mut state = SipHasher13::new();
value.hash(&mut state);
state.finish128().as_u128()
}
/// An extra constant for [`NonZeroUsize`].
pub trait NonZeroExt {
/// The number `1`.
const ONE: Self;
}
impl NonZeroExt for NonZeroUsize {
const ONE: Self = match Self::new(1) {
Some(v) => v,
None => unreachable!(),
};
}
/// Extra methods for [`Arc`].
pub trait ArcExt<T> {
/// Takes the inner value if there is exactly one strong reference and
/// clones it otherwise.
fn take(self) -> T;
}
impl<T: Clone> ArcExt<T> for Arc<T> {
fn take(self) -> T {
match Arc::try_unwrap(self) {
Ok(v) => v,
Err(rc) => (*rc).clone(),
}
}
}
/// Extra methods for [`Option`].
pub trait OptionExt<T> {
/// Maps an `Option<T>` to `U` by applying a function to a contained value
/// (if `Some`) or returns a default (if `None`).
fn map_or_default<U: Default, F>(self, f: F) -> U
where
F: FnOnce(T) -> U;
}
impl<T> OptionExt<T> for Option<T> {
fn map_or_default<U: Default, F>(self, f: F) -> U
where
F: FnOnce(T) -> U,
{
match self {
Some(x) => f(x),
None => U::default(),
}
}
}
/// Extra methods for [`[T]`](slice).
pub trait SliceExt<T> {
/// Returns a slice with all matching elements from the start of the slice
/// removed.
fn trim_start_matches<F>(&self, f: F) -> &[T]
where
F: FnMut(&T) -> bool;
/// Returns a slice with all matching elements from the end of the slice
/// removed.
fn trim_end_matches<F>(&self, f: F) -> &[T]
where
F: FnMut(&T) -> bool;
/// Split a slice into consecutive runs with the same key and yield for
/// each such run the key and the slice of elements with that key.
fn group_by_key<K, F>(&self, f: F) -> GroupByKey<'_, T, F>
where
F: FnMut(&T) -> K,
K: PartialEq;
/// Computes two indices which split a slice into three parts.
///
/// - A prefix which matches `f`
/// - An inner portion
/// - A suffix which matches `f` and does not overlap with the prefix
///
/// If all elements match `f`, the prefix becomes `self` and the suffix
/// will be empty.
///
/// Returns the indices at which the inner portion and the suffix start.
fn split_prefix_suffix<F>(&self, f: F) -> (usize, usize)
where
F: FnMut(&T) -> bool;
}
impl<T> SliceExt<T> for [T] {
fn trim_start_matches<F>(&self, mut f: F) -> &[T]
where
F: FnMut(&T) -> bool,
{
let len = self.len();
let mut i = 0;
while i < len && f(&self[i]) {
i += 1;
}
&self[i..]
}
fn trim_end_matches<F>(&self, mut f: F) -> &[T]
where
F: FnMut(&T) -> bool,
{
let mut i = self.len();
while i > 0 && f(&self[i - 1]) {
i -= 1;
}
&self[..i]
}
fn group_by_key<K, F>(&self, f: F) -> GroupByKey<'_, T, F> {
GroupByKey { slice: self, f }
}
fn split_prefix_suffix<F>(&self, mut f: F) -> (usize, usize)
where
F: FnMut(&T) -> bool,
{
let start = self.iter().position(|v| !f(v)).unwrap_or(self.len());
let end = self
.iter()
.skip(start)
.rposition(|v| !f(v))
.map_or(start, |i| start + i + 1);
(start, end)
}
}
/// This struct is created by [`SliceExt::group_by_key`].
pub struct GroupByKey<'a, T, F> {
slice: &'a [T],
f: F,
}
impl<'a, T, K, F> Iterator for GroupByKey<'a, T, F>
where
F: FnMut(&T) -> K,
K: PartialEq,
{
type Item = (K, &'a [T]);
fn next(&mut self) -> Option<Self::Item> {
let mut iter = self.slice.iter();
let key = (self.f)(iter.next()?);
let count = 1 + iter.take_while(|t| (self.f)(t) == key).count();
let (head, tail) = self.slice.split_at(count);
self.slice = tail;
Some((key, head))
}
}
/// Adapter for reversing iterators conditionally.
pub trait MaybeReverseIter {
type RevIfIter;
/// Reverse this iterator (apply .rev()) based on some condition.
fn rev_if(self, condition: bool) -> Self::RevIfIter
where
Self: Sized;
}
impl<I: Iterator + DoubleEndedIterator> MaybeReverseIter for I {
type RevIfIter =
Chain<Flatten<std::option::IntoIter<I>>, Flatten<std::option::IntoIter<Rev<I>>>>;
fn rev_if(self, condition: bool) -> Self::RevIfIter
where
Self: Sized,
{
let (maybe_self_iter, maybe_rev_iter) =
if condition { (None, Some(self.rev())) } else { (Some(self), None) };
maybe_self_iter
.into_iter()
.flatten()
.chain(maybe_rev_iter.into_iter().flatten())
}
}
/// Check if the [`Option`]-wrapped L is same to R.
pub fn option_eq<L, R>(left: Option<L>, other: R) -> bool
where
L: PartialEq<R>,
{
left.is_some_and(|v| v == other)
}
/// A container around a static reference that is cheap to clone and hash.
#[derive(Debug)]
pub struct Static<T: 'static>(pub &'static T);
impl<T> Deref for Static<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<T> Copy for Static<T> {}
impl<T> Clone for Static<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Eq for Static<T> {}
impl<T> PartialEq for Static<T> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.0, other.0)
}
}
impl<T> Hash for Static<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_usize(self.0 as *const _ as _);
}
}
/// Generic access to a structure's components.
pub trait Get<Index> {
/// The structure's component type.
type Component;
/// Borrow the component for the specified index.
fn get_ref(&self, index: Index) -> &Self::Component;
/// Borrow the component for the specified index mutably.
fn get_mut(&mut self, index: Index) -> &mut Self::Component;
/// Convenience method for getting a copy of a component.
fn get(self, index: Index) -> Self::Component
where
Self: Sized,
Self::Component: Copy,
{
*self.get_ref(index)
}
/// Convenience method for setting a component.
fn set(&mut self, index: Index, component: Self::Component) {
*self.get_mut(index) = component;
}
/// Builder-style method for setting a component.
fn with(mut self, index: Index, component: Self::Component) -> Self
where
Self: Sized,
{
self.set(index, component);
self
}
}
/// A numeric type.
pub trait Numeric:
Sized
+ Debug
+ Copy
+ PartialEq
+ Neg<Output = Self>
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<f64, Output = Self>
+ Div<f64, Output = Self>
{
/// The identity element for addition.
fn zero() -> Self;
/// Whether `self` is zero.
fn is_zero(self) -> bool {
self == Self::zero()
}
/// Whether `self` consists only of finite parts.
fn is_finite(self) -> bool;
}
/// Returns the default math class of a character in Typst, if it has one.
///
/// This is determined by the Unicode math class, with some manual overrides.
pub fn default_math_class(c: char) -> Option<MathClass> {
match c {
// Better spacing.
// https://github.com/typst/typst/commit/2e039cb052fcb768027053cbf02ce396f6d7a6be
':' => Some(MathClass::Relation),
// Better spacing when used alongside + PLUS SIGN.
// https://github.com/typst/typst/pull/1726
'⋯' | '⋱' | '⋰' | '⋮' => Some(MathClass::Normal),
// Better spacing.
// https://github.com/typst/typst/pull/1855
'.' | '/' => Some(MathClass::Normal),
// ⊥ UP TACK should not be a relation, contrary to ⟂ PERPENDICULAR.
// https://github.com/typst/typst/pull/5714
'\u{22A5}' => Some(MathClass::Normal),
// Used as a binary connector in linear logic, where it is referred to
// as "par".
// https://github.com/typst/typst/issues/5764
'⅋' => Some(MathClass::Binary),
// Those overrides should become the default in the next revision of
// MathClass.txt.
// https://github.com/typst/typst/issues/5764#issuecomment-2632435247
'⎰' | '⟅' => Some(MathClass::Opening),
'⎱' | '⟆' => Some(MathClass::Closing),
// Both and ⟑ are classified as Binary.
// https://github.com/typst/typst/issues/5764
'⟇' => Some(MathClass::Binary),
c => unicode_math_class::class(c),
}
}