Pixel-perfect fonts
This commit is contained in:
parent
cd8ca47e76
commit
c2c94ddda5
14 changed files with 207 additions and 212 deletions
2
TODO.md
2
TODO.md
|
@ -2,7 +2,7 @@
|
|||
* Break off example app from emigui_wasm
|
||||
* Try it on iPhone
|
||||
* Read TTF from browser?
|
||||
* Device pixel density aware font rendering
|
||||
* requestAnimationFrame
|
||||
|
||||
# Additional bindings, e.g. Piston
|
||||
|
||||
|
|
|
@ -25,13 +25,14 @@
|
|||
}
|
||||
/**
|
||||
* @param {string} arg0
|
||||
* @param {number} arg1
|
||||
* @returns {State}
|
||||
*/
|
||||
__exports.new_webgl_gui = function(arg0) {
|
||||
__exports.new_webgl_gui = function(arg0, arg1) {
|
||||
const ptr0 = passStringToWasm(arg0);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
try {
|
||||
return State.__wrap(wasm.new_webgl_gui(ptr0, len0));
|
||||
return State.__wrap(wasm.new_webgl_gui(ptr0, len0, arg1));
|
||||
|
||||
} finally {
|
||||
wasm.__wbindgen_free(ptr0, len0 * 1);
|
||||
|
@ -330,20 +331,20 @@ __exports.__widl_f_performance_Window = function(arg0) {
|
|||
|
||||
};
|
||||
|
||||
__exports.__wbg_new_1b06d86d496d7b40 = function(arg0) {
|
||||
__exports.__wbg_new_12b9ae8fdb332911 = function(arg0) {
|
||||
return addHeapObject(new Float32Array(getObject(arg0)));
|
||||
};
|
||||
|
||||
__exports.__wbg_subarray_fe30ee182e9ec716 = function(arg0, arg1, arg2) {
|
||||
__exports.__wbg_subarray_1c02edccacdc6b96 = function(arg0, arg1, arg2) {
|
||||
return addHeapObject(getObject(arg0).subarray(arg1, arg2));
|
||||
};
|
||||
|
||||
__exports.__wbg_newnoargs_a6ad1b52f5989ea9 = function(arg0, arg1) {
|
||||
__exports.__wbg_newnoargs_970ffcd96c15d34e = function(arg0, arg1) {
|
||||
let varg0 = getStringFromWasm(arg0, arg1);
|
||||
return addHeapObject(new Function(varg0));
|
||||
};
|
||||
|
||||
__exports.__wbg_call_720151a19a4c6808 = function(arg0, arg1, exnptr) {
|
||||
__exports.__wbg_call_6ecd167e59b01396 = function(arg0, arg1, exnptr) {
|
||||
try {
|
||||
return addHeapObject(getObject(arg0).call(getObject(arg1)));
|
||||
} catch (e) {
|
||||
|
@ -354,62 +355,38 @@ __exports.__wbg_call_720151a19a4c6808 = function(arg0, arg1, exnptr) {
|
|||
}
|
||||
};
|
||||
|
||||
__exports.__wbg_new_e9ac55ad4b35397d = function(arg0) {
|
||||
__exports.__wbg_new_24372bdd16e7ac17 = function(arg0) {
|
||||
return addHeapObject(new Int16Array(getObject(arg0)));
|
||||
};
|
||||
|
||||
__exports.__wbg_subarray_930aeb4c907055d1 = function(arg0, arg1, arg2) {
|
||||
__exports.__wbg_subarray_333aec38f24ecc8c = function(arg0, arg1, arg2) {
|
||||
return addHeapObject(getObject(arg0).subarray(arg1, arg2));
|
||||
};
|
||||
|
||||
__exports.__wbg_new_d90640b4228ff695 = function(arg0) {
|
||||
__exports.__wbg_new_4e991c7c717b13c1 = function(arg0) {
|
||||
return addHeapObject(new Uint8Array(getObject(arg0)));
|
||||
};
|
||||
|
||||
__exports.__wbg_subarray_ba3c433705738bca = function(arg0, arg1, arg2) {
|
||||
__exports.__wbg_subarray_0de502469162fe71 = function(arg0, arg1, arg2) {
|
||||
return addHeapObject(getObject(arg0).subarray(arg1, arg2));
|
||||
};
|
||||
|
||||
__exports.__wbg_new_1c85449424e3413d = function(arg0) {
|
||||
__exports.__wbg_new_ebb3136fdb1b1152 = function(arg0) {
|
||||
return addHeapObject(new Uint16Array(getObject(arg0)));
|
||||
};
|
||||
|
||||
__exports.__wbg_subarray_08927d6d29836298 = function(arg0, arg1, arg2) {
|
||||
__exports.__wbg_subarray_acb28098200224ff = function(arg0, arg1, arg2) {
|
||||
return addHeapObject(getObject(arg0).subarray(arg1, arg2));
|
||||
};
|
||||
|
||||
__exports.__wbg_instanceof_Memory_7db9a3f810fae661 = function(idx) {
|
||||
__exports.__wbg_instanceof_Memory_48643a8591466d1a = function(idx) {
|
||||
return getObject(idx) instanceof WebAssembly.Memory ? 1 : 0;
|
||||
};
|
||||
|
||||
__exports.__wbg_buffer_0346d756c794d630 = function(arg0) {
|
||||
__exports.__wbg_buffer_74e21c76ddf2eb17 = function(arg0) {
|
||||
return addHeapObject(getObject(arg0).buffer);
|
||||
};
|
||||
|
||||
function freeState(ptr) {
|
||||
|
||||
wasm.__wbg_state_free(ptr);
|
||||
}
|
||||
/**
|
||||
*/
|
||||
class State {
|
||||
|
||||
static __wrap(ptr) {
|
||||
const obj = Object.create(State.prototype);
|
||||
obj.ptr = ptr;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
free() {
|
||||
const ptr = this.ptr;
|
||||
this.ptr = 0;
|
||||
freeState(ptr);
|
||||
}
|
||||
|
||||
}
|
||||
__exports.State = State;
|
||||
|
||||
__exports.__wbindgen_object_clone_ref = function(idx) {
|
||||
return addHeapObject(getObject(idx));
|
||||
};
|
||||
|
@ -472,6 +449,30 @@ function takeObject(idx) {
|
|||
|
||||
__exports.__wbindgen_rethrow = function(idx) { throw takeObject(idx); };
|
||||
|
||||
function freeState(ptr) {
|
||||
|
||||
wasm.__wbg_state_free(ptr);
|
||||
}
|
||||
/**
|
||||
*/
|
||||
class State {
|
||||
|
||||
static __wrap(ptr) {
|
||||
const obj = Object.create(State.prototype);
|
||||
obj.ptr = ptr;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
free() {
|
||||
const ptr = this.ptr;
|
||||
this.ptr = 0;
|
||||
freeState(ptr);
|
||||
}
|
||||
|
||||
}
|
||||
__exports.State = State;
|
||||
|
||||
__exports.__wbindgen_throw = function(ptr, len) {
|
||||
throw new Error(getStringFromWasm(ptr, len));
|
||||
};
|
||||
|
|
Binary file not shown.
|
@ -56,7 +56,7 @@
|
|||
|
||||
function paint_gui(canvas, input) {
|
||||
if (g_webgl_painter === null) {
|
||||
g_webgl_painter = wasm_bindgen.new_webgl_gui("canvas");
|
||||
g_webgl_painter = wasm_bindgen.new_webgl_gui("canvas", pixels_per_point());
|
||||
}
|
||||
wasm_bindgen.run_gui(g_webgl_painter, JSON.stringify(input));
|
||||
}
|
||||
|
@ -64,24 +64,24 @@
|
|||
var g_mouse_pos = { x: -1000.0, y: -1000.0 };
|
||||
var g_mouse_down = false;
|
||||
|
||||
function pixels_per_point() {
|
||||
// return 1.0;
|
||||
return window.devicePixelRatio || 1.0;
|
||||
}
|
||||
|
||||
function auto_resize_canvas(canvas) {
|
||||
// TODO: figure out why this isn't quite working.
|
||||
if (true) {
|
||||
canvas.setAttribute("width", window.innerWidth);
|
||||
canvas.setAttribute("height", window.innerHeight);
|
||||
} else {
|
||||
// TODO: this stuff
|
||||
var pixels_per_point = window.devicePixelRatio || 1;
|
||||
canvas.setAttribute("width", window.innerWidth * pixels_per_point);
|
||||
canvas.setAttribute("height", window.innerHeight * pixels_per_point);
|
||||
}
|
||||
canvas.style.width = window.innerWidth + "px";
|
||||
canvas.style.height = window.innerHeight + "px";
|
||||
canvas.width = window.innerWidth * pixels_per_point();
|
||||
canvas.height = window.innerHeight * pixels_per_point();
|
||||
}
|
||||
|
||||
function get_input(canvas) {
|
||||
return {
|
||||
mouse_down: g_mouse_down,
|
||||
mouse_pos: g_mouse_pos,
|
||||
screen_size: { x: canvas.width, y: canvas.height }
|
||||
screen_size: { x: window.innerWidth, y: window.innerHeight },
|
||||
pixels_per_point: pixels_per_point(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,10 @@ fn show_font_texture(texture: &Texture, gui: &mut Region) {
|
|||
"Font texture size: {} x {} (hover to zoom)",
|
||||
texture.width, texture.height
|
||||
)));
|
||||
let size = vec2(texture.width as f32, texture.height as f32);
|
||||
let mut size = vec2(texture.width as f32, texture.height as f32);
|
||||
if size.x > gui.width() {
|
||||
size *= gui.width() / size.x;
|
||||
}
|
||||
let interact = gui.reserve_space(size, None);
|
||||
let rect = interact.rect;
|
||||
let top_left = Vertex {
|
||||
|
@ -118,10 +121,10 @@ pub struct Emigui {
|
|||
}
|
||||
|
||||
impl Emigui {
|
||||
pub fn new() -> Emigui {
|
||||
pub fn new(pixels_per_point: f32) -> Emigui {
|
||||
Emigui {
|
||||
last_input: Default::default(),
|
||||
data: Arc::new(layout::Data::new()),
|
||||
data: Arc::new(layout::Data::new(pixels_per_point)),
|
||||
style: Default::default(),
|
||||
stats: Default::default(),
|
||||
}
|
||||
|
@ -182,7 +185,7 @@ impl Emigui {
|
|||
show_font_texture(self.texture(), gui);
|
||||
if *old_font_sizes != new_font_sizes {
|
||||
let mut new_data = (*self.data).clone();
|
||||
let fonts = Fonts::from_sizes(new_font_sizes);
|
||||
let fonts = Fonts::from_sizes(new_font_sizes, self.data.input.pixels_per_point);
|
||||
new_data.fonts = Arc::new(fonts);
|
||||
self.data = Arc::new(new_data);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,11 @@ use crate::{
|
|||
|
||||
pub struct TextFragment {
|
||||
/// The start of each character, starting at zero.
|
||||
/// Unit: points.
|
||||
pub x_offsets: Vec<f32>,
|
||||
|
||||
/// 0 for the first line, n * line_spacing for the rest
|
||||
/// Unit: points.
|
||||
pub y_offset: f32,
|
||||
pub text: String,
|
||||
}
|
||||
|
@ -27,22 +30,24 @@ impl TextFragment {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UvRect {
|
||||
/// X/Y offset for nice rendering
|
||||
pub offset: (i16, i16),
|
||||
/// X/Y offset for nice rendering (unit: points).
|
||||
pub offset: Vec2,
|
||||
pub size: Vec2,
|
||||
|
||||
/// Top left corner.
|
||||
/// Top left corner UV in texture.
|
||||
pub min: (u16, u16),
|
||||
|
||||
/// Inclusive
|
||||
/// Bottom right corner (exclusive).
|
||||
pub max: (u16, u16),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct GlyphInfo {
|
||||
id: rusttype::GlyphId,
|
||||
|
||||
/// Unit: points.
|
||||
pub advance_width: f32,
|
||||
|
||||
/// Texture coordinates. None for space.
|
||||
|
@ -53,24 +58,28 @@ pub struct GlyphInfo {
|
|||
const FIRST_ASCII: usize = 32; // 32 == space
|
||||
const LAST_ASCII: usize = 126;
|
||||
|
||||
/// The interface uses points as the unit for everything.
|
||||
#[derive(Clone)]
|
||||
pub struct Font {
|
||||
font: rusttype::Font<'static>,
|
||||
/// Maximum character height
|
||||
scale: f32,
|
||||
scale_in_pixels: f32,
|
||||
pixels_per_point: f32,
|
||||
/// NUM_CHARS big
|
||||
glyph_infos: Vec<GlyphInfo>,
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
}
|
||||
|
||||
impl Font {
|
||||
pub fn new(atlas: Arc<Mutex<TextureAtlas>>, font_data: &'static [u8], scale: f32) -> Font {
|
||||
pub fn new(
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
font_data: &'static [u8],
|
||||
scale_in_points: f32,
|
||||
pixels_per_point: f32,
|
||||
) -> Font {
|
||||
let font = rusttype::Font::from_bytes(font_data).expect("Error constructing Font");
|
||||
|
||||
// println!(
|
||||
// "font.v_metrics: {:?}",
|
||||
// font.v_metrics(Scale::uniform(scale))
|
||||
// );
|
||||
let scale_in_pixels = pixels_per_point * scale_in_points;
|
||||
|
||||
let glyphs: Vec<_> = Self::supported_characters()
|
||||
.map(|c| {
|
||||
|
@ -81,7 +90,7 @@ impl Font {
|
|||
"Failed to find a glyph for the character '{}'",
|
||||
c
|
||||
);
|
||||
let glyph = glyph.scaled(Scale::uniform(scale));
|
||||
let glyph = glyph.scaled(Scale::uniform(scale_in_pixels));
|
||||
glyph.positioned(point(0.0, 0.0))
|
||||
})
|
||||
.collect();
|
||||
|
@ -90,7 +99,7 @@ impl Font {
|
|||
let mut atlas_lock = atlas.lock().unwrap();
|
||||
|
||||
for glyph in glyphs {
|
||||
if let Some(bb) = glyph.pixel_bounding_box() {
|
||||
let uv = if let Some(bb) = glyph.pixel_bounding_box() {
|
||||
let glyph_width = bb.width() as usize;
|
||||
let glyph_height = bb.height() as usize;
|
||||
assert!(glyph_width >= 1);
|
||||
|
@ -107,41 +116,53 @@ impl Font {
|
|||
}
|
||||
});
|
||||
|
||||
let offset_y = scale as i16 + bb.min.y as i16 - 4; // TODO: use font.v_metrics
|
||||
glyph_infos.push(GlyphInfo {
|
||||
id: glyph.id(),
|
||||
advance_width: glyph.unpositioned().h_metrics().advance_width,
|
||||
uv: Some(UvRect {
|
||||
offset: (bb.min.x as i16, offset_y as i16),
|
||||
min: (glyph_pos.0 as u16, glyph_pos.1 as u16),
|
||||
max: (
|
||||
(glyph_pos.0 + glyph_width - 1) as u16,
|
||||
(glyph_pos.1 + glyph_height - 1) as u16,
|
||||
),
|
||||
}),
|
||||
});
|
||||
let offset_y_in_pixels =
|
||||
scale_in_pixels as f32 + bb.min.y as f32 - 4.0 * pixels_per_point; // TODO: use font.v_metrics
|
||||
Some(UvRect {
|
||||
offset: vec2(
|
||||
bb.min.x as f32 / pixels_per_point,
|
||||
offset_y_in_pixels / pixels_per_point,
|
||||
),
|
||||
size: vec2(glyph_width as f32, glyph_height as f32) / pixels_per_point,
|
||||
min: (glyph_pos.0 as u16, glyph_pos.1 as u16),
|
||||
max: (
|
||||
(glyph_pos.0 + glyph_width) as u16,
|
||||
(glyph_pos.1 + glyph_height) as u16,
|
||||
),
|
||||
})
|
||||
} else {
|
||||
// No bounding box. Maybe a space?
|
||||
glyph_infos.push(GlyphInfo {
|
||||
id: glyph.id(),
|
||||
advance_width: glyph.unpositioned().h_metrics().advance_width,
|
||||
uv: None,
|
||||
});
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
let advance_width_in_points =
|
||||
glyph.unpositioned().h_metrics().advance_width / pixels_per_point;
|
||||
|
||||
glyph_infos.push(GlyphInfo {
|
||||
id: glyph.id(),
|
||||
advance_width: advance_width_in_points,
|
||||
uv,
|
||||
});
|
||||
}
|
||||
|
||||
drop(atlas_lock);
|
||||
|
||||
Font {
|
||||
font,
|
||||
scale,
|
||||
scale_in_pixels,
|
||||
pixels_per_point,
|
||||
glyph_infos,
|
||||
atlas,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
}
|
||||
|
||||
/// In points
|
||||
pub fn line_spacing(&self) -> f32 {
|
||||
self.scale
|
||||
self.scale_in_pixels / self.pixels_per_point
|
||||
}
|
||||
|
||||
pub fn supported_characters() -> impl Iterator<Item = char> {
|
||||
|
@ -168,7 +189,7 @@ impl Font {
|
|||
|
||||
/// Returns the a single line of characters separated into words
|
||||
pub fn layout_single_line(&self, text: &str) -> Vec<TextFragment> {
|
||||
let scale = Scale::uniform(self.scale);
|
||||
let scale_in_pixels = Scale::uniform(self.scale_in_pixels);
|
||||
|
||||
let mut current_fragment = TextFragment {
|
||||
x_offsets: vec![0.0],
|
||||
|
@ -176,16 +197,19 @@ impl Font {
|
|||
text: String::new(),
|
||||
};
|
||||
let mut all_fragments = vec![];
|
||||
let mut cursor_x = 0.0f32;
|
||||
let mut cursor_x_in_points = 0.0f32;
|
||||
let mut last_glyph_id = None;
|
||||
|
||||
for c in text.chars() {
|
||||
if let Some(glyph) = self.glyph_info(c) {
|
||||
if let Some(last_glyph_id) = last_glyph_id {
|
||||
cursor_x += self.font.pair_kerning(scale, last_glyph_id, glyph.id)
|
||||
cursor_x_in_points +=
|
||||
self.font
|
||||
.pair_kerning(scale_in_pixels, last_glyph_id, glyph.id)
|
||||
/ self.pixels_per_point
|
||||
}
|
||||
cursor_x += glyph.advance_width;
|
||||
cursor_x = cursor_x.round();
|
||||
cursor_x_in_points += glyph.advance_width;
|
||||
cursor_x_in_points = self.round_to_pixel(cursor_x_in_points);
|
||||
last_glyph_id = Some(glyph.id);
|
||||
|
||||
let is_space = glyph.uv.is_none();
|
||||
|
@ -194,14 +218,14 @@ impl Font {
|
|||
if !current_fragment.text.is_empty() {
|
||||
all_fragments.push(current_fragment);
|
||||
current_fragment = TextFragment {
|
||||
x_offsets: vec![cursor_x],
|
||||
x_offsets: vec![cursor_x_in_points],
|
||||
y_offset: 0.0,
|
||||
text: String::new(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
current_fragment.text.push(c);
|
||||
current_fragment.x_offsets.push(cursor_x);
|
||||
current_fragment.x_offsets.push(cursor_x_in_points);
|
||||
}
|
||||
} else {
|
||||
// Ignore unknown glyph
|
||||
|
@ -214,9 +238,13 @@ impl Font {
|
|||
all_fragments
|
||||
}
|
||||
|
||||
pub fn layout_single_line_max_width(&self, text: &str, max_width: f32) -> Vec<TextFragment> {
|
||||
pub fn layout_single_line_max_width(
|
||||
&self,
|
||||
text: &str,
|
||||
max_width_in_points: f32,
|
||||
) -> Vec<TextFragment> {
|
||||
let mut words = self.layout_single_line(text);
|
||||
if words.is_empty() || words.last().unwrap().max_x() <= max_width {
|
||||
if words.is_empty() || words.last().unwrap().max_x() <= max_width_in_points {
|
||||
return words; // Early-out
|
||||
}
|
||||
|
||||
|
@ -227,7 +255,7 @@ impl Font {
|
|||
let mut cursor_y = 0.0;
|
||||
|
||||
for word in words.iter_mut().skip(1) {
|
||||
if word.max_x() - line_start_x >= max_width {
|
||||
if word.max_x() - line_start_x >= max_width_in_points {
|
||||
// Time for a new line:
|
||||
cursor_y += line_spacing;
|
||||
line_start_x = word.min_x();
|
||||
|
@ -243,12 +271,16 @@ impl Font {
|
|||
}
|
||||
|
||||
/// Returns each line + total bounding box size.
|
||||
pub fn layout_multiline(&self, text: &str, max_width: f32) -> (Vec<TextFragment>, Vec2) {
|
||||
pub fn layout_multiline(
|
||||
&self,
|
||||
text: &str,
|
||||
max_width_in_points: f32,
|
||||
) -> (Vec<TextFragment>, Vec2) {
|
||||
let line_spacing = self.line_spacing();
|
||||
let mut cursor_y = 0.0;
|
||||
let mut text_fragments = Vec::new();
|
||||
for line in text.split('\n') {
|
||||
let mut line_fragments = self.layout_single_line_max_width(&line, max_width);
|
||||
let mut line_fragments = self.layout_single_line_max_width(&line, max_width_in_points);
|
||||
if let Some(last_word) = line_fragments.last() {
|
||||
let line_height = last_word.y_offset + line_spacing;
|
||||
for fragment in &mut line_fragments {
|
||||
|
@ -259,7 +291,7 @@ impl Font {
|
|||
} else {
|
||||
cursor_y += line_spacing;
|
||||
}
|
||||
cursor_y = cursor_y.round();
|
||||
cursor_y = self.round_to_pixel(cursor_y);
|
||||
}
|
||||
|
||||
let mut widest_line = 0.0;
|
||||
|
@ -270,80 +302,4 @@ impl Font {
|
|||
let bounding_size = vec2(widest_line, cursor_y);
|
||||
(text_fragments, bounding_size)
|
||||
}
|
||||
|
||||
pub fn debug_print_all_chars(&self) {
|
||||
let mut atlas_lock = self.atlas.lock().unwrap();
|
||||
let texture_mut = atlas_lock.texture_mut();
|
||||
|
||||
let max_width = 160;
|
||||
let scale = Scale::uniform(self.scale);
|
||||
let mut pixel_rows = vec![vec![0; max_width]; self.scale.ceil() as usize];
|
||||
let mut cursor_x = 0.0;
|
||||
let cursor_y = 0;
|
||||
let mut last_glyph_id = None;
|
||||
for c in Self::supported_characters() {
|
||||
if let Some(glyph) = self.glyph_info(c) {
|
||||
if let Some(last_glyph_id) = last_glyph_id {
|
||||
cursor_x += self.font.pair_kerning(scale, last_glyph_id, glyph.id)
|
||||
}
|
||||
if cursor_x + glyph.advance_width >= max_width as f32 {
|
||||
println!("{}", (0..max_width).map(|_| "X").collect::<String>());
|
||||
for row in pixel_rows {
|
||||
println!("{}", as_ascii(&row));
|
||||
}
|
||||
pixel_rows = vec![vec![0; max_width]; self.scale.ceil() as usize];
|
||||
cursor_x = 0.0;
|
||||
}
|
||||
if let Some(uv) = glyph.uv {
|
||||
for x in uv.min.0..=uv.max.0 {
|
||||
for y in uv.min.1..=uv.max.1 {
|
||||
let pixel = texture_mut[(x as usize, y as usize)];
|
||||
let rx = uv.offset.0 + x as i16 - uv.min.0 as i16;
|
||||
let ry = uv.offset.1 + y as i16 - uv.min.1 as i16;
|
||||
let px = (cursor_x + rx as f32).round();
|
||||
let py = cursor_y + ry;
|
||||
if 0.0 <= px && 0 <= py {
|
||||
pixel_rows[py as usize][px as usize] = pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor_x += glyph.advance_width;
|
||||
last_glyph_id = Some(glyph.id);
|
||||
}
|
||||
}
|
||||
println!("{}", (0..max_width).map(|_| "X").collect::<String>());
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ascii(pixels: &[u8]) -> String {
|
||||
pixels
|
||||
.iter()
|
||||
.map(|pixel| {
|
||||
if *pixel == 0 {
|
||||
' '
|
||||
} else if *pixel < 85 {
|
||||
'.'
|
||||
} else if *pixel < 170 {
|
||||
'o'
|
||||
} else if *pixel < 255 {
|
||||
'O'
|
||||
} else {
|
||||
'X'
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn font_test() {
|
||||
let atlas = TextureAtlas::new(128, 8);
|
||||
let atlas = Arc::new(Mutex::new(atlas));
|
||||
let font_data = include_bytes!("../fonts/Roboto-Regular.ttf");
|
||||
let font = Font::new(atlas, font_data, 13.0);
|
||||
font.debug_print_all_chars();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,22 +21,24 @@ pub enum TextStyle {
|
|||
pub type FontSizes = BTreeMap<TextStyle, f32>;
|
||||
|
||||
pub struct Fonts {
|
||||
pixels_per_point: f32,
|
||||
sizes: FontSizes,
|
||||
fonts: BTreeMap<TextStyle, Font>,
|
||||
texture: Texture,
|
||||
}
|
||||
|
||||
impl Fonts {
|
||||
pub fn new() -> Fonts {
|
||||
pub fn new(pixels_per_point: f32) -> Fonts {
|
||||
let mut sizes = FontSizes::new();
|
||||
sizes.insert(TextStyle::Body, 18.0);
|
||||
sizes.insert(TextStyle::Button, 22.0);
|
||||
sizes.insert(TextStyle::Heading, 28.0);
|
||||
Fonts::from_sizes(sizes)
|
||||
Fonts::from_sizes(sizes, pixels_per_point)
|
||||
}
|
||||
|
||||
pub fn from_sizes(sizes: FontSizes) -> Fonts {
|
||||
pub fn from_sizes(sizes: FontSizes, pixels_per_point: f32) -> Fonts {
|
||||
let mut fonts = Fonts {
|
||||
pixels_per_point,
|
||||
sizes: Default::default(),
|
||||
fonts: Default::default(),
|
||||
texture: Default::default(),
|
||||
|
@ -69,7 +71,12 @@ impl Fonts {
|
|||
self.sizes = sizes.clone();
|
||||
self.fonts = sizes
|
||||
.into_iter()
|
||||
.map(|(text_style, size)| (text_style, Font::new(atlas.clone(), typeface_data, size)))
|
||||
.map(|(text_style, size)| {
|
||||
(
|
||||
text_style,
|
||||
Font::new(atlas.clone(), typeface_data, size, self.pixels_per_point),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
self.texture = atlas.lock().unwrap().texture().clone();
|
||||
|
||||
|
|
|
@ -189,10 +189,10 @@ impl Clone for Data {
|
|||
}
|
||||
|
||||
impl Data {
|
||||
pub fn new() -> Data {
|
||||
pub fn new(pixels_per_point: f32) -> Data {
|
||||
Data {
|
||||
options: Default::default(),
|
||||
fonts: Arc::new(Fonts::new()),
|
||||
fonts: Arc::new(Fonts::new(pixels_per_point)),
|
||||
input: Default::default(),
|
||||
memory: Default::default(),
|
||||
graphics: Default::default(),
|
||||
|
@ -319,6 +319,10 @@ impl Region {
|
|||
self.dir
|
||||
}
|
||||
|
||||
pub fn cursor(&self) -> Vec2 {
|
||||
self.cursor
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Sub-regions:
|
||||
|
||||
|
|
|
@ -28,10 +28,10 @@ impl Vec2 {
|
|||
}
|
||||
|
||||
impl std::ops::AddAssign for Vec2 {
|
||||
fn add_assign(&mut self, other: Vec2) {
|
||||
fn add_assign(&mut self, rhs: Vec2) {
|
||||
*self = Vec2 {
|
||||
x: self.x + other.x,
|
||||
y: self.y + other.y,
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,13 @@ impl std::ops::Sub for Vec2 {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::ops::MulAssign<f32> for Vec2 {
|
||||
fn mul_assign(&mut self, rhs: f32) {
|
||||
self.x *= rhs;
|
||||
self.y *= rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for Vec2 {
|
||||
type Output = Vec2;
|
||||
fn mul(self, factor: f32) -> Vec2 {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const ANTI_ALIAS: bool = true;
|
||||
const AA_SIZE: f32 = 1.0;
|
||||
const AA_SIZE: f32 = 0.5; // TODO: 1.0 / pixels_per_point
|
||||
|
||||
/// Outputs render info in a format suitable for e.g. OpenGL.
|
||||
use crate::{
|
||||
|
@ -336,23 +336,15 @@ impl Frame {
|
|||
for (c, x_offset) in text.chars().zip(x_offsets.iter()) {
|
||||
if let Some(glyph) = font.uv_rect(c) {
|
||||
let mut top_left = Vertex {
|
||||
pos: *pos
|
||||
+ vec2(
|
||||
x_offset + (glyph.offset.0 as f32),
|
||||
glyph.offset.1 as f32,
|
||||
),
|
||||
uv: (glyph.min.0, glyph.min.1),
|
||||
pos: *pos + glyph.offset + vec2(*x_offset, 0.0),
|
||||
uv: glyph.min,
|
||||
color: *color,
|
||||
};
|
||||
top_left.pos.x = top_left.pos.x.round(); // Pixel-perfection.
|
||||
top_left.pos.y = top_left.pos.y.round(); // Pixel-perfection.
|
||||
top_left.pos.x = font.round_to_pixel(top_left.pos.x); // Pixel-perfection.
|
||||
top_left.pos.y = font.round_to_pixel(top_left.pos.y); // Pixel-perfection.
|
||||
let bottom_right = Vertex {
|
||||
pos: top_left.pos
|
||||
+ vec2(
|
||||
(1 + glyph.max.0 - glyph.min.0) as f32,
|
||||
(1 + glyph.max.1 - glyph.min.1) as f32,
|
||||
),
|
||||
uv: (glyph.max.0 + 1, glyph.max.1 + 1),
|
||||
pos: top_left.pos + glyph.size,
|
||||
uv: glyph.max,
|
||||
color: *color,
|
||||
};
|
||||
frame.add_rect(top_left, bottom_right);
|
||||
|
|
|
@ -17,6 +17,9 @@ pub struct RawInput {
|
|||
|
||||
/// Size of the screen in points.
|
||||
pub screen_size: Vec2,
|
||||
|
||||
/// Also known as device pixel ratio, > 1 for HDPI screens.
|
||||
pub pixels_per_point: f32,
|
||||
}
|
||||
|
||||
/// What the gui maintains
|
||||
|
@ -36,6 +39,9 @@ pub struct GuiInput {
|
|||
|
||||
/// Size of the screen in points.
|
||||
pub screen_size: Vec2,
|
||||
|
||||
/// Also known as device pixel ratio, > 1 for HDPI screens.
|
||||
pub pixels_per_point: f32,
|
||||
}
|
||||
|
||||
impl GuiInput {
|
||||
|
@ -46,6 +52,7 @@ impl GuiInput {
|
|||
mouse_released: last.mouse_down && !new.mouse_down,
|
||||
mouse_pos: new.mouse_pos,
|
||||
screen_size: new.screen_size,
|
||||
pixels_per_point: new.pixels_per_point,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,9 +30,20 @@ impl App {
|
|||
gui.add(Separator::new());
|
||||
|
||||
gui.add(label(format!(
|
||||
"Screen size: {} x {}",
|
||||
"Screen size: {} x {}, pixels_per_point: {}",
|
||||
gui.input().screen_size.x,
|
||||
gui.input().screen_size.y,
|
||||
gui.input().pixels_per_point,
|
||||
)));
|
||||
gui.add(label(format!(
|
||||
"mouse_pos: {} x {}",
|
||||
gui.input().mouse_pos.x,
|
||||
gui.input().mouse_pos.y,
|
||||
)));
|
||||
gui.add(label(format!(
|
||||
"gui cursor: {} x {}",
|
||||
gui.cursor().x,
|
||||
gui.cursor().y,
|
||||
)));
|
||||
|
||||
gui.horizontal(Align::Min, |gui| {
|
||||
|
|
|
@ -29,10 +29,10 @@ pub struct State {
|
|||
}
|
||||
|
||||
impl State {
|
||||
fn new(canvas_id: &str) -> Result<State, JsValue> {
|
||||
fn new(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> {
|
||||
Ok(State {
|
||||
app: Default::default(),
|
||||
emigui: Emigui::new(),
|
||||
emigui: Emigui::new(pixels_per_point),
|
||||
webgl_painter: webgl::Painter::new(canvas_id)?,
|
||||
everything_ms: 0.0,
|
||||
})
|
||||
|
@ -56,7 +56,9 @@ impl State {
|
|||
region.add(label(format!("Everything: {:.1} ms", self.everything_ms)));
|
||||
|
||||
let frame = self.emigui.paint();
|
||||
let result = self.webgl_painter.paint(&frame, self.emigui.texture());
|
||||
let result =
|
||||
self.webgl_painter
|
||||
.paint(&frame, self.emigui.texture(), raw_input.pixels_per_point);
|
||||
|
||||
self.everything_ms = now_ms() - everything_start;
|
||||
|
||||
|
@ -65,8 +67,8 @@ impl State {
|
|||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn new_webgl_gui(canvas_id: &str) -> Result<State, JsValue> {
|
||||
State::new(canvas_id)
|
||||
pub fn new_webgl_gui(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> {
|
||||
State::new(canvas_id, pixels_per_point)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
|
|
@ -143,7 +143,12 @@ impl Painter {
|
|||
self.current_texture_id = Some(texture.id);
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, frame: &Frame, texture: &Texture) -> Result<(), JsValue> {
|
||||
pub fn paint(
|
||||
&mut self,
|
||||
frame: &Frame,
|
||||
texture: &Texture,
|
||||
pixels_per_point: f32,
|
||||
) -> Result<(), JsValue> {
|
||||
self.upload_texture(texture);
|
||||
|
||||
let gl = &self.gl;
|
||||
|
@ -280,8 +285,8 @@ impl Painter {
|
|||
.unwrap();
|
||||
gl.uniform2f(
|
||||
Some(&u_screen_size_loc),
|
||||
self.canvas.width() as f32,
|
||||
self.canvas.height() as f32,
|
||||
self.canvas.width() as f32 / pixels_per_point,
|
||||
self.canvas.height() as f32 / pixels_per_point,
|
||||
);
|
||||
|
||||
let u_tex_size_loc = gl
|
||||
|
|
Loading…
Reference in a new issue