2021-04-08 10:14:09 -07:00
|
|
|
use std::ops::RangeBounds;
|
|
|
|
|
2021-10-21 12:10:57 -07:00
|
|
|
use swash::scale::{ScaleContext, Scaler};
|
2021-08-18 12:04:43 -07:00
|
|
|
use swash::zeno::{Vector, Verb};
|
|
|
|
use swash::{FontRef, GlyphId};
|
2021-04-08 10:14:09 -07:00
|
|
|
|
|
|
|
use piet::kurbo::{Point, Rect, Size};
|
|
|
|
use piet::{
|
|
|
|
Error, FontFamily, HitTestPoint, HitTestPosition, LineMetric, Text, TextAttribute, TextLayout,
|
|
|
|
TextLayoutBuilder, TextStorage,
|
|
|
|
};
|
|
|
|
|
2021-08-25 13:59:26 -07:00
|
|
|
use piet_gpu_types::scene::{CubicSeg, Element, FillColor, LineSeg, QuadSeg, Transform};
|
2021-04-08 10:14:09 -07:00
|
|
|
|
|
|
|
use crate::render_ctx::{self, FillMode};
|
|
|
|
use crate::PietGpuRenderContext;
|
|
|
|
|
|
|
|
// This is very much a hack to get things working.
|
2021-08-25 13:59:26 -07:00
|
|
|
// On Windows, can set this to "c:\\Windows\\Fonts\\seguiemj.ttf" to get color emoji
|
|
|
|
const FONT_DATA: &[u8] = include_bytes!("../third-party/Roboto-Regular.ttf");
|
2021-04-08 10:14:09 -07:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Font {
|
2021-08-18 12:04:43 -07:00
|
|
|
// Storing the font_ref is ok for static font data, but the better way to do
|
|
|
|
// this is to store the CacheKey.
|
|
|
|
font_ref: FontRef<'static>,
|
2021-04-08 10:14:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct PietGpuText {
|
|
|
|
font: Font,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct PietGpuTextLayout {
|
|
|
|
font: Font,
|
|
|
|
size: f64,
|
|
|
|
glyphs: Vec<Glyph>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct PietGpuTextLayoutBuilder {
|
|
|
|
font: Font,
|
|
|
|
text: String,
|
|
|
|
size: f64,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
struct Glyph {
|
|
|
|
glyph_id: GlyphId,
|
|
|
|
x: f32,
|
|
|
|
y: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct PathEncoder {
|
|
|
|
elements: Vec<Element>,
|
2021-08-20 12:20:27 -07:00
|
|
|
n_segs: usize,
|
|
|
|
// If this is zero, then it's a text glyph and should be followed by a fill
|
|
|
|
n_colr_layers: usize,
|
2021-04-08 10:14:09 -07:00
|
|
|
}
|
|
|
|
|
2021-10-21 12:10:57 -07:00
|
|
|
struct TextRenderCtx<'a> {
|
|
|
|
scaler: Scaler<'a>,
|
|
|
|
}
|
|
|
|
|
2021-04-08 10:14:09 -07:00
|
|
|
impl PietGpuText {
|
|
|
|
pub(crate) fn new(font: Font) -> PietGpuText {
|
|
|
|
PietGpuText { font }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Text for PietGpuText {
|
|
|
|
type TextLayout = PietGpuTextLayout;
|
|
|
|
type TextLayoutBuilder = PietGpuTextLayoutBuilder;
|
|
|
|
|
|
|
|
fn load_font(&mut self, _data: &[u8]) -> Result<FontFamily, Error> {
|
|
|
|
Ok(FontFamily::default())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn new_text_layout(&mut self, text: impl TextStorage) -> Self::TextLayoutBuilder {
|
|
|
|
PietGpuTextLayoutBuilder::new(&self.font, &text.as_str())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn font_family(&mut self, _family_name: &str) -> Option<FontFamily> {
|
|
|
|
Some(FontFamily::default())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TextLayout for PietGpuTextLayout {
|
|
|
|
fn size(&self) -> Size {
|
|
|
|
Size::ZERO
|
|
|
|
}
|
|
|
|
|
|
|
|
fn image_bounds(&self) -> Rect {
|
|
|
|
Rect::ZERO
|
|
|
|
}
|
|
|
|
|
|
|
|
fn line_text(&self, _line_number: usize) -> Option<&str> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn line_metric(&self, _line_number: usize) -> Option<LineMetric> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn line_count(&self) -> usize {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
|
|
|
|
fn hit_test_point(&self, _point: Point) -> HitTestPoint {
|
|
|
|
HitTestPoint::default()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn hit_test_text_position(&self, _text_position: usize) -> HitTestPosition {
|
|
|
|
HitTestPosition::default()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn text(&self) -> &str {
|
|
|
|
""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Font {
|
|
|
|
pub fn new() -> Font {
|
2021-08-18 12:04:43 -07:00
|
|
|
let font_ref = FontRef::from_index(FONT_DATA, 0).expect("error parsing font");
|
|
|
|
Font { font_ref }
|
2021-04-08 10:14:09 -07:00
|
|
|
}
|
|
|
|
|
2021-10-21 12:10:57 -07:00
|
|
|
fn make_path<'a>(&self, glyph_id: GlyphId, tc: &mut TextRenderCtx<'a>) -> PathEncoder {
|
2021-08-18 12:04:43 -07:00
|
|
|
let mut encoder = PathEncoder::default();
|
2021-10-21 12:10:57 -07:00
|
|
|
if tc.scaler.has_color_outlines() {
|
|
|
|
if let Some(outline) = tc.scaler.scale_color_outline(glyph_id) {
|
2021-08-25 13:59:26 -07:00
|
|
|
// TODO: be more sophisticated choosing a palette
|
|
|
|
let palette = self.font_ref.color_palettes().next().unwrap();
|
|
|
|
let mut i = 0;
|
|
|
|
while let Some(layer) = outline.get(i) {
|
|
|
|
if let Some(color_ix) = layer.color_index() {
|
|
|
|
let color = palette.get(color_ix);
|
|
|
|
encoder.append_outline(layer.verbs(), layer.points());
|
|
|
|
encoder.append_solid_fill(color);
|
2021-08-18 12:04:43 -07:00
|
|
|
}
|
2021-08-25 13:59:26 -07:00
|
|
|
i += 1;
|
2021-08-18 12:04:43 -07:00
|
|
|
}
|
2021-08-25 13:59:26 -07:00
|
|
|
return encoder;
|
2021-08-18 12:04:43 -07:00
|
|
|
}
|
|
|
|
}
|
2021-10-21 12:10:57 -07:00
|
|
|
if let Some(outline) = tc.scaler.scale_outline(glyph_id) {
|
2021-08-25 13:59:26 -07:00
|
|
|
encoder.append_outline(outline.verbs(), outline.points());
|
|
|
|
}
|
2021-08-18 12:04:43 -07:00
|
|
|
encoder
|
2021-04-08 10:14:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PietGpuTextLayout {
|
|
|
|
pub(crate) fn make_layout(font: &Font, text: &str, size: f64) -> PietGpuTextLayout {
|
|
|
|
let mut glyphs = Vec::new();
|
|
|
|
let mut x = 0.0;
|
|
|
|
let y = 0.0;
|
|
|
|
for c in text.chars() {
|
2021-08-18 12:04:43 -07:00
|
|
|
let glyph_id = font.font_ref.charmap().map(c);
|
|
|
|
let glyph = Glyph { glyph_id, x, y };
|
|
|
|
glyphs.push(glyph);
|
|
|
|
let adv = font.font_ref.glyph_metrics(&[]).advance_width(glyph_id);
|
|
|
|
x += adv;
|
2021-04-08 10:14:09 -07:00
|
|
|
}
|
|
|
|
PietGpuTextLayout {
|
|
|
|
glyphs,
|
|
|
|
font: font.clone(),
|
2021-08-18 12:04:43 -07:00
|
|
|
size,
|
2021-04-08 10:14:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn draw_text(&self, ctx: &mut PietGpuRenderContext, pos: Point) {
|
2021-10-21 12:10:57 -07:00
|
|
|
let mut scale_ctx = ScaleContext::new();
|
2021-11-05 14:00:14 -07:00
|
|
|
let scaler = scale_ctx.builder(self.font.font_ref).size(2048.).build();
|
|
|
|
let mut tc = TextRenderCtx { scaler };
|
2021-08-18 12:04:43 -07:00
|
|
|
// Should we use ppem from font, or let swash scale?
|
|
|
|
const DEFAULT_UPEM: u16 = 2048;
|
|
|
|
let scale = self.size as f32 / DEFAULT_UPEM as f32;
|
2021-04-08 10:14:09 -07:00
|
|
|
let mut inv_transform = None;
|
|
|
|
// TODO: handle y offsets also
|
|
|
|
let mut last_x = 0.0;
|
|
|
|
ctx.set_fill_mode(FillMode::Nonzero);
|
|
|
|
for glyph in &self.glyphs {
|
|
|
|
let transform = match &mut inv_transform {
|
|
|
|
None => {
|
|
|
|
let inv_scale = scale.recip();
|
|
|
|
let translate = render_ctx::to_f32_2(pos);
|
|
|
|
inv_transform = Some(Transform {
|
|
|
|
mat: [inv_scale, 0.0, 0.0, -inv_scale],
|
|
|
|
translate: [
|
|
|
|
-translate[0] * inv_scale - glyph.x,
|
|
|
|
translate[1] * inv_scale,
|
|
|
|
],
|
|
|
|
});
|
|
|
|
let tpos = render_ctx::to_f32_2(pos);
|
|
|
|
let translate = [tpos[0] + scale * glyph.x, tpos[1]];
|
|
|
|
Transform {
|
|
|
|
mat: [scale, 0.0, 0.0, -scale],
|
|
|
|
translate,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(inv) => {
|
|
|
|
let delta_x = glyph.x - last_x;
|
|
|
|
inv.translate[0] -= delta_x;
|
|
|
|
Transform {
|
|
|
|
mat: [1.0, 0.0, 0.0, 1.0],
|
|
|
|
translate: [delta_x, 0.0],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
last_x = glyph.x;
|
|
|
|
//println!("{:?}, {:?}", transform.mat, transform.translate);
|
|
|
|
ctx.encode_transform(transform);
|
2021-10-21 12:10:57 -07:00
|
|
|
let path = self.font.make_path(glyph.glyph_id, &mut tc);
|
2021-04-08 10:14:09 -07:00
|
|
|
ctx.append_path_encoder(&path);
|
2021-08-25 13:59:26 -07:00
|
|
|
if path.n_colr_layers == 0 {
|
|
|
|
ctx.fill_glyph(0xff_ff_ff_ff);
|
|
|
|
} else {
|
|
|
|
ctx.bump_n_paths(path.n_colr_layers);
|
|
|
|
}
|
2021-04-08 10:14:09 -07:00
|
|
|
}
|
|
|
|
if let Some(transform) = inv_transform {
|
|
|
|
ctx.encode_transform(transform);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PietGpuTextLayoutBuilder {
|
|
|
|
pub(crate) fn new(font: &Font, text: &str) -> PietGpuTextLayoutBuilder {
|
|
|
|
PietGpuTextLayoutBuilder {
|
|
|
|
font: font.clone(),
|
|
|
|
text: text.to_owned(),
|
|
|
|
size: 12.0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TextLayoutBuilder for PietGpuTextLayoutBuilder {
|
|
|
|
type Out = PietGpuTextLayout;
|
|
|
|
|
|
|
|
fn max_width(self, _width: f64) -> Self {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
fn alignment(self, _alignment: piet::TextAlignment) -> Self {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
fn default_attribute(mut self, attribute: impl Into<TextAttribute>) -> Self {
|
|
|
|
let attribute = attribute.into();
|
|
|
|
match attribute {
|
|
|
|
TextAttribute::FontSize(size) => self.size = size,
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
fn range_attribute(
|
|
|
|
self,
|
|
|
|
_range: impl RangeBounds<usize>,
|
|
|
|
_attribute: impl Into<TextAttribute>,
|
|
|
|
) -> Self {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build(self) -> Result<Self::Out, Error> {
|
|
|
|
Ok(PietGpuTextLayout::make_layout(
|
|
|
|
&self.font, &self.text, self.size,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PathEncoder {
|
|
|
|
pub(crate) fn elements(&self) -> &[Element] {
|
|
|
|
&self.elements
|
|
|
|
}
|
2021-08-20 12:20:27 -07:00
|
|
|
|
|
|
|
pub(crate) fn n_segs(&self) -> usize {
|
|
|
|
self.n_segs
|
|
|
|
}
|
2021-08-25 13:59:26 -07:00
|
|
|
|
|
|
|
fn append_outline(&mut self, verbs: &[Verb], points: &[Vector]) {
|
|
|
|
let elements = &mut self.elements;
|
|
|
|
let old_len = elements.len();
|
|
|
|
let mut i = 0;
|
|
|
|
let mut start_pt = [0.0f32; 2];
|
|
|
|
let mut last_pt = [0.0f32; 2];
|
|
|
|
for verb in verbs {
|
|
|
|
match verb {
|
|
|
|
Verb::MoveTo => {
|
|
|
|
start_pt = convert_swash_point(points[i]);
|
|
|
|
last_pt = start_pt;
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
Verb::LineTo => {
|
|
|
|
let p1 = convert_swash_point(points[i]);
|
|
|
|
elements.push(Element::Line(LineSeg { p0: last_pt, p1 }));
|
|
|
|
last_pt = p1;
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
Verb::QuadTo => {
|
|
|
|
let p1 = convert_swash_point(points[i]);
|
|
|
|
let p2 = convert_swash_point(points[i + 1]);
|
|
|
|
elements.push(Element::Quad(QuadSeg {
|
|
|
|
p0: last_pt,
|
|
|
|
p1,
|
|
|
|
p2,
|
|
|
|
}));
|
|
|
|
last_pt = p2;
|
|
|
|
i += 2;
|
|
|
|
}
|
|
|
|
Verb::CurveTo => {
|
|
|
|
let p1 = convert_swash_point(points[i]);
|
|
|
|
let p2 = convert_swash_point(points[i + 1]);
|
|
|
|
let p3 = convert_swash_point(points[i + 2]);
|
|
|
|
elements.push(Element::Cubic(CubicSeg {
|
|
|
|
p0: last_pt,
|
|
|
|
p1,
|
|
|
|
p2,
|
|
|
|
p3,
|
|
|
|
}));
|
|
|
|
last_pt = p3;
|
|
|
|
i += 3;
|
|
|
|
}
|
|
|
|
Verb::Close => {
|
|
|
|
if start_pt != last_pt {
|
|
|
|
elements.push(Element::Line(LineSeg {
|
|
|
|
p0: last_pt,
|
|
|
|
p1: start_pt,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.n_segs += elements.len() - old_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn append_solid_fill(&mut self, color: [u8; 4]) {
|
|
|
|
let rgba_color = u32::from_be_bytes(color);
|
|
|
|
self.elements
|
|
|
|
.push(Element::FillColor(FillColor { rgba_color }));
|
|
|
|
self.n_colr_layers += 1;
|
|
|
|
}
|
2021-04-08 10:14:09 -07:00
|
|
|
}
|
2021-08-18 12:04:43 -07:00
|
|
|
|
|
|
|
fn convert_swash_point(v: Vector) -> [f32; 2] {
|
|
|
|
[v.x, v.y]
|
|
|
|
}
|