From 0d6a38d49d631da305a6f17704f06f35c36cbddc Mon Sep 17 00:00:00 2001 From: DarkFreedman Date: Fri, 19 Apr 2019 17:00:39 +0300 Subject: Added full support for UTF-8 font. With MIT license. --- fpdf.go | 784 +++++++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 656 insertions(+), 128 deletions(-) (limited to 'fpdf.go') diff --git a/fpdf.go b/fpdf.go index bd4aa4e..1400a85 100644 --- a/fpdf.go +++ b/fpdf.go @@ -912,17 +912,43 @@ func (f *Fpdf) GetTextColor() (int, int, int) { // GetStringWidth returns the length of a string in user units. A font must be // currently selected. func (f *Fpdf) GetStringWidth(s string) float64 { + if f.err != nil { + return 0 + } + w := f.GetStringSymbolWidth(s) + return float64(w) * f.fontSize / 1000 +} + +// GetStringSymbolWidth returns the length of a string in glyf units. A font must be +// currently selected. +func (f *Fpdf) GetStringSymbolWidth(s string) int { if f.err != nil { return 0 } w := 0 - for _, ch := range []byte(s) { - if ch == 0 { - break + if f.isCurrentUTF8 { + 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] + } + } else if f.currentFont.Desc.MissingWidth != 0 { + w += f.currentFont.Desc.MissingWidth + } else { + w += 500 + } + } + } else { + for _, ch := range []byte(s) { + if ch == 0 { + break + } + w += f.currentFont.Cw[ch] } - w += f.currentFont.Cw[ch] } - return float64(w) * f.fontSize / 1000 + return w } // SetLineWidth defines the line width. By default, the value equals 0.2 mm. @@ -1522,30 +1548,128 @@ func (f *Fpdf) ClipEnd() { // definition file to be added. The file will be loaded from the font directory // specified in the call to New() or SetFontLocation(). func (f *Fpdf) AddFont(familyStr, styleStr, fileStr string) { + f.addFont(familyStr, styleStr, fileStr, false) +} + +// AddUTF8Font imports a TrueType font with utf-8 symbols and makes it available. +// It is necessary to generate a font definition file first with the makefont +// utility. It is not necessary to call this function for the core PDF fonts +// (courier, helvetica, times, zapfdingbats). +// +// The JSON definition file (and the font file itself when embedding) must be +// present in the font directory. If it is not found, the error "Could not +// include font definition file" is set. +// +// family specifies the font family. The name can be chosen arbitrarily. If it +// is a standard family name, it will override the corresponding font. This +// string is used to subsequently set the font with the SetFont method. +// +// style specifies the font style. Acceptable values are (case insensitive) the +// empty string for regular style, "B" for bold, "I" for italic, or "BI" or +// "IB" for bold and italic combined. +// +// fileStr specifies the base name with ".json" extension of the font +// definition file to be added. The file will be loaded from the font directory +// specified in the call to New() or SetFontLocation(). +func (f *Fpdf) AddUTF8Font(familyStr, styleStr, fileStr string) { + f.addFont(familyStr, styleStr, fileStr, true) +} + +func (f *Fpdf) addFont(familyStr, styleStr, fileStr string, isUTF8 bool) { if fileStr == "" { - fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".json" + if isUTF8 { + fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".ttf" + } else { + fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".json" + } } + if isUTF8 { + fontKey := getFontKey(familyStr, styleStr) + _, ok := f.fonts[fontKey] + if ok { + return + } + ttfStat, _ := os.Stat(fileStr) + originalSize := ttfStat.Size() + Type := "UTF8" - if f.fontLoader != nil { - reader, err := f.fontLoader.Open(fileStr) - if err == nil { - f.AddFontFromReader(familyStr, styleStr, reader) - if closer, ok := reader.(io.Closer); ok { - closer.Close() + utf8Bytes, _ := ioutil.ReadFile(fileStr) + reader := fileReader{readerPosition: 0, array: utf8Bytes} + utf8File := newUTF8Font(&reader) + + err := utf8File.parseFile() + if err != nil { + fmt.Printf("get metrics Error: %e\n", err) + return + } + + desc := FontDescType{ + Ascent: int(utf8File.Ascent), + Descent: int(utf8File.Descent), + CapHeight: utf8File.CapHeight, + Flags: utf8File.Flags, + FontBBox: utf8File.Bbox, + ItalicAngle: utf8File.ItalicAngle, + StemV: utf8File.StemV, + MissingWidth: round(utf8File.DefaultWidth), + } + + var sbarr map[int]int + if f.aliasNbPagesStr == "" { + sbarr = makeSubsetRange(57) + } else { + sbarr = makeSubsetRange(32) + } + def := fontDefType{ + Tp: Type, + Name: fontKey, + Desc: desc, + Up: int(round(utf8File.UnderlinePosition)), + Ut: round(utf8File.UnderlineThickness), + Cw: utf8File.CharWidths, + usedRunes: sbarr, + File: fileStr, + utf8File: utf8File, + } + def.i, _ = generateFontID(def) + f.fonts[fontKey] = def + f.fontFiles[fontKey] = fontFileType{ + length1: originalSize, + fontType: "UTF8", + } + f.fontFiles[fileStr] = fontFileType{ + fontType: "UTF8", + } + } else { + if f.fontLoader != nil { + reader, err := f.fontLoader.Open(fileStr) + if err == nil { + f.AddFontFromReader(familyStr, styleStr, reader) + if closer, ok := reader.(io.Closer); ok { + closer.Close() + } + return } + } + + fileStr = path.Join(f.fontpath, fileStr) + file, err := os.Open(fileStr) + if err != nil { + f.err = err return } - } + defer file.Close() - fileStr = path.Join(f.fontpath, fileStr) - file, err := os.Open(fileStr) - if err != nil { - f.err = err - return + f.AddFontFromReader(familyStr, styleStr, file) } - defer file.Close() +} - f.AddFontFromReader(familyStr, styleStr, file) +func makeSubsetRange(end int) map[int]int { + answer := make(map[int]int) + for i := 0; i < end; i++ { + answer[i] = 0 + } + return answer } // AddFontFromBytes imports a TrueType, OpenType or Type1 font from static @@ -1564,6 +1688,29 @@ func (f *Fpdf) AddFont(familyStr, styleStr, fileStr string) { // // zFileBytes contain all bytes of Z file. func (f *Fpdf) AddFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes []byte) { + f.addFontFromBytes(familyStr, styleStr, jsonFileBytes, zFileBytes, nil) +} + +// AddUTF8FontFromBytes imports a TrueType font with utf-8 symbols from static +// bytes within the executable and makes it available for use in the generated +// document. +// +// family specifies the font family. The name can be chosen arbitrarily. If it +// is a standard family name, it will override the corresponding font. This +// string is used to subsequently set the font with the SetFont method. +// +// style specifies the font style. Acceptable values are (case insensitive) the +// empty string for regular style, "B" for bold, "I" for italic, or "BI" or +// "IB" for bold and italic combined. +// +// jsonFileBytes contain all bytes of JSON file. +// +// zFileBytes contain all bytes of Z file. +func (f *Fpdf) AddUTF8FontFromBytes(familyStr, styleStr string, utf8Bytes []byte) { + f.addFontFromBytes(familyStr, styleStr, nil, nil, utf8Bytes) +} + +func (f *Fpdf) addFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes, utf8Bytes []byte) { if f.err != nil { return } @@ -1577,61 +1724,108 @@ func (f *Fpdf) AddFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFile return } - // load font definitions - var info fontDefType - err := json.Unmarshal(jsonFileBytes, &info) + if utf8Bytes != nil { - if err != nil { - f.err = err - } + if styleStr == "IB" { + styleStr = "BI" + } - if f.err != nil { - return - } + Type := "UTF8" + reader := fileReader{readerPosition: 0, array: utf8Bytes} - if info.i, err = generateFontID(info); err != nil { - f.err = err - return - } + utf8File := newUTF8Font(&reader) - // search existing encodings - if len(info.Diff) > 0 { - n := -1 + err := utf8File.parseFile() + if err != nil { + fmt.Printf("get metrics Error: %e\n", err) + return + } + desc := FontDescType{ + Ascent: int(utf8File.Ascent), + Descent: int(utf8File.Descent), + CapHeight: utf8File.CapHeight, + Flags: utf8File.Flags, + FontBBox: utf8File.Bbox, + ItalicAngle: utf8File.ItalicAngle, + StemV: utf8File.StemV, + MissingWidth: round(utf8File.DefaultWidth), + } - for j, str := range f.diffs { - if str == info.Diff { - n = j + 1 - break - } + var sbarr map[int]int + if f.aliasNbPagesStr == "" { + sbarr = makeSubsetRange(57) + } else { + sbarr = makeSubsetRange(32) } + def := fontDefType{ + Tp: Type, + Name: fontkey, + Desc: desc, + Up: int(round(utf8File.UnderlinePosition)), + Ut: round(utf8File.UnderlineThickness), + Cw: utf8File.CharWidths, + utf8File: utf8File, + usedRunes: sbarr, + } + def.i, _ = generateFontID(def) + f.fonts[fontkey] = def + } else { + // load font definitions + var info fontDefType + err := json.Unmarshal(jsonFileBytes, &info) - if n < 0 { - f.diffs = append(f.diffs, info.Diff) - n = len(f.diffs) + if err != nil { + f.err = err } - info.DiffN = n - } + if f.err != nil { + return + } - // embed font - if len(info.File) > 0 { - if info.Tp == "TrueType" { - f.fontFiles[info.File] = fontFileType{ - length1: int64(info.OriginalSize), - embedded: true, - content: zFileBytes, + if info.i, err = generateFontID(info); err != nil { + f.err = err + return + } + + // search existing encodings + if len(info.Diff) > 0 { + n := -1 + + for j, str := range f.diffs { + if str == info.Diff { + n = j + 1 + break + } } - } else { - f.fontFiles[info.File] = fontFileType{ - length1: int64(info.Size1), - length2: int64(info.Size2), - embedded: true, - content: zFileBytes, + + if n < 0 { + f.diffs = append(f.diffs, info.Diff) + n = len(f.diffs) + } + + info.DiffN = n + } + + // embed font + if len(info.File) > 0 { + if info.Tp == "TrueType" { + f.fontFiles[info.File] = fontFileType{ + length1: int64(info.OriginalSize), + embedded: true, + content: zFileBytes, + } + } else { + f.fontFiles[info.File] = fontFileType{ + length1: int64(info.Size1), + length2: int64(info.Size2), + embedded: true, + content: zFileBytes, + } } } - } - f.fonts[fontkey] = info + f.fonts[fontkey] = info + } } // getFontKey is used by AddFontFromReader and GetFontDesc @@ -1756,8 +1950,8 @@ func (f *Fpdf) SetFont(familyStr, styleStr string, size float64) { } // Test if font is already loaded - fontkey := familyStr + styleStr - _, ok = f.fonts[fontkey] + fontKey := familyStr + styleStr + _, ok = f.fonts[fontKey] if !ok { // Test if one of the core fonts if familyStr == "arial" { @@ -1771,8 +1965,8 @@ func (f *Fpdf) SetFont(familyStr, styleStr string, size float64) { if familyStr == "zapfdingbats" { styleStr = "" } - fontkey = familyStr + styleStr - _, ok = f.fonts[fontkey] + fontKey = familyStr + styleStr + _, ok = f.fonts[fontKey] if !ok { rdr := f.coreFontReader(familyStr, styleStr) if f.err == nil { @@ -1792,7 +1986,12 @@ func (f *Fpdf) SetFont(familyStr, styleStr string, size float64) { f.fontStyle = styleStr f.fontSizePt = size f.fontSize = size / f.k - f.currentFont = f.fonts[fontkey] + f.currentFont = f.fonts[fontKey] + if f.currentFont.Tp == "UTF8" { + f.isCurrentUTF8 = true + } else { + f.isCurrentUTF8 = false + } if f.page > 0 { f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt) } @@ -1890,7 +2089,16 @@ func (f *Fpdf) Bookmark(txtStr string, level int, y float64) { // precisely on the page, but it is usually easier to use Cell(), MultiCell() // or Write() which are the standard methods to print text. func (f *Fpdf) Text(x, y float64, txtStr string) { - s := sprintf("BT %.2f %.2f Td (%s) Tj ET", x*f.k, (f.h-y)*f.k, f.escape(txtStr)) + var txt2 string + if f.isCurrentUTF8 { + txt2 = f.escape(utf8toutf16(txtStr, false)) + for _, uni := range []rune(txtStr) { + f.currentFont.usedRunes[int(uni)] = int(uni) + } + } else { + txt2 = f.escape(txtStr) + } + s := sprintf("BT %.2f %.2f Td (%s) Tj ET", x*f.k, (f.h-y)*f.k, txt2) if f.underline && txtStr != "" { s += " " + f.dounderline(x, y, txtStr) } @@ -2069,14 +2277,46 @@ func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int, if f.colorFlag { s.printf("q %s ", f.color.text.str) } - txt2 := strings.Replace(txtStr, "\\", "\\\\", -1) - txt2 = strings.Replace(txt2, "(", "\\(", -1) - txt2 = strings.Replace(txt2, ")", "\\)", -1) - // if strings.Contains(txt2, "end of excerpt") { - // dbg("f.h %.2f, f.y %.2f, h %.2f, f.fontSize %.2f, k %.2f", f.h, f.y, h, f.fontSize, k) - // } - s.printf("BT %.2f %.2f Td (%s) Tj ET", (f.x+dx)*k, (f.h-(f.y+dy+.5*h+.3*f.fontSize))*k, txt2) - //BT %.2F %.2F Td (%s) Tj ET',($this->x+$dx)*$k,($this->h-($this->y+.5*$h+.3*$this->FontSize))*$k,$txt2); + //If multibyte, Tw has no effect - do word spacing using an adjustment before each space + if (f.ws != 0 || alignStr == "J") && f.isCurrentUTF8 { // && f.ws != 0 + 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)) + 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)) + ")" + s.printf("%s ", tx) + if (i + 1) < numt { + s.printf("%.3f(%s) ", -shift, space) + } + } + s.printf("] TJ ET") + } else { + var txt2 string + if f.isCurrentUTF8 { + txt2 = f.escape(utf8toutf16(txtStr, false)) + for _, uni := range []rune(txtStr) { + f.currentFont.usedRunes[int(uni)] = int(uni) + } + } else { + + txt2 = strings.Replace(txtStr, "\\", "\\\\", -1) + txt2 = strings.Replace(txt2, "(", "\\(", -1) + txt2 = strings.Replace(txt2, ")", "\\)", -1) + } + bt := (f.x + dx) * k + td := (f.h - (f.y + dy + .5*h + .3*f.fontSize)) * k + s.printf("BT %.2f %.2f Td (%s)Tj ET", bt, td, txt2) + //BT %.2F %.2F Td (%s) Tj ET',(f.x+dx)*k,(f.h-(f.y+.5*h+.3*f.FontSize))*k,txt2); + } + if f.underline { s.printf(" %s", f.dounderline(f.x+dx, f.y+dy+.5*h+.3*f.fontSize, txtStr)) } @@ -2127,7 +2367,7 @@ func (f *Fpdf) Cellf(w, h float64, fmtStr string, args ...interface{}) { func (f *Fpdf) SplitLines(txt []byte, w float64) [][]byte { // Function contributed by Bruno Michel lines := [][]byte{} - cw := &f.currentFont.Cw + cw := f.currentFont.Cw wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize)) s := bytes.Replace(txt, []byte("\r"), []byte{}, -1) nb := len(s) @@ -2187,17 +2427,26 @@ func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill if alignStr == "" { alignStr = "J" } - cw := &f.currentFont.Cw + cw := f.currentFont.Cw if w == 0 { w = f.w - f.rMargin - f.x } wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize)) s := strings.Replace(txtStr, "\r", "", -1) - nb := len(s) - // if nb > 0 && s[nb-1:nb] == "\n" { - if nb > 0 && []byte(s)[nb-1] == '\n' { - nb-- - s = s[0:nb] + + var nb int + if f.isCurrentUTF8 { + nb = len([]rune(s)) + for nb > 0 && []rune(s)[nb-1] == '\n' { + nb-- + s = string([]rune(s)[0:nb]) + } + } else { + nb = len(s) + if nb > 0 && []byte(s)[nb-1] == '\n' { + nb-- + s = s[0:nb] + } } // dbg("[%s]\n", s) var b, b2 string @@ -2231,14 +2480,28 @@ func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill nl := 1 for i < nb { // Get next character - c := []byte(s)[i] + var c rune + if f.isCurrentUTF8 { + c = []rune(s)[i] + } else { + c = rune([]byte(s)[i]) + } if c == '\n' { // Explicit line break if f.ws > 0 { f.ws = 0 f.out("0 Tw") } - f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "") + + if f.isCurrentUTF8 { + newAlignStr := alignStr + if newAlignStr == "J" { + newAlignStr = "L" + } + f.CellFormat(w, h, string([]rune(s)[j:i]), b, 2, newAlignStr, fill, 0, "") + } else { + f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "") + } i++ sep = -1 j = i @@ -2255,7 +2518,11 @@ func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill ls = l ns++ } - l += cw[c] + if cw[int(c)] == 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)] + } if l > wmax { // Automatic line break if sep == -1 { @@ -2266,7 +2533,11 @@ func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill f.ws = 0 f.out("0 Tw") } - f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "") + if f.isCurrentUTF8 { + f.CellFormat(w, h, string([]rune(s)[j:i]), b, 2, alignStr, fill, 0, "") + } else { + f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "") + } } else { if alignStr == "J" { if ns > 1 { @@ -2276,7 +2547,11 @@ func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill } f.outf("%.3f Tw", f.ws*f.k) } - f.CellFormat(w, h, s[j:sep], b, 2, alignStr, fill, 0, "") + if f.isCurrentUTF8 { + f.CellFormat(w, h, string([]rune(s)[j:sep]), b, 2, alignStr, fill, 0, "") + } else { + f.CellFormat(w, h, s[j:sep], b, 2, alignStr, fill, 0, "") + } i = sep + 1 } sep = -1 @@ -2299,18 +2574,34 @@ func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill if len(borderStr) > 0 && strings.Contains(borderStr, "B") { b += "B" } - f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "") + if f.isCurrentUTF8 { + if alignStr == "J" { + alignStr = "" + } + f.CellFormat(w, h, string([]rune(s)[j:i]), b, 2, alignStr, fill, 0, "") + } else { + f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "") + } f.x = f.lMargin } // write outputs text in flowing mode func (f *Fpdf) write(h float64, txtStr string, link int, linkStr string) { // dbg("Write") - cw := &f.currentFont.Cw + cw := f.currentFont.Cw w := f.w - f.rMargin - f.x wmax := (w - 2*f.cMargin) * 1000 / f.fontSize s := strings.Replace(txtStr, "\r", "", -1) - nb := len(s) + var nb int + if f.isCurrentUTF8 { + nb = len([]rune(s)) + if nb == 1 && s == " " { + f.x += f.GetStringWidth(s) + return + } + } else { + nb = len(s) + } sep := -1 i := 0 j := 0 @@ -2318,10 +2609,19 @@ func (f *Fpdf) write(h float64, txtStr string, link int, linkStr string) { nl := 1 for i < nb { // Get next character - c := []byte(s)[i] + var c rune + if f.isCurrentUTF8 { + c = []rune(s)[i] + } else { + c = rune([]byte(s)[i]) + } if c == '\n' { // Explicit line break - f.CellFormat(w, h, s[j:i], "", 2, "", false, link, linkStr) + if f.isCurrentUTF8 { + f.CellFormat(w, h, string([]rune(s)[j:i]), "", 2, "", false, link, linkStr) + } else { + f.CellFormat(w, h, s[j:i], "", 2, "", false, link, linkStr) + } i++ sep = -1 j = i @@ -2337,7 +2637,7 @@ func (f *Fpdf) write(h float64, txtStr string, link int, linkStr string) { if c == ' ' { sep = i } - l += float64(cw[c]) + l += float64(cw[int(c)]) if l > wmax { // Automatic line break if sep == -1 { @@ -2354,9 +2654,17 @@ func (f *Fpdf) write(h float64, txtStr string, link int, linkStr string) { if i == j { i++ } - f.CellFormat(w, h, s[j:i], "", 2, "", false, link, linkStr) + if f.isCurrentUTF8 { + f.CellFormat(w, h, string([]rune(s)[j:i]), "", 2, "", false, link, linkStr) + } else { + f.CellFormat(w, h, s[j:i], "", 2, "", false, link, linkStr) + } } else { - f.CellFormat(w, h, s[j:sep], "", 2, "", false, link, linkStr) + if f.isCurrentUTF8 { + f.CellFormat(w, h, string([]rune(s)[j:sep]), "", 2, "", false, link, linkStr) + } else { + f.CellFormat(w, h, s[j:sep], "", 2, "", false, link, linkStr) + } i = sep + 1 } sep = -1 @@ -2374,7 +2682,11 @@ func (f *Fpdf) write(h float64, txtStr string, link int, linkStr string) { } // Last chunk if i != j { - f.CellFormat(l/1000*f.fontSize, h, s[j:], "", 0, "", false, link, linkStr) + if f.isCurrentUTF8 { + f.CellFormat(l/1000*f.fontSize, h, string([]rune(s)[j:]), "", 0, "", false, link, linkStr) + } else { + f.CellFormat(l/1000*f.fontSize, h, s[j:], "", 0, "", false, link, linkStr) + } } } @@ -3239,6 +3551,9 @@ func (f *Fpdf) putpages() { nb := f.page if len(f.aliasNbPagesStr) > 0 { // Replace number of pages + alias := utf8toutf16(f.aliasNbPagesStr, false) + r := utf8toutf16(sprintf("%d", nb), false) + f.RegisterAlias(alias, r) f.RegisterAlias(f.aliasNbPagesStr, sprintf("%d", nb)) } f.replaceAliases() @@ -3345,42 +3660,41 @@ func (f *Fpdf) putfonts() { } for _, file = range fileList { info = f.fontFiles[file] - // Font file embedding - f.newobj() - info.n = f.n - f.fontFiles[file] = info + if info.fontType != "UTF8" { + f.newobj() + info.n = f.n + f.fontFiles[file] = info - var font []byte + var font []byte - if info.embedded { - font = info.content - } else { - var err error - font, err = f.loadFontFile(file) - if err != nil { - f.err = err - return + if info.embedded { + font = info.content + } else { + var err error + font, err = f.loadFontFile(file) + if err != nil { + f.err = err + return + } } + compressed := file[len(file)-2:] == ".z" + if !compressed && info.length2 > 0 { + buf := font[6:info.length1] + buf = append(buf, font[6+info.length1+6:info.length2]...) + font = buf + } + f.outf("< 0 { + f.outf("/Length2 %d /Length3 0", info.length2) + } + f.out(">>") + f.putstream(font) + f.out("endobj") } - - // dbg("font file [%s], ext [%s]", file, file[len(file)-2:]) - compressed := file[len(file)-2:] == ".z" - if !compressed && info.length2 > 0 { - buf := font[6:info.length1] - buf = append(buf, font[6+info.length1+6:info.length2]...) - font = buf - } - f.outf("< 0 { - f.outf("/Length2 %d /Length3 0", info.length2) - } - f.out(">>") - f.putstream(font) - f.out("endobj") } } { @@ -3460,6 +3774,81 @@ func (f *Fpdf) putfonts() { s.printf("/FontFile%s %d 0 R>>", suffix, f.fontFiles[font.File].n) f.out(s.String()) f.out("endobj") + case "UTF8": + fontName := "utf8" + font.Name + usedRunes := font.usedRunes + delete(usedRunes, 0) + utf8FontStream := font.utf8File.GenerateСutFont(usedRunes) + utf8FontSize := len(utf8FontStream) + compressedFontStream := sliceCompress(utf8FontStream) + CodeSignDictionary := font.utf8File.CodeSymbolDictionary + delete(CodeSignDictionary, 0) + + f.newobj() + f.out(fmt.Sprintf("<>\n"+"endobj", fontName, f.n+1, f.n+2)) + + f.newobj() + f.out("<>") + f.out("endobj") + + f.newobj() + f.out("<>") + f.putstream([]byte(toUnicode)) + f.out("endobj") + + // CIDInfo + f.newobj() + f.out("<>") + f.out("endobj") + + // Font descriptor + f.newobj() + var s fmtBuffer + s.printf("<>") + f.out(s.String()) + f.out("endobj") + + // Embed CIDToGIDMap + cidToGidMap := make([]byte, 256*256*2) + + for cc, glyph := range CodeSignDictionary { + cidToGidMap[cc*2] = byte(glyph >> 8) + cidToGidMap[cc*2+1] = byte(glyph & 0xFF) + } + + cidToGidMap = sliceCompress(cidToGidMap) + f.newobj() + f.out("<>") + f.putstream(cidToGidMap) + f.out("endobj") + + //Font file + f.newobj() + f.out("<>") + f.putstream(compressedFontStream) + f.out("endobj") default: f.err = fmt.Errorf("unsupported font type: %s", tp) return @@ -3469,6 +3858,145 @@ func (f *Fpdf) putfonts() { return } +func (f *Fpdf) generateCIDFontMap(font *fontDefType, LastRune int) { + rangeID := 0 + cidArray := make(map[int]*untypedKeyMap) + cidArrayKeys := make([]int, 0) + prevCid := -2 + prevWidth := -1 + interval := false + startCid := 1 + cwLen := LastRune + 1 + + // for each character + for cid := startCid; cid < cwLen; cid++ { + if font.Cw[cid] == 0x00 { + continue + } + width := font.Cw[cid] + if width == 65535 { + width = 0 + } + if numb, OK := font.usedRunes[cid]; cid > 255 && (!OK || numb == 0) { + continue + } + + if cid == prevCid+1 { + if width == prevWidth { + + if width == cidArray[rangeID].get(0) { + cidArray[rangeID].put(nil, width) + } else { + cidArray[rangeID].pop() + rangeID = prevCid + r := untypedKeyMap{ + valueSet: make([]int, 0), + keySet: make([]interface{}, 0), + } + cidArray[rangeID] = &r + cidArrayKeys = append(cidArrayKeys, rangeID) + cidArray[rangeID].put(nil, prevWidth) + cidArray[rangeID].put(nil, width) + } + interval = true + cidArray[rangeID].put("interval", 1) + ui := 0 + ui = ui + 1 + } else { + if interval { + // new range + rangeID = cid + r := untypedKeyMap{ + valueSet: make([]int, 0), + keySet: make([]interface{}, 0), + } + cidArray[rangeID] = &r + cidArrayKeys = append(cidArrayKeys, rangeID) + cidArray[rangeID].put(nil, width) + } else { + cidArray[rangeID].put(nil, width) + } + interval = false + } + } else { + rangeID = cid + r := untypedKeyMap{ + valueSet: make([]int, 0), + keySet: make([]interface{}, 0), + } + cidArray[rangeID] = &r + cidArrayKeys = append(cidArrayKeys, rangeID) + cidArray[rangeID].put(nil, width) + interval = false + } + prevCid = cid + prevWidth = width + + } + previousKey := -1 + nextKey := -1 + isInterval := false + for g := 0; g < len(cidArrayKeys); { + key := cidArrayKeys[g] + ws := *cidArray[key] + cws := len(ws.keySet) + if (key == nextKey) && (!isInterval) && (ws.getIndex("interval") < 0 || cws < 4) { + if cidArray[key].getIndex("interval") >= 0 { + cidArray[key].delete("interval") + } + cidArray[previousKey] = arrayMerge(cidArray[previousKey], cidArray[key]) + cidArrayKeys = remove(cidArrayKeys, key) + } else { + g++ + previousKey = key + } + nextKey = key + cws + ui := ws.getIndex("interval") + ui = ui + 1 + if ws.getIndex("interval") >= 0 { + if cws > 3 { + isInterval = true + } else { + isInterval = false + } + cidArray[key].delete("interval") + nextKey-- + } else { + isInterval = false + } + } + var w fmtBuffer + for _, k := range cidArrayKeys { + ws := cidArray[k] + if len(arrayCountValues(ws.valueSet)) == 1 { + w.printf(" %d %d %d", k, k+len(ws.valueSet)-1, ws.get(0)) + } else { + w.printf(" %d [ %s ]\n", k, implode(" ", ws.valueSet)) + } + } + f.out("/W [" + w.String() + " ]") +} + +func implode(sep string, arr []int) string { + var s fmtBuffer + for i := 0; i < len(arr)-1; i++ { + s.printf("%v", arr[i]) + s.printf(sep) + } + if len(arr) > 0 { + s.printf("%v", arr[len(arr)-1]) + } + return s.String() +} + +func arrayCountValues(mp []int) map[int]int { + answer := make(map[int]int) + for _, v := range mp { + answer[v] = answer[v] + 1 + } + return answer +} + func (f *Fpdf) loadFontFile(name string) ([]byte, error) { if f.fontLoader != nil { reader, err := f.fontLoader.Open(name) -- cgit v1.2.1-24-ge1ad