harmonize blitstr2 and graphics-server to an i16 pixel representation

previously blitstr2 is using usize for x/y coords, which means that
for layout purposes points could not go to the top or left boundary
without causing arithmetic overflow. Of course, you want to render
everything on-screen, but, as we have multi-line text with overflow
possibility, it's reasonable to be able to use negative coordinates
to locate text boxes that flow into the visible region.

This of course means that occassionally you can come out with
things like "negative widths", but even then, there can be reasonable
uses for this as an intermediate in calculations that ultimately
arrive at a positive result.
This commit is contained in:
bunnie 2022-03-28 23:17:18 +08:00
parent 287aea90b6
commit 5452fae33c
10 changed files with 49 additions and 49 deletions

View file

@ -29,8 +29,8 @@ pub use blitstr2::*;
use std::hash::{Hash, Hasher};
pub const LINES: usize = 536;
pub const WIDTH: usize = 336;
pub const LINES: i16 = 536;
pub const WIDTH: i16 = 336;
//////////////// IPC APIs
#[derive(Debug, Copy, Clone, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]

View file

@ -42,9 +42,9 @@ pub(crate) struct TypesetWord {
/// top left origin point for rendering of the glyphs
pub origin: Pt,
/// width of the word
pub width: usize,
pub width: i16,
/// overall height for the word
pub height: usize,
pub height: i16,
/// set if this `word` is not drawable, e.g. a newline placeholder.
/// *however* the Vec<GlyphSprite> should still be checked for an insertion point, so that
/// successive newlines properly get their insertion point drawn

View file

@ -17,7 +17,7 @@ pub struct ClipRect {
#[allow(dead_code)]
impl ClipRect {
/// Initialize a rectangle using automatic min/max fixup for corner points
pub fn new(min_x: usize, min_y: usize, max_x: usize, max_y: usize) -> ClipRect {
pub fn new(min_x: i16, min_y: i16, max_x: i16, max_y: i16) -> ClipRect {
// Make sure min_x <= max_x && min_y <= max_y
let mut min = Pt { x: min_x, y: min_y };
let mut max = Pt { x: max_x, y: max_y };

View file

@ -17,7 +17,7 @@ pub struct Cursor {
#[allow(dead_code)]
impl Cursor {
// Make a new Cursor. When in doubt, set line_height = 0.
pub fn new(x: usize, y: usize, line_height: usize) -> Cursor {
pub fn new(x: i16, y: i16, line_height: usize) -> Cursor {
Cursor {
pt: Pt { x, y },
line_height,
@ -33,13 +33,13 @@ impl Cursor {
}
pub fn update_glyph(&mut self, glyph: &GlyphSprite) {
self.pt.x += glyph.wide as usize;
self.pt.x += glyph.wide as i16;
self.line_height = self.line_height.max(glyph.high as usize);
}
pub(crate) fn update_word(&mut self, word: &TypesetWord) {
self.pt.x += word.width;
self.line_height = self.line_height.max(word.height);
self.line_height = self.line_height.max(word.height as usize);
}
pub fn add(&self, other: Cursor) -> Cursor {

View file

@ -5,13 +5,13 @@
/// Point specifies a pixel coordinate
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct Pt {
pub x: usize,
pub y: usize,
pub x: i16,
pub y: i16,
}
impl Pt {
/// Make a new point
pub fn new(x: usize, y: usize) -> Pt {
pub fn new(x: i16, y: i16) -> Pt {
Pt { x, y }
}
}

View file

@ -4,14 +4,14 @@ use crate::api::Point;
use minifb::{Key, Window, WindowOptions};
use crate::api::{LINES, WIDTH};
const HEIGHT: usize = LINES;
const HEIGHT: i16 = LINES;
/// Width of the screen in 32-bit words
const WIDTH_WORDS: usize = 11;
pub const FB_WIDTH_WORDS: usize = WIDTH_WORDS;
pub const FB_WIDTH_PIXELS: usize = WIDTH;
pub const FB_LINES: usize = HEIGHT;
pub const FB_SIZE: usize = WIDTH_WORDS * HEIGHT; // 44 bytes by 536 lines
pub const FB_WIDTH_PIXELS: usize = WIDTH as usize;
pub const FB_LINES: usize = HEIGHT as usize;
pub const FB_SIZE: usize = WIDTH_WORDS * HEIGHT as usize; // 44 bytes by 536 lines
const MAX_FPS: u64 = 60;
const DARK_COLOUR: u32 = 0xB5B5AD;
@ -34,8 +34,8 @@ impl XousDisplay {
pub fn new() -> XousDisplay {
let mut window = Window::new(
"Precursor",
WIDTH,
HEIGHT,
WIDTH as usize,
HEIGHT as usize,
WindowOptions {
scale_mode: minifb::ScaleMode::AspectRatioStretch,
resize: true,
@ -51,9 +51,9 @@ impl XousDisplay {
// 1000 * 1000 / MAX_FPS,
// )));
let native_buffer = vec![DARK_COLOUR; WIDTH * HEIGHT];
let native_buffer = vec![DARK_COLOUR; WIDTH as usize * HEIGHT as usize];
window
.update_with_buffer(&native_buffer, WIDTH, HEIGHT)
.update_with_buffer(&native_buffer, WIDTH as usize, HEIGHT as usize)
.unwrap();
let xns = xous_names::XousNames::new().unwrap();
@ -102,7 +102,7 @@ impl XousDisplay {
pub fn redraw(&mut self) {
self.emulated_to_native();
self.window
.update_with_buffer(&self.native_buffer, WIDTH, HEIGHT)
.update_with_buffer(&self.native_buffer, WIDTH as usize, HEIGHT as usize)
.unwrap();
}

View file

@ -5,10 +5,10 @@ pub use cliprect::*;
pub(crate) mod fonts;
pub(crate) use fonts::*;
const LINES: usize = crate::backend::FB_LINES;
const WIDTH: usize = crate::backend::FB_WIDTH_PIXELS;
const LINES: i16 = crate::backend::FB_LINES as i16;
const WIDTH: i16 = crate::backend::FB_WIDTH_PIXELS as i16;
const WORDS_PER_LINE: usize = crate::backend::FB_WIDTH_WORDS;
pub type FrBuf = [u32; WORDS_PER_LINE * LINES];
pub type FrBuf = [u32; WORDS_PER_LINE * LINES as usize];
// add more fonts (an example):
// https://github.com/samblenny/blitstr2/commit/bb7d4ab6a2d8913dcb520895a3c242c933413aae

View file

@ -17,7 +17,7 @@ pub struct ClipRect {
#[allow(dead_code)]
impl ClipRect {
/// Initialize a rectangle using automatic min/max fixup for corner points
pub fn new(min_x: usize, min_y: usize, max_x: usize, max_y: usize) -> ClipRect {
pub fn new(min_x: i16, min_y: i16, max_x: i16, max_y: i16) -> ClipRect {
// Make sure min_x <= max_x && min_y <= max_y
let mut min = Pt { x: min_x, y: min_y };
let mut max = Pt { x: max_x, y: max_y };

View file

@ -230,15 +230,15 @@ fn xmain() -> ! {
let typeset_extent = match tv.bounds_hint {
TextBounds::BoundingBox(r) =>
Pt::new((r.br().x - r.tl().x - tv.margin.x * 2) as usize, (r.br().y - r.tl().y - tv.margin.y * 2) as usize),
Pt::new(r.br().x - r.tl().x - tv.margin.x * 2, r.br().y - r.tl().y - tv.margin.y * 2),
TextBounds::GrowableFromBr(br, width) =>
Pt::new(width as usize - tv.margin.x as usize * 2, br.y as usize - tv.margin.y as usize * 2),
Pt::new(width as i16 - tv.margin.x * 2, br.y - tv.margin.y * 2),
TextBounds::GrowableFromBl(bl, width) =>
Pt::new(width as usize - tv.margin.x as usize * 2, bl.y as usize - tv.margin.y as usize * 2),
Pt::new(width as i16 - tv.margin.x * 2, bl.y - tv.margin.y * 2),
TextBounds::GrowableFromTl(tl, width) =>
Pt::new(width as usize - tv.margin.x as usize * 2, (clip_rect.br().y - clip_rect.tl().y - tl.y) as usize - tv.margin.y as usize * 2),
Pt::new(width as i16 - tv.margin.x * 2, (clip_rect.br().y - clip_rect.tl().y - tl.y) - tv.margin.y * 2),
TextBounds::GrowableFromTr(tr, width) =>
Pt::new(width as usize - tv.margin.x as usize * 2, (clip_rect.br().y - clip_rect.tl().y - tr.y) as usize - tv.margin.y as usize * 2),
Pt::new(width as i16 - tv.margin.x * 2, (clip_rect.br().y - clip_rect.tl().y - tr.y) - tv.margin.y * 2),
};
let mut typesetter = Typesetter::setup(
tv.to_str(),

View file

@ -46,21 +46,21 @@ impl TypesetWord {
TypesetWord {
gs: vec![gs],
origin,
width: gs.wide as usize,
height: gs.high as usize,
width: gs.wide as i16,
height: gs.high as i16,
non_drawable: false,
strpos,
}
}
pub fn push(&mut self, gs: GlyphSprite) {
self.width += (gs.wide + gs.kern) as usize;
self.height = self.height.max(gs.high as usize);
self.width += (gs.wide + gs.kern) as i16;
self.height = self.height.max(gs.high as i16);
self.gs.push(gs);
}
/// if the pop is invalid, we'll return an invalid character. Just...don't do that. k?
pub fn pop(&mut self) -> GlyphSprite {
let gs = self.gs.pop().unwrap_or(NULL_GLYPH_SPRITE);
self.width -= (gs.wide + gs.kern) as usize;
self.width -= (gs.wide + gs.kern) as i16;
// we can't undo any height transformations, unfortunately, because we don't know what the previous state was
// but it's fairly minor if text is set funny on a line because text overflowed and had e.g. emoji buried amongst
// small font text...
@ -102,10 +102,10 @@ impl ComposedType {
cursor,
}
}
pub fn bb_width(&self) -> usize {
pub fn bb_width(&self) -> i16 {
self.bounding_box.max.x - self.bounding_box.min.x
}
pub fn bb_height(&self) -> usize {
pub fn bb_height(&self) -> i16 {
self.bounding_box.max.y - self.bounding_box.min.y
}
/// Note: it is up to the caller to ensure that clip_rect is within the renderable screen area. We do no
@ -132,14 +132,14 @@ impl ComposedType {
log::trace!("not renderable maybe_y: {}, {:?}", maybe_y, clip_rect);
renderable = false;
}
point.x += (glyph.wide + glyph.kern) as usize; // keep scorekeeping on this, because it could eventually become renderable
point.x += (glyph.wide + glyph.kern) as i16; // keep scorekeeping on this, because it could eventually become renderable
if !renderable {
// quickly short circuit over any text that is definitely outside of our clipping rectangle
continue;
} else {
let cr = ClipRect::new(
clip_rect.tl().x as usize, clip_rect.tl().y as usize,
clip_rect.br().x as usize, clip_rect.br().y as usize
clip_rect.tl().x, clip_rect.tl().y,
clip_rect.br().x, clip_rect.br().y
);
if !glyph.double {
blitstr2::xor_glyph(
@ -211,7 +211,7 @@ pub(crate) struct Typesetter {
s: String,
base_style: GlyphStyle,
overflow: bool,
max_width: usize,
max_width: i16,
last_line_height: usize, // scorecarding for the very last line on the loop exit
}
impl Typesetter {
@ -261,7 +261,7 @@ impl Typesetter {
// going out of scope at the end of the call.
let mut composition = Vec::<TypesetWord>::new();
if self.bb.max.x - self.bb.min.y < glyph_to_height_hint(GlyphStyle::Regular) {
if self.bb.max.x - self.bb.min.y < glyph_to_height_hint(GlyphStyle::Regular) as i16 {
// we flag this because the typesetter algorithm may never converge if it can't set any characters
// because the region is just too narrow.
log::error!("Words cannot be typset because the width of the typset region is too narrow.");
@ -420,7 +420,7 @@ impl Typesetter {
let ret = ComposedType::new(composition,
ClipRect::new(
self.bb.min.x, self.bb.min.y,
self.max_width, self.cursor.pt.y + self.last_line_height,
self.max_width as i16, self.cursor.pt.y + self.last_line_height as i16,
),
self.cursor
);
@ -439,12 +439,12 @@ impl Typesetter {
fn is_newline_available(&self) -> bool {
// repeated, bare newlines will have a candidate height of 0, as it contains no glyphs. correct for that.
let corrected_height = if self.candidate.height == 0 {
self.cursor.line_height
self.cursor.line_height as i16
} else {
self.candidate.height
};
log::trace!("{} < {}", corrected_height + self.cursor.pt.y + self.cursor.line_height, self.bb.max.y);
corrected_height + self.cursor.pt.y + self.cursor.line_height < self.bb.max.y
log::trace!("{} < {}", corrected_height + self.cursor.pt.y + self.cursor.line_height as i16, self.bb.max.y);
corrected_height + self.cursor.pt.y + (self.cursor.line_height as i16) < self.bb.max.y
}
fn does_word_fit_on_line(&self) -> bool {
self.candidate.width + self.cursor.pt.x < self.bb.max.x
@ -467,7 +467,7 @@ impl Typesetter {
if !self.candidate.non_drawable { // this is mainly for "non-drawable spaces" at the beginning of a line
self.max_width = self.max_width.max(self.candidate.width + self.candidate.origin.x);
self.cursor.pt.x += self.candidate.width;
self.cursor.line_height = self.cursor.line_height.max(self.candidate.height);
self.cursor.line_height = self.cursor.line_height.max(self.candidate.height as usize);
self.last_line_height = self.cursor.line_height;
}
if false {tsw_debug(&self.candidate)};
@ -493,9 +493,9 @@ impl Typesetter {
fn move_candidate_to_newline(&mut self) {
// advance the rendering line, without inserting a newline placeholder
self.last_line_height = self.cursor.line_height;
self.cursor.pt.y += self.cursor.line_height;
self.cursor.pt.y += self.cursor.line_height as i16;
self.cursor.pt.x = self.bb.min.x;
self.cursor.line_height = self.candidate.height;
self.cursor.line_height = self.candidate.height as usize;
// now set the current candidate word's origin to the beginning of this new line
self.candidate.origin = self.cursor.pt;
}
@ -524,7 +524,7 @@ impl Typesetter {
/// set to our space point, because the caller will have already stashed the previously formed word
fn try_append_space(&mut self, composition: &mut Vec::<TypesetWord>) -> bool {
assert!(self.candidate.gs.len() == 0, "self.candidate was not set to a new state prior to this call");
if (self.cursor.pt.x + self.space.wide as usize) < self.bb.max.x {
if (self.cursor.pt.x + self.space.wide as i16) < self.bb.max.x {
// our candidate word is "just as space"
let mut candidate_space = self.space.clone();
if self.is_insert_point() {