fix wrong git ignore
This commit is contained in:
393
internal/pdf/model/font.go
Normal file
393
internal/pdf/model/font.go
Normal file
@@ -0,0 +1,393 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"gitea.tecamino.com/paadi/pdfmerge/internal/pdf/common"
|
||||
"gitea.tecamino.com/paadi/pdfmerge/internal/pdf/core"
|
||||
"gitea.tecamino.com/paadi/pdfmerge/internal/pdf/model/fonts"
|
||||
"gitea.tecamino.com/paadi/pdfmerge/internal/pdf/model/textencoding"
|
||||
)
|
||||
|
||||
// The PdfFont structure represents an underlying font structure which can be of type:
|
||||
// - Type0
|
||||
// - Type1
|
||||
// - TrueType
|
||||
// etc.
|
||||
type PdfFont struct {
|
||||
context any // The underlying font: Type0, Type1, Truetype, etc..
|
||||
}
|
||||
|
||||
// Set the encoding for the underlying font.
|
||||
func (font PdfFont) SetEncoder(encoder textencoding.TextEncoder) {
|
||||
switch t := font.context.(type) {
|
||||
case *pdfFontTrueType:
|
||||
t.SetEncoder(encoder)
|
||||
}
|
||||
}
|
||||
|
||||
func (font PdfFont) GetGlyphCharMetrics(glyph string) (fonts.CharMetrics, bool) {
|
||||
switch t := font.context.(type) {
|
||||
case *pdfFontTrueType:
|
||||
return t.GetGlyphCharMetrics(glyph)
|
||||
}
|
||||
|
||||
return fonts.CharMetrics{}, false
|
||||
}
|
||||
|
||||
func (font PdfFont) ToPdfObject() core.PdfObject {
|
||||
switch f := font.context.(type) {
|
||||
case *pdfFontTrueType:
|
||||
return f.ToPdfObject()
|
||||
}
|
||||
|
||||
// If not supported, return null..
|
||||
common.Log.Debug("Unsupported font (%T) - returning null object", font.context)
|
||||
return core.MakeNull()
|
||||
}
|
||||
|
||||
type pdfFontTrueType struct {
|
||||
Encoder textencoding.TextEncoder
|
||||
|
||||
firstChar int
|
||||
lastChar int
|
||||
charWidths []float64
|
||||
|
||||
// Subtype shall be TrueType.
|
||||
// Encoding is subject to limitations that are described in 9.6.6, "Character Encoding".
|
||||
// BaseFont is derived differently.
|
||||
BaseFont core.PdfObject
|
||||
FirstChar core.PdfObject
|
||||
LastChar core.PdfObject
|
||||
Widths core.PdfObject
|
||||
FontDescriptor *PdfFontDescriptor
|
||||
Encoding core.PdfObject
|
||||
ToUnicode core.PdfObject
|
||||
|
||||
container *core.PdfIndirectObject
|
||||
}
|
||||
|
||||
func (font pdfFontTrueType) SetEncoder(encoder textencoding.TextEncoder) {
|
||||
font.Encoder = encoder
|
||||
}
|
||||
|
||||
func (font pdfFontTrueType) GetGlyphCharMetrics(glyph string) (fonts.CharMetrics, bool) {
|
||||
metrics := fonts.CharMetrics{}
|
||||
|
||||
code, found := font.Encoder.GlyphToCharcode(glyph)
|
||||
if !found {
|
||||
return metrics, false
|
||||
}
|
||||
|
||||
if int(code) < font.firstChar {
|
||||
common.Log.Debug("Code lower than firstchar (%d < %d)", code, font.firstChar)
|
||||
return metrics, false
|
||||
}
|
||||
|
||||
if int(code) > font.lastChar {
|
||||
common.Log.Debug("Code higher than lastchar (%d < %d)", code, font.lastChar)
|
||||
return metrics, false
|
||||
}
|
||||
|
||||
index := int(code) - font.firstChar
|
||||
if index >= len(font.charWidths) {
|
||||
common.Log.Debug("Code outside of widths range")
|
||||
return metrics, false
|
||||
}
|
||||
|
||||
width := font.charWidths[index]
|
||||
metrics.Wx = width
|
||||
|
||||
return metrics, true
|
||||
}
|
||||
|
||||
func (ftt *pdfFontTrueType) ToPdfObject() core.PdfObject {
|
||||
if ftt.container == nil {
|
||||
ftt.container = &core.PdfIndirectObject{}
|
||||
}
|
||||
d := core.MakeDict()
|
||||
ftt.container.PdfObject = d
|
||||
|
||||
d.Set("Type", core.MakeName("Font"))
|
||||
d.Set("Subtype", core.MakeName("TrueType"))
|
||||
|
||||
if ftt.BaseFont != nil {
|
||||
d.Set("BaseFont", ftt.BaseFont)
|
||||
}
|
||||
if ftt.FirstChar != nil {
|
||||
d.Set("FirstChar", ftt.FirstChar)
|
||||
}
|
||||
if ftt.LastChar != nil {
|
||||
d.Set("LastChar", ftt.LastChar)
|
||||
}
|
||||
if ftt.Widths != nil {
|
||||
d.Set("Widths", ftt.Widths)
|
||||
}
|
||||
if ftt.FontDescriptor != nil {
|
||||
d.Set("FontDescriptor", ftt.FontDescriptor.ToPdfObject())
|
||||
}
|
||||
if ftt.Encoding != nil {
|
||||
d.Set("Encoding", ftt.Encoding)
|
||||
}
|
||||
if ftt.ToUnicode != nil {
|
||||
d.Set("ToUnicode", ftt.ToUnicode)
|
||||
}
|
||||
|
||||
return ftt.container
|
||||
}
|
||||
|
||||
func NewPdfFontFromTTFFile(filePath string) (*PdfFont, error) {
|
||||
ttf, err := fonts.TtfParse(filePath)
|
||||
if err != nil {
|
||||
common.Log.Debug("error loading ttf font: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
truefont := &pdfFontTrueType{}
|
||||
|
||||
truefont.Encoder = textencoding.NewWinAnsiTextEncoder()
|
||||
truefont.firstChar = 32
|
||||
truefont.lastChar = 255
|
||||
|
||||
truefont.BaseFont = core.MakeName(ttf.PostScriptName)
|
||||
truefont.FirstChar = core.MakeInteger(32)
|
||||
truefont.LastChar = core.MakeInteger(255)
|
||||
|
||||
k := 1000.0 / float64(ttf.UnitsPerEm)
|
||||
|
||||
if len(ttf.Widths) <= 0 {
|
||||
return nil, errors.New("missing required attribute (Widths)")
|
||||
}
|
||||
|
||||
missingWidth := k * float64(ttf.Widths[0])
|
||||
vals := []float64{}
|
||||
|
||||
for charcode := 32; charcode <= 255; charcode++ {
|
||||
runeVal, found := truefont.Encoder.CharcodeToRune(byte(charcode))
|
||||
if !found {
|
||||
common.Log.Debug("Rune not found (charcode: %d)", charcode)
|
||||
vals = append(vals, missingWidth)
|
||||
continue
|
||||
}
|
||||
|
||||
pos, ok := ttf.Chars[uint16(runeVal)]
|
||||
if !ok {
|
||||
common.Log.Debug("Rune not in TTF Chars")
|
||||
vals = append(vals, missingWidth)
|
||||
continue
|
||||
}
|
||||
|
||||
w := k * float64(ttf.Widths[pos])
|
||||
|
||||
vals = append(vals, w)
|
||||
}
|
||||
|
||||
truefont.Widths = &core.PdfIndirectObject{PdfObject: core.MakeArrayFromFloats(vals)}
|
||||
|
||||
if len(vals) < (255 - 32 + 1) {
|
||||
common.Log.Debug("invalid length of widths, %d < %d", len(vals), 255-32+1)
|
||||
return nil, errors.New("range check error")
|
||||
}
|
||||
|
||||
truefont.charWidths = vals[:255-32+1]
|
||||
|
||||
// Default.
|
||||
// XXX/FIXME TODO: Only use the encoder object.
|
||||
|
||||
truefont.Encoding = core.MakeName("WinAnsiEncoding")
|
||||
|
||||
descriptor := &PdfFontDescriptor{}
|
||||
descriptor.Ascent = core.MakeFloat(k * float64(ttf.TypoAscender))
|
||||
descriptor.Descent = core.MakeFloat(k * float64(ttf.TypoDescender))
|
||||
descriptor.CapHeight = core.MakeFloat(k * float64(ttf.CapHeight))
|
||||
descriptor.FontBBox = core.MakeArrayFromFloats([]float64{k * float64(ttf.Xmin), k * float64(ttf.Ymin), k * float64(ttf.Xmax), k * float64(ttf.Ymax)})
|
||||
descriptor.ItalicAngle = core.MakeFloat(float64(ttf.ItalicAngle))
|
||||
descriptor.MissingWidth = core.MakeFloat(k * float64(ttf.Widths[0]))
|
||||
|
||||
ttfBytes, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
common.Log.Debug("Unable to read file contents: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// XXX/TODO: Encode the file...
|
||||
stream, err := core.MakeStream(ttfBytes, core.NewFlateEncoder())
|
||||
if err != nil {
|
||||
common.Log.Debug("Unable to make stream: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
stream.PdfObjectDictionary.Set("Length1", core.MakeInteger(int64(len(ttfBytes))))
|
||||
descriptor.FontFile2 = stream
|
||||
|
||||
if ttf.Bold {
|
||||
descriptor.StemV = core.MakeInteger(120)
|
||||
} else {
|
||||
descriptor.StemV = core.MakeInteger(70)
|
||||
}
|
||||
|
||||
// Flags.
|
||||
flags := 1 << 5
|
||||
if ttf.IsFixedPitch {
|
||||
flags |= 1
|
||||
}
|
||||
if ttf.ItalicAngle != 0 {
|
||||
flags |= 1 << 6
|
||||
}
|
||||
descriptor.Flags = core.MakeInteger(int64(flags))
|
||||
|
||||
// Build Font.
|
||||
truefont.FontDescriptor = descriptor
|
||||
|
||||
font := &PdfFont{}
|
||||
font.context = truefont
|
||||
|
||||
return font, nil
|
||||
}
|
||||
|
||||
// Font descriptors specifies metrics and other attributes of a font.
|
||||
type PdfFontDescriptor struct {
|
||||
FontName core.PdfObject
|
||||
FontFamily core.PdfObject
|
||||
FontStretch core.PdfObject
|
||||
FontWeight core.PdfObject
|
||||
Flags core.PdfObject
|
||||
FontBBox core.PdfObject
|
||||
ItalicAngle core.PdfObject
|
||||
Ascent core.PdfObject
|
||||
Descent core.PdfObject
|
||||
Leading core.PdfObject
|
||||
CapHeight core.PdfObject
|
||||
XHeight core.PdfObject
|
||||
StemV core.PdfObject
|
||||
StemH core.PdfObject
|
||||
AvgWidth core.PdfObject
|
||||
MaxWidth core.PdfObject
|
||||
MissingWidth core.PdfObject
|
||||
FontFile core.PdfObject
|
||||
FontFile2 core.PdfObject
|
||||
FontFile3 core.PdfObject
|
||||
CharSet core.PdfObject
|
||||
|
||||
// Additional entries for CIDFonts
|
||||
Style core.PdfObject
|
||||
Lang core.PdfObject
|
||||
FD core.PdfObject
|
||||
CIDSet core.PdfObject
|
||||
|
||||
// Container.
|
||||
container *core.PdfIndirectObject
|
||||
}
|
||||
|
||||
// Convert to a PDF dictionary inside an indirect object.
|
||||
func (pfd *PdfFontDescriptor) ToPdfObject() core.PdfObject {
|
||||
d := core.MakeDict()
|
||||
if pfd.container == nil {
|
||||
pfd.container = &core.PdfIndirectObject{}
|
||||
}
|
||||
pfd.container.PdfObject = d
|
||||
|
||||
d.Set("Type", core.MakeName("FontDescriptor"))
|
||||
|
||||
if pfd.FontName != nil {
|
||||
d.Set("FontName", pfd.FontName)
|
||||
}
|
||||
|
||||
if pfd.FontFamily != nil {
|
||||
d.Set("FontFamily", pfd.FontFamily)
|
||||
}
|
||||
|
||||
if pfd.FontStretch != nil {
|
||||
d.Set("FontStretch", pfd.FontStretch)
|
||||
}
|
||||
|
||||
if pfd.FontWeight != nil {
|
||||
d.Set("FontWeight", pfd.FontWeight)
|
||||
}
|
||||
|
||||
if pfd.Flags != nil {
|
||||
d.Set("Flags", pfd.Flags)
|
||||
}
|
||||
|
||||
if pfd.FontBBox != nil {
|
||||
d.Set("FontBBox", pfd.FontBBox)
|
||||
}
|
||||
|
||||
if pfd.ItalicAngle != nil {
|
||||
d.Set("ItalicAngle", pfd.ItalicAngle)
|
||||
}
|
||||
|
||||
if pfd.Ascent != nil {
|
||||
d.Set("Ascent", pfd.Ascent)
|
||||
}
|
||||
|
||||
if pfd.Descent != nil {
|
||||
d.Set("Descent", pfd.Descent)
|
||||
}
|
||||
|
||||
if pfd.Leading != nil {
|
||||
d.Set("Leading", pfd.Leading)
|
||||
}
|
||||
|
||||
if pfd.CapHeight != nil {
|
||||
d.Set("CapHeight", pfd.CapHeight)
|
||||
}
|
||||
|
||||
if pfd.XHeight != nil {
|
||||
d.Set("XHeight", pfd.XHeight)
|
||||
}
|
||||
|
||||
if pfd.StemV != nil {
|
||||
d.Set("StemV", pfd.StemV)
|
||||
}
|
||||
|
||||
if pfd.StemH != nil {
|
||||
d.Set("StemH", pfd.StemH)
|
||||
}
|
||||
|
||||
if pfd.AvgWidth != nil {
|
||||
d.Set("AvgWidth", pfd.AvgWidth)
|
||||
}
|
||||
|
||||
if pfd.MaxWidth != nil {
|
||||
d.Set("MaxWidth", pfd.MaxWidth)
|
||||
}
|
||||
|
||||
if pfd.MissingWidth != nil {
|
||||
d.Set("MissingWidth", pfd.MissingWidth)
|
||||
}
|
||||
|
||||
if pfd.FontFile != nil {
|
||||
d.Set("FontFile", pfd.FontFile)
|
||||
}
|
||||
|
||||
if pfd.FontFile2 != nil {
|
||||
d.Set("FontFile2", pfd.FontFile2)
|
||||
}
|
||||
|
||||
if pfd.FontFile3 != nil {
|
||||
d.Set("FontFile3", pfd.FontFile3)
|
||||
}
|
||||
|
||||
if pfd.CharSet != nil {
|
||||
d.Set("CharSet", pfd.CharSet)
|
||||
}
|
||||
|
||||
if pfd.Style != nil {
|
||||
d.Set("FontName", pfd.FontName)
|
||||
}
|
||||
|
||||
if pfd.Lang != nil {
|
||||
d.Set("Lang", pfd.Lang)
|
||||
}
|
||||
|
||||
if pfd.FD != nil {
|
||||
d.Set("FD", pfd.FD)
|
||||
}
|
||||
|
||||
if pfd.CIDSet != nil {
|
||||
d.Set("CIDSet", pfd.CIDSet)
|
||||
}
|
||||
|
||||
return pfd.container
|
||||
}
|
||||
Reference in New Issue
Block a user