mirror of
https://github.com/unidoc/unipdf.git
synced 2025-05-01 22:17:29 +08:00
614 lines
15 KiB
Go
614 lines
15 KiB
Go
/*
|
|
* This file is subject to the terms and conditions defined in
|
|
* file 'LICENSE.md', which is part of this source code package.
|
|
*/
|
|
|
|
package model
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"io/ioutil"
|
|
|
|
"github.com/unidoc/unidoc/common"
|
|
"github.com/unidoc/unidoc/pdf/core"
|
|
"github.com/unidoc/unidoc/pdf/model/fonts"
|
|
"github.com/unidoc/unidoc/pdf/model/textencoding"
|
|
)
|
|
|
|
// The PdfFont structure represents an underlying font structure which can be of type:
|
|
// - Type0
|
|
// - Type1
|
|
// - TrueType
|
|
// etc.
|
|
type PdfFont struct {
|
|
context interface{} // 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 newPdfFontFromPdfObject(obj core.PdfObject) (*PdfFont, error) {
|
|
font := &PdfFont{}
|
|
|
|
dictObj := obj
|
|
if ind, is := obj.(*core.PdfIndirectObject); is {
|
|
dictObj = ind.PdfObject
|
|
}
|
|
|
|
d, ok := dictObj.(*core.PdfObjectDictionary)
|
|
if !ok {
|
|
common.Log.Debug("Font not given by a dictionary (%T)", obj)
|
|
return nil, errors.New("Type check error")
|
|
}
|
|
|
|
if obj := d.Get("Type"); obj != nil {
|
|
oname, is := obj.(*core.PdfObjectName)
|
|
if !is || string(*oname) != "Font" {
|
|
common.Log.Debug("Incompatibility ERROR: Type (Required) defined but not Font name")
|
|
return nil, errors.New("Range check error")
|
|
}
|
|
} else {
|
|
common.Log.Debug("Incompatibility ERROR: Type (Required) missing")
|
|
return nil, errors.New("Required attribute missing")
|
|
}
|
|
|
|
obj = d.Get("Subtype")
|
|
if obj == nil {
|
|
common.Log.Debug("Incompatibility ERROR: Subtype (Required) missing")
|
|
return nil, errors.New("Required attribute missing")
|
|
}
|
|
|
|
subtype, ok := core.TraceToDirectObject(obj).(*core.PdfObjectName)
|
|
if !ok {
|
|
common.Log.Debug("Incompatibility ERROR: subtype not a name (%T) ", obj)
|
|
return nil, errors.New("Type check error")
|
|
}
|
|
|
|
switch subtype.String() {
|
|
case "TrueType":
|
|
truefont, err := newPdfFontTrueTypeFromPdfObject(obj)
|
|
if err != nil {
|
|
common.Log.Debug("Error loading truetype font: %v", truefont)
|
|
return nil, err
|
|
}
|
|
|
|
font.context = truefont
|
|
default:
|
|
common.Log.Debug("Unsupported font type: %s", subtype.String())
|
|
return nil, errors.New("Unsupported font type")
|
|
}
|
|
|
|
return font, nil
|
|
}
|
|
|
|
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 newPdfFontTrueTypeFromPdfObject(obj core.PdfObject) (*pdfFontTrueType, error) {
|
|
font := &pdfFontTrueType{}
|
|
|
|
if ind, is := obj.(*core.PdfIndirectObject); is {
|
|
font.container = ind
|
|
obj = ind.PdfObject
|
|
}
|
|
|
|
d, ok := obj.(*core.PdfObjectDictionary)
|
|
if !ok {
|
|
common.Log.Debug("Font object invalid, not a dictionary (%T)", obj)
|
|
return nil, errors.New("Type check error")
|
|
}
|
|
|
|
if obj := d.Get("Type"); obj != nil {
|
|
oname, is := obj.(*core.PdfObjectName)
|
|
if !is || oname.String() != "Font" {
|
|
common.Log.Debug("Incompatibility: Type defined but not Font")
|
|
}
|
|
}
|
|
|
|
if obj := d.Get("Subtype"); obj != nil {
|
|
oname, is := obj.(*core.PdfObjectName)
|
|
if !is || oname.String() != "TrueType" {
|
|
common.Log.Debug("Incompatibility: Loading TrueType font but Subtype != TrueType")
|
|
}
|
|
}
|
|
|
|
font.BaseFont = d.Get("BaseFont")
|
|
|
|
if obj := d.Get("FirstChar"); obj != nil {
|
|
font.FirstChar = obj
|
|
|
|
intVal, ok := core.TraceToDirectObject(obj).(*core.PdfObjectInteger)
|
|
if !ok {
|
|
common.Log.Debug("Invalid FirstChar type (%T)", obj)
|
|
return nil, errors.New("Type check error")
|
|
}
|
|
font.firstChar = int(*intVal)
|
|
} else {
|
|
common.Log.Debug("ERROR: FirstChar attribute missing")
|
|
return nil, errors.New("Required attribute missing")
|
|
}
|
|
|
|
if obj := d.Get("LastChar"); obj != nil {
|
|
font.LastChar = obj
|
|
|
|
intVal, ok := core.TraceToDirectObject(obj).(*core.PdfObjectInteger)
|
|
if !ok {
|
|
common.Log.Debug("Invalid LastChar type (%T)", obj)
|
|
return nil, errors.New("Type check error")
|
|
}
|
|
font.lastChar = int(*intVal)
|
|
} else {
|
|
common.Log.Debug("ERROR: FirstChar attribute missing")
|
|
return nil, errors.New("Required attribute missing")
|
|
}
|
|
|
|
font.charWidths = []float64{}
|
|
if obj := d.Get("Widths"); obj != nil {
|
|
font.Widths = obj
|
|
|
|
arr, ok := core.TraceToDirectObject(obj).(*core.PdfObjectArray)
|
|
if !ok {
|
|
common.Log.Debug("Widths attribute != array (%T)", arr)
|
|
return nil, errors.New("Type check error")
|
|
}
|
|
|
|
widths, err := arr.ToFloat64Array()
|
|
if err != nil {
|
|
common.Log.Debug("Error converting widths to array")
|
|
return nil, err
|
|
}
|
|
|
|
if len(widths) != (font.lastChar - font.firstChar + 1) {
|
|
common.Log.Debug("Invalid widths length != %d (%d)", font.lastChar-font.firstChar+1, len(widths))
|
|
return nil, errors.New("Range check error")
|
|
}
|
|
|
|
font.charWidths = widths
|
|
} else {
|
|
common.Log.Debug("Widths missing from font")
|
|
return nil, errors.New("Required attribute missing")
|
|
}
|
|
|
|
if obj := d.Get("FontDescriptor"); obj != nil {
|
|
descriptor, err := newPdfFontDescriptorFromPdfObject(obj)
|
|
if err != nil {
|
|
common.Log.Debug("Error loading font descriptor: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
font.FontDescriptor = descriptor
|
|
}
|
|
|
|
font.Encoding = d.Get("Encoding")
|
|
font.ToUnicode = d.Get("ToUnicode")
|
|
|
|
return font, nil
|
|
}
|
|
|
|
func (this *pdfFontTrueType) ToPdfObject() core.PdfObject {
|
|
if this.container == nil {
|
|
this.container = &core.PdfIndirectObject{}
|
|
}
|
|
d := core.MakeDict()
|
|
this.container.PdfObject = d
|
|
|
|
d.Set("Type", core.MakeName("Font"))
|
|
d.Set("Subtype", core.MakeName("TrueType"))
|
|
|
|
if this.BaseFont != nil {
|
|
d.Set("BaseFont", this.BaseFont)
|
|
}
|
|
if this.FirstChar != nil {
|
|
d.Set("FirstChar", this.FirstChar)
|
|
}
|
|
if this.LastChar != nil {
|
|
d.Set("LastChar", this.LastChar)
|
|
}
|
|
if this.Widths != nil {
|
|
d.Set("Widths", this.Widths)
|
|
}
|
|
if this.FontDescriptor != nil {
|
|
d.Set("FontDescriptor", this.FontDescriptor.ToPdfObject())
|
|
}
|
|
if this.Encoding != nil {
|
|
d.Set("Encoding", this.Encoding)
|
|
}
|
|
if this.ToUnicode != nil {
|
|
d.Set("ToUnicode", this.ToUnicode)
|
|
}
|
|
|
|
return this.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 := ioutil.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
|
|
}
|
|
|
|
// Load the font descriptor from a PdfObject. Can either be a *PdfIndirectObject or
|
|
// a *PdfObjectDictionary.
|
|
func newPdfFontDescriptorFromPdfObject(obj core.PdfObject) (*PdfFontDescriptor, error) {
|
|
descriptor := &PdfFontDescriptor{}
|
|
|
|
if ind, is := obj.(*core.PdfIndirectObject); is {
|
|
descriptor.container = ind
|
|
obj = ind.PdfObject
|
|
}
|
|
|
|
d, ok := obj.(*core.PdfObjectDictionary)
|
|
if !ok {
|
|
common.Log.Debug("FontDescriptor not given by a dictionary (%T)", obj)
|
|
return nil, errors.New("Type check error")
|
|
}
|
|
|
|
if obj := d.Get("Type"); obj != nil {
|
|
oname, is := obj.(*core.PdfObjectName)
|
|
if !is || string(*oname) != "FontDescriptor" {
|
|
common.Log.Debug("Incompatibility: Font descriptor Type invalid (%T)", obj)
|
|
}
|
|
} else {
|
|
common.Log.Debug("Incompatibility: Type (Required) missing")
|
|
}
|
|
|
|
if obj := d.Get("FontName"); obj != nil {
|
|
descriptor.FontName = obj
|
|
} else {
|
|
common.Log.Debug("Incompatibility: FontName (Required) missing")
|
|
}
|
|
|
|
descriptor.FontFamily = d.Get("FontFamily")
|
|
descriptor.FontStretch = d.Get("FontStretch")
|
|
descriptor.FontWeight = d.Get("FontWeight")
|
|
descriptor.Flags = d.Get("Flags")
|
|
descriptor.FontBBox = d.Get("FontBBox")
|
|
descriptor.ItalicAngle = d.Get("ItalicAngle")
|
|
descriptor.Ascent = d.Get("Ascent")
|
|
descriptor.Descent = d.Get("Descent")
|
|
descriptor.Leading = d.Get("Leading")
|
|
descriptor.CapHeight = d.Get("CapHeight")
|
|
descriptor.XHeight = d.Get("XHeight")
|
|
descriptor.StemV = d.Get("StemV")
|
|
descriptor.StemH = d.Get("StemH")
|
|
descriptor.AvgWidth = d.Get("AvgWidth")
|
|
descriptor.MaxWidth = d.Get("MaxWidth")
|
|
descriptor.MissingWidth = d.Get("MissingWidth")
|
|
descriptor.FontFile = d.Get("FontFile")
|
|
descriptor.FontFile2 = d.Get("FontFile2")
|
|
descriptor.FontFile3 = d.Get("FontFile3")
|
|
descriptor.CharSet = d.Get("CharSet")
|
|
descriptor.Style = d.Get("Style")
|
|
descriptor.Lang = d.Get("Lang")
|
|
descriptor.FD = d.Get("FD")
|
|
descriptor.CIDSet = d.Get("CIDSet")
|
|
|
|
return descriptor, nil
|
|
}
|
|
|
|
// Convert to a PDF dictionary inside an indirect object.
|
|
func (this *PdfFontDescriptor) ToPdfObject() core.PdfObject {
|
|
d := core.MakeDict()
|
|
if this.container == nil {
|
|
this.container = &core.PdfIndirectObject{}
|
|
}
|
|
this.container.PdfObject = d
|
|
|
|
d.Set("Type", core.MakeName("FontDescriptor"))
|
|
|
|
if this.FontName != nil {
|
|
d.Set("FontName", this.FontName)
|
|
}
|
|
|
|
if this.FontFamily != nil {
|
|
d.Set("FontFamily", this.FontFamily)
|
|
}
|
|
|
|
if this.FontStretch != nil {
|
|
d.Set("FontStretch", this.FontStretch)
|
|
}
|
|
|
|
if this.FontWeight != nil {
|
|
d.Set("FontWeight", this.FontWeight)
|
|
}
|
|
|
|
if this.Flags != nil {
|
|
d.Set("Flags", this.Flags)
|
|
}
|
|
|
|
if this.FontBBox != nil {
|
|
d.Set("FontBBox", this.FontBBox)
|
|
}
|
|
|
|
if this.ItalicAngle != nil {
|
|
d.Set("ItalicAngle", this.ItalicAngle)
|
|
}
|
|
|
|
if this.Ascent != nil {
|
|
d.Set("Ascent", this.Ascent)
|
|
}
|
|
|
|
if this.Descent != nil {
|
|
d.Set("Descent", this.Descent)
|
|
}
|
|
|
|
if this.Leading != nil {
|
|
d.Set("Leading", this.Leading)
|
|
}
|
|
|
|
if this.CapHeight != nil {
|
|
d.Set("CapHeight", this.CapHeight)
|
|
}
|
|
|
|
if this.XHeight != nil {
|
|
d.Set("XHeight", this.XHeight)
|
|
}
|
|
|
|
if this.StemV != nil {
|
|
d.Set("StemV", this.StemV)
|
|
}
|
|
|
|
if this.StemH != nil {
|
|
d.Set("StemH", this.StemH)
|
|
}
|
|
|
|
if this.AvgWidth != nil {
|
|
d.Set("AvgWidth", this.AvgWidth)
|
|
}
|
|
|
|
if this.MaxWidth != nil {
|
|
d.Set("MaxWidth", this.MaxWidth)
|
|
}
|
|
|
|
if this.MissingWidth != nil {
|
|
d.Set("MissingWidth", this.MissingWidth)
|
|
}
|
|
|
|
if this.FontFile != nil {
|
|
d.Set("FontFile", this.FontFile)
|
|
}
|
|
|
|
if this.FontFile2 != nil {
|
|
d.Set("FontFile2", this.FontFile2)
|
|
}
|
|
|
|
if this.FontFile3 != nil {
|
|
d.Set("FontFile3", this.FontFile3)
|
|
}
|
|
|
|
if this.CharSet != nil {
|
|
d.Set("CharSet", this.CharSet)
|
|
}
|
|
|
|
if this.Style != nil {
|
|
d.Set("FontName", this.FontName)
|
|
}
|
|
|
|
if this.Lang != nil {
|
|
d.Set("Lang", this.Lang)
|
|
}
|
|
|
|
if this.FD != nil {
|
|
d.Set("FD", this.FD)
|
|
}
|
|
|
|
if this.CIDSet != nil {
|
|
d.Set("CIDSet", this.CIDSet)
|
|
}
|
|
|
|
return this.container
|
|
}
|