@@ -10,6 +10,8 @@ use core::f64::consts::{FRAC_PI_2, PI, TAU};
10
10
use glam:: { DAffine2 , DVec2 } ;
11
11
use graphene_std:: Color ;
12
12
use graphene_std:: math:: quad:: Quad ;
13
+ use graphene_std:: table:: Table ;
14
+ use graphene_std:: text:: { TextAlign , TypesettingConfig , load_font, to_path} ;
13
15
use graphene_std:: vector:: click_target:: ClickTargetType ;
14
16
use graphene_std:: vector:: { PointId , SegmentId , Vector } ;
15
17
use std:: collections:: HashMap ;
@@ -378,7 +380,8 @@ impl OverlayContext {
378
380
}
379
381
380
382
pub fn text ( & self , text : & str , font_color : & str , background_color : Option < & str > , transform : DAffine2 , padding : f64 , pivot : [ Pivot ; 2 ] ) {
381
- self . internal ( ) . text ( text, font_color, background_color, transform, padding, pivot) ;
383
+ let mut internal = self . internal ( ) ;
384
+ internal. text ( text, font_color, background_color, transform, padding, pivot) ;
382
385
}
383
386
384
387
pub fn translation_box ( & mut self , translation : DVec2 , quad : Quad , typed_string : Option < String > ) {
@@ -972,32 +975,195 @@ impl OverlayContextInternal {
972
975
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
973
976
/// Used by the fill tool to show the area to be filled.
974
977
fn fill_path_pattern ( & mut self , subpaths : impl Iterator < Item = impl Borrow < Subpath < PointId > > > , transform : DAffine2 , color : & Color ) {
975
- // TODO: Implement pattern fill in Vello
976
- // For now, just fill with a semi-transparent version of the color
978
+ const PATTERN_WIDTH : u32 = 4 ;
979
+ const PATTERN_HEIGHT : u32 = 4 ;
980
+
981
+ // Create a 4x4 pixel pattern with colored pixels at (0,0) and (2,2)
982
+ // This matches the Canvas2D checkerboard pattern
983
+ let mut data = vec ! [ 0u8 ; ( PATTERN_WIDTH * PATTERN_HEIGHT * 4 ) as usize ] ;
984
+ let rgba = color. to_rgba8_srgb ( ) ;
985
+
986
+ // ┌▄▄┬──┬──┬──┐
987
+ // ├▀▀┼──┼──┼──┤
988
+ // ├──┼──┼▄▄┼──┤
989
+ // ├──┼──┼▀▀┼──┤
990
+ // └──┴──┴──┴──┘
991
+ // Set pixels at (0,0) and (2,2) to the specified color
992
+ let pixels = [ ( 0 , 0 ) , ( 2 , 2 ) ] ;
993
+ for & ( x, y) in & pixels {
994
+ let index = ( ( y * PATTERN_WIDTH + x) * 4 ) as usize ;
995
+ data[ index..index + 4 ] . copy_from_slice ( & rgba) ;
996
+ }
997
+
998
+ let image = peniko:: Image {
999
+ data : data. into ( ) ,
1000
+ format : peniko:: ImageFormat :: Rgba8 ,
1001
+ width : PATTERN_WIDTH ,
1002
+ height : PATTERN_HEIGHT ,
1003
+ x_extend : peniko:: Extend :: Repeat ,
1004
+ y_extend : peniko:: Extend :: Repeat ,
1005
+ alpha : 1.0 ,
1006
+ quality : peniko:: ImageQuality :: default ( ) ,
1007
+ } ;
1008
+
977
1009
let path = self . push_path ( subpaths, transform) ;
978
- let semi_transparent_color = color. with_alpha ( 0.5 ) ;
979
-
980
- self . scene . fill (
981
- peniko:: Fill :: NonZero ,
982
- self . get_transform ( ) ,
983
- peniko:: Color :: from_rgba8 (
984
- ( semi_transparent_color. r ( ) * 255. ) as u8 ,
985
- ( semi_transparent_color. g ( ) * 255. ) as u8 ,
986
- ( semi_transparent_color. b ( ) * 255. ) as u8 ,
987
- ( semi_transparent_color. a ( ) * 255. ) as u8 ,
988
- ) ,
989
- None ,
990
- & path,
991
- ) ;
992
- }
1010
+ let brush = peniko:: Brush :: Image ( image) ;
1011
+
1012
+ self . scene . fill ( peniko:: Fill :: NonZero , self . get_transform ( ) , & brush, None , & path) ;
1013
+ }
1014
+
1015
+ fn get_width ( & self , text : & str ) -> f64 {
1016
+ // Use the actual text-to-path system to get precise text width
1017
+ const FONT_SIZE : f64 = 12.0 ;
1018
+
1019
+ let typesetting = TypesettingConfig {
1020
+ font_size : FONT_SIZE ,
1021
+ line_height_ratio : 1.2 ,
1022
+ character_spacing : 0.0 ,
1023
+ max_width : None ,
1024
+ max_height : None ,
1025
+ tilt : 0.0 ,
1026
+ align : TextAlign :: Left ,
1027
+ } ;
1028
+
1029
+ // Load Source Sans Pro font data
1030
+ const FONT_DATA : & [ u8 ] = include_bytes ! ( "source-sans-pro-regular.ttf" ) ;
1031
+ let font_blob = Some ( load_font ( FONT_DATA ) ) ;
1032
+
1033
+ // Convert text to paths and calculate actual bounds
1034
+ let text_table = to_path ( text, font_blob, typesetting, false ) ;
1035
+ let text_bounds = self . calculate_text_bounds ( & text_table) ;
1036
+ text_bounds. width ( )
1037
+ }
1038
+
1039
+ fn text ( & mut self , text : & str , font_color : & str , background_color : Option < & str > , transform : DAffine2 , padding : f64 , pivot : [ Pivot ; 2 ] ) {
1040
+ // Use the proper text-to-path system for accurate text rendering
1041
+ const FONT_SIZE : f64 = 12.0 ;
1042
+
1043
+ // Create typesetting configuration
1044
+ let typesetting = TypesettingConfig {
1045
+ font_size : FONT_SIZE ,
1046
+ line_height_ratio : 1.2 ,
1047
+ character_spacing : 0.0 ,
1048
+ max_width : None ,
1049
+ max_height : None ,
1050
+ tilt : 0.0 ,
1051
+ align : TextAlign :: Left , // We'll handle alignment manually via pivot
1052
+ } ;
1053
+
1054
+ // Load Source Sans Pro font data
1055
+ const FONT_DATA : & [ u8 ] = include_bytes ! ( "source-sans-pro-regular.ttf" ) ;
1056
+ let font_blob = Some ( load_font ( FONT_DATA ) ) ;
1057
+
1058
+ // Convert text to vector paths using the existing text system
1059
+ let text_table = to_path ( text, font_blob, typesetting, false ) ;
1060
+ // Calculate text bounds from the generated paths
1061
+ let text_bounds = self . calculate_text_bounds ( & text_table) ;
1062
+ let text_width = text_bounds. width ( ) ;
1063
+ let text_height = text_bounds. height ( ) ;
1064
+
1065
+ // Calculate position based on pivot
1066
+ let mut position = DVec2 :: ZERO ;
1067
+ match pivot[ 0 ] {
1068
+ Pivot :: Start => position. x = padding,
1069
+ Pivot :: Middle => position. x = -text_width / 2.0 ,
1070
+ Pivot :: End => position. x = -padding - text_width,
1071
+ }
1072
+ match pivot[ 1 ] {
1073
+ Pivot :: Start => position. y = padding,
1074
+ Pivot :: Middle => position. y -= text_height * 0.5 ,
1075
+ Pivot :: End => position. y = -padding - text_height,
1076
+ }
1077
+
1078
+ let text_transform = transform * DAffine2 :: from_translation ( position) ;
1079
+ let device_transform = self . get_transform ( ) ;
1080
+ let combined_transform = kurbo:: Affine :: new ( text_transform. to_cols_array ( ) ) ;
1081
+ let vello_transform = device_transform * combined_transform;
1082
+
1083
+ // Draw background if specified
1084
+ if let Some ( bg_color) = background_color {
1085
+ let bg_rect = kurbo:: Rect :: new (
1086
+ text_bounds. min_x ( ) - padding,
1087
+ text_bounds. min_y ( ) - padding,
1088
+ text_bounds. max_x ( ) + padding,
1089
+ text_bounds. max_y ( ) + padding,
1090
+ ) ;
1091
+ self . scene . fill ( peniko:: Fill :: NonZero , vello_transform, Self :: parse_color ( bg_color) , None , & bg_rect) ;
1092
+ }
1093
+
1094
+ // Render the actual text paths
1095
+ self . render_text_paths ( & text_table, font_color, vello_transform) ;
1096
+ }
1097
+
1098
+ // Calculate bounds of text from vector table
1099
+ fn calculate_text_bounds ( & self , text_table : & Table < Vector > ) -> kurbo:: Rect {
1100
+ let mut min_x = f64:: INFINITY ;
1101
+ let mut min_y = f64:: INFINITY ;
1102
+ let mut max_x = f64:: NEG_INFINITY ;
1103
+ let mut max_y = f64:: NEG_INFINITY ;
1104
+
1105
+ for row in text_table. iter ( ) {
1106
+ // Use the existing segment_bezier_iter to get all bezier curves
1107
+ for ( _, bezier, _, _) in row. element . segment_bezier_iter ( ) {
1108
+ let transformed_bezier = bezier. apply_transformation ( |point| row. transform . transform_point2 ( point) ) ;
1109
+
1110
+ // Add start and end points to bounds
1111
+ let points = [ transformed_bezier. start , transformed_bezier. end ] ;
1112
+ for point in points {
1113
+ min_x = min_x. min ( point. x ) ;
1114
+ min_y = min_y. min ( point. y ) ;
1115
+ max_x = max_x. max ( point. x ) ;
1116
+ max_y = max_y. max ( point. y ) ;
1117
+ }
1118
+
1119
+ // Add handle points if they exist
1120
+ match transformed_bezier. handles {
1121
+ bezier_rs:: BezierHandles :: Quadratic { handle } => {
1122
+ min_x = min_x. min ( handle. x ) ;
1123
+ min_y = min_y. min ( handle. y ) ;
1124
+ max_x = max_x. max ( handle. x ) ;
1125
+ max_y = max_y. max ( handle. y ) ;
1126
+ }
1127
+ bezier_rs:: BezierHandles :: Cubic { handle_start, handle_end } => {
1128
+ for handle in [ handle_start, handle_end] {
1129
+ min_x = min_x. min ( handle. x ) ;
1130
+ min_y = min_y. min ( handle. y ) ;
1131
+ max_x = max_x. max ( handle. x ) ;
1132
+ max_y = max_y. max ( handle. y ) ;
1133
+ }
1134
+ }
1135
+ _ => { }
1136
+ }
1137
+ }
1138
+ }
993
1139
994
- fn get_width ( & self , _text : & str ) -> f64 {
995
- // TODO: Implement proper text measurement in Vello
996
- 0.
1140
+ if min_x. is_finite ( ) && min_y. is_finite ( ) && max_x. is_finite ( ) && max_y. is_finite ( ) {
1141
+ kurbo:: Rect :: new ( min_x, min_y, max_x, max_y)
1142
+ } else {
1143
+ // Fallback for empty text
1144
+ kurbo:: Rect :: new ( 0.0 , 0.0 , 0.0 , 12.0 )
1145
+ }
997
1146
}
998
1147
999
- fn text ( & self , _text : & str , _font_color : & str , _background_color : Option < & str > , _transform : DAffine2 , _padding : f64 , _pivot : [ Pivot ; 2 ] ) {
1000
- // TODO: Implement text rendering in Vello
1148
+ // Render text paths to the vello scene using existing infrastructure
1149
+ fn render_text_paths ( & mut self , text_table : & Table < Vector > , font_color : & str , base_transform : kurbo:: Affine ) {
1150
+ let color = Self :: parse_color ( font_color) ;
1151
+
1152
+ for row in text_table. iter ( ) {
1153
+ // Use the existing bezier_to_path infrastructure to convert Vector to BezPath
1154
+ let mut path = BezPath :: new ( ) ;
1155
+ let mut last_point = None ;
1156
+
1157
+ for ( _, bezier, start_id, end_id) in row. element . segment_bezier_iter ( ) {
1158
+ let move_to = last_point != Some ( start_id) ;
1159
+ last_point = Some ( end_id) ;
1160
+
1161
+ self . bezier_to_path ( bezier, row. transform . clone ( ) , move_to, & mut path) ;
1162
+ }
1163
+
1164
+ // Render the path
1165
+ self . scene . fill ( peniko:: Fill :: NonZero , base_transform, color, None , & path) ;
1166
+ }
1001
1167
}
1002
1168
1003
1169
fn translation_box ( & mut self , translation : DVec2 , quad : Quad , typed_string : Option < String > ) {
0 commit comments