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  }