Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Support for Custom Fonts #385

Merged
merged 3 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ set -ex

main() {
local cargo=cross

# all features except those that don't easily work with cross such as font-loading
local all_features="--features std,more-image-formats,image-shrinking,rendering,software-rendering,wasm-web,networking"

if [ "$SKIP_CROSS" = "skip" ]; then
cargo=cargo
all_features="--all-features"
fi

if [ "$TARGET" = "wasm32-wasi" ]; then
Expand All @@ -13,7 +18,7 @@ main() {
return
fi

$cargo test -p livesplit-core --all-features --target $TARGET
$cargo test -p livesplit-core $all_features --target $TARGET
$cargo test -p livesplit-core --no-default-features --features std --target $TARGET
}

Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ lyon = { version = "0.16.0", default-features = false, optional = true }
rustybuzz = { version = "0.3.0", optional = true }
ttf-parser = { version = "0.9.0", optional = true }

# Font Loading
font-kit = { version = "0.10.0", optional = true }

# Software Rendering
euc = { version = "0.5.2", default-features = false, features = ["libm"], optional = true }
vek = { version = "0.12.1", default-features = false, features = ["libm"], optional = true }
Expand All @@ -86,6 +89,7 @@ std = ["byteorder", "chrono/std", "chrono/clock", "euc/std", "image", "indexmap"
more-image-formats = ["image/webp", "image/pnm", "image/ico", "image/jpeg", "image/tiff", "image/tga", "image/bmp", "image/hdr"]
image-shrinking = ["std", "bytemuck", "more-image-formats"]
rendering = ["std", "more-image-formats", "euclid", "lyon", "ttf-parser", "rustybuzz", "bytemuck/derive"]
font-loading = ["std", "rendering", "font-kit"]
software-rendering = ["rendering", "euc", "vek", "derive_more/mul"]
wasm-web = ["std", "web-sys", "chrono/wasmbind", "livesplit-hotkey/wasm-web"]
networking = ["std", "splits-io-api"]
Expand Down
90 changes: 90 additions & 0 deletions capi/bind_gen/src/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ export type Alignment = "Auto" | "Left" | "Center";
export interface LayoutStateJson {
/** The state objects for all of the components in the layout. */
components: ComponentStateJson[],
/** The direction which the components are laid out in. */
direction: LayoutDirection,
/**
* The font to use for the timer text. `null` means a default font should be
* used.
*/
timer_font: Font | null,
/**
* The font to use for the times and other values. `null` means a default
* font should be used.
*/
times_font: Font | null,
/**
* The font to use for regular text. `null` means a default font should be
* used.
*/
text_font: Font | null,
/** The background to show behind the layout. */
background: Gradient,
/** The color of thin separators. */
Expand All @@ -52,6 +69,78 @@ export interface LayoutStateJson {
text_color: Color,
}

/**
* Describes a Font to visualize text with. Depending on the platform a font
* that matches the settings most closely is chosen. The settings may be ignored
* entirely if the platform can't support different fonts such as in a terminal
* for example.
*/
export interface Font {
/**
* The family name of the font to use. This corresponds with the
* `Typographic Family Name` (Name ID 16) in the name table of the font. If
* no such entry exists, the `Font Family Name` (Name ID 1) is to be used
* instead. If there are multiple entries for the name, the english entry is
* the one to choose. The subfamily is not specified at all and instead a
* suitable subfamily is chosen based on the style, weight and stretch
* values. https://docs.microsoft.com/en-us/typography/opentype/spec/name
*
* This is to ensure the highest portability across various platforms.
* Platforms often select fonts very differently, so if necessary it is also
* fine to store a different font identifier here at the cost of sacrificing
* portability.
*/
family: string,
/** The style of the font to prefer selecting. */
style: FontStyle,
/** The weight of the font to prefer selecting. */
weight: FontWeight,
/** The stretch of the font to prefer selecting. */
stretch: FontStretch,
}

/**
* The style specifies whether to use a normal or italic version of a font. The
* style may be emulated if no font dedicated to the style can be found.
*/
export type FontStyle = "normal" | "italic";

/**
* The weight specifies the weight / boldness of a font. If there is no font
* with the exact weight value, a font with a similar weight is to be chosen
* based on an algorithm similar to this:
* https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#Fallback_weights
*/
export type FontWeight =
"thin" |
"extra-light" |
"light" |
"semi-light" |
"normal" |
"medium" |
"semi-bold" |
"bold" |
"extra-bold" |
"black" |
"extra-black";

/**
* The stretch specifies how wide a font should be. For example it may make
* sense to reduce the stretch of a font to ensure split names are not cut off.
* A font with a stretch value that is close is to be selected.
* https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch#Font_face_selection
*/
export type FontStretch =
"ultra-condensed" |
"extra-condensed" |
"condensed" |
"semi-condensed" |
"normal" |
"semi-expanded" |
"expanded" |
"extra-expanded" |
"ultra-expanded";

/**
* A Timing Method describes which form of timing is used. This can either be
* Real Time or Game Time.
Expand Down Expand Up @@ -523,6 +612,7 @@ export type SettingsDescriptionValueJson =
{ ColumnUpdateTrigger: ColumnUpdateTrigger } |
{ Hotkey: string } |
{ LayoutDirection: LayoutDirection } |
{ Font: Font | null } |
{ CustomCombobox: CustomCombobox };

/** Describes the direction the components of a layout are laid out in. */
Expand Down
11 changes: 5 additions & 6 deletions capi/src/parse_run_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ use livesplit_core::run::parser::{
composite::{ParsedRun, Result},
TimerKind,
};
use std::io::Write;
use std::os::raw::c_char;
use std::{io::Write, os::raw::c_char};

/// type
pub type ParseRunResult = Result<ParsedRun>;
Expand Down Expand Up @@ -47,11 +46,11 @@ pub extern "C" fn ParseRunResult_timer_kind(this: &ParseRunResult) -> *const c_c
/// timer format was parsed, instead of one of the more specific timer formats.
#[no_mangle]
pub extern "C" fn ParseRunResult_is_generic_timer(this: &ParseRunResult) -> bool {
match this {
matches!(
this,
Ok(ParsedRun {
kind: TimerKind::Generic(_),
..
}) => true,
_ => false,
}
})
)
}
67 changes: 63 additions & 4 deletions capi/src/setting_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
//! types.

use crate::str;
use livesplit_core::component::splits::{ColumnStartWith, ColumnUpdateTrigger, ColumnUpdateWith};
use livesplit_core::settings::{Alignment, Color, Gradient, ListGradient, Value as SettingValue};
use livesplit_core::timing::formatter::{Accuracy, DigitsFormat};
use livesplit_core::{layout::LayoutDirection, TimingMethod};
use livesplit_core::{
component::splits::{ColumnStartWith, ColumnUpdateTrigger, ColumnUpdateWith},
layout::LayoutDirection,
settings::{
Alignment, Color, Font, FontStretch, FontStyle, FontWeight, Gradient, ListGradient,
Value as SettingValue,
},
timing::formatter::{Accuracy, DigitsFormat},
TimingMethod,
};
use std::os::raw::c_char;

/// type
Expand Down Expand Up @@ -295,3 +301,56 @@ pub unsafe extern "C" fn SettingValue_from_layout_direction(
};
Some(Box::new(value.into()))
}

