@@ -10,6 +10,8 @@ use core::f64::consts::{FRAC_PI_2, PI, TAU};
1010use glam:: { DAffine2 , DVec2 } ;
1111use graphene_std:: Color ;
1212use graphene_std:: math:: quad:: Quad ;
13+ use graphene_std:: table:: Table ;
14+ use graphene_std:: text:: { TextAlign , TypesettingConfig , load_font, to_path} ;
1315use graphene_std:: vector:: click_target:: ClickTargetType ;
1416use graphene_std:: vector:: { PointId , SegmentId , Vector } ;
1517use std:: collections:: HashMap ;
@@ -378,7 +380,8 @@ impl OverlayContext {
378380 }
379381
380382 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) ;
382385 }
383386
384387 pub fn translation_box ( & mut self , translation : DVec2 , quad : Quad , typed_string : Option < String > ) {
@@ -972,32 +975,195 @@ impl OverlayContextInternal {
972975 /// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
973976 /// Used by the fill tool to show the area to be filled.
974977 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+
9771009 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+ }
9931139
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+ }
9971146 }
9981147
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+ }
10011167 }
10021168
10031169 fn translation_box ( & mut self , translation : DVec2 , quad : Quad , typed_string : Option < String > ) {
0 commit comments