Skip to content

Commit c25bdc9

Browse files
committed
Implement text rendering for overlays
1 parent 5b67eb4 commit c25bdc9

2 files changed

Lines changed: 133 additions & 43 deletions

File tree

Binary file not shown.

editor/src/messages/portfolio/document/overlays/utility_types_vello.rs

Lines changed: 133 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use core::f64::consts::{FRAC_PI_2, PI, TAU};
1010
use glam::{DAffine2, DVec2};
1111
use graphene_std::Color;
1212
use graphene_std::math::quad::Quad;
13+
use graphene_std::table::Table;
14+
use graphene_std::text::{TextAlign, TypesettingConfig, load_font, to_path};
1315
use graphene_std::vector::click_target::ClickTargetType;
1416
use graphene_std::vector::{PointId, SegmentId, Vector};
1517
use std::collections::HashMap;
@@ -980,7 +982,7 @@ impl OverlayContextInternal {
980982
// This matches the Canvas2D checkerboard pattern
981983
let mut data = vec![0u8; (PATTERN_WIDTH * PATTERN_HEIGHT * 4) as usize];
982984
let rgba = color.to_rgba8_srgb();
983-
985+
984986
// Set pixels at (0,0) and (2,2) to the specified color
985987
let pixels = [(0, 0), (2, 2)];
986988
for &(x, y) in &pixels {
@@ -1006,24 +1008,55 @@ impl OverlayContextInternal {
10061008
}
10071009

10081010
fn get_width(&self, text: &str) -> f64 {
1009-
// Basic text width estimation that matches the text() function implementation
1010-
// This uses the same character width estimation for consistency
1011-
// Note: This is primarily used for manual positioning calculations, but ideally
1012-
// the pivot system in text() should handle centering automatically
1013-
const CHAR_WIDTH: f64 = 7.0; // Approximate character width at 12px font size
1014-
text.len() as f64 * CHAR_WIDTH
1011+
// Use the actual text-to-path system to get precise text width
1012+
const FONT_SIZE: f64 = 12.0;
1013+
1014+
let typesetting = TypesettingConfig {
1015+
font_size: FONT_SIZE,
1016+
line_height_ratio: 1.2,
1017+
character_spacing: 0.0,
1018+
max_width: None,
1019+
max_height: None,
1020+
tilt: 0.0,
1021+
align: TextAlign::Left,
1022+
};
1023+
1024+
// Load Source Sans Pro font data
1025+
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
1026+
let font_blob = Some(load_font(FONT_DATA));
1027+
1028+
// Convert text to paths and calculate actual bounds
1029+
let text_table = to_path(text, font_blob, typesetting, false);
1030+
let text_bounds = self.calculate_text_bounds(&text_table);
1031+
text_bounds.width()
10151032
}
10161033

10171034
fn text(&mut self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
1018-
// For now, implement a basic text rendering approach
1019-
// This is a simplified version that can be enhanced later with full font support
1020-
1021-
// Calculate approximate text dimensions (basic estimation)
1022-
let char_width = 7.0; // Approximate character width at 12px font size
1023-
let char_height = 12.0; // Font size
1024-
let text_width = text.len() as f64 * char_width;
1025-
let text_height = char_height;
1026-
1035+
// Use the proper text-to-path system for accurate text rendering
1036+
const FONT_SIZE: f64 = 12.0;
1037+
1038+
// Create typesetting configuration
1039+
let typesetting = TypesettingConfig {
1040+
font_size: FONT_SIZE,
1041+
line_height_ratio: 1.2,
1042+
character_spacing: 0.0,
1043+
max_width: None,
1044+
max_height: None,
1045+
tilt: 0.0,
1046+
align: TextAlign::Left, // We'll handle alignment manually via pivot
1047+
};
1048+
1049+
// Load Source Sans Pro font data
1050+
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
1051+
let font_blob = Some(load_font(FONT_DATA));
1052+
1053+
// Convert text to vector paths using the existing text system
1054+
let text_table = to_path(text, font_blob, typesetting, false);
1055+
// Calculate text bounds from the generated paths
1056+
let text_bounds = self.calculate_text_bounds(&text_table);
1057+
let text_width = text_bounds.width();
1058+
let text_height = text_bounds.height();
1059+
10271060
// Calculate position based on pivot
10281061
let mut position = DVec2::ZERO;
10291062
match pivot[0] {
@@ -1032,43 +1065,100 @@ impl OverlayContextInternal {
10321065
Pivot::End => position.x = -padding - text_width,
10331066
}
10341067
match pivot[1] {
1035-
Pivot::Start => position.y = padding + text_height,
1036-
Pivot::Middle => position.y = text_height / 2.0,
1037-
Pivot::End => position.y = -padding,
1068+
Pivot::Start => position.y = padding + text_height * 0.8, // Account for baseline
1069+
Pivot::Middle => position.y = text_height * 0.3, // Center on x-height
1070+
Pivot::End => position.y = -padding - text_height * 0.2,
10381071
}
1039-
1072+
10401073
let text_transform = transform * DAffine2::from_translation(position);
10411074
let device_transform = self.get_transform();
10421075
let combined_transform = kurbo::Affine::new(text_transform.to_cols_array());
10431076
let vello_transform = device_transform * combined_transform;
1044-
1077+
10451078
// Draw background if specified
10461079
if let Some(bg_color) = background_color {
10471080
let bg_rect = kurbo::Rect::new(
1048-
-padding,
1049-
-padding,
1050-
text_width + padding,
1051-
text_height + padding
1052-
);
1053-
self.scene.fill(
1054-
peniko::Fill::NonZero,
1055-
vello_transform,
1056-
Self::parse_color(bg_color),
1057-
None,
1058-
&bg_rect,
1081+
text_bounds.min_x() - padding,
1082+
text_bounds.min_y() - padding,
1083+
text_bounds.max_x() + padding,
1084+
text_bounds.max_y() + padding,
10591085
);
1086+
self.scene.fill(peniko::Fill::NonZero, vello_transform, Self::parse_color(bg_color), None, &bg_rect);
1087+
}
1088+
1089+
// Render the actual text paths
1090+
self.render_text_paths(&text_table, font_color, vello_transform);
1091+
}
1092+
1093+
// Calculate bounds of text from vector table
1094+
fn calculate_text_bounds(&self, text_table: &Table<Vector>) -> kurbo::Rect {
1095+
let mut min_x = f64::INFINITY;
1096+
let mut min_y = f64::INFINITY;
1097+
let mut max_x = f64::NEG_INFINITY;
1098+
let mut max_y = f64::NEG_INFINITY;
1099+
1100+
for row in text_table.iter() {
1101+
// Use the existing segment_bezier_iter to get all bezier curves
1102+
for (_, bezier, _, _) in row.element.segment_bezier_iter() {
1103+
let transformed_bezier = bezier.apply_transformation(|point| row.transform.transform_point2(point));
1104+
1105+
// Add start and end points to bounds
1106+
let points = [transformed_bezier.start, transformed_bezier.end];
1107+
for point in points {
1108+
min_x = min_x.min(point.x);
1109+
min_y = min_y.min(point.y);
1110+
max_x = max_x.max(point.x);
1111+
max_y = max_y.max(point.y);
1112+
}
1113+
1114+
// Add handle points if they exist
1115+
match transformed_bezier.handles {
1116+
bezier_rs::BezierHandles::Quadratic { handle } => {
1117+
min_x = min_x.min(handle.x);
1118+
min_y = min_y.min(handle.y);
1119+
max_x = max_x.max(handle.x);
1120+
max_y = max_y.max(handle.y);
1121+
}
1122+
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
1123+
for handle in [handle_start, handle_end] {
1124+
min_x = min_x.min(handle.x);
1125+
min_y = min_y.min(handle.y);
1126+
max_x = max_x.max(handle.x);
1127+
max_y = max_y.max(handle.y);
1128+
}
1129+
}
1130+
_ => {}
1131+
}
1132+
}
1133+
}
1134+
1135+
if min_x.is_finite() && min_y.is_finite() && max_x.is_finite() && max_y.is_finite() {
1136+
kurbo::Rect::new(min_x, min_y, max_x, max_y)
1137+
} else {
1138+
// Fallback for empty text
1139+
kurbo::Rect::new(0.0, 0.0, 0.0, 12.0)
1140+
}
1141+
}
1142+
1143+
// Render text paths to the vello scene using existing infrastructure
1144+
fn render_text_paths(&mut self, text_table: &Table<Vector>, font_color: &str, base_transform: kurbo::Affine) {
1145+
let color = Self::parse_color(font_color);
1146+
1147+
for row in text_table.iter() {
1148+
// Use the existing bezier_to_path infrastructure to convert Vector to BezPath
1149+
let mut path = BezPath::new();
1150+
let mut last_point = None;
1151+
1152+
for (_, bezier, start_id, end_id) in row.element.segment_bezier_iter() {
1153+
let move_to = last_point != Some(start_id);
1154+
last_point = Some(end_id);
1155+
1156+
self.bezier_to_path(bezier, row.transform.clone(), move_to, &mut path);
1157+
}
1158+
1159+
// Render the path
1160+
self.scene.fill(peniko::Fill::NonZero, base_transform, color, None, &path);
10601161
}
1061-
1062-
// For now, draw a simple rectangle to represent text
1063-
// TODO: Implement proper font rendering using vello's text capabilities
1064-
let text_rect = kurbo::Rect::new(0.0, 0.0, text_width, text_height);
1065-
self.scene.stroke(
1066-
&kurbo::Stroke::new(1.0),
1067-
vello_transform,
1068-
Self::parse_color(font_color),
1069-
None,
1070-
&text_rect,
1071-
);
10721162
}
10731163

10741164
fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {

0 commit comments

Comments
 (0)