github.com/pdfcpu/pdfcpu@v0.11.1/pkg/api/font.go (about)

     1  /*
     2  	Copyright 2020 The pdfcpu Authors.
     3  
     4  	Licensed under the Apache License, Version 2.0 (the "License");
     5  	you may not use this file except in compliance with the License.
     6  	You may obtain a copy of the License at
     7  
     8  		http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  	Unless required by applicable law or agreed to in writing, software
    11  	distributed under the License is distributed on an "AS IS" BASIS,
    12  	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  	See the License for the specific language governing permissions and
    14  	limitations under the License.
    15  */
    16  
    17  package api
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  
    23  	"path/filepath"
    24  	"sort"
    25  	"unicode/utf8"
    26  
    27  	"github.com/pdfcpu/pdfcpu/pkg/font"
    28  	"github.com/pdfcpu/pdfcpu/pkg/log"
    29  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
    30  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/color"
    31  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/draw"
    32  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
    33  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types"
    34  	"github.com/pkg/errors"
    35  )
    36  
    37  // ListFonts returns a list of supported fonts.
    38  func ListFonts() ([]string, error) {
    39  	// Get list of PDF core fonts.
    40  	coreFonts := font.CoreFontNames()
    41  	for i, s := range coreFonts {
    42  		coreFonts[i] = "  " + s
    43  	}
    44  	sort.Strings(coreFonts)
    45  
    46  	sscf := []string{"Corefonts:"}
    47  	sscf = append(sscf, coreFonts...)
    48  
    49  	// Get installed fonts from pdfcpu config dir in users home dir
    50  	userFonts := font.UserFontNamesVerbose()
    51  	for i, s := range userFonts {
    52  		userFonts[i] = "  " + s
    53  	}
    54  	sort.Strings(userFonts)
    55  
    56  	ssuf := []string{fmt.Sprintf("Userfonts(%s):", font.UserFontDir)}
    57  	ssuf = append(ssuf, userFonts...)
    58  
    59  	sscf = append(sscf, "")
    60  	return append(sscf, ssuf...), nil
    61  }
    62  
    63  // InstallFonts installs true type fonts for embedding.
    64  func InstallFonts(fileNames []string) error {
    65  	if log.CLIEnabled() {
    66  		log.CLI.Printf("installing to %s...", font.UserFontDir)
    67  	}
    68  
    69  	for _, fn := range fileNames {
    70  		switch filepath.Ext(fn) {
    71  		case ".ttf":
    72  			//log.CLI.Println(filepath.Base(fn))
    73  			if err := font.InstallTrueTypeFont(font.UserFontDir, fn); err != nil {
    74  				if log.CLIEnabled() {
    75  					log.CLI.Printf("%v", err)
    76  				}
    77  			}
    78  		case ".ttc":
    79  			//log.CLI.Println(filepath.Base(fn))
    80  			if err := font.InstallTrueTypeCollection(font.UserFontDir, fn); err != nil {
    81  				if log.CLIEnabled() {
    82  					log.CLI.Printf("%v", err)
    83  				}
    84  			}
    85  		}
    86  	}
    87  
    88  	return font.LoadUserFonts()
    89  }
    90  
    91  func rowLabel(xRefTable *model.XRefTable, i int, td model.TextDescriptor, baseFontName, baseFontKey string, buf *bytes.Buffer, mb *types.Rectangle, left bool) {
    92  	x := 39.
    93  	if !left {
    94  		x = 7750
    95  	}
    96  	s := fmt.Sprintf("#%02X", i)
    97  	td.X, td.Y, td.Text = x, float64(7677-i*30), s
    98  	td.StrokeCol, td.FillCol = color.Black, color.SimpleColor{B: .8}
    99  	td.FontName, td.FontKey, td.FontSize = baseFontName, baseFontKey, 14
   100  
   101  	model.WriteMultiLine(xRefTable, buf, mb, nil, td)
   102  }
   103  
   104  func columnsLabel(xRefTable *model.XRefTable, td model.TextDescriptor, baseFontName, baseFontKey string, buf *bytes.Buffer, mb *types.Rectangle, top bool) {
   105  	y := 7700.
   106  	if !top {
   107  		y = 0
   108  	}
   109  
   110  	td.FontName, td.FontKey = baseFontName, baseFontKey
   111  
   112  	for i := 0; i < 256; i++ {
   113  		s := fmt.Sprintf("#%02X", i)
   114  		td.X, td.Y, td.Text, td.FontSize = float64(70+i*30), y, s, 14
   115  		td.StrokeCol, td.FillCol = color.Black, color.SimpleColor{B: .8}
   116  		model.WriteMultiLine(xRefTable, buf, mb, nil, td)
   117  	}
   118  }
   119  
   120  func surrogate(r rune) bool {
   121  	return r >= 0xD800 && r <= 0xDFFF
   122  }
   123  
   124  func writeUserFontDemoContent(xRefTable *model.XRefTable, p model.Page, fontName string, plane int) {
   125  	baseFontName := "Helvetica"
   126  	baseFontSize := 24
   127  	baseFontKey := p.Fm.EnsureKey(baseFontName)
   128  
   129  	fontKey := p.Fm.EnsureKey(fontName)
   130  	fontSize := 24
   131  
   132  	fillCol := color.NewSimpleColor(0xf7e6c7)
   133  	draw.DrawGrid(p.Buf, 16*16, 16*16, types.RectForWidthAndHeight(55, 16, 16*480, 16*480), color.Black, &fillCol)
   134  
   135  	td := model.TextDescriptor{
   136  		FontName:       fontName,
   137  		Embed:          true,
   138  		FontKey:        fontKey,
   139  		FontSize:       baseFontSize,
   140  		HAlign:         types.AlignCenter,
   141  		VAlign:         types.AlignBaseline,
   142  		Scale:          1.0,
   143  		ScaleAbs:       true,
   144  		RMode:          draw.RMFill,
   145  		StrokeCol:      color.Black,
   146  		FillCol:        color.NewSimpleColor(0xab6f30),
   147  		ShowBackground: true,
   148  		BackgroundCol:  color.SimpleColor{R: 1., G: .98, B: .77},
   149  	}
   150  
   151  	from := plane * 0x10000
   152  	to := (plane+1)*0x10000 - 1
   153  	s := fmt.Sprintf("%s %d points (%04X - %04X)", fontName, fontSize, from, to)
   154  
   155  	td.X, td.Y, td.Text = p.MediaBox.Width()/2, 7750, s
   156  	td.FontName, td.FontKey = baseFontName, baseFontKey
   157  	td.StrokeCol, td.FillCol = color.NewSimpleColor(0x77bdbd), color.NewSimpleColor(0xab6f30)
   158  	model.WriteMultiLine(xRefTable, p.Buf, p.MediaBox, nil, td)
   159  
   160  	columnsLabel(xRefTable, td, baseFontName, baseFontKey, p.Buf, p.MediaBox, true)
   161  	base := rune(plane * 0x10000)
   162  	for j := 0; j < 256; j++ {
   163  		rowLabel(xRefTable, j, td, baseFontName, baseFontKey, p.Buf, p.MediaBox, true)
   164  		buf := make([]byte, 4)
   165  		td.StrokeCol, td.FillCol = color.Black, color.Black
   166  		td.FontName, td.FontKey, td.FontSize = fontName, fontKey, fontSize-2
   167  		for i := 0; i < 256; i++ {
   168  			r := base + rune(j*256+i)
   169  			s = " "
   170  			if !surrogate(r) {
   171  				n := utf8.EncodeRune(buf, r)
   172  				s = string(buf[:n])
   173  			}
   174  			td.X, td.Y, td.Text = float64(70+i*30), float64(7672-j*30), s
   175  			model.WriteMultiLine(xRefTable, p.Buf, p.MediaBox, nil, td)
   176  		}
   177  		rowLabel(xRefTable, j, td, baseFontName, baseFontKey, p.Buf, p.MediaBox, false)
   178  	}
   179  	columnsLabel(xRefTable, td, baseFontName, baseFontKey, p.Buf, p.MediaBox, false)
   180  }
   181  
   182  func createUserFontDemoPage(xRefTable *model.XRefTable, w, h, plane int, fontName string) model.Page {
   183  	mediaBox := types.RectForDim(float64(w), float64(h))
   184  	p := model.NewPageWithBg(mediaBox, color.NewSimpleColor(0xbeded9))
   185  	writeUserFontDemoContent(xRefTable, p, fontName, plane)
   186  	return p
   187  }
   188  
   189  func planeString(i int) string {
   190  	switch i {
   191  	case 0:
   192  		return "BMP" // Basic Multilingual Plane
   193  	case 1:
   194  		return "SMP" // Supplementary Multilingual Plane
   195  	case 2:
   196  		return "SIP" // Supplementary Ideographic Plane
   197  	case 3:
   198  		return "TIP" // Tertiary Ideographic Plane
   199  	case 14:
   200  		return "SSP" // Supplementary Special-purpose Plane
   201  	case 15:
   202  		return "SPUA" // Supplementary Private Use Area Plane
   203  	}
   204  	return ""
   205  }
   206  
   207  // CreateUserFontDemoFiles creates single page PDF for each Unicode plane covered.
   208  func CreateUserFontDemoFiles(dir, fn string) error {
   209  	w, h := 7800, 7800
   210  	font.UserFontMetricsLock.RLock()
   211  	ttf, ok := font.UserFontMetrics[fn]
   212  	font.UserFontMetricsLock.RUnlock()
   213  	if !ok {
   214  		return errors.Errorf("pdfcpu: font %s not available\n", fn)
   215  	}
   216  	// Create a single page PDF for each Unicode plane with existing glyphs.
   217  	for i := range ttf.Planes {
   218  		xRefTable, err := pdfcpu.CreateDemoXRef()
   219  		if err != nil {
   220  			return err
   221  		}
   222  		p := createUserFontDemoPage(xRefTable, w, h, i, fn)
   223  
   224  		rootDict, err := xRefTable.Catalog()
   225  		if err != nil {
   226  			return err
   227  		}
   228  		if err = pdfcpu.AddPageTreeWithSamplePage(xRefTable, rootDict, p); err != nil {
   229  			return err
   230  		}
   231  		fileName := filepath.Join(dir, fn+"_"+planeString(i)+".pdf")
   232  		if err := CreatePDFFile(xRefTable, fileName, nil); err != nil {
   233  			return err
   234  		}
   235  	}
   236  	return nil
   237  }
   238  
   239  // CreateCheatSheetsUserFonts creates single page PDF cheat sheets for installed user fonts.
   240  func CreateCheatSheetsUserFonts(fontNames []string) error {
   241  	if len(fontNames) == 0 {
   242  		fontNames = font.UserFontNames()
   243  	}
   244  	sort.Strings(fontNames)
   245  	for _, fn := range fontNames {
   246  		if !font.IsUserFont(fn) {
   247  			if log.CLIEnabled() {
   248  				log.CLI.Printf("unknown user font: %s\n", fn)
   249  			}
   250  			continue
   251  		}
   252  		if log.CLIEnabled() {
   253  			log.CLI.Println("creating cheatsheets for: " + fn)
   254  		}
   255  		if err := CreateUserFontDemoFiles(".", fn); err != nil {
   256  			return err
   257  		}
   258  	}
   259  	return nil
   260  }