Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion def.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"encoding/json"
"fmt"
"io"
"strconv"
"time"
)

Expand Down Expand Up @@ -702,7 +703,7 @@ type fontDefType struct {
Desc FontDescType // Font descriptor
Up int // Underline position
Ut int // Underline thickness
Cw []int // Character width by ordinal
Cw map[int]int // Character width by ordinal
Enc string // "cp1252", ...
Diff string // Differences from reference encoding
File string // "Redressed.z"
Expand All @@ -713,6 +714,45 @@ type fontDefType struct {
i string // 1-based position in font list, set by font loader, not this program
utf8File *utf8FontFile // UTF-8 font
usedRunes map[int]int // Array of used runes
runeToCid map[int]int // Map of rune to CID (for remapping)
nextFreeCID int // Next available CID for remapping
}

// UnmarshalJSON handles both array (legacy) and map (new) formats for Cw
func (f *fontDefType) UnmarshalJSON(data []byte) error {
type Alias fontDefType
aux := &struct {
Cw interface{}
*Alias
}{
Alias: (*Alias)(f),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}

f.Cw = make(map[int]int)
if aux.Cw != nil {
switch v := aux.Cw.(type) {
case []interface{}:
for i, val := range v {
if fVal, ok := val.(float64); ok {
if fVal != 0 {
f.Cw[i] = int(fVal)
}
}
}
case map[string]interface{}:
for k, val := range v {
if fVal, ok := val.(float64); ok {
if i, err := strconv.Atoi(k); err == nil {
f.Cw[i] = int(fVal)
}
}
}
}
}
return nil
}

// generateFontID generates a font Id from the font definition
Expand Down
8 changes: 7 additions & 1 deletion font.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,13 @@ func makeDefinitionFile(fileStr, tpStr, encodingFileStr string, embed bool, encL
// dump(def.Desc.FontBBox)
def.Up = info.UnderlinePosition
def.Ut = info.UnderlineThickness
def.Cw = info.Widths
// def.Cw = info.Widths
def.Cw = make(map[int]int)
for i, w := range info.Widths {
if w != 0 {
def.Cw[i] = w
}
}
def.Enc = baseNoExt(encodingFileStr)
// fmt.Printf("encodingFileStr [%s], def.Enc [%s]\n", encodingFileStr, def.Enc)
// fmt.Printf("reference [%s]\n", filepath.Join(filepath.Dir(encodingFileStr), "cp1252.map"))
Expand Down
157 changes: 123 additions & 34 deletions fpdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -963,9 +963,10 @@ func (f *Fpdf) GetStringSymbolWidth(s string) int {
unicode := []rune(s)
for _, char := range unicode {
intChar := int(char)
if len(f.currentFont.Cw) >= intChar && f.currentFont.Cw[intChar] > 0 {
if f.currentFont.Cw[intChar] != 65535 {
w += f.currentFont.Cw[intChar]
width, ok := f.currentFont.Cw[intChar]
if ok && width > 0 {
if width != 65535 {
w += width
}
} else if f.currentFont.Desc.MissingWidth != 0 {
w += f.currentFont.Desc.MissingWidth
Expand All @@ -974,11 +975,22 @@ func (f *Fpdf) GetStringSymbolWidth(s string) int {
}
}
} else {
for _, ch := range []byte(s) {
if ch == 0 {
for _, char := range []byte(s) {
if char == 0 {
break
}
w += f.currentFont.Cw[ch]
ch := int(char)
if width, ok := f.currentFont.Cw[ch]; ok {
w += width
} else {
// Default behavior for non-existent char in map (should ideally not happen for byte fonts if properly initialized)
// Or assume missing width
if f.currentFont.Desc.MissingWidth != 0 {
w += f.currentFont.Desc.MissingWidth
} else {
w += 500
}
}
}
}
return w
Expand Down Expand Up @@ -1733,6 +1745,10 @@ func (f *Fpdf) addFont(familyStr, styleStr, fileStr string, isUTF8 bool) {
usedRunes: sbarr,
File: fileStr,
utf8File: utf8File,
runeToCid: make(map[int]int),
}
for cid, r := range sbarr {
def.runeToCid[r] = cid
}
def.i, _ = generateFontID(def)
f.fonts[fontKey] = def
Expand Down Expand Up @@ -1869,6 +1885,10 @@ func (f *Fpdf) addFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFile
Cw: utf8File.CharWidths,
utf8File: utf8File,
usedRunes: sbarr,
runeToCid: make(map[int]int),
}
for cid, r := range sbarr {
def.runeToCid[r] = cid
}
def.i, _ = generateFontID(def)
f.fonts[fontkey] = def
Expand Down Expand Up @@ -2201,6 +2221,61 @@ func (f *Fpdf) Bookmark(txtStr string, level int, y float64) {
f.outlines = append(f.outlines, outlineType{text: txtStr, level: level, y: y, p: f.PageNo(), prev: -1, last: -1, next: -1, first: -1})
}

func (f *Fpdf) getOrAssignCID(r int) int {
if cid, ok := f.currentFont.runeToCid[r]; ok {
return cid
}

cid := r
// If the rune is in BMP and not already used as a CID for another rune (identity mapping), use it.
// But we must check if 'cid' is already occupied by a different rune?
// If runeToCid is empty initially, and usedRunes is empty.
// We want to prefer Identity.
// Check if this CID slot is free in usedRunes.
// Note: usedRunes[cid] = original_rune
if r < 0xFFFF {
if original, used := f.currentFont.usedRunes[r]; !used || original == r {
cid = r
} else {
cid = f.findNextFreeCID()
}
} else {
cid = f.findNextFreeCID()
}

f.currentFont.runeToCid[r] = cid
f.currentFont.usedRunes[cid] = r
return cid
}

func (f *Fpdf) findNextFreeCID() int {
// Start searching from PUA
start := 0xE000
for i := start; i < 0xFFFF; i++ {
if _, used := f.currentFont.usedRunes[i]; !used {
return i
}
}
// If PUA full, search from beginning?
for i := 32; i < 0xE000; i++ {
if _, used := f.currentFont.usedRunes[i]; !used {
return i
}
}
// Fallback to 0 if full (should panic?)
return 0
}

func (f *Fpdf) stringToCIDs(s string) string {
var b bytes.Buffer
for _, r := range s {
cid := f.getOrAssignCID(int(r))
b.WriteByte(byte(cid >> 8))
b.WriteByte(byte(cid))
}
return b.String()
}

// Text prints a character string. The origin (x, y) is on the left of the
// first character at the baseline. This method permits a string to be placed
// precisely on the page, but it is usually easier to use Cell(), MultiCell()
Expand All @@ -2212,10 +2287,7 @@ func (f *Fpdf) Text(x, y float64, txtStr string) {
txtStr = reverseText(txtStr)
x -= f.GetStringWidth(txtStr)
}
txt2 = f.escape(utf8toutf16(txtStr, false))
for _, uni := range []rune(txtStr) {
f.currentFont.usedRunes[int(uni)] = int(uni)
}
txt2 = f.escape(f.stringToCIDs(txtStr))
} else {
txt2 = f.escape(txtStr)
}
Expand Down Expand Up @@ -2424,18 +2496,18 @@ func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
txtStr = reverseText(txtStr)
}
wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
for _, uni := range []rune(txtStr) {
f.currentFont.usedRunes[int(uni)] = int(uni)
}
space := f.escape(utf8toutf16(" ", false))
// for _, uni := range []rune(txtStr) {
// f.currentFont.usedRunes[int(uni)] = int(uni)
// }
space := f.escape(f.stringToCIDs(" "))
strSize := f.GetStringSymbolWidth(txtStr)
s.printf("BT 0 Tw %.2f %.2f Td [", (f.x+dx)*k, (f.h-(f.y+.5*h+.3*f.fontSize))*k)
t := strings.Split(txtStr, " ")
shift := float64((wmax - strSize)) / float64(len(t)-1)
numt := len(t)
for i := 0; i < numt; i++ {
tx := t[i]
tx = "(" + f.escape(utf8toutf16(tx, false)) + ")"
tx = "(" + f.escape(f.stringToCIDs(tx)) + ")"
s.printf("%s ", tx)
if (i + 1) < numt {
s.printf("%.3f(%s) ", -shift, space)
Expand All @@ -2448,10 +2520,10 @@ func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
if f.isRTL {
txtStr = reverseText(txtStr)
}
txt2 = f.escape(utf8toutf16(txtStr, false))
for _, uni := range []rune(txtStr) {
f.currentFont.usedRunes[int(uni)] = int(uni)
}
txt2 = f.escape(f.stringToCIDs(txtStr))
// for _, uni := range []rune(txtStr) {
// f.currentFont.usedRunes[int(uni)] = int(uni)
// }
} else {

txt2 = strings.Replace(txtStr, "\\", "\\\\", -1)
Expand Down Expand Up @@ -2545,7 +2617,7 @@ func (f *Fpdf) SplitLines(txt []byte, w float64) [][]byte {
l := 0
for i < nb {
c := s[i]
l += cw[c]
l += cw[int(c)]
if c == ' ' || c == '\t' || c == '\n' {
sep = i
}
Expand Down Expand Up @@ -2709,14 +2781,14 @@ func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill
ls = l
ns++
}
if int(c) >= len(cw) {
f.err = fmt.Errorf("character outside the supported range: %s", string(c))
return
}
if cw[int(c)] == 0 { //Marker width 0 used for missing symbols
// if int(c) >= len(cw) {
// f.err = fmt.Errorf("character outside the supported range: %s", string(c))
// return
// }
if width, ok := cw[int(c)]; !ok || width == 0 { //Marker width 0 used for missing symbols
l += f.currentFont.Desc.MissingWidth
} else if cw[int(c)] != 65535 { //Marker width 65535 used for zero width symbols
l += cw[int(c)]
} else if width != 65535 { //Marker width 65535 used for zero width symbols
l += width
}
if l > wmax {
// Automatic line break
Expand Down Expand Up @@ -2836,7 +2908,12 @@ func (f *Fpdf) write(h float64, txtStr string, link int, linkStr string) {
if c == ' ' {
sep = i
}
l += float64(cw[int(c)])
// l += float64(cw[int(c)])
if width, ok := cw[int(c)]; ok {
l += float64(width)
} else {
l += float64(f.currentFont.Desc.MissingWidth)
}
if l > wmax {
// Automatic line break
if sep == -1 {
Expand Down Expand Up @@ -4079,7 +4156,11 @@ func (f *Fpdf) putfonts() {
var s fmtBuffer
s.WriteString("[")
for j := 32; j < 256; j++ {
s.printf("%d ", font.Cw[j])
if width, ok := font.Cw[j]; ok {
s.printf("%d ", width)
} else {
s.WriteString("0 ")
}
}
s.WriteString("]")
f.out(s.String())
Expand Down Expand Up @@ -4200,16 +4281,24 @@ func (f *Fpdf) generateCIDFontMap(font *fontDefType, LastRune int) {

// for each character
for cid := startCid; cid < cwLen; cid++ {
if font.Cw[cid] == 0x00 {
runa, used := font.usedRunes[cid]
if cid > 255 && (!used || runa == 0) {
continue
}
width := font.Cw[cid]
if width == 65535 {
width = 0
if !used {
runa = cid
}
if numb, OK := font.usedRunes[cid]; cid > 255 && (!OK || numb == 0) {

width, ok := font.Cw[runa]
if !ok || width == 0x00 {
continue
}
if width == 65535 {
width = 0
}
// if numb, OK := font.usedRunes[cid]; cid > 255 && (!OK || numb == 0) {
// continue
// }

if cid == prevCid+1 {
if width == prevWidth {
Expand Down
2 changes: 1 addition & 1 deletion splittext.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (f *Fpdf) SplitText(txt string, w float64) (lines []string) {
l := 0
for i < nb {
c := s[i]
l += cw[c]
l += cw[int(c)]
if unicode.IsSpace(c) || isChinese(c) {
sep = i
}
Expand Down
Loading