use std::borrow::Cow;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::Window,
};
// new femto-like framework:
// wgpu renderer
// kurbo, (alternative is euclid + lyon)
// vector math: glam? and drop euclid (glam is faster https://docs.rs/glam/latest/glam/)
// swash + parley for text
// imgref, bitflags
// fnv, rgb
// resource, image
// usvg for svg
use swash::{
scale::ScaleContext,
shape::ShapeContext,
text::Script,
zeno::{Vector, Verb},
Attributes, CacheKey, Charmap, FontRef,
};
pub struct Font {
// Full content of the font file
data: Vec<u8>,
// Offset to the table directory
offset: u32,
// Cache key
key: CacheKey,
}
impl Font {
pub fn from_file(path: &str, index: usize) -> Option<Self> {
// Read the full font file
let data = std::fs::read(path).ok()?;
// Create a temporary font reference for the first font in the file.
// This will do some basic validation, compute the necessary offset
// and generate a fresh cache key for us.
let font = FontRef::from_index(&data, index)?;
let (offset, key) = (font.offset, font.key);
// Return our struct with the original file data and copies of the
// offset and key from the font reference
Some(Self { data, offset, key })
}
// As a convenience, you may want to forward some methods.
pub fn attributes(&self) -> Attributes {
self.as_ref().attributes()
}
pub fn charmap(&self) -> Charmap {
self.as_ref().charmap()
}
// Create the transient font reference for accessing this crate's
// functionality.
pub fn as_ref(&self) -> FontRef {
// Note that you'll want to initialize the struct directly here as
// using any of the FontRef constructors will generate a new key which,
// while completely safe, will nullify the performance optimizations of
// the caching mechanisms used in this crate.
FontRef {
data: &self.data,
offset: self.offset,
key: self.key,
}
}
}
fn font() {
let font = Font::from_file("assets/fonts/Inter Variable/Inter.ttf", 0).unwrap();
let font = font.as_ref();
// -- Shaping
let mut context = ShapeContext::new();
let mut shaper = context
.builder(font)
.script(Script::Latin)
.size(14.)
.variations(&[("wght", 520.5)])
.build();
shaper.add_str("a quick brown fox?");
// add_str with boundary analysis
// use swash::text::{analyze, Script};
// use swash::text::cluster::{CharInfo, Parser, Token};
// let text = "a quick brown fox?";
// let mut parser = Parser::new(
// Script::Latin,
// text.char_indices()
// // Call analyze passing the same text and zip
// // the results
// .zip(analyze(text.chars()))
// // Analyze yields the tuple (Properties, Boundary)
// .map(|((i, ch), (props, boundary))| Token {
// ch,
// offset: i as u32,
// len: ch.len_utf8() as u8,
// // Create character information from properties and boundary
// info: CharInfo::new(props, boundary),
// data: 0,
// }),
// );
shaper.shape_with(|c| {
// use the glyph cluster
// c.glyphs
});
let mut context = ScaleContext::new();
let mut scaler = context
.builder(font)
.hint(true)
.size(12.)
.variations(&[("wght", 520.5)])
.build();
let glyph_id = font.charmap().map('Q');
let outline = scaler.scale_outline(glyph_id).unwrap();
append_outline((), outline.verbs(), outline.points());
// -- Scaling
}
fn append_outline(_encoder: (), verbs: &[Verb], points: &[Vector]) {
for verb in verbs {
println!("{:?}", verb);
match verb {
Verb::MoveTo => {
//
}
Verb::LineTo => {
//
}
Verb::QuadTo => {
//
}
Verb::CurveTo => {
//
}
Verb::Close => {
//
}
}
}
}
async fn run(event_loop: EventLoop<()>, window: Window) {
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::Backends::all());
let surface = unsafe { instance.create_surface(&window) };
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(), // TODO: select based on backend
force_fallback_adapter: false,
// Request an adapter which can render to our surface
compatible_surface: Some(&surface),
})
.await
.expect("Failed to find an appropriate adapter");
// Create the logical device and command queue
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
limits: wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits()),
},
None,
)
.await
.expect("Failed to create device");
// Load the shaders from disk
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let swapchain_format = surface.get_preferred_format(&adapter).unwrap();
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[swapchain_format.into()],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let mut config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: swapchain_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
};
surface.configure(&device, &config);
font();
event_loop.run(move |event, _, control_flow| {
// Have the closure take ownership of the resources.
// `event_loop.run` never returns, therefore we must do this to ensure
// the resources are properly cleaned up.
let _ = (&instance, &adapter, &shader, &pipeline_layout);
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::Resized(size),
..
} => {
// Reconfigure the surface with the new size
config.width = size.width;
config.height = size.height;
surface.configure(&device, &config);
// On macos the window needs to be redrawn manually after resizing
window.request_redraw();
}
Event::RedrawRequested(_) => {
let frame = surface
.get_current_texture()
.expect("Failed to acquire next swap chain texture");
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
store: true,
},
}],
depth_stencil_attachment: None,
});
rpass.set_pipeline(&render_pipeline);
rpass.draw(0..3, 0..1);
}
queue.submit(Some(encoder.finish()));
frame.present();
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => {}
}
});
}
fn main() {
let event_loop = EventLoop::new();
let window = winit::window::Window::new(&event_loop).unwrap();
#[cfg(not(target_arch = "wasm32"))]
{
env_logger::init();
// Temporarily avoid srgb formats for the swapchain on the web
pollster::block_on(run(event_loop, window));
}
#[cfg(target_arch = "wasm32")]
{
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init().expect("could not initialize logger");
use winit::platform::web::WindowExtWebSys;
// On wasm, append the canvas to the document body
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.body())
.and_then(|body| {
body.append_child(&web_sys::Element::from(window.canvas()))
.ok()
})
.expect("couldn't append canvas to document body");
wasm_bindgen_futures::spawn_local(run(event_loop, window));
}
}