blob: 0a6fec00b5eed9fb65a42089501bbfa72e210abb [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use failure::Error;
use rusttype::{Font, FontCollection, Scale};
use shared_buffer::SharedBuffer;
/// Struct representing an RGBA color value
#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
#[allow(missing_docs)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
/// Struct representing an location
#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
#[allow(missing_docs)]
pub struct Point {
pub x: u32,
pub y: u32,
}
/// Struct representing an size
#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
#[allow(missing_docs)]
pub struct Size {
pub width: u32,
pub height: u32,
}
/// Struct representing a rectangle area.
#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
#[allow(missing_docs)]
pub struct Rect {
pub left: u32,
pub top: u32,
pub right: u32,
pub bottom: u32,
}
/// Struct combining a foreground and background color.
#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
#[allow(missing_docs)]
pub struct Paint {
pub fg: Color,
pub bg: Color,
}
/// Opaque type representing a glyph ID.
#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
struct Glyph(u16);
/// Struct representing a glyph at a specific size.
#[derive(Hash, Eq, PartialEq, Debug)]
struct GlyphDescriptor {
size: u32,
glyph: Glyph,
}
/// Struct containing a font and a cache of renderered glyphs.
pub struct FontFace<'a> {
font: Font<'a>,
}
/// Struct containint font, size and baseline.
#[allow(missing_docs)]
pub struct FontDescription<'a, 'b: 'a> {
pub face: &'a mut FontFace<'b>,
pub size: u32,
pub baseline: i32,
}
#[allow(missing_docs)]
impl<'a> FontFace<'a> {
pub fn new(data: &'a [u8]) -> Result<FontFace<'a>, Error> {
let collection = FontCollection::from_bytes(data as &[u8])?;
let font = collection.into_font()?;
Ok(FontFace { font: font })
}
}
const BYTES_PER_PIXEL: u32 = 4;
/// Trait abstracting a target to which pixels can be written.
pub trait PixelSink {
/// Write an RGBA pixel at an x,y location in the sink.
fn write_pixel_at_location(&mut self, x: u32, y: u32, value: &[u8]);
/// Write an RGBA pixel at a byte offset within the sink.
fn write_pixel_at_offset(&mut self, offset: usize, value: &[u8]);
}
/// Pixel sink targetting a shared buffer.
pub struct SharedBufferPixelSink {
buffer: SharedBuffer,
stride: u32,
}
impl PixelSink for SharedBufferPixelSink {
fn write_pixel_at_location(&mut self, x: u32, y: u32, value: &[u8]) {
let offset = (y * self.stride + x * BYTES_PER_PIXEL) as usize;
self.write_pixel_at_offset(offset, &value);
}
fn write_pixel_at_offset(&mut self, offset: usize, value: &[u8]) {
self.buffer.write_at(offset, &value);
}
}
/// Canvas is used to do simple graphics and text rendering into a
/// SharedBuffer that can then be displayed using Scenic or
/// Display Manager.
pub struct Canvas<T: PixelSink> {
// Assumes a pixel format of BGRA8 and a color space of sRGB.
pixel_sink: T,
stride: u32,
}
impl<T: PixelSink> Canvas<T> {
/// Create a canvas targetting a shared buffer with stride.
pub fn new(buffer: SharedBuffer, stride: u32) -> Canvas<SharedBufferPixelSink> {
let sink = SharedBufferPixelSink { buffer, stride };
Canvas {
pixel_sink: sink,
stride,
}
}
/// Create a canvas targetting a particular pixel sink and
/// with a specific row stride in bytes.
pub fn new_with_sink(pixel_sink: T, stride: u32) -> Canvas<T> {
Canvas { pixel_sink, stride }
}
#[inline]
/// Update the pixel at a particular byte offset with a particular
/// color.
fn write_color_at_offset(&mut self, offset: usize, color: Color) {
let pixel = [color.b, color.g, color.r, color.a];
self.pixel_sink.write_pixel_at_offset(offset, &pixel);
}
#[inline]
fn set_pixel_at_location(&mut self, location: &Point, value: u8, paint: &Paint) {
let col_stride = BYTES_PER_PIXEL as u32;
let row_stride = self.stride as u32;
let row_offset = location.y * row_stride + location.x * col_stride;
self.set_pixel_at_offset(row_offset as usize, value, paint);
}
#[inline]
fn set_pixel_at_offset(&mut self, offset: usize, value: u8, paint: &Paint) {
match value {
0 => (),
255 => self.write_color_at_offset(offset, paint.fg),
_ => {
let fg = &paint.fg;
let bg = &paint.bg;
let a = ((value as u32) * (fg.a as u32)) >> 8;
let blend_factor = ((bg.a as u32) * (255 - a)) >> 8;
let pixel = [
(((bg.b as u32) * blend_factor + (fg.b as u32) * a) >> 8) as u8,
(((bg.g as u32) * blend_factor + (fg.g as u32) * a) >> 8) as u8,
(((bg.r as u32) * blend_factor + (fg.r as u32) * a) >> 8) as u8,
(blend_factor + (fg.a as u32)) as u8,
];
self.pixel_sink.write_pixel_at_offset(offset, &pixel);
}
}
}
/// Fill a rectangle with a particular color.
pub fn fill_rect(&mut self, rect: &Rect, color: Color) {
let col_stride = BYTES_PER_PIXEL;
let row_stride = self.stride;
for y in rect.top..rect.bottom {
for x in rect.left..rect.right {
let offset = y * row_stride + x * col_stride;
self.write_color_at_offset(offset as usize, color);
}
}
}
/// Draw line of text `text` at location `point` with foreground and background colors specified
/// by `paint` and with the typographic characterists in `font`. This method uses
/// fixed size cells of size `size` for each character.
pub fn fill_text_cells(
&mut self, text: &str, location: Point, size: Size, font: &mut FontDescription,
paint: &Paint,
) {
let mut x = location.x;
let advance = size.width;
let scale = Scale::uniform(font.size as f32);
for scalar in text.chars() {
let cell = Rect {
left: x,
top: location.y,
right: x + advance,
bottom: location.y + size.height,
};
self.fill_rect(&cell, paint.bg);
if scalar != ' ' {
let glyph =
font.face
.font
.glyph(scalar)
.scaled(scale)
.positioned(rusttype::Point {
x: x as f32,
y: (location.y + font.baseline as u32) as f32,
});
if let Some(bounding_box) = glyph.pixel_bounding_box() {
glyph.draw(|pixel_x, pixel_y, v| {
let value = (v * 255.0) as u8;
let glyph_location = Point {
x: pixel_x + bounding_box.min.x as u32,
y: pixel_y + bounding_box.min.y as u32,
};
self.set_pixel_at_location(&glyph_location, value, paint);
});
}
}
x += advance;
}
}
/// Draw line of text `text` at location `point` with foreground and background colors specified
/// by `paint` and with the typographic characterists in `font`.
pub fn fill_text(
&mut self, text: &str, location: Point, font: &mut FontDescription, paint: &Paint,
) {
let scale = Scale::uniform(font.size as f32);
let v_metrics = font.face.font.v_metrics(scale);
let offset = rusttype::point(location.x as f32, location.y as f32 + v_metrics.ascent);
let glyphs: Vec<rusttype::PositionedGlyph<'_>> = font.face.font.layout(text, scale, offset).collect();
for glyph in glyphs {
if let Some(bounding_box) = glyph.pixel_bounding_box() {
glyph.draw(|pixel_x, pixel_y, v| {
let value = (v * 255.0) as u8;
let glyph_location = Point {
x: pixel_x + bounding_box.min.x as u32,
y: pixel_y + bounding_box.min.y as u32,
};
self.set_pixel_at_location(&glyph_location, value, paint);
})
}
}
}
/// Measure a line of text `text` and with the typographic characterists in `font`.
/// Returns a tuple containing the measured width and height.
pub fn measure_text(&mut self, text: &str, font: &mut FontDescription) -> (i32, i32) {
let scale = Scale::uniform(font.size as f32);
let v_metrics = font.face.font.v_metrics(scale);
let offset = rusttype::point(0.0, v_metrics.ascent);
let glyphs: Vec<rusttype::PositionedGlyph<'_>> = font.face.font.layout(text, scale, offset).collect();
let width = glyphs
.iter()
.rev()
.map(|g| g.position().x as f32 + g.unpositioned().h_metrics().advance_width)
.next()
.unwrap_or(0.0)
.ceil() as usize;
(width as i32, font.size as i32)
}
}