okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
2e306d5
main
1//! Rows of glyphs drawn onto an RGB canvas and encoded as PNG. 2 3use crate :: layout:: Row ; 4use arborium_theme:: theme:: Color ; 5use fontdue::{ Font , FontSettings , Metrics }; 6use std:: collections:: HashMap ; 7 8const FONT : & [ u8 ] =include_bytes! ( "../fonts/JetBrainsMonoNL-Medium.ttf" ); 9/// Large enough to stay legible after a provider scales an 80-row page down. 10const FONT_PX : f32 =16.0 ; 11const PADDING : usize =12 ; 12 13pub struct Painter { 14font : Font , 15cache : HashMap < char , ( Metrics , Vec < u8 >)>, 16cell : usize , 17row : usize , 18baseline : usize , 19} 20 21pub struct Page < ' a > { 22pub rows : &' a [ Row ], 23/// Cells left of the code, so every page of one view aligns. 24pub gutter : usize , 25/// False for text whose own line numbers are not the source's, like a diff. 26pub numbers : bool , 27pub columns : usize , 28pub foreground : Color , 29pub background : Color , 30} 31 32impl Painter { 33pub fn new () ->Painter { 34let font =Font :: from_bytes ( FONT , FontSettings :: default ()). expect ( "bundled font parses" ); 35let line = font 36. horizontal_line_metrics ( FONT_PX ) 37. expect ( "bundled font is horizontal" ); 38let cell = font. metrics ( 'M' , FONT_PX ). advance_width . ceil () as usize ; 39let row =( FONT_PX * 1.4 ). ceil () as usize ; 40let glyph = line. ascent - line. descent ; 41let baseline =(( rowas f32 - glyph) /2.0 + line. ascent ). round () as usize ; 42Painter { 43 font, 44cache : HashMap :: new (), 45 cell, 46 row, 47 baseline, 48} 49} 50 51pub fn png ( & mut self , page : & Page ) ->Vec < u8 > { 52let ( width, height, pixels) =self . paint ( page); 53let mut out =Vec :: new (); 54let mut encoder = png:: Encoder :: new ( & mut out, widthas u32 , heightas u32 ); 55 encoder. set_color ( png:: ColorType :: Rgb ); 56 encoder. set_depth ( png:: BitDepth :: Eight ); 57 encoder. set_compression ( png:: Compression :: Balanced ); 58let mut writer = encoder. write_header (). expect ( "header fits in memory" ); 59 writer 60. write_image_data ( & pixels) 61. expect ( "pixels match the header" ); 62drop ( writer); 63 out 64} 65 66fn paint ( & mut self , page : & Page ) ->( usize , usize , Vec < u8 >) { 67// the gutter, a gap holding the separator, then the code. 68let code_x =PADDING +( page. gutter +2 ) * self . cell ; 69let width = code_x + page. columns * self . cell +PADDING ; 70let height =2 * PADDING + page. rows . len (). max ( 1 ) * self . row ; 71let mut canvas =Canvas { 72 width, 73pixels : vec! [ 0 ; width* height* 3 ], 74}; 75 canvas. fill ( 0 , 0 , width, height, page. background ); 76let number =mix ( page. foreground , page. background , 0.5 ); 77let rule =mix ( page. foreground , page. background , 0.85 ); 78let x =PADDING +( page. gutter +1 ) * self . cell ; 79 canvas. fill ( x, PADDING , 1 , height -2 * PADDING , rule); 80for ( index, row) in page. rows . iter (). enumerate () { 81let top =PADDING + index* self . row ; 82// A wrapped line's later rows say so, since they start at column 0. 83let label =match ( row. continued , page. numbers ) { 84( true , _) =>"↪" . to_string (), 85( false , true ) => row. line . to_string (), 86( false , false ) =>String :: new (), 87}; 88let first = page. gutter . saturating_sub ( label. chars (). count ()); 89for ( offset, ch) in label. chars (). enumerate () { 90let x =PADDING +( first + offset) * self . cell ; 91self . glyph ( & mut canvas, x, top, ch, number); 92} 93for glyphin & row. glyphs { 94self . glyph ( 95& mut canvas, 96 code_x + glyph. column * self . cell , 97 top, 98 glyph. ch , 99 glyph. color , 100); 101} 102} 103( width, height, canvas. pixels ) 104} 105 106fn glyph ( & mut self , canvas : & mut Canvas , x : usize , top : usize , ch : char , color : Color ) { 107let font =& self . font ; 108let ( metrics, coverage) =self 109. cache 110. entry ( ch) 111. or_insert_with ( || font. rasterize ( ch, FONT_PX )); 112let left = xas i32 + metrics. xmin ; 113let top =( top +self . baseline ) as i32 - metrics. height as i32 - metrics. ymin ; 114for rowin 0 ..metrics. height { 115for columnin 0 ..metrics. width { 116let alpha = coverage[ row* metrics. width + column]; 117if alpha >0 { 118 canvas. blend ( left + columnas i32 , top + rowas i32 , color, alpha); 119} 120} 121} 122} 123} 124 125struct Canvas { 126width : usize , 127pixels : Vec < u8 >, 128} 129 130impl Canvas { 131fn fill ( & mut self , x : usize , y : usize , width : usize , height : usize , color : Color ) { 132for rowin y..y + height{ 133for columnin x..x + width{ 134let at =( row* self . width + column) * 3 ; 135self . pixels [ at..at +3 ]. copy_from_slice ( & [ color. r , color. g , color. b ]); 136} 137} 138} 139 140/// Glyphs may overhang their cell; anything off the canvas is dropped. 141fn blend ( & mut self , x : i32 , y : i32 , color : Color , alpha : u8 ) { 142let height =self . pixels . len () /3 /self . width ; 143if x <0 || y <0 || xas usize >=self . width || yas usize >= height{ 144return ; 145} 146let at =( yas usize * self . width + xas usize ) * 3 ; 147let alpha = alphaas u32 ; 148for ( channel, value) in [ color. r , color. g , color. b ]. into_iter (). enumerate () { 149let under =self . pixels [ at + channel] as u32 ; 150self . pixels [ at + channel] = 151(( valueas u32 * alpha + under* ( 255 - alpha)) /255 ) as u8 ; 152} 153} 154} 155 156/// `color` moved `amount` of the way toward `toward`. 157fn mix ( color : Color , toward : Color , amount : f32 ) ->Color { 158let channel = |a : u8 , b : u8 |( aas f32 +( bas f32 - aas f32 ) * amount). round () as u8 ; 159Color :: new ( 160channel ( color. r , toward. r ), 161channel ( color. g , toward. g ), 162channel ( color. b , toward. b ), 163) 164}