9fans.net/go@v0.0.5/draw/openfont.go (about)

     1  package draw
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"strings"
    11  )
    12  
    13  func parsefontscale(name string) (scale int, fname string) {
    14  	i := 0
    15  	scale = 0
    16  	for i < len(name) && '0' <= name[i] && name[i] <= '9' {
    17  		scale = scale*10 + int(name[i]) - '0'
    18  		i++
    19  	}
    20  	if i < len(name) && name[i] == '*' && scale > 0 {
    21  		return scale, name[i+1:]
    22  	}
    23  	return 1, name
    24  }
    25  
    26  // OpenFont reads the named file and returns the font it defines.
    27  // The name may be an absolute path, or it may identify a file
    28  // in a standard font directory:
    29  // /lib/font/bit, /usr/local/plan9, /mnt/font, etc.
    30  //
    31  // In contrast to Plan 9, but matching Plan 9 from User Space,
    32  // font names are a small language describing the desired font.
    33  // See the package documentation for details.
    34  func (d *Display) OpenFont(name string) (*Font, error) {
    35  	// nil display is allowed, for querying font metrics
    36  	// in non-draw program.
    37  	if d != nil {
    38  		d.mu.Lock()
    39  		defer d.mu.Unlock()
    40  	}
    41  	return d.openFont(name)
    42  }
    43  
    44  func (d *Display) openFont1(name string) (*Font, error) {
    45  	scale, fname := parsefontscale(name)
    46  
    47  	data, err := ioutil.ReadFile(fname)
    48  
    49  	if err != nil && strings.HasPrefix(fname, "/lib/font/bit/") {
    50  		root := os.Getenv("PLAN9")
    51  		if root == "" {
    52  			root = "/usr/local/plan9"
    53  		}
    54  		name1 := root + "/font/" + fname[len("/lib/font/bit/"):]
    55  		data1, err1 := ioutil.ReadFile(name1)
    56  		fname, data, err = name1, data1, err1
    57  		if scale > 1 {
    58  			name = fmt.Sprintf("%d*%s", scale, fname)
    59  		} else {
    60  			name = fname
    61  		}
    62  	}
    63  
    64  	if err != nil && strings.HasPrefix(fname, "/mnt/font/") {
    65  		data1, err1 := fontPipe(fname[len("/mnt/font/"):])
    66  		if err1 == nil {
    67  			data, err = data1, err1
    68  		}
    69  	}
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	f, err := d.buildFont(data, name)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	if scale != 1 {
    80  		f.Scale = scale
    81  		f.Height *= scale
    82  		f.Ascent *= scale
    83  		f.width *= scale
    84  	}
    85  	return f, nil
    86  }
    87  
    88  func swapfont(targ *Font, oldp, newp **Font) {
    89  	if targ != *oldp {
    90  		log.Fatalf("bad swapfont %p %p %p", targ, *oldp, *newp)
    91  	}
    92  
    93  	old := *oldp
    94  	new := *newp
    95  	var tmp Font
    96  	copyfont(&tmp, old)
    97  	copyfont(old, new)
    98  	copyfont(new, &tmp)
    99  
   100  	*oldp = new
   101  	*newp = old
   102  }
   103  
   104  func copyfont(dst, src *Font) {
   105  	dst.Display = src.Display
   106  	dst.Name = src.Name
   107  	dst.Height = src.Height
   108  	dst.Ascent = src.Ascent
   109  	dst.Scale = src.Scale
   110  	dst.width = src.width
   111  	dst.age = src.age
   112  	dst.maxdepth = src.maxdepth
   113  	dst.cache = src.cache
   114  	dst.subf = src.subf
   115  	dst.sub = src.sub
   116  	dst.cacheimage = src.cacheimage
   117  }
   118  
   119  func hidpiname(f *Font) string {
   120  	// If font name has form x,y return y.
   121  	i := strings.Index(f.namespec, ",")
   122  	if i >= 0 {
   123  		return f.namespec[i+1:]
   124  	}
   125  
   126  	// If font name is /mnt/font/Name/Size/font, scale Size.
   127  	if strings.HasPrefix(f.Name, "/mnt/font/") {
   128  		i := strings.Index(f.Name[len("/mnt/font/"):], "/")
   129  		if i < 0 {
   130  			goto Scale
   131  		}
   132  		i += len("/mnt/font/") + 1
   133  		if i >= len(f.Name) || f.Name[i] < '0' || '9' < f.Name[i] {
   134  			goto Scale
   135  		}
   136  		j := i
   137  		size := 0
   138  		for j < len(f.Name) && '0' <= f.Name[j] && f.Name[j] <= '9' {
   139  			size = size*10 + int(f.Name[j]) - '0'
   140  			j++
   141  		}
   142  		return fmt.Sprintf("%s%d%s", f.Name[:i], size*2, f.Name[j:])
   143  	}
   144  
   145  	// Otherwise use pixel doubling.
   146  Scale:
   147  	return fmt.Sprintf("%d*%s", f.Scale*2, f.Name)
   148  }
   149  
   150  func loadhidpi(f *Font) {
   151  	if f.hidpi == f {
   152  		return
   153  	}
   154  	if f.hidpi != nil {
   155  		swapfont(f, &f.lodpi, &f.hidpi)
   156  		return
   157  	}
   158  
   159  	name := hidpiname(f)
   160  	fnew, err := f.Display.openFont1(name)
   161  	if err != nil {
   162  		return
   163  	}
   164  	f.hidpi = fnew
   165  	swapfont(f, &f.lodpi, &f.hidpi)
   166  }
   167  
   168  func (d *Display) openFont(name string) (*Font, error) {
   169  	// If font name has form x,y use x for lodpi, y for hidpi
   170  	namespec := name
   171  	if i := strings.Index(name, ","); i >= 0 {
   172  		name = name[:i]
   173  	}
   174  
   175  	f, err := d.openFont1(name)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	f.lodpi = f
   180  	f.namespec = namespec
   181  
   182  	// add to display list for when dpi changes.
   183  	// d can be nil when invoked from mc.
   184  	if d != nil {
   185  		f.ondisplaylist = true
   186  		f.prev = d.lastfont
   187  		f.next = nil
   188  		if f.prev != nil {
   189  			f.prev.next = f
   190  		} else {
   191  			d.firstfont = f
   192  		}
   193  		d.lastfont = f
   194  
   195  		// if this is a hi-dpi display, find hi-dpi version and swap
   196  		if d.HiDPI() {
   197  			loadhidpi(f)
   198  		}
   199  	}
   200  
   201  	return f, nil
   202  }
   203  
   204  func fontPipe(name string) ([]byte, error) {
   205  	data, err := exec.Command("fontsrv", "-pp", name).CombinedOutput()
   206  
   207  	// Success marked with leading \001. Otherwise an error happened.
   208  	if len(data) > 0 && data[0] != '\001' {
   209  		i := bytes.IndexByte(data, '\n')
   210  		if i >= 0 {
   211  			data = data[:i]
   212  		}
   213  		return nil, fmt.Errorf("fontsrv -pp %s: %v", name, data)
   214  	}
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	return data[1:], nil
   219  }