github.com/andybalholm/giopdf@v0.0.0-20220317170119-aad9a095ad48/cff/charstring.go (about)

     1  package cff
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/benoitkugler/textlayout/fonts"
     7  	ps "github.com/benoitkugler/textlayout/fonts/psinterpreter"
     8  )
     9  
    10  type Glyph struct {
    11  	Outlines []fonts.Segment
    12  	Width    int
    13  	Bounds   ps.PathBounds
    14  }
    15  
    16  // LoadGlyph parses the glyph charstring to compute segments and path bounds.
    17  // It returns an error if the glyph is invalid or if decoding the charstring fails.
    18  func (f *Font) LoadGlyph(glyph fonts.GID) (Glyph, error) {
    19  	var (
    20  		psi    ps.Machine
    21  		loader type2CharstringHandler
    22  		index  byte = 0
    23  		err    error
    24  	)
    25  	if f.fdSelect != nil {
    26  		index, err = f.fdSelect.fontDictIndex(glyph)
    27  		if err != nil {
    28  			return Glyph{}, err
    29  		}
    30  	}
    31  	if int(glyph) >= len(f.charstrings) {
    32  		return Glyph{}, fmt.Errorf("invalid glyph index %d", glyph)
    33  	}
    34  
    35  	subrs := f.localSubrs[index]
    36  	priv := f.priv[index]
    37  	loader.nominalWidthX = priv.nominalWidthX
    38  	loader.width = priv.defaultWidthX
    39  	err = psi.Run(f.charstrings[glyph], subrs, f.globalSubrs, &loader)
    40  	return Glyph{
    41  		Outlines: loader.cs.Segments,
    42  		Width:    int(loader.width),
    43  		Bounds:   loader.cs.Bounds,
    44  	}, nil
    45  }
    46  
    47  // type2CharstringHandler implements operators needed to fetch Type2 charstring metrics
    48  type type2CharstringHandler struct {
    49  	cs ps.CharstringReader
    50  
    51  	// found in private DICT, needed since we can't differenciate
    52  	// no width set from 0 width
    53  	// `width` must be initialized to default width
    54  	nominalWidthX int32
    55  	width         int32
    56  }
    57  
    58  func (type2CharstringHandler) Context() ps.PsContext { return ps.Type2Charstring }
    59  
    60  func (met *type2CharstringHandler) Apply(op ps.PsOperator, state *ps.Machine) error {
    61  	var err error
    62  	if !op.IsEscaped {
    63  		switch op.Operator {
    64  		case 11: // return
    65  			return state.Return() // do not clear the arg stack
    66  		case 14: // endchar
    67  			if state.ArgStack.Top > 0 { // width is optional
    68  				met.width = met.nominalWidthX + state.ArgStack.Vals[0]
    69  			}
    70  			met.cs.ClosePath()
    71  			return ps.ErrInterrupt
    72  		case 10: // callsubr
    73  			return ps.LocalSubr(state) // do not clear the arg stack
    74  		case 29: // callgsubr
    75  			return ps.GlobalSubr(state) // do not clear the arg stack
    76  		case 21: // rmoveto
    77  			if state.ArgStack.Top > 2 { // width is optional
    78  				met.width = met.nominalWidthX + state.ArgStack.Vals[0]
    79  			}
    80  			err = met.cs.Rmoveto(state)
    81  		case 22: // hmoveto
    82  			if state.ArgStack.Top > 1 { // width is optional
    83  				met.width = met.nominalWidthX + state.ArgStack.Vals[0]
    84  			}
    85  			err = met.cs.Hmoveto(state)
    86  		case 4: // vmoveto
    87  			if state.ArgStack.Top > 1 { // width is optional
    88  				met.width = met.nominalWidthX + state.ArgStack.Vals[0]
    89  			}
    90  			err = met.cs.Vmoveto(state)
    91  		case 1, 18: // hstem, hstemhm
    92  			met.cs.Hstem(state)
    93  		case 3, 23: // vstem, vstemhm
    94  			met.cs.Vstem(state)
    95  		case 19, 20: // hintmask, cntrmask
    96  			// variable number of arguments, but always even
    97  			// for xxxmask, if there are arguments on the stack, then this is an impliied stem
    98  			if state.ArgStack.Top&1 != 0 {
    99  				met.width = met.nominalWidthX + state.ArgStack.Vals[0]
   100  			}
   101  			met.cs.Hintmask(state)
   102  			// the stack is managed by the previous call
   103  			return nil
   104  
   105  		case 5: // rlineto
   106  			met.cs.Rlineto(state)
   107  		case 6: // hlineto
   108  			met.cs.Hlineto(state)
   109  		case 7: // vlineto
   110  			met.cs.Vlineto(state)
   111  		case 8: // rrcurveto
   112  			met.cs.Rrcurveto(state)
   113  		case 24: // rcurveline
   114  			err = met.cs.Rcurveline(state)
   115  		case 25: // rlinecurve
   116  			err = met.cs.Rlinecurve(state)
   117  		case 26: // vvcurveto
   118  			met.cs.Vvcurveto(state)
   119  		case 27: // hhcurveto
   120  			met.cs.Hhcurveto(state)
   121  		case 30: // vhcurveto
   122  			met.cs.Vhcurveto(state)
   123  		case 31: // hvcurveto
   124  			met.cs.Hvcurveto(state)
   125  		default:
   126  			// no other operands are allowed before the ones handled above
   127  			err = fmt.Errorf("invalid operator %s in charstring", op)
   128  		}
   129  	} else {
   130  		switch op.Operator {
   131  		case 34: // hflex
   132  			err = met.cs.Hflex(state)
   133  		case 35: // flex
   134  			err = met.cs.Flex(state)
   135  		case 36: // hflex1
   136  			err = met.cs.Hflex1(state)
   137  		case 37: // flex1
   138  			err = met.cs.Flex1(state)
   139  		default:
   140  			// no other operands are allowed before the ones handled above
   141  			err = fmt.Errorf("invalid operator %s in charstring", op)
   142  		}
   143  	}
   144  	state.ArgStack.Clear()
   145  	return err
   146  }