#lang debug racket/base
( require ffi/unsafe
ffi/unsafe/define
racket/draw/private/libs
" freetype.rkt "
" harfbuzz-helper.rkt " )
( provide ( all-defined-out ) )
( define-runtime-lib harfbuzz-lib
[ ( unix ) ( ffi-lib " libharfbuzz " ' ( " 0 " " " ) ) ]
[ ( macosx ) ( ffi-lib " libharfbuzz.0.dylib " ) ]
[ ( windows ) ( ffi-lib " libharfbuzz-0.dll " ) ] )
( define-ffi-definer define-harfbuzz harfbuzz-lib #:provide provide )
;; simple example
;; https://harfbuzz.github.io/ch03s03.html
;; types
( define _void-pointer ( _cpointer ' void-pointer ) )
( define _char _byte )
( define _bstring _bytes/nul-terminated )
( define _hb_buffer_t ( _cpointer ' hb_buffer_t ) )
( define _hb_language_t ( _cpointer ' hb_language_t ) )
( define _hb_bool_t _int )
( define-harfbuzz hb_version ( _fun ( major : ( _ptr o _uint ) )
( minor : ( _ptr o _uint ) )
( micro : ( _ptr o _uint ) )
-> _void
-> ( format " ~a.~a.~a " major minor micro ) ) )
( define ( default-buffer-setup buf )
( hb_buffer_set_direction buf ' HB_DIRECTION_LTR )
( hb_buffer_set_script buf ' HB_SCRIPT_LATIN )
( hb_buffer_set_cluster_level buf ' HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS )
( hb_buffer_set_language buf ( hb_language_from_string #" en " ) )
buf )
( define-harfbuzz hb_buffer_create ( _fun -> ( buf : _hb_buffer_t )
-> ( default-buffer-setup buf ) ) )
;; using `codepoints` will track clusters by codepoints,
;; whereas `utf8` will track clusters by bytes (so high-bytes characters will have bigger clusters)
;; see https://lists.freedesktop.org/archives/harfbuzz/2012-October/002526.html
( define-harfbuzz hb_buffer_add_utf8 ( _fun _hb_buffer_t
( text : _string/utf-8 )
( text-length : _int = ( string-length text ) )
( _uint = 0 )
( _int = text-length )
-> _void ) )
( define-harfbuzz hb_buffer_add_codepoints ( _fun _hb_buffer_t
( codepoints : ( _list i _hb_codepoint_t ) )
( text-length : _int = ( length codepoints ) )
( _uint = 0 )
( _int = text-length )
-> _void ) )
( define _hb_cluster_t ( _enum hb-cluster-levels ) )
( define-harfbuzz hb_buffer_set_cluster_level ( _fun _hb_buffer_t _hb_cluster_t -> _void ) )
( define-harfbuzz hb_buffer_get_cluster_level ( _fun _hb_buffer_t -> _hb_cluster_t ) )
( define _hb_direction_t ( _enum hb-direction-values ) )
( define-harfbuzz hb_buffer_set_direction ( _fun _hb_buffer_t _hb_direction_t -> _void ) )
( define-harfbuzz hb_buffer_get_direction ( _fun _hb_buffer_t -> _hb_direction_t ) )
( define _hb_script_t ( _enum hb-script-values ) )
( define-harfbuzz hb_buffer_set_script ( _fun _hb_buffer_t _hb_script_t -> _void ) )
( define-harfbuzz hb_buffer_get_script ( _fun _hb_buffer_t -> _hb_script_t ) )
( define-harfbuzz hb_language_from_string ( _fun _bstring ( _int = -1 ) -> _hb_language_t ) )
( define-harfbuzz hb_language_to_string ( _fun _hb_language_t -> _bstring ) )
( define-harfbuzz hb_buffer_set_language ( _fun _hb_buffer_t _hb_language_t -> _void ) )
( define-harfbuzz hb_buffer_get_language ( _fun _hb_buffer_t -> _hb_language_t ) )
( define _hb_font_t _pointer )
( define _hb_destroy_func_t ( _or-null _pointer ) )
( define-harfbuzz hb_ft_font_create ( _fun _FT_Face
( _hb_destroy_func_t = #false ) -> _hb_font_t ) )
( define _hb_tag_t _uint32 )
( define-cstruct _hb_feature_t
( [ tag_ _hb_tag_t ]
[ value _uint32 ]
[ start _uint ]
[ end _uint ] ) )
( define _hb_features_t ( _or-null _hb_feature_t-pointer ) )
( define-harfbuzz hb_shape ( _fun _hb_font_t
_hb_buffer_t
( feats : ( _list i _hb_feature_t ) )
( _uint = ( length feats ) )
-> _void ) )
( define-harfbuzz hb_feature_from_string ( _fun ( bstr : _bstring )
( _int = ( bytes-length bstr ) )
( fs : ( _ptr o _hb_feature_t ) )
-> ( success? : _hb_bool_t )
-> ( if success? fs ( error ' hb_feature_from_string ) ) ) )
#; ( define-harfbuzz hb_feature_to_string ( _fun _hb_feature_t
( buf : ( _ptr o ( make-array-type _byte 128 ) ) )
( _int = 128 )
-> _void ) )
( define _hb_codepoint_t _uint32 )
( define _hb_mask_t _uint32 )
( define _hb_var_int_t _uint32 ) ; todo: union type at https://github.com/harfbuzz/harfbuzz/blob/04981ee05d83ed30c9f818106589a4de9c3e9b7f/src/hb-common.h#L96
( define-cstruct _hb_glyph_info_t
( [ codepoint _hb_codepoint_t ] ; holds a glyph id after shaping
[ mask _hb_mask_t ]
[ cluster _uint32 ]
[ var1 _hb_var_int_t ]
[ var2 _hb_var_int_t ] ) )
( define-harfbuzz hb_buffer_get_glyph_infos ( _fun _hb_buffer_t
( length : ( _ptr o _uint ) )
-> ( res : _hb_glyph_info_t-pointer )
-> ( ptr-ref res ( _array/list _hb_glyph_info_t length ) 0 ) ) )
( define _hb_position_t _int32 )
( define-cstruct _hb_glyph_position_t
( [ x_advance _hb_position_t ]
[ y_advance _hb_position_t ]
[ x_offset _hb_position_t ]
[ y_offset _hb_position_t ]
[ var _hb_var_int_t ] ) )
( define-harfbuzz hb_buffer_get_glyph_positions ( _fun _hb_buffer_t
( length : ( _ptr o _uint ) )
-> ( res : _hb_glyph_position_t-pointer )
-> ( ptr-ref res ( _array/list _hb_glyph_position_t length ) 0 ) ) )
( define-harfbuzz hb_buffer_reset ( _fun ( buf : _hb_buffer_t )
-> _void
-> ( let ( )
( default-buffer-setup buf )
( void ) ) ) )
( define-harfbuzz hb_buffer_clear_contents ( _fun ( buf : _hb_buffer_t )
-> _void
-> ( let ( )
( default-buffer-setup buf )
( void ) ) ) )
( define-harfbuzz hb_buffer_destroy ( _fun _hb_buffer_t -> _void ) )
( define-harfbuzz hb_font_destroy ( _fun _hb_font_t -> _void ) )
( define ft-lib ( FT_Init_FreeType ) )
( require racket/runtime-path )
( define-runtime-path test-font-path " ../assets/fira.ttf " )
( module+ test
( require rackunit )
;; Create a buffer and put your text in it.
( define buf ( hb_buffer_create ) )
( define text " Hello World " )
#; ( hb_buffer_add_utf8 buf text )
( hb_buffer_add_codepoints buf ( map char->integer ( string->list text ) ) )
;; Set the script, language and direction of the buffer.
( check-true ( eq? ' HB_DIRECTION_LTR ( hb_buffer_get_direction buf ) ) )
( check-true ( eq? ' HB_SCRIPT_LATIN ( hb_buffer_get_script buf ) ) )
( check-equal? ( hb_language_to_string ( hb_buffer_get_language buf ) ) #" en " )
;; Create a face and a font, using FreeType for now.
( define face ( FT_New_Face ft-lib test-font-path ) )
( define font ( hb_ft_font_create face ) )
;; Shape!
( hb_shape font buf null )
;; Get the glyph and position information.
( define glyph_infos ( hb_buffer_get_glyph_infos buf ) )
( check-equal? ( map hb_glyph_info_t-codepoint glyph_infos ) ' ( 111 412 514 514 555 3 296 555 609 514 393 ) )
( define glyph_positions ( hb_buffer_get_glyph_positions buf ) )
( check-equal? ( map hb_glyph_position_t-x_advance glyph_positions ) ' ( 678 547 291 281 581 268 792 581 383 281 595 ) )
;; Tidy up.
( hb_buffer_destroy buf )
( hb_font_destroy font ) )
( define ( make-font path-string )
( define face ( FT_New_Face ft-lib path-string ) )
( hb_ft_font_create face ) )
( define HB_FEATURE_GLOBAL_START 0 )
( define HB_FEATURE_GLOBAL_END 4294967295 )
( define ( tag->hb-feature bs [ val 1 ] )
( unless ( and ( bytes? bs ) ( = ( bytes-length bs ) 4 ) )
( raise-argument-error ' tag->hb-feature " 4 bytes " bs ) )
( make-hb_feature_t ( ->tag bs ) val HB_FEATURE_GLOBAL_START HB_FEATURE_GLOBAL_END ) )
( define liga_on ( tag->hb-feature #" liga " ) )
( define liga_off ( tag->hb-feature #" liga " 0 ) )
( define kern_on ( tag->hb-feature #" kern " ) )
( define kern_off ( tag->hb-feature #" kern " 0 ) )
( define onum_on ( tag->hb-feature #" onum " ) )
( define onum_off ( tag->hb-feature #" onum " 0 ) )
( define ( shape font text [ feats null ] )
( define buf ( hb_buffer_create ) )
( hb_buffer_set_direction buf ' HB_DIRECTION_LTR )
( hb_buffer_set_script buf ' HB_SCRIPT_LATIN )
( hb_buffer_set_language buf ( hb_language_from_string #" en " ) )
( hb_buffer_add_utf8 buf text )
( hb_shape font buf feats )
( begin0
( map cons ( map hb_glyph_info_t-codepoint ( hb_buffer_get_glyph_infos buf ) )
( map hb_glyph_position_t-x_advance ( hb_buffer_get_glyph_positions buf ) ) )
( hb_buffer_destroy buf ) ) )
( require racket/list )
( define ( random-string len )
( define chars ( map integer->char ( range 65 91 ) ) )
( list->string ( for/list ( [ i ( in-range len ) ] )
( list-ref chars ( random ( length chars ) ) ) ) ) )
( module+ test
( define f ( make-font test-font-path ) )
( define test-str " Tofl 3 " )
( check-equal?
( shape f test-str )
' ( ( 249 . 432 ) ( 555 . 581 ) ( 732 . 590 ) ( 3 . 268 ) ( 2017 . 496 ) ) )
( check-equal?
( shape f test-str ( list kern_off liga_off ) )
' ( ( 249 . 512 ) ( 555 . 581 ) ( 450 . 332 ) ( 514 . 291 ) ( 3 . 268 ) ( 2017 . 496 ) ) )
( check-equal?
( shape f test-str ( list kern_on liga_on onum_on ) )
' ( ( 249 . 432 ) ( 555 . 581 ) ( 732 . 590 ) ( 3 . 268 ) ( 2027 . 487 ) ) ) )
( require racket/match sugar/debug racket/string )
( define ( wrap str limit )
( define f ( make-font test-font-path ) )
( for/fold ( [ lines null ] ; listof string
[ current-line " " ] ; string
#:result ( reverse ( cons current-line lines ) ) )
( [ word ( in-list ( string-split str " " ) ) ] )
( define next-line ( string-append current-line " " word ) )
( match ( shape f next-line )
[ ( list ( cons _ widths ) ... ) #:when ( > ( apply + widths ) limit )
( values ( cons current-line lines ) word ) ]
[ _ ( values lines next-line ) ] ) ) )
( module+ main
( time-avg 10 ( wrap " This tutorial provides a brief introduction to the Racket programming language by using one of its picture-drawing libraries. Even if you don’ t intend to use Racket for your artistic endeavours, the picture library supports interesting and enlightening examples. This tutorial provides a brief introduction to the Racket programming language by using one of its picture-drawing libraries. Even if you don’ t intend to use Racket for your artistic endeavours, the picture library supports interesting and enlightening examples. This tutorial provides a brief introduction to the Racket programming language by using one of its picture-drawing libraries. Even if you don’ t intend to use Racket for your artistic endeavours, the picture library supports interesting and enlightening examples. " 30000 ) ) )