Commit 683c55e8 authored by Alex Butler's avatar Alex Butler

Merge branch 'otf' into 'master'

Support .otf fonts by switching to ttf-parser & ab_glyph_rasterizer

Closes #137

See merge request !151
parents 767a7275 069cec55
Pipeline #7304 passed with stages
in 6 minutes and 55 seconds
......@@ -43,7 +43,7 @@ test:stable:gpu_cache:
script:
- cargo +stable test --features 'gpu_cache'
# Heavier testing using rusttype-dev
# Heavier testing using "dev"
test:dev:
stage: test
variables:
......
## Unreleased
* Major rework to use crates **ttf-parser** & **ab_glyph_rasterizer** to respectively read and render OpenType .oft format fonts.
* Remove dependencies **approx**, **stb_truetype** & **ordered-float** along with in-crate rasterization code.
* Strip back some non-vital API functionality.
- Remove support for `.standalone()` variants which are sparsely used.
- Remove some functions that didn't immediately translate to ttf-parser. Please raise issues to re-add any you relied on via the new stack.
## 0.8.3
* Remove arrayvec dependency.
* Add `Default` implementations for geometry structs.
......
......@@ -26,9 +26,8 @@ exclude = ["/dev/**"]
features = ["gpu_cache"]
[dependencies]
stb_truetype = { version = "0.3.1", default-features = false }
ordered-float = { version = "1", default-features = false }
approx = { version = "0.3", default-features = false }
ttf-parser = { version = "0.5", default-features = false }
ab_glyph_rasterizer = { version = "0.1", default-features = false }
libm = { version = "0.2.1", default-features = false, optional = true }
......@@ -41,14 +40,15 @@ crossbeam-utils = { version = "0.7", optional = true }
num_cpus = { version = "1.0", optional = true }
[dev-dependencies]
# don't add any, instead use ./dev
# don't add any more, instead use ./dev
approx = { version = "0.3", default-features = false }
[features]
default = ["std", "has-atomics"]
default = ["std"]
# Activates usage of std.
std = ["has-atomics", "stb_truetype/std"]
std = ["has-atomics", "ttf-parser/default", "ab_glyph_rasterizer/default"]
# Uses libm when not using std. This needs to be active in that case.
libm-math = ["libm", "stb_truetype/libm"]
libm-math = ["libm", "ab_glyph_rasterizer/libm"]
# Some targets don't have atomics, this activates usage of Arc<T> instead of Rc<T>.
has-atomics = []
# Adds `gpu_cache` module
......
......@@ -6,26 +6,22 @@ RustType is a pure Rust alternative to libraries like FreeType.
The current capabilities of RustType:
* Reading TrueType formatted fonts and font collections. This includes `*.ttf`
as well as a subset of `*.otf` font files.
* Reading OpenType formatted fonts and font collections. This includes `*.ttf`
as well as `*.otf` font files.
* Retrieving glyph shapes and commonly used properties for a font and its glyphs.
* Laying out glyphs horizontally using horizontal and vertical metrics, and
glyph-pair-specific kerning.
* Rasterising glyphs with sub-pixel positioning using an accurate analytical
algorithm (not based on sampling).
* Managing a font cache on the GPU with the `gpu_cache` module. This keeps
recently used glyph renderings
in a dynamic cache in GPU memory to minimise texture uploads per-frame. It
also allows you keep the draw call count for text very low, as all glyphs are
kept in one GPU texture.
recently used glyph renderings in a dynamic cache in GPU memory to minimise
texture uploads per-frame. It also allows you keep the draw call count for
text very low, as all glyphs are kept in one GPU texture.
Notable things that RustType does not support *yet*:
* OpenType formatted fonts that are not just TrueType fonts (OpenType is a
superset of TrueType). Notably there is no support yet for cubic Bezier curves
used in glyphs.
* Font hinting.
* Ligatures of any kind
* Ligatures of any kind.
* Some less common TrueType sub-formats.
* Right-to-left and vertical text layout.
......@@ -34,28 +30,21 @@ Heavier examples, tests & benchmarks are in the `./dev` directory. This avoids d
Run all tests with `cargo test --all --all-features`.
Run examples with `cargo run --example <NAME> -p rusttype-dev`
Run examples with `cargo run --example <NAME> -p dev`
## Getting Started
To hit the ground running with RustType, look at `dev/examples/simple.rs`
To hit the ground running with RustType, look at `dev/examples/ascii.rs`
supplied with the crate. It demonstrates loading a font file, rasterising an
arbitrary string, and displaying the result as ASCII art. If you prefer to just
look at the documentation, the entry point for loading fonts is
`FontCollection`, from which you can access individual fonts, then their glyphs.
look at the documentation, the entry point for loading fonts is `Font`,
from which you can access individual fonts, then their glyphs.
## Future Plans
The initial motivation for the project was to provide easy-to-use font rendering for games.
There are numerous avenues for improving RustType. Ideas:
* Some form of hinting for improved legibility at small font sizes.
* Replacing the dependency on
[stb_truetype-rs](https://gitlab.redox-os.org/redox-os/stb_truetype-rs)
(a translation of [stb_truetype.h](https://github.com/nothings/stb/blob/master/stb_truetype.h)),
with OpenType font loading written in idiomatic Rust.
* Add support for cubic curves in OpenType fonts.
* Extract the rasterisation code into a separate vector graphics rendering crate.
* Support for some common forms of ligatures.
* And, eventually, support for embedded right-to-left Unicode text.
......
[package]
name = "rusttype-dev"
name = "dev"
version = "0.1.0"
description = "Tests, examples & benchmarks avoiding dependency feature bleed"
edition = "2018"
......@@ -7,11 +7,12 @@ publish = false
[dev-dependencies]
rusttype = { path = "../", features = ["gpu_cache"] }
glium = "0.26"
glium = "0.27"
image = { version = "0.23", default-features = false, features = ["png"] }
once_cell = "1"
blake2 = "0.8"
criterion = "0.3"
ttf-parser = "0.5"
[[bench]]
name = "cache"
......
......@@ -67,7 +67,7 @@ static FONTS: Lazy<Vec<Font<'static>>> = Lazy::new(|| {
include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8],
]
.into_iter()
.map(|bytes| Font::from_bytes(bytes).unwrap())
.map(|bytes| Font::try_from_bytes(bytes).unwrap())
.collect()
});
......
use blake2::{Blake2s, Digest};
use criterion::{criterion_group, criterion_main, Criterion};
use once_cell::sync::Lazy;
use rusttype::*;
static DEJA_VU_MONO: Lazy<Font<'static>> = Lazy::new(|| {
Font::from_bytes(include_bytes!("../fonts/dejavu/DejaVuSansMono.ttf") as &[u8]).unwrap()
Font::try_from_bytes(include_bytes!("../fonts/dejavu/DejaVuSansMono.ttf") as &[u8]).unwrap()
});
static OPEN_SANS_ITALIC: Lazy<Font<'static>> = Lazy::new(|| {
Font::from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8]).unwrap()
Font::try_from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8]).unwrap()
});
static EXO2_OFT: Lazy<Font<'static>> = Lazy::new(|| {
Font::try_from_bytes(include_bytes!("../fonts/Exo2-Light.otf") as &[u8]).unwrap()
});
fn bench_draw_big_biohazard(c: &mut Criterion) {
fn draw_big_biohazard(c: &mut Criterion) {
let glyph = DEJA_VU_MONO
.glyph('☣')
.scaled(Scale::uniform(600.0))
......@@ -33,15 +35,10 @@ fn bench_draw_big_biohazard(c: &mut Criterion) {
target[WIDTH * y + x] = (alpha * 255.0) as u8;
})
});
// verify the draw result against static reference hash
assert_eq!(
format!("{:x}", Blake2s::digest(&target)),
"8e3927a33c6d563d45f82fb9620dea8036274b403523a2e98cd5f93eafdb2125"
);
});
}
fn bench_draw_w(c: &mut Criterion) {
fn draw_w(c: &mut Criterion) {
let glyph = DEJA_VU_MONO
.glyph('w')
.scaled(Scale::uniform(16.0))
......@@ -64,15 +61,10 @@ fn bench_draw_w(c: &mut Criterion) {
target[WIDTH * y + x] = (alpha * 255.0) as u8;
})
});
// verify the draw result against static reference hash
assert_eq!(
format!("{:x}", Blake2s::digest(&target)),
"c0e795601e3412144d1bfdc0cd94d9507aa9775a0f0f4f9862fe7ec7e83d7684"
);
});
}
fn bench_draw_iota(c: &mut Criterion) {
fn draw_iota(c: &mut Criterion) {
let glyph = OPEN_SANS_ITALIC
.glyph('ΐ')
.scaled(Scale::uniform(60.0))
......@@ -95,19 +87,41 @@ fn bench_draw_iota(c: &mut Criterion) {
target[WIDTH * y + x] = (alpha * 255.0) as u8;
})
});
// verify the draw result against static reference hash
assert_eq!(
format!("{:x}", Blake2s::digest(&target)),
"cdad348e38263a13f68ae41a95ce3b900d2881375a745232309ebd568a27cd4c"
);
});
}
fn draw_oft_tailed_e(c: &mut Criterion) {
let glyph = EXO2_OFT
.glyph('ę')
.scaled(Scale::uniform(300.0))
.positioned(point(0.0, 0.0));
const WIDTH: usize = 106;
const HEIGHT: usize = 183;
let bounds = glyph.pixel_bounding_box().unwrap();
assert_eq!(
(bounds.width() as usize, bounds.height() as usize),
(WIDTH, HEIGHT)
);
let mut target = [0u8; WIDTH * HEIGHT];
c.bench_function("draw_oft_tailed_e", |b| {
b.iter(|| {
glyph.draw(|x, y, alpha| {
let (x, y) = (x as usize, y as usize);
target[WIDTH * y + x] = (alpha * 255.0) as u8;
})
});
});
}
criterion_group!(
benches,
bench_draw_big_biohazard,
bench_draw_w,
bench_draw_iota,
draw_benches,
draw_big_biohazard,
draw_w,
draw_iota,
draw_oft_tailed_e,
);
criterion_main!(benches);
criterion_main!(draw_benches);
......@@ -10,7 +10,8 @@ fn bench_layout_a_sentence(c: &mut Criterion) {
clause and sometimes one or more subordinate clauses.";
let font =
Font::from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8]).unwrap();
Font::try_from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8])
.unwrap();
c.bench_function("layout_a_sentence", |b| {
let mut glyphs = vec![];
......@@ -39,8 +40,41 @@ fn bench_layout_a_sentence(c: &mut Criterion) {
"c2a3483ddf5598ec869440c62d17efa5a4fe72f9893bcc05dd17be2adcaa7629"
);
});
let font = Font::try_from_vec(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf").to_vec())
.unwrap();
c.bench_function("layout_a_sentence (vec)", |b| {
let mut glyphs = vec![];
b.iter(|| {
glyphs.clear();
glyphs.extend(font.layout(SENTENCE, Scale::uniform(25.0), point(100.0, 25.0)))
});
// verify the layout result against static reference hash
let mut hash = Blake2s::default();
for g in glyphs {
write!(
hash,
"{id}:{scale_x}:{scale_y}:{pos_x}:{pos_y}",
id = g.id().0,
scale_x = g.scale().x,
scale_y = g.scale().y,
pos_x = g.position().x,
pos_y = g.position().y,
)
.unwrap();
}
assert_eq!(
format!("{:x}", hash.result()),
"c2a3483ddf5598ec869440c62d17efa5a4fe72f9893bcc05dd17be2adcaa7629"
);
});
}
criterion_group!(benches, bench_layout_a_sentence);
criterion_group!(
name = benches;
config = Criterion::default().sample_size(400);
targets = bench_layout_a_sentence);
criterion_main!(benches);
use rusttype::{point, FontCollection, PositionedGlyph, Scale};
//! Render example where each glyph pixel is output as an ascii character.
use rusttype::{point, Font, Scale};
use std::io::Write;
fn main() {
let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
let collection = FontCollection::from_bytes(font_data as &[u8]).unwrap_or_else(|e| {
panic!("error constructing a FontCollection from bytes: {}", e);
});
let font = collection
.into_font() // only succeeds if collection consists of one font
.unwrap_or_else(|e| {
panic!("error turning FontCollection into a Font: {}", e);
});
let font = if let Some(font_path) = std::env::args().nth(1) {
let font_path = std::env::current_dir().unwrap().join(font_path);
let data = std::fs::read(&font_path).unwrap();
Font::try_from_vec(data).unwrap_or_else(|| {
panic!(format!(
"error constructing a Font from data at {:?}",
font_path
));
})
} else {
eprintln!("No font specified ... using WenQuanYiMicroHei.ttf");
let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
Font::try_from_bytes(font_data as &[u8]).expect("error constructing a Font from bytes")
};
// Desired font pixel height
let height: f32 = 12.4; // to get 80 chars across (fits most terminals); adjust as desired
......@@ -31,7 +37,7 @@ fn main() {
let offset = point(0.0, v_metrics.ascent);
// Glyphs to draw for "RustType". Feel free to try other strings.
let glyphs: Vec<PositionedGlyph<'_>> = font.layout("RustType", scale, offset).collect();
let glyphs: Vec<_> = font.layout("RustType", scale, offset).collect();
// Find the most visually pleasing width to display
let width = glyphs
......
......@@ -56,7 +56,7 @@ fn main() -> Result<(), Box<dyn Error>> {
}
let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
let font: Font<'static> = Font::from_bytes(font_data as &[u8])?;
let font = Font::try_from_bytes(font_data as &[u8]).unwrap();
let window = glium::glutin::window::WindowBuilder::new()
.with_inner_size(glium::glutin::dpi::PhysicalSize::new(512, 512))
......
......@@ -5,7 +5,7 @@ fn main() {
// Load the font
let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
// This only succeeds if collection consists of one font
let font = Font::from_bytes(font_data as &[u8]).expect("Error constructing Font");
let font = Font::try_from_bytes(font_data as &[u8]).expect("Error constructing Font");
// The font size to use
let scale = Scale::uniform(32.0);
......
use rusttype::*;
#[test]
fn static_lazy_shared_bytes() {
use once_cell::sync::Lazy;
static FONT_BYTES: Lazy<Vec<u8>> = Lazy::new(|| vec![0, 1, 2, 3]);
let shared_bytes: SharedBytes<'static> = (&*FONT_BYTES).into();
assert_eq!(&*shared_bytes, &[0, 1, 2, 3]);
}
......@@ -4,7 +4,7 @@ static ROBOTO_REGULAR: &[u8] = include_bytes!("../fonts/Roboto-Regular.ttf");
#[test]
fn consistent_bounding_box_subpixel_size_proxy() {
let font = Font::from_bytes(ROBOTO_REGULAR).unwrap();
let font = Font::try_from_bytes(ROBOTO_REGULAR).unwrap();
let height_at_y = |y| {
font.glyph('s')
.scaled(rusttype::Scale::uniform(20.0))
......@@ -15,18 +15,3 @@ fn consistent_bounding_box_subpixel_size_proxy() {
};
assert_eq!(height_at_y(50.833_336), height_at_y(110.833_336));
}
#[test]
fn consistent_bounding_box_subpixel_size_standalone() {
let font = Font::from_bytes(ROBOTO_REGULAR).unwrap();
let height_at_y = |y| {
font.glyph('s')
.standalone()
.scaled(rusttype::Scale::uniform(20.0))
.positioned(rusttype::Point { x: 0.0, y })
.pixel_bounding_box()
.unwrap()
.height()
};
assert_eq!(height_at_y(50.833_336), height_at_y(110.833_336));
}
use rusttype::*;
#[test]
fn move_and_use() {
let owned_data = include_bytes!("../fonts/opensans/OpenSans-Italic.ttf").to_vec();
let pin_font = Font::try_from_vec(owned_data).unwrap();
let ascent = pin_font.v_metrics_unscaled().ascent;
// force a move
let moved = Box::new(pin_font);
assert_eq!(moved.v_metrics_unscaled().ascent, ascent);
}
dev/tests/reference_big_biohazard.png

9.85 KB | W: | H:

dev/tests/reference_big_biohazard.png

7.27 KB | W: | H:

dev/tests/reference_big_biohazard.png
dev/tests/reference_big_biohazard.png
dev/tests/reference_big_biohazard.png
dev/tests/reference_big_biohazard.png
  • 2-up
  • Swipe
  • Onion skin
dev/tests/reference_iota.png

540 Bytes | W: | H:

dev/tests/reference_iota.png

403 Bytes | W: | H:

dev/tests/reference_iota.png
dev/tests/reference_iota.png
dev/tests/reference_iota.png
dev/tests/reference_iota.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -4,10 +4,13 @@ use rusttype::{point, Font, Scale, ScaledGlyph};
use std::io::Cursor;
static DEJA_VU_MONO: Lazy<Font<'static>> = Lazy::new(|| {
Font::from_bytes(include_bytes!("../fonts/dejavu/DejaVuSansMono.ttf") as &[u8]).unwrap()
Font::try_from_bytes(include_bytes!("../fonts/dejavu/DejaVuSansMono.ttf") as &[u8]).unwrap()
});
static OPEN_SANS_ITALIC: Lazy<Font<'static>> = Lazy::new(|| {
Font::from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8]).unwrap()
Font::try_from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8]).unwrap()
});
static EXO2_OFT: Lazy<Font<'static>> = Lazy::new(|| {
Font::try_from_bytes(include_bytes!("../fonts/Exo2-Light.otf") as &[u8]).unwrap()
});
fn draw_luma_alpha(glyph: ScaledGlyph<'_>) -> image::GrayAlphaImage {
......@@ -113,3 +116,33 @@ fn render_to_reference_iota() {
}
}
}
/// Render a 300px 'ę' character that uses cubic beziers & require it to match the reference.
#[test]
fn render_to_reference_oft_tailed_e() {
let new_image = draw_luma_alpha(EXO2_OFT.glyph('ę').scaled(Scale::uniform(300.0)));
// save the new render for manual inspection
new_image.save("../target/otf_tailed_e.png").unwrap();
let reference = image::load(
Cursor::new(include_bytes!("reference_otf_tailed_e.png") as &[u8]),
image::ImageFormat::Png,
)
.expect("!image::load")
.to_luma_alpha();
assert_eq!(reference.dimensions(), new_image.dimensions());
for y in 0..reference.height() {
for x in 0..reference.width() {
assert_eq!(
reference.get_pixel(x, y),
new_image.get_pixel(x, y),
"unexpected alpha difference at ({}, {})",
x,
y
);
}
}
}
This diff is collapsed.
use core::ops;
#[cfg(all(feature = "libm-math", not(feature = "std")))]
use crate::nostd_float::FloatExt;
/// A point in 2-dimensional space, with each dimension of type `N`.
///
/// Legal operations on points are addition and subtraction by vectors, and
......@@ -143,17 +140,6 @@ impl<N: ops::Add<Output = N>> ops::Add<Point<N>> for Vector<N> {
}
}
/// A straight line between two points, `p[0]` and `p[1]`
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)]
pub struct Line {
pub p: [Point<f32>; 2],
}
/// A quadratic Bezier curve, starting at `p[0]`, ending at `p[2]`, with control
/// point `p[1]`.
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)]
pub struct Curve {
pub p: [Point<f32>; 3],
}
/// A rectangle, with top-left corner at `min`, and bottom-right corner at
/// `max`.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
......@@ -183,175 +169,3 @@ pub trait BoundingBox<N> {
fn x_bounds(&self) -> (N, N);
fn y_bounds(&self) -> (N, N);
}
impl BoundingBox<f32> for Line {
fn x_bounds(&self) -> (f32, f32) {
let p = &self.p;
if p[0].x < p[1].x {
(p[0].x, p[1].x)
} else {
(p[1].x, p[0].x)
}
}
fn y_bounds(&self) -> (f32, f32) {
let p = &self.p;
if p[0].y < p[1].y {
(p[0].y, p[1].y)
} else {
(p[1].y, p[0].y)
}
}
}
impl BoundingBox<f32> for Curve {
fn x_bounds(&self) -> (f32, f32) {
let p = &self.p;
if p[0].x <= p[1].x && p[1].x <= p[2].x {
(p[0].x, p[2].x)
} else if p[0].x >= p[1].x && p[1].x >= p[2].x {
(p[2].x, p[0].x)
} else {
let t = (p[0].x - p[1].x) / (p[0].x - 2.0 * p[1].x + p[2].x);
let _1mt = 1.0 - t;
let inflection = _1mt * _1mt * p[0].x + 2.0 * _1mt * t * p[1].x + t * t * p[2].x;
if p[1].x < p[0].x {
(inflection, p[0].x.max(p[2].x))
} else {
(p[0].x.min(p[2].x), inflection)
}
}
}
fn y_bounds(&self) -> (f32, f32) {
let p = &self.p;
if p[0].y <= p[1].y && p[1].y <= p[2].y {
(p[0].y, p[2].y)
} else if p[0].y >= p[1].y && p[1].y >= p[2].y {
(p[2].y, p[0].y)
} else {
let t = (p[0].y - p[1].y) / (p[0].y - 2.0 * p[1].y + p[2].y);
let _1mt = 1.0 - t;
let inflection = _1mt * _1mt * p[0].y + 2.0 * _1mt * t * p[1].y + t * t * p[2].y;
if p[1].y < p[0].y {
(inflection, p[0].y.max(p[2].y))
} else {
(p[0].y.min(p[2].y), inflection)
}
}
}
}
pub trait Cut: Sized {
fn cut_to(self, t: f32) -> Self;
fn cut_from(self, t: f32) -> Self;
fn cut_from_to(self, t0: f32, t1: f32) -> Self {
self.cut_from(t0).cut_to((t1 - t0) / (1.0 - t0))
}
}
impl Cut for Curve {
fn cut_to(self, t: f32) -> Curve {
let p = self.p;
let a = p[0] + t * (p[1] - p[0]);
let b = p[1] + t * (p[2] - p[1]);
let c = a + t * (b - a);
Curve { p: [p[0], a, c] }
}
fn cut_from(self, t: f32) -> Curve {
let p = self.p;
let a = p[0] + t * (p[1] - p[0]);
let b = p[1] + t * (p[2] - p[1]);
let c = a + t * (b - a);
Curve { p: [c, b, p[2]] }
}
}
impl Cut for Line {
fn cut_to(self, t: f32) -> Line {
let p = self.p;
Line {
p: [p[0], p[0] + t * (p[1] - p[0])],
}
}
fn cut_from(self, t: f32) -> Line {
let p = self.p;
Line {
p: [p[0] + t * (p[1] - p[0]), p[1]],
}
}
fn cut_from_to(self, t0: f32, t1: f32) -> Line {
let p = self.p;
let v = p[1] - p[0];
Line {
p: [p[0] + t0 * v, p[0] + t1 * v],
}
}
}
/// The real valued solutions to a real quadratic equation.
#[derive(Copy, Clone, Debug)]
pub enum RealQuadraticSolution {
/// Two zero-crossing solutions
Two(f32, f32),
/// One zero-crossing solution (equation is a straight line)
One(f32),
/// One zero-touching solution
Touch(f32),
/// No solutions
None,
/// All real numbers are solutions since a == b == c == 0.0
All,
}
impl RealQuadraticSolution {
/// If there are two solutions, this function ensures that they are in order
/// (first < second)
pub fn in_order(self) -> RealQuadraticSolution {
use self::RealQuadraticSolution::*;
match self {
Two(x, y) => {
if x < y {
Two(x, y)
} else {
Two(y, x)
}
}
other => other,
}
}
}
/// Solve a real quadratic equation, giving all real solutions, if any.
pub fn solve_quadratic_real(a: f32, b: f32, c: f32) -> RealQuadraticSolution {
let discriminant = b * b - 4.0 * a * c;
if discriminant > 0.0 {
let sqrt_d = discriminant.sqrt();
let common = -b + if b >= 0.0 { -sqrt_d } else { sqrt_d };
let x1 = 2.0 * c / common;
if a == 0.0 {
RealQuadraticSolution::One(x1)
} else {
let x2 = common / (2.0 * a);
RealQuadraticSolution::Two(x1, x2)
}
} else if discriminant < 0.0 {
RealQuadraticSolution::None
} else if b == 0.0 {
if a == 0.0 {
if c == 0.0 {
RealQuadraticSolution::All
} else {
RealQuadraticSolution::None
}
} else {
RealQuadraticSolution::Touch(0.0)
}
} else {
RealQuadraticSolution::Touch(2.0 * c / -b)
}
}
#[test]
fn quadratic_test() {
solve_quadratic_real(-0.000_000_1, -2.0, 10.0);
}
......@@ -45,7 +45,7 @@
//! # use std::error::Error;
//! # fn example() -> Result<(), Box<dyn Error>> {
//! # let font_data: &[u8] = include_bytes!("../dev/fonts/dejavu/DejaVuSansMono.ttf");
//! # let font: Font<'static> = Font::from_bytes(font_data)?;
//! # let font: Font<'static> = Font::try_from_bytes(font_data).unwrap();
//! # let glyph = font.glyph('a').scaled(Scale::uniform(25.0)).positioned(point(0.0, 0.0));
//! # let glyph2 = glyph.clone();
//! # fn update_gpu_texture(_: rusttype::Rect<u32>, _: &[u8]) {};
......@@ -973,13 +973,13 @@ fn draw_glyph(tex_coords: Rect<u32>, glyph: &PositionedGlyph<'_>, pad_glyphs: bo
let mut pixels = ByteArray2d::zeros(tex_coords.height() as usize, tex_coords.width() as usize);
if pad_glyphs {
glyph.draw(|x, y, v| {
let v = (v * 255.0).round().max(0.0).min(255.0) as u8;
let v = (v * 255.0).round() as u8;
// `+ 1` accounts for top/left glyph padding
pixels[(y as usize + 1, x as usize + 1)] = v;
});
} else {
glyph.draw(|x, y, v| {
let v = (v * 255.0).round().max(0.0).min(255.0) as u8;
let v = (v * 255.0).round() as u8;
pixels[(y as usize, x as usize)] = v;
});
}
......@@ -989,16 +989,13 @@ fn draw_glyph(tex_coords: Rect<u32>, glyph: &PositionedGlyph<'_>, pad_glyphs: bo
#[cfg(test)]
mod test {
use super::*;