/// Creates a new setting value with the type `font`.
#[no_mangle]
pub unsafe extern "C" fn SettingValue_from_font(
family: *const c_char,
style: *const c_char,
weight: *const c_char,
stretch: *const c_char,
) -> NullableOwnedSettingValue {
Some(Box::new(
Some(Font {
family: str(family).to_owned(),
style: match str(style) {
"normal" => FontStyle::Normal,
"italic" => FontStyle::Italic,
_ => return None,
},
weight: match str(weight) {
"thin" => FontWeight::Thin,
"extra-light" => FontWeight::ExtraLight,
"light" => FontWeight::Light,
"semi-light" => FontWeight::SemiLight,
"normal" => FontWeight::Normal,
"medium" => FontWeight::Medium,
"semi-bold" => FontWeight::SemiBold,
"bold" => FontWeight::Bold,
"extra-bold" => FontWeight::ExtraBold,
"black" => FontWeight::Black,
"extra-black" => FontWeight::ExtraBlack,
_ => return None,
},
stretch: match str(stretch) {
"ultra-condensed" => FontStretch::UltraCondensed,
"extra-condensed" => FontStretch::ExtraCondensed,
"condensed" => FontStretch::Condensed,
"semi-condensed" => FontStretch::SemiCondensed,
"normal" => FontStretch::Normal,
"semi-expanded" => FontStretch::SemiExpanded,
"expanded" => FontStretch::Expanded,
"extra-expanded" => FontStretch::ExtraExpanded,
"ultra-expanded" => FontStretch::UltraExpanded,
_ => return None,
},
})
.into(),
))
}

