@@ -565,40 +565,49 @@ class FreeType extends Init
565565 init : =>
566566 unless has_freetype
567567 error " freetype library couldn't be loaded" , 2
568-
568+ @face_cache = {}
569569 -- Check that the font has a bold and italic variant if necessary
570570 @found_bold , @found_italic = false , false
571-
572- -- Get the font path
573- font_path = @getFontPath !
574- unless font_path
575- error " Couldn't find #{@family} among your fonts"
576-
577571 -- Init FreeType
578- @library = new " FT_Library[1]"
579- err = freetype. FT_Init_FreeType @library
580-
581- if err != 0
582- error " Failed to load freetype library"
583-
584- ffi. gc @library , ( lib) -> freetype. FT_Done_FreeType lib[ 0 ]
585-
572+ @library = @getLib !
573+ -- Get the font path
574+ font = @fontSelect !
586575 -- Load font face
587- @face = new " FT_Face[1]"
588- err = freetype. FT_New_Face @library [ 0 ] , font_path, 0 , @face
589-
590- if err != 0
591- error " Failed to load freetype face"
592-
593- ffi. gc @face , ( face) -> freetype. FT_Done_Face face[ 0 ]
594-
576+ @face = @getFaceFromPath font. path
595577 set_font_metrics @face [ 0 ]
596578 ass_face_set_size @face [ 0 ] , @size
597-
598579 @ascender , @descender = ass_font_get_asc_desc @face [ 0 ]
599580 @height = @ascender + @descender
600581 @weight = tonumber ass_face_get_weight @face [ 0 ]
601582
583+ -- Get FreeType library
584+ getLib : =>
585+ library = new " FT_Library[1]"
586+ err = freetype. FT_Init_FreeType library
587+ if err != 0
588+ error " Failed to load freetype library"
589+ ffi. gc library, ( lib) -> freetype. FT_Done_FreeType lib[ 0 ]
590+ return library
591+
592+ -- Get face from path
593+ getFaceFromPath : ( path) =>
594+ face = @face_cache [ path]
595+ return face if face
596+ face = new " FT_Face[1]"
597+ err = freetype. FT_New_Face @library [ 0 ] , path, 0 , face
598+ if err != 0
599+ error " Failed to load freetype face"
600+ ffi. gc face, ( f) -> freetype. FT_Done_Face f[ 0 ]
601+ @face_cache [ path] = face
602+ return face
603+
604+ -- Check if a glyph exists for a given codepoint in a specific font path
605+ hasGlyphFromPath : ( path, codepoint) =>
606+ return true if codepoint == 0
607+ face = @getFaceFromPath path
608+ return false unless face
609+ freetype. FT_Get_Char_Index ( face[ 0 ] , codepoint) != 0
610+
602611 -- Callback to access the glyphs for each character
603612 callBackCharsGlyph : ( text, callback) =>
604613 face_size = @face [ 0 ] . size. face
@@ -633,7 +642,6 @@ class FreeType extends Init
633642 paths, x = {} , 0
634643 @callBackCharsGlyph text, ( glyph) ->
635644 build, path = {} , {}
636- -- FIXME
637645 if @bold and @weight < 700 and not @found_bold
638646 ass_glyph_embolden glyph
639647 if @italic and not @found_italic
@@ -710,7 +718,7 @@ class FreeType extends Init
710718 table.insert paths, table.concat path, " "
711719 if @underline or @strikeout
712720 table.insert paths, ass_get_glyph_outline @face [ 0 ] , @underline , @strikeout , x, @ascender * FONT_UPSCALE
713- x += tonumber ( glyph. metrics. horiAdvance) + ( @hspace * FONT_UPSCALE )
721+ x += tonumber ( glyph. metrics. horiAdvance) + ( @hspace * FONT_UPSCALE ) - ( 0.1 * FONT_UPSCALE )
714722 return table.concat paths, " "
715723
716724 -- Gets the complete list of fonts available on the system
@@ -755,96 +763,66 @@ class FreeType extends Init
755763 -- Return collected fonts
756764 return fonts
757765
766+ -- Normalize a string for font matching
767+ norm : ( s) ->
768+ return " " unless s
769+ s\ lower!\ gsub( " [%s%-%_]+" , " " ) \ match " ^%s*(.-)%s*$"
770+
771+ -- Check if a style is bold
772+ is_bold : ( s) ->
773+ z = FreeType . norm s
774+ z\ find( " bold" ) or z\ find( " black" ) or z\ find( " heavy" ) or z\ find( " semibold" ) or z\ find( " demibold" ) or z\ find( " demi" )
775+
776+ -- Check if a style is italic
777+ is_italic : ( s) ->
778+ z = FreeType . norm s
779+ z\ find( " italic" ) or z\ find( " oblique" )
780+
781+ -- Check if a style is regular
782+ is_regular : ( s) -> not FreeType . is_bold( s) and not FreeType . is_italic( s)
783+
784+ -- Score a font based on bold and italic requirements
785+ scoreFont : ( font, bold, italic) ->
786+ style = FreeType . norm font. style
787+ font_bold = style\ find( " bold" ) or style\ find( " black" ) or style\ find( " heavy" ) or style\ find( " semibold" ) or style\ find( " demi" )
788+ font_italic = style\ find( " italic" ) or style\ find( " oblique" )
789+ score = 0
790+ if bold == font_bold and italic == font_italic
791+ return 0
792+ if bold and not font_bold
793+ score += 200
794+ if not bold and font_bold
795+ score += 300
796+ if italic and not font_italic
797+ score += 200
798+ if not italic and font_italic
799+ score += 300
800+ if style\ find " condensed"
801+ score += 20
802+ if style\ find " narrow"
803+ score += 10
804+ return score
805+
758806 -- Gets the directory path of the best matching font for the requested family
759- getFontPath : =>
807+ fontSelect : =>
760808 fonts = @getFonts !
761-
762- norm = ( s) ->
763- return " " unless s
764- return s\ lower!\ gsub( " %s+" , " " ) \ match " ^%s*(.-)%s*$"
765-
766- -- remove only style tokens
767- strip_style_words = ( s) ->
768- return " " unless s
769- s = norm s\ gsub " [%s%-_]+" , " "
770- -- words that represent style
771- style_words = {
772- " bold" ,
773- " black" ,
774- " heavy" ,
775- " semibold" ,
776- " demibold" ,
777- " demi" ,
778- " italic" ,
779- " oblique" ,
780- " regular" ,
781- " light" ,
782- " medium" ,
783- " thin" ,
784- " condensed" ,
785- " narrow"
786- }
787- style_set = {}
788- for i = 1 , # style_words
789- style_set[ style_words[ i]] = true
790- out = {}
791- for token in s\ gmatch " %S+"
792- unless style_set[ token]
793- table.insert out, token
794- return table.concat out, " "
795-
796- family = strip_style_words @family
797- is_bold = ( s) -> s\ lower!\ find( " bold" ) != nil or s\ find( " black" ) != nil or s\ find( " heavy" ) != nil or s\ find( " semibold" ) != nil or s\ find( " demibold" ) != nil or s\ find( " demi" ) != nil
798- is_italic = ( s) -> s\ lower!\ find( " italic" ) != nil or s\ find( " oblique" ) != nil
799- is_regular = ( s) -> not is_bold( s) and not is_italic( s)
800-
801- candidates = {}
802- for i = 1 , # fonts
809+ family = FreeType . norm @family
810+ best, best_score = nil , math. huge
811+ for i = 1 , fonts. n
803812 font = fonts[ i]
804- if norm( font. name) == family
805- table.insert candidates, font
806-
807- return false if # candidates == 0
808-
809- score_font = ( font) ->
810- style = norm font. style
811- score = 0
812- font_bold = is_bold style
813- font_italic = is_italic style
814- -- exact match
815- if bold == font_bold and italic == font_italic
816- score += 100
817- -- fallbacks
818- if bold and italic
819- if font_bold and not font_italic
820- score += 70
821- if font_italic and not font_bold
822- score += 60
823- elseif bold and not italic
824- if font_bold
825- score += 80
826- elseif not bold and italic
827- if font_italic
828- score += 80
829- else
830- if is_regular style
831- score += 50
832- -- light penalties
833- if style\ find " condensed"
834- score -= 5
835- if style\ find " narrow"
836- score -= 2
837- return score
838-
839- table.sort candidates, ( a, b) -> score_font( b) < score_font( a)
840- chosen = candidates[ 1 ]
841-
842- style = norm chosen. style
843-
844- @found_italic = is_italic style
845- @found_bold = is_bold style
846- @found_regular = is_regular style
847-
848- return chosen. path
813+ if FreeType . norm( font. name) != family and FreeType . norm( font. longname) != family
814+ continue
815+ unless @hasGlyphFromPath font. path, UTF8 . charcodepoint " a"
816+ continue
817+ score = FreeType . scoreFont font, @bold , @italic
818+ if score < best_score
819+ best = font
820+ best_score = score
821+ break if score == 0
822+ unless best
823+ error " Couldn't find #{@family} among your fonts"
824+ @found_italic = FreeType . is_italic best. style
825+ @found_bold = FreeType . is_bold best. style
826+ return best
849827
850828{ : FreeType }
0 commit comments