gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/font/opentype/opentype.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 // Package opentype implements text layout and shaping for OpenType 4 // files. 5 // 6 // NOTE: the OpenType specification allows for fonts to include bitmap images 7 // in a variety of formats. In the interest of small binary sizes, the opentype 8 // package only automatically imports the PNG image decoder. If you have a font 9 // with glyphs in JPEG or TIFF formats, register those decoders with the image 10 // package in order to ensure those glyphs are visible in text. 11 package opentype 12 13 import ( 14 "bytes" 15 "fmt" 16 _ "image/png" 17 18 giofont "gioui.org/font" 19 "github.com/go-text/typesetting/font" 20 fontapi "github.com/go-text/typesetting/opentype/api/font" 21 "github.com/go-text/typesetting/opentype/api/metadata" 22 "github.com/go-text/typesetting/opentype/loader" 23 ) 24 25 // Face is a thread-safe representation of a loaded font. For efficiency, applications 26 // should construct a face for any given font file once, reusing it across different 27 // text shapers. 28 type Face struct { 29 face font.Font 30 font giofont.Font 31 } 32 33 // Parse constructs a Face from source bytes. 34 func Parse(src []byte) (Face, error) { 35 ld, err := loader.NewLoader(bytes.NewReader(src)) 36 if err != nil { 37 return Face{}, err 38 } 39 font, md, err := parseLoader(ld) 40 if err != nil { 41 return Face{}, fmt.Errorf("failed parsing truetype font: %w", err) 42 } 43 return Face{ 44 face: font, 45 font: md, 46 }, nil 47 } 48 49 // ParseCollection parse an Opentype font file, with support for collections. 50 // Single font files are supported, returning a slice with length 1. 51 // The returned fonts are automatically wrapped in a text.FontFace with 52 // inferred font metadata. 53 // BUG(whereswaldon): the only Variant that can be detected automatically is 54 // "Mono". 55 func ParseCollection(src []byte) ([]giofont.FontFace, error) { 56 lds, err := loader.NewLoaders(bytes.NewReader(src)) 57 if err != nil { 58 return nil, err 59 } 60 out := make([]giofont.FontFace, len(lds)) 61 for i, ld := range lds { 62 face, md, err := parseLoader(ld) 63 if err != nil { 64 return nil, fmt.Errorf("reading font %d of collection: %s", i, err) 65 } 66 ff := Face{ 67 face: face, 68 font: md, 69 } 70 out[i] = giofont.FontFace{ 71 Face: ff, 72 Font: ff.Font(), 73 } 74 } 75 76 return out, nil 77 } 78 79 func DescriptionToFont(md metadata.Description) giofont.Font { 80 return giofont.Font{ 81 Typeface: giofont.Typeface(md.Family), 82 Style: gioStyle(md.Aspect.Style), 83 Weight: gioWeight(md.Aspect.Weight), 84 } 85 } 86 87 func FontToDescription(font giofont.Font) metadata.Description { 88 return metadata.Description{ 89 Family: string(font.Typeface), 90 Aspect: metadata.Aspect{ 91 Style: mdStyle(font.Style), 92 Weight: mdWeight(font.Weight), 93 }, 94 } 95 } 96 97 // parseLoader parses the contents of the loader into a face and its metadata. 98 func parseLoader(ld *loader.Loader) (font.Font, giofont.Font, error) { 99 ft, err := fontapi.NewFont(ld) 100 if err != nil { 101 return nil, giofont.Font{}, err 102 } 103 data := DescriptionToFont(metadata.Metadata(ld)) 104 return ft, data, nil 105 } 106 107 // Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper. 108 // Face many be invoked any number of times and is safe so long as each return value is 109 // only used by one goroutine. 110 func (f Face) Face() font.Face { 111 return &fontapi.Face{Font: f.face} 112 } 113 114 // FontFace returns a text.Font with populated font metadata for the 115 // font. 116 // BUG(whereswaldon): the only Variant that can be detected automatically is 117 // "Mono". 118 func (f Face) Font() giofont.Font { 119 return f.font 120 } 121 122 func gioStyle(s metadata.Style) giofont.Style { 123 switch s { 124 case metadata.StyleItalic: 125 return giofont.Italic 126 case metadata.StyleNormal: 127 fallthrough 128 default: 129 return giofont.Regular 130 } 131 } 132 133 func mdStyle(g giofont.Style) metadata.Style { 134 switch g { 135 case giofont.Italic: 136 return metadata.StyleItalic 137 case giofont.Regular: 138 fallthrough 139 default: 140 return metadata.StyleNormal 141 } 142 } 143 144 func gioWeight(w metadata.Weight) giofont.Weight { 145 switch w { 146 case metadata.WeightThin: 147 return giofont.Thin 148 case metadata.WeightExtraLight: 149 return giofont.ExtraLight 150 case metadata.WeightLight: 151 return giofont.Light 152 case metadata.WeightNormal: 153 return giofont.Normal 154 case metadata.WeightMedium: 155 return giofont.Medium 156 case metadata.WeightSemibold: 157 return giofont.SemiBold 158 case metadata.WeightBold: 159 return giofont.Bold 160 case metadata.WeightExtraBold: 161 return giofont.ExtraBold 162 case metadata.WeightBlack: 163 return giofont.Black 164 default: 165 return giofont.Normal 166 } 167 } 168 169 func mdWeight(g giofont.Weight) metadata.Weight { 170 switch g { 171 case giofont.Thin: 172 return metadata.WeightThin 173 case giofont.ExtraLight: 174 return metadata.WeightExtraLight 175 case giofont.Light: 176 return metadata.WeightLight 177 case giofont.Normal: 178 return metadata.WeightNormal 179 case giofont.Medium: 180 return metadata.WeightMedium 181 case giofont.SemiBold: 182 return metadata.WeightSemibold 183 case giofont.Bold: 184 return metadata.WeightBold 185 case giofont.ExtraBold: 186 return metadata.WeightExtraBold 187 case giofont.Black: 188 return metadata.WeightBlack 189 default: 190 return metadata.WeightNormal 191 } 192 }