/// Creates a new empty setting value with the type `font`.
#[no_mangle]
pub extern "C" fn SettingValue_from_empty_font() -> OwnedSettingValue {
Box::new(None::<Font>.into())
}
6 changes: 1 addition & 5 deletions capi/src/text_component_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,5 @@ pub extern "C" fn TextComponentState_center(this: &TextComponentState) -> *const
/// Returns whether the text is split up into a left and right part.
#[no_mangle]
pub extern "C" fn TextComponentState_is_split(this: &TextComponentState) -> bool {
if let TextState::Split(_, _) = this.text {
true
} else {
false
}
matches!(this.text, TextState::Split(_, _))
}
4 changes: 2 additions & 2 deletions crates/livesplit-title-abbreviations/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,12 @@ pub fn abbreviate_category(category: &str) -> Vec<Box<str>> {
buf.push_str(variable);
let old_len = buf.len();

buf.push_str(")");
buf.push(')');
buf.push_str(after);
abbrevs.push(buf.as_str().into());

buf.drain(old_len..);
buf.push_str(",");
buf.push(',');
variable = next_variable;
}

Expand Down
10 changes: 6 additions & 4 deletions src/component/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
//! the chosen comparison throughout the whole attempt. All the individual
//! deltas are shown as points in a graph.

use crate::platform::prelude::*;
use crate::settings::{Color, Field, SettingsDescription, Value};
use crate::{
analysis, comparison, timing::Snapshot, GeneralLayoutSettings, TimeSpan, Timer, TimerPhase,
analysis, comparison,
platform::prelude::*,
settings::{Color, Field, SettingsDescription, Value},
timing::Snapshot,
GeneralLayoutSettings, TimeSpan, Timer, TimerPhase,
};
use alloc::borrow::Cow;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -442,7 +444,7 @@ impl Component {
let current_time = timer.current_time();
let timing_method = timer.current_timing_method();
draw_info.final_split = current_time[timing_method]
.or_else(|| current_time.real_time)
.or(current_time.real_time)
.unwrap_or_else(TimeSpan::zero);
} else {
let timing_method = timer.current_timing_method();
Expand Down
14 changes: 4 additions & 10 deletions src/component/splits/column.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::platform::prelude::*;
use crate::{
analysis::{self, possible_time_save, split_color},
clear_vec::Clear,
comparison,
platform::prelude::*,
settings::{Color, SemanticColor},
timing::{
formatter::{Delta, Regular, SegmentTime, TimeFormatter},
Expand Down Expand Up @@ -148,7 +148,7 @@ pub fn update_state(
current_split: Option<usize>,
method: TimingMethod,
) {
let method = column.timing_method.unwrap_or_else(|| method);
let method = column.timing_method.unwrap_or(method);
let resolved_comparison = comparison::resolve(&column.comparison_override, timer);
let comparison = comparison::or_current(resolved_comparison, timer);

Expand Down Expand Up @@ -328,16 +328,10 @@ fn column_update_value(
impl ColumnUpdateWith {
fn is_segment_based(self) -> bool {
use self::ColumnUpdateWith::*;
match self {
SegmentDelta | SegmentTime | SegmentDeltaWithFallback => true,
_ => false,
}
matches!(self, SegmentDelta | SegmentTime | SegmentDeltaWithFallback)
}
fn has_fallback(self) -> bool {
use self::ColumnUpdateWith::*;
match self {
DeltaWithFallback | SegmentDeltaWithFallback => true,
_ => false,
}
matches!(self, DeltaWithFallback | SegmentDeltaWithFallback)
}
}
4 changes: 2 additions & 2 deletions src/component/text/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
//! a situation where you have a label and a value.

use super::key_value;
use crate::platform::prelude::*;
use crate::{
platform::prelude::*,
settings::{Color, Field, Gradient, SettingsDescription, Value},
timing::formatter,
Timer,
Expand Down Expand Up @@ -192,7 +192,7 @@ impl Component {
let mut name = String::with_capacity(left.len() + right.len() + 1);
name.push_str(left);
if !left.is_empty() && !right.is_empty() {
name.push_str(" ");
name.push(' ');
}
name.push_str(right);
name.into()
Expand Down
Loading