Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ features = [
"EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement",
"HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PageTransitionEvent", "PointerEvent",
"Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window", "ReadableStream", "RequestCredentials",
"Url", "WebGlContextEvent", "Clipboard", "FocusEvent", "ShadowRoot", "Gamepad", "GamepadButton"
"Url", "WebGlContextEvent", "Clipboard", "FocusEvent", "ShadowRoot", "Gamepad", "GamepadButton", "OffscreenCanvas",
"TextMetrics", "OffscreenCanvasRenderingContext2d"
]

[target.'cfg(target_family = "wasm")'.dependencies.getrandom]
Expand Down
2 changes: 2 additions & 0 deletions web/src/ui.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod font_renderer;

use super::JavascriptPlayer;
use rfd::{AsyncFileDialog, FileHandle};
use ruffle_core::backend::ui::{
Expand Down
119 changes: 119 additions & 0 deletions web/src/ui/font_renderer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use ruffle_core::font::FontMetrics;
use ruffle_core::font::FontRenderer;
use ruffle_core::font::Glyph;
use ruffle_core::swf::Twips;
use ruffle_render::bitmap::Bitmap;
use ruffle_render::bitmap::BitmapFormat;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::OffscreenCanvas;
use web_sys::OffscreenCanvasRenderingContext2d;

#[derive(Debug)]
pub struct CanvasFontRenderer {
canvas: OffscreenCanvas,
ctx: OffscreenCanvasRenderingContext2d,
ascent: f64,
descent: f64,
}

impl CanvasFontRenderer {
/// Render fonts with size 64px. It affects the bitmap size.
const SIZE_PX: f64 = 64.0;

/// Divide each pixel into 20 (use twips precision). It affects metrics.
const SCALE: f64 = 20.0;

pub fn new(italic: bool, bold: bool, font_family: &str) -> Result<Self, JsValue> {
let canvas = OffscreenCanvas::new(1024, 1024)?;

let ctx = canvas.get_context("2d")?.expect("2d context");
let ctx = ctx
.dyn_into::<OffscreenCanvasRenderingContext2d>()
.map_err(|err| JsValue::from_str(&format!("Not a 2d context: {err:?}")))?;

ctx.set_fill_style_str("white");
ctx.set_font(&Self::to_font_str(italic, bold, Self::SIZE_PX, font_family));

let measurement = ctx.measure_text("Myjg")?;
let ascent = measurement.font_bounding_box_ascent();
let descent = measurement.font_bounding_box_descent();

Ok(Self {
canvas,
ctx,
ascent,
descent,
})
}

fn to_font_str(italic: bool, bold: bool, size: f64, font_family: &str) -> String {
let italic = if italic { "italic " } else { "" };
let bold = if bold { "bold " } else { "" };
format!("{italic}{bold}{size}px {font_family}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might missing quotes around the family?

}

fn calculate_width(&self, text: &str) -> Result<f64, JsValue> {
Ok(self.ctx.measure_text(text)?.width())
}

fn render_glyph_internal(&self, character: char) -> Result<Glyph, JsValue> {
let text = &character.to_string();

self.ctx.clear_rect(
0.0,
0.0,
self.canvas.width() as f64,
self.canvas.height() as f64,
);
self.ctx.fill_text(text, 0.0, self.ascent)?;

let width = self.calculate_width(text)?;
let height = self.ascent + self.descent;

let image_data = self.ctx.get_image_data(0.0, 0.0, width, height)?;
let width = image_data.width();
let height = image_data.height();
let pixels = image_data.data().0;

let bitmap = Bitmap::new(width, height, BitmapFormat::Rgba, pixels);
let advance = Twips::from_pixels(width as f64);
Ok(Glyph::from_bitmap(character, bitmap, advance))
}

fn calculate_kerning_internal(&self, left: char, right: char) -> Result<Twips, JsValue> {
let left_width = self.calculate_width(&left.to_string())?;
let right_width = self.calculate_width(&right.to_string())?;
let both_width = self.calculate_width(&format!("{left}{right}"))?;

let kern = both_width - left_width - right_width;
Ok(Twips::from_pixels(kern))
}
}

impl FontRenderer for CanvasFontRenderer {
fn get_font_metrics(&self) -> FontMetrics {
FontMetrics {
scale: (Self::SIZE_PX * Self::SCALE) as f32,
ascent: (self.ascent * Self::SCALE) as i32,
descent: (self.descent * Self::SCALE) as i32,
leading: 0,
}
}

fn has_kerning_info(&self) -> bool {
true
}

fn render_glyph(&self, character: char) -> Option<Glyph> {
self.render_glyph_internal(character)
.map_err(|err| tracing::error!("Failed to render a glyph: {err:?}"))
.ok()
}

fn calculate_kerning(&self, left: char, right: char) -> Twips {
self.calculate_kerning_internal(left, right)
.map_err(|err| tracing::error!("Failed to calculate kerning: {err:?}"))
.unwrap_or(Twips::ZERO)
}
}