summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKurt <kurt.w.jung@gmail.com>2019-05-06 07:38:16 -0400
committerKurt <kurt.w.jung@gmail.com>2019-05-06 07:38:16 -0400
commit28729d396b31110213024b82d8655a69e855662d (patch)
tree8463f34e022c2143951e3e868a1cadd453054579
parent50996f28baf0361e2171a1f0cebdcc7106fef2ac (diff)
parent4ca1a43173413971a6bb92c78be31a71b29b416b (diff)
Merge ArtemKor' UTF-8 pull request
-rw-r--r--README.md127
-rw-r--r--def.go35
-rw-r--r--font.go2
-rw-r--r--font/DejaVuSansCondensed-Bold.ttfbin0 -> 665028 bytes
-rw-r--r--font/DejaVuSansCondensed-BoldOblique.ttfbin0 -> 611836 bytes
-rw-r--r--font/DejaVuSansCondensed-Oblique.ttfbin0 -> 599292 bytes
-rw-r--r--font/DejaVuSansCondensed.json1
-rw-r--r--font/DejaVuSansCondensed.ttfbin0 -> 680264 bytes
-rw-r--r--fpdf.go823
-rw-r--r--fpdf_test.go33
-rw-r--r--pdf/reference/Fpdf_AddUTF8Font.pdfbin0 -> 67549 bytes
-rw-r--r--text/utf-8test.txt38
-rw-r--r--text/utf-8test2.txt1
-rw-r--r--utf8fontfile.go1142
-rw-r--r--util.go142
15 files changed, 2137 insertions, 207 deletions
diff --git a/README.md b/README.md
index 82f5bd5..1135f0c 100644
--- a/README.md
+++ b/README.md
@@ -12,34 +12,33 @@ support for text, drawing and images.
## Features
- - Choice of measurement unit, page format and margins
- - Page header and footer management
- - Automatic page breaks, line breaks, and text justification
- - Inclusion of JPEG, PNG, GIF, TIFF and basic path-only SVG images
- - Colors, gradients and alpha channel transparency
- - Outline bookmarks
- - Internal and external links
- - TrueType, Type1 and encoding support
- - Page compression
- - Lines, Bézier curves, arcs, and ellipses
- - Rotation, scaling, skewing, translation, and mirroring
- - Clipping
- - Document protection
- - Layers
- - Templates
- - Barcodes
- - Charting facility
-
-gofpdf has no dependencies other than the Go standard library. All tests
-pass on Linux, Mac and Windows platforms.
-
-Like FPDF version 1.7, from which gofpdf is derived, this package does
-not yet support UTF-8 fonts. In particular, languages that require more
-than one code page such as Chinese, Japanese, and Arabic are not
-currently supported. This is explained in
-[issue 109](https://github.com/jung-kurt/gofpdf/issues/109). However,
-support is provided to automatically translate UTF-8 runes to code page
-encodings for languages that have fewer than 256 glyphs.
+* Choice of measurement unit, page format and margins
+* Page header and footer management
+* Automatic page breaks, line breaks, and text justification
+* Inclusion of JPEG, PNG, GIF, TIFF and basic path-only SVG images
+* Colors, gradients and alpha channel transparency
+* Outline bookmarks
+* Internal and external links
+* TrueType, Type1 and encoding support
+* Page compression
+* Lines, Bézier curves, arcs, and ellipses
+* Rotation, scaling, skewing, translation, and mirroring
+* Clipping
+* Document protection
+* Layers
+* Templates
+* Barcodes
+* Charting facility
+* UTF-8 support
+
+gofpdf has no dependencies other than the Go standard library. All tests pass
+on Linux, Mac and Windows platforms.
+
+gofpdf supports UTF-8 fonts and "right to left" languages.
+
+Also, support is provided to automatically translate
+UTF-8 runes to code page encodings for languages that have fewer than 256
+glyphs.
## Installation
@@ -151,7 +150,18 @@ run `go build`. This will produce a standalone executable named
makefont. Select the appropriate encoding file from the font
subdirectory and run the command as in the following example.
-``` shell
+You should use AddUTF8Font or AddUTF8FontFromBytes to add UTF-8 TTF font.
+RTL() and LTR() methods switch between "right to left" and "left to right" mode.
+
+In order to use a different non-UTF-8 TrueType or Type1 font, you will need to generate a
+font definition file and, if the font will be embedded into PDFs, a compressed
+version of the font file. This is done by calling the MakeFont function or
+using the included makefont command line utility. To create the utility, cd
+into the makefont subdirectory and run "go build". This will produce a
+standalone executable named makefont. Select the appropriate encoding file from
+the font subdirectory and run the command as in the following example.
+
+```
./makefont --embed --enc=../font/cp1252.map --dst=../font ../font/calligra.ttf
```
@@ -211,38 +221,33 @@ FPDF scripts by Andreas Würmser. Support for outline bookmarks is
adapted from Olivier Plathey by Manuel Cornes. Layer support is adapted
from Olivier Plathey. Support for transformations is adapted from the
FPDF transformation script by Moritz Wagner and Andreas Würmser. PDF
-protection is adapted from the work of Klemen Vodopivec for the FPDF
-product. Lawrence Kesteloot provided code to allow an image’s extent to
-be determined prior to placement. Support for vertical alignment within
-a cell was provided by Stefan Schroeder. Ivan Daniluk generalized the
-font and image loading code to use the Reader interface while
-maintaining backward compatibility. Anthony Starks provided code for the
-Polygon function. Robert Lillack provided the Beziergon function and
-corrected some naming issues with the internal curve function. Claudio
-Felber provided implementations for dashed line drawing and generalized
-font loading. Stani Michiels provided support for multi-segment path
-drawing with smooth line joins, line join styles, enhanced fill modes,
-and has helped greatly with package presentation and tests. Templating
-is adapted by Marcus Downing from the FPDF\_Tpl library created by Jan
-Slabon and Setasign. Jelmer Snoeck contributed packages that generate a
-variety of barcodes and help with registering images on the web. Jelmer
-Snoek and Guillermo Pascual augmented the basic HTML functionality with
-aligned text. Kent Quirk implemented backwards-compatible support for
-reading DPI from images that support it, and for setting DPI manually
-and then having it properly taken into account when calculating image
-size. Paulo Coutinho provided support for static embedded fonts. Dan
-Meyers added support for embedded JavaScript. David Fish added a generic
-alias-replacement function to enable, among other things, table of
-contents functionality. Andy Bakun identified and corrected a problem in
-which the internal catalogs were not sorted stably. Paul Montag added
-encoding and decoding functionality for templates, including images that
-are embedded in templates; this allows templates to be stored
-independently of gofpdf. Paul also added support for page boxes used in
-printing PDF documents. Wojciech Matusiak added supported for word
-spacing.
+protection is adapted from the work of Klemen Vodopivec for the FPDF product.
+Lawrence Kesteloot provided code to allow an image's extent to be determined
+prior to placement. Support for vertical alignment within a cell was provided
+by Stefan Schroeder. Ivan Daniluk generalized the font and image loading code
+to use the Reader interface while maintaining backward compatibility. Anthony
+Starks provided code for the Polygon function. Robert Lillack provided the
+Beziergon function and corrected some naming issues with the internal curve
+function. Claudio Felber provided implementations for dashed line drawing and
+generalized font loading. Stani Michiels provided support for multi-segment
+path drawing with smooth line joins, line join styles, enhanced fill modes, and
+has helped greatly with package presentation and tests. Templating is adapted
+by Marcus Downing from the FPDF\_Tpl library created by Jan Slabon and Setasign.
+Jelmer Snoeck contributed packages that generate a variety of barcodes and help
+with registering images on the web. Jelmer Snoek and Guillermo Pascual
+augmented the basic HTML functionality with aligned text. Kent Quirk
+implemented backwards-compatible support for reading DPI from images that
+support it, and for setting DPI manually and then having it properly taken into
+account when calculating image size. Paulo Coutinho provided support for static
+embedded fonts. Dan Meyers added support for embedded JavaScript. David Fish
+added a generic alias-replacement function to enable, among other things, table
+of contents functionality. Andy Bakun identified and corrected a problem in
+which the internal catalogs were not sorted stably. Paul Montag added encoding
+and decoding functionality for templates, including images that are embedded in
+templates; this allows templates to be stored independently of gofpdf. Paul
+also added support for page boxes used in printing PDF documents. Wojciech
+Matusiak added supported for word spacing. Artem Korotkiy added support of UTF-8 fonts.
## Roadmap
- - Handle UTF-8 source text natively. Until then, automatic translation
- of UTF-8 runes to code page bytes is provided.
- - Improve test coverage as reported by the coverage tool.
+* Improve test coverage as reported by the coverage tool.
diff --git a/def.go b/def.go
index ee0d3a6..ab4e81d 100644
--- a/def.go
+++ b/def.go
@@ -266,6 +266,7 @@ type fontFileType struct {
n int
embedded bool
content []byte
+ fontType string
}
type linkType struct {
@@ -496,6 +497,8 @@ type PageBox struct {
// Fpdf is the principal structure for creating a single PDF document
type Fpdf struct {
+ isCurrentUTF8 bool // is current font used in utf-8 mode
+ isRTL bool // is is right to left mode enabled
page int // current page number
n int // current object number
offsets []int // array of object offsets
@@ -683,20 +686,22 @@ type FontDescType struct {
}
type fontDefType struct {
- Tp string // "Core", "TrueType", ...
- Name string // "Courier-Bold", ...
- Desc FontDescType // Font descriptor
- Up int // Underline position
- Ut int // Underline thickness
- Cw [256]int // Character width by ordinal
- Enc string // "cp1252", ...
- Diff string // Differences from reference encoding
- File string // "Redressed.z"
- Size1, Size2 int // Type1 values
- OriginalSize int // Size of uncompressed font file
- N int // Set by font loader
- DiffN int // Position of diff in app array, set by font loader
- i string // 1-based position in font list, set by font loader, not this program
+ Tp string // "Core", "TrueType", ...
+ Name string // "Courier-Bold", ...
+ Desc FontDescType // Font descriptor
+ Up int // Underline position
+ Ut int // Underline thickness
+ Cw []int // Character width by ordinal
+ Enc string // "cp1252", ...
+ Diff string // Differences from reference encoding
+ File string // "Redressed.z"
+ Size1, Size2 int // Type1 values
+ OriginalSize int // Size of uncompressed font file
+ N int // Set by font loader
+ DiffN int // Position of diff in app array, set by font loader
+ 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
}
// generateFontID generates a font Id from the font definition
@@ -716,7 +721,7 @@ type fontInfoType struct {
IsFixedPitch bool
UnderlineThickness int
UnderlinePosition int
- Widths [256]int
+ Widths []int
Size1, Size2 uint32
Desc FontDescType
}
diff --git a/font.go b/font.go
index 7443a30..29417bb 100644
--- a/font.go
+++ b/font.go
@@ -83,6 +83,7 @@ func loadMap(encodingFileStr string) (encList encListType, err error) {
// getInfoFromTrueType returns information from a TrueType font
func getInfoFromTrueType(fileStr string, msgWriter io.Writer, embed bool, encList encListType) (info fontInfoType, err error) {
+ info.Widths = make([]int, 256)
var ttf TtfType
ttf, err = TtfParse(fileStr)
if err != nil {
@@ -168,6 +169,7 @@ func segmentRead(r io.Reader) (s segmentType, err error) {
// getInfoFromType1 return information from a Type1 font
func getInfoFromType1(fileStr string, msgWriter io.Writer, embed bool, encList encListType) (info fontInfoType, err error) {
+ info.Widths = make([]int, 256)
if embed {
var f *os.File
f, err = os.Open(fileStr)
diff --git a/font/DejaVuSansCondensed-Bold.ttf b/font/DejaVuSansCondensed-Bold.ttf
new file mode 100644
index 0000000..22987c6
--- /dev/null
+++ b/font/DejaVuSansCondensed-Bold.ttf
Binary files differ
diff --git a/font/DejaVuSansCondensed-BoldOblique.ttf b/font/DejaVuSansCondensed-BoldOblique.ttf
new file mode 100644
index 0000000..f5fa0ca
--- /dev/null
+++ b/font/DejaVuSansCondensed-BoldOblique.ttf
Binary files differ
diff --git a/font/DejaVuSansCondensed-Oblique.ttf b/font/DejaVuSansCondensed-Oblique.ttf
new file mode 100644
index 0000000..7fde907
--- /dev/null
+++ b/font/DejaVuSansCondensed-Oblique.ttf
Binary files differ
diff --git a/font/DejaVuSansCondensed.json b/font/DejaVuSansCondensed.json
new file mode 100644
index 0000000..f833dff
--- /dev/null
+++ b/font/DejaVuSansCondensed.json
@@ -0,0 +1 @@
+{"Tp":"TrueType","Name":"DejaVuSansCondensed","Desc":{"Ascent":760,"Descent":-240,"CapHeight":760,"Flags":32,"FontBBox":{"Xmin":-918,"Ymin":-463,"Xmax":1614,"Ymax":1232},"ItalicAngle":0,"StemV":70,"MissingWidth":540},"Up":-63,"Ut":44,"Cw":[540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,540,286,360,414,754,572,855,702,247,351,351,450,754,286,325,286,303,572,572,572,572,572,572,572,572,572,572,303,303,754,754,754,478,900,615,617,628,693,568,518,697,677,265,265,590,501,776,673,708,542,708,625,571,549,659,615,890,616,549,616,351,303,351,754,450,450,551,571,495,571,554,316,571,570,250,250,521,250,876,570,550,571,571,370,469,353,570,532,736,532,532,472,572,303,572,754,540,708,549,286,473,466,900,450,450,572,1208,984,360,940,639,708,677,563,286,286,466,466,531,450,900,540,900,812,360,809,543,586,588,286,548,532,265,572,549,303,450,568,900,628,550,754,325,900,265,450,754,265,250,473,572,572,286,554,936,494,550,250,571,469,250,615,617,617,549,703,568,969,577,673,673,639,677,776,677,708,677,542,628,549,548,774,616,699,617,962,984,749,794,617,628,971,625,551,555,530,473,622,554,811,479,584,584,543,575,679,588,550,588,571,495,524,532,769,532,612,532,823,848,636,710,530,494,757,541],"Enc":"cp1251","Diff":"128 /afii10051 /afii10052 131 /afii10100 136 /Euro 138 /afii10058 140 /afii10059 /afii10061 /afii10060 /afii10145 /afii10099 152 /.notdef 154 /afii10106 156 /afii10107 /afii10109 /afii10108 /afii10193 161 /afii10062 /afii10110 /afii10057 165 /afii10050 168 /afii10023 170 /afii10053 175 /afii10056 178 /afii10055 /afii10103 /afii10098 184 /afii10071 /afii61352 /afii10101 188 /afii10105 /afii10054 /afii10102 /afii10104 /afii10017 /afii10018 /afii10019 /afii10020 /afii10021 /afii10022 /afii10024 /afii10025 /afii10026 /afii10027 /afii10028 /afii10029 /afii10030 /afii10031 /afii10032 /afii10033 /afii10034 /afii10035 /afii10036 /afii10037 /afii10038 /afii10039 /afii10040 /afii10041 /afii10042 /afii10043 /afii10044 /afii10045 /afii10046 /afii10047 /afii10048 /afii10049 /afii10065 /afii10066 /afii10067 /afii10068 /afii10069 /afii10070 /afii10072 /afii10073 /afii10074 /afii10075 /afii10076 /afii10077 /afii10078 /afii10079 /afii10080 /afii10081 /afii10082 /afii10083 /afii10084 /afii10085 /afii10086 /afii10087 /afii10088 /afii10089 /afii10090 /afii10091 /afii10092 /afii10093 /afii10094 /afii10095 /afii10096 /afii10097","File":"","Size1":0,"Size2":0,"OriginalSize":0,"N":0,"DiffN":0} \ No newline at end of file
diff --git a/font/DejaVuSansCondensed.ttf b/font/DejaVuSansCondensed.ttf
new file mode 100644
index 0000000..3259bc2
--- /dev/null
+++ b/font/DejaVuSansCondensed.ttf
Binary files differ
diff --git a/fpdf.go b/fpdf.go
index 4053802..47a95e2 100644
--- a/fpdf.go
+++ b/fpdf.go
@@ -618,6 +618,16 @@ func (f *Fpdf) AliasNbPages(aliasStr string) {
f.aliasNbPagesStr = aliasStr
}
+// enable right to left mode
+func (f *Fpdf) RTL() {
+ f.isRTL = true
+}
+
+// disable right to left mode
+func (f *Fpdf) LTR() {
+ f.isRTL = false
+}
+
// open begins a document
func (f *Fpdf) open() {
f.state = 1
@@ -915,14 +925,40 @@ 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 +1558,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 +1698,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 +1734,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
}
- }
- f.fonts[fontkey] = info
+ // 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
+ }
}
// getFontKey is used by AddFontFromReader and GetFontDesc
@@ -1756,8 +1960,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 +1975,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 +1996,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)
}
@@ -1895,7 +2104,20 @@ 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 {
+ if f.isRTL {
+ txtStr = revertText(txtStr)
+ x -= f.GetStringWidth(txtStr)
+ }
+ 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)
}
@@ -2074,14 +2296,52 @@ 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
+ if f.isRTL {
+ txtStr = revertText(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))
+ 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 {
+ if f.isRTL {
+ txtStr = revertText(txtStr)
+ }
+ 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))
}
@@ -2109,6 +2369,17 @@ func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
return
}
+// Revert string to use in RTL languages
+func revertText(text string) string {
+ oldText := []rune(text)
+ newText := make([]rune, len(oldText))
+ lenght := len(oldText) - 1
+ for i, r := range oldText {
+ newText[lenght-i] = r
+ }
+ return string(newText)
+}
+
// Cell is a simpler version of CellFormat with no fill, border, links or
// special alignment.
func (f *Fpdf) Cell(w, h float64, txtStr string) {
@@ -2132,7 +2403,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)
@@ -2192,17 +2463,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
@@ -2236,14 +2516,32 @@ 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" {
+ if f.isRTL {
+ newAlignStr = "R"
+ } else {
+ 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
@@ -2260,7 +2558,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 {
@@ -2271,7 +2573,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 {
@@ -2281,7 +2587,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
@@ -2304,18 +2614,38 @@ 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" {
+ if f.isRTL {
+ alignStr = "R"
+ } else {
+ 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
@@ -2323,10 +2653,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
@@ -2342,7 +2681,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 {
@@ -2359,9 +2698,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
@@ -2379,7 +2726,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)
+ }
}
}
@@ -3244,6 +3595,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()
@@ -3350,42 +3704,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("<</Length %d", len(font))
+ if compressed {
+ f.out("/Filter /FlateDecode")
+ }
+ f.outf("/Length1 %d", info.length1)
+ if info.length2 > 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("<</Length %d", len(font))
- if compressed {
- f.out("/Filter /FlateDecode")
- }
- f.outf("/Length1 %d", info.length1)
- if info.length2 > 0 {
- f.outf("/Length2 %d /Length3 0", info.length2)
- }
- f.out(">>")
- f.putstream(font)
- f.out("endobj")
}
}
{
@@ -3465,6 +3818,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("<</Type /Font\n/Subtype /Type0\n/BaseFont /%s\n/Encoding /Identity-H\n/DescendantFonts [%d 0 R]\n/ToUnicode %d 0 R>>\n"+"endobj", fontName, f.n+1, f.n+2))
+
+ f.newobj()
+ f.out("<</Type /Font\n/Subtype /CIDFontType2\n/BaseFont /" + fontName + "\n" +
+ "/CIDSystemInfo " + strconv.Itoa(f.n+2) + " 0 R\n/FontDescriptor " + strconv.Itoa(f.n+3) + " 0 R")
+ if font.Desc.MissingWidth != 0 {
+ f.out("/DW " + strconv.Itoa(font.Desc.MissingWidth) + "")
+ }
+ f.generateCIDFontMap(&font, font.utf8File.LastRune)
+ f.out("/CIDToGIDMap " + strconv.Itoa(f.n+4) + " 0 R>>")
+ f.out("endobj")
+
+ f.newobj()
+ f.out("<</Length " + strconv.Itoa(len(toUnicode)) + ">>")
+ f.putstream([]byte(toUnicode))
+ f.out("endobj")
+
+ // CIDInfo
+ f.newobj()
+ f.out("<</Registry (Adobe)\n/Ordering (UCS)\n/Supplement 0>>")
+ f.out("endobj")
+
+ // Font descriptor
+ f.newobj()
+ var s fmtBuffer
+ s.printf("<</Type /FontDescriptor /FontName /%s\n /Ascent %d", fontName, font.Desc.Ascent)
+ s.printf(" /Descent %d", font.Desc.Descent)
+ s.printf(" /CapHeight %d", font.Desc.CapHeight)
+ v := font.Desc.Flags
+ v = v | 4
+ v = v &^ 32
+ s.printf(" /Flags %d", v)
+ s.printf("/FontBBox [%d %d %d %d] ", font.Desc.FontBBox.Xmin, font.Desc.FontBBox.Ymin,
+ font.Desc.FontBBox.Xmax, font.Desc.FontBBox.Ymax)
+ s.printf(" /ItalicAngle %d", font.Desc.ItalicAngle)
+ s.printf(" /StemV %d", font.Desc.StemV)
+ s.printf(" /MissingWidth %d", font.Desc.MissingWidth)
+ s.printf("/FontFile2 %d 0 R", f.n+2)
+ 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("<</Length " + strconv.Itoa(len(cidToGidMap)) + "/Filter /FlateDecode>>")
+ f.putstream(cidToGidMap)
+ f.out("endobj")
+
+ //Font file
+ f.newobj()
+ f.out("<</Length " + strconv.Itoa(len(compressedFontStream)))
+ f.out("/Filter /FlateDecode")
+ f.out("/Length1 " + strconv.Itoa(utf8FontSize))
+ f.out(">>")
+ f.putstream(compressedFontStream)
+ f.out("endobj")
default:
f.err = fmt.Errorf("unsupported font type: %s", tp)
return
@@ -3474,6 +3902,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)
diff --git a/fpdf_test.go b/fpdf_test.go
index 7d956ce..6042fab 100644
--- a/fpdf_test.go
+++ b/fpdf_test.go
@@ -2519,3 +2519,36 @@ func ExampleFpdf_TransformRotate() {
// Output:
// Successfully generated pdf/Fpdf_RotateText.pdf
}
+
+// ExampleFpdf_AddUTF8Font demonstrates how use the font
+// with utf-8 mode
+func ExampleFpdf_AddUTF8Font() {
+ pdf := gofpdf.New("P", "mm", "A4", "")
+
+ pdf.AddPage()
+
+ pdf.AddUTF8Font("dejavu", "", example.FontFile("DejaVuSansCondensed.ttf"))
+ pdf.AddUTF8Font("dejavu", "B", example.FontFile("DejaVuSansCondensed-Bold.ttf"))
+ pdf.AddUTF8Font("dejavu", "I", example.FontFile("DejaVuSansCondensed-Oblique.ttf"))
+ pdf.AddUTF8Font("dejavu", "BI", example.FontFile("DejaVuSansCondensed-BoldOblique.ttf"))
+
+ txtStr, _ := ioutil.ReadFile(example.TextFile("utf-8test.txt"))
+
+ pdf.SetFont("dejavu", "B", 17)
+ pdf.MultiCell(100, 8, "Text in different languages :", "", "C", false)
+ pdf.SetFont("dejavu", "", 14)
+ pdf.MultiCell(100, 5, string(txtStr), "", "C", false)
+ pdf.Ln(15)
+
+ txtStr, _ = ioutil.ReadFile(example.TextFile("utf-8test2.txt"))
+ pdf.SetFont("dejavu", "BI", 17)
+ pdf.MultiCell(100, 8, "Greek text with alignStr = \"J\":", "", "C", false)
+ pdf.SetFont("dejavu", "I", 14)
+ pdf.MultiCell(100, 5, string(txtStr), "", "J", false)
+
+ fileStr := example.Filename("Fpdf_AddUTF8Font")
+ err := pdf.OutputFileAndClose(fileStr)
+ example.Summary(err, fileStr)
+ // Output:
+ // Successfully generated pdf/Fpdf_AddUTF8Font.pdf
+}
diff --git a/pdf/reference/Fpdf_AddUTF8Font.pdf b/pdf/reference/Fpdf_AddUTF8Font.pdf
new file mode 100644
index 0000000..cb2b6e2
--- /dev/null
+++ b/pdf/reference/Fpdf_AddUTF8Font.pdf
Binary files differ
diff --git a/text/utf-8test.txt b/text/utf-8test.txt
new file mode 100644
index 0000000..c9596a3
--- /dev/null
+++ b/text/utf-8test.txt
@@ -0,0 +1,38 @@
+English:
+Eat some more of these soft French buns and drink some tea.
+
+Greek:
+Φάτε μερικά από αυτά τα μαλακά γαλλικά κουλουράκια και πίνετε λίγο τσάι.
+
+Polish:
+Zjedz trochę tych miękkich francuskich bułeczek i wypij herbatę.
+
+Portuguese:
+Coma um pouco mais desses delicados pãezinhos franceses e beba um pouco de chá.
+
+Russian:
+Ешьте еще эти мягкие французские булочки и пейте чай.
+
+Vietnamese:
+Ăn thêm một số bánh Pháp mềm và uống một ít trà.
+
+Arabic:
+أكل بعض أكثر من هذه الكعك الفرنسي لينة وشرب بعض الشاي.
+
+Armenian:
+Կերեք այս փափուկ ֆրանսիական բանջարեղեններից մի քանիսը եւ մի քանի թեյ խմեք:
+
+Georgian:
+ჭამე კიდევ უფრო ამ რბილი ფრანგული buns და ვსვავ გარკვეული ჩაი.
+
+Hebrew:
+לאכול קצת יותר אלה לחמניות צרפתית רכה לשתות תה.
+
+Icelandic:
+Borða meira af þessum mjúka franska bollum og drekkaðu te.
+
+Igbo:
+Rie ụfọdụ n'ime ndị na-asụ French bun ma ṅụọ ụfọdụ tii.
+
+Kazakh:
+Осы жұмсақ француз көкөністерінің кейбіреулерін жеп, кейбір шай ішіңіз.
diff --git a/text/utf-8test2.txt b/text/utf-8test2.txt
new file mode 100644
index 0000000..cc83e14
--- /dev/null
+++ b/text/utf-8test2.txt
@@ -0,0 +1 @@
+Η μορφή φορητού εγγράφου (PDF) είναι μια ανοικτή ηλεκτρονική μορφή εγγράφων, η οποία αναπτύχθηκε αρχικά από την Adobe Systems, χρησιμοποιώντας μια ποικιλία λειτουργιών γλώσσας PostScript. Πρώτα απ 'όλα προορίζεται για την παρουσίαση έντυπων προϊόντων σε ηλεκτρονική μορφή. Για να δείτε υπάρχουν πολλά προγράμματα, καθώς και το επίσημο δωρεάν πρόγραμμα Adobe Reader. Ένα σημαντικό μέρος του σύγχρονου επαγγελματικού εξοπλισμού εκτύπωσης έχει υποστήριξη υλικού για τη μορφή PDF, η οποία επιτρέπει την εκτύπωση εγγράφων σε αυτή τη μορφή χωρίς τη χρήση λογισμικού. Ο παραδοσιακός τρόπος δημιουργίας εγγράφων PDF είναι ένας εικονικός εκτυπωτής, δηλαδή το ίδιο το έγγραφο είναι έτοιμο στο εξειδικευμένο του πρόγραμμα - ένα γραφικό πρόγραμμα ή ένα πρόγραμμα επεξεργασίας κειμένου.
diff --git a/utf8fontfile.go b/utf8fontfile.go
new file mode 100644
index 0000000..964e919
--- /dev/null
+++ b/utf8fontfile.go
@@ -0,0 +1,1142 @@
+/*
+ * Copyright (c) 2019 Arteom Korotkiy (Gmail: arteomkorotkiy)
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package gofpdf
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "math"
+ "sort"
+)
+
+// flags
+const symbolWords = 1 << 0
+const symbolScale = 1 << 3
+const symbolContinue = 1 << 5
+const symbolAllScale = 1 << 6
+const symbol2x2 = 1 << 7
+
+// CID map Init
+const toUnicode = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo\n<</Registry (Adobe)\n/Ordering (UCS)\n/Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n1 beginbfrange\n<0000> <FFFF> <0000>\nendbfrange\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"
+
+type utf8FontFile struct {
+ fileReader *fileReader
+ LastRune int
+ tableDescriptions map[string]*tableDescription
+ outTablesData map[string][]byte
+ symbolPosition []int
+ charSymbolDictionary map[int]int
+ Ascent int
+ Descent int
+ fontElementSize int
+ Bbox fontBoxType
+ CapHeight int
+ StemV int
+ ItalicAngle int
+ Flags int
+ UnderlinePosition float64
+ UnderlineThickness float64
+ CharWidths []int
+ DefaultWidth float64
+ symbolData map[int]map[string][]int
+ CodeSymbolDictionary map[int]int
+}
+
+type tableDescription struct {
+ name string
+ checksum []int
+ position int
+ size int
+}
+
+type fileReader struct {
+ readerPosition int64
+ array []byte
+}
+
+func (fr *fileReader) Read(s int) []byte {
+ b := fr.array[fr.readerPosition : fr.readerPosition+int64(s)]
+ fr.readerPosition += int64(s)
+ return b
+}
+
+func (fr *fileReader) seek(shift int64, flag int) (int64, error) {
+ if flag == 0 {
+ fr.readerPosition = shift
+ } else if flag == 1 {
+ fr.readerPosition += shift
+ } else if flag == 2 {
+ fr.readerPosition = int64(len(fr.array)) - shift
+ }
+ return int64(fr.readerPosition), nil
+}
+
+func newUTF8Font(reader *fileReader) *utf8FontFile {
+ utf := utf8FontFile{
+ fileReader: reader,
+ }
+ return &utf
+}
+
+func (utf *utf8FontFile) parseFile() error {
+ utf.fileReader.readerPosition = 0
+ utf.symbolPosition = make([]int, 0)
+ utf.charSymbolDictionary = make(map[int]int)
+ utf.tableDescriptions = make(map[string]*tableDescription)
+ utf.outTablesData = make(map[string][]byte)
+ utf.Ascent = 0
+ utf.Descent = 0
+ codeType := uint32(utf.readUint32())
+ if codeType == 0x4F54544F {
+ return fmt.Errorf("not supported\n ")
+ }
+ if codeType == 0x74746366 {
+ return fmt.Errorf("not supported\n ")
+ }
+ if codeType != 0x00010000 && codeType != 0x74727565 {
+ return fmt.Errorf("Not a TrueType font: codeType=%v\n ", codeType)
+ }
+ utf.generateTableDescriptions()
+ utf.parseTables()
+ return nil
+}
+
+func (utf *utf8FontFile) generateTableDescriptions() {
+
+ tablesCount := utf.readUint16()
+ _ = utf.readUint16()
+ _ = utf.readUint16()
+ _ = utf.readUint16()
+ utf.tableDescriptions = make(map[string]*tableDescription)
+
+ for i := 0; i < tablesCount; i++ {
+ record := tableDescription{
+ name: utf.readTableName(),
+ checksum: []int{utf.readUint16(), utf.readUint16()},
+ position: utf.readUint32(),
+ size: utf.readUint32(),
+ }
+ utf.tableDescriptions[record.name] = &record
+ }
+}
+
+func (utf *utf8FontFile) readTableName() string {
+ return string(utf.fileReader.Read(4))
+}
+
+func (utf *utf8FontFile) readUint16() int {
+ s := utf.fileReader.Read(2)
+ return (int(s[0]) << 8) + int(s[1])
+}
+
+func (utf *utf8FontFile) readUint32() int {
+ s := utf.fileReader.Read(4)
+ return (int(s[0]) * 16777216) + (int(s[1]) << 16) + (int(s[2]) << 8) + int(s[3]) // 16777216 = 1<<24
+}
+
+func (utf *utf8FontFile) calcInt32(x, y []int) []int {
+ answer := make([]int, 2)
+ if y[1] > x[1] {
+ x[1] += 1 << 16
+ x[0]++
+ }
+ answer[1] = x[1] - y[1]
+ if y[0] > x[0] {
+ x[0] += 1 << 16
+ }
+ answer[0] = x[0] - y[0]
+ answer[0] = answer[0] & 0xFFFF
+ return answer
+}
+
+func (utf *utf8FontFile) generateChecksum(data []byte) []int {
+ if (len(data) % 4) != 0 {
+ for i := 0; (len(data) % 4) != 0; i++ {
+ data = append(data, 0)
+ }
+ }
+ answer := []int{0x0000, 0x0000}
+ for i := 0; i < len(data); i += 4 {
+ answer[0] += (int(data[i]) << 8) + int(data[i+1])
+ answer[1] += (int(data[i+2]) << 8) + int(data[i+3])
+ answer[0] += answer[1] >> 16
+ answer[1] = answer[1] & 0xFFFF
+ answer[0] = answer[0] & 0xFFFF
+ }
+ return answer
+}
+
+func (utf *utf8FontFile) seek(shift int) {
+ _, _ = utf.fileReader.seek(int64(shift), 0)
+}
+
+func (utf *utf8FontFile) skip(delta int) {
+ _, _ = utf.fileReader.seek(int64(delta), 1)
+}
+
+//SeekTable position
+func (utf *utf8FontFile) SeekTable(name string) int {
+ return utf.seekTable(name, 0)
+}
+
+func (utf *utf8FontFile) seekTable(name string, offsetInTable int) int {
+ _, _ = utf.fileReader.seek(int64(utf.tableDescriptions[name].position+offsetInTable), 0)
+ return int(utf.fileReader.readerPosition)
+}
+
+func (utf *utf8FontFile) readInt16() int16 {
+ s := utf.fileReader.Read(2)
+ a := (int16(s[0]) << 8) + int16(s[1])
+ if (int(a) & (1 << 15)) == 0 {
+ a = int16(int(a) - (1 << 16))
+ }
+ return a
+}
+
+func (utf *utf8FontFile) getUint16(pos int) int {
+ _, _ = utf.fileReader.seek(int64(pos), 0)
+ s := utf.fileReader.Read(2)
+ return (int(s[0]) << 8) + int(s[1])
+}
+
+func (utf *utf8FontFile) splice(stream []byte, offset int, value []byte) []byte {
+ return append(append(stream[:offset], value...), stream[offset+len(value):]...)
+}
+
+func (utf *utf8FontFile) insertUint16(stream []byte, offset int, value int) []byte {
+ up := make([]byte, 2)
+ binary.BigEndian.PutUint16(up, uint16(value))
+ return utf.splice(stream, offset, up)
+}
+
+func (utf *utf8FontFile) getRange(pos, length int) []byte {
+ utf.fileReader.seek(int64(pos), 0)
+ if length < 1 {
+ return make([]byte, 0)
+ }
+ s := utf.fileReader.Read(length)
+ return s
+}
+
+func (utf *utf8FontFile) getTableData(name string) []byte {
+ desckrip := utf.tableDescriptions[name]
+ if desckrip == nil {
+ return nil
+ }
+ if desckrip.size == 0 {
+ return nil
+ }
+ utf.fileReader.seek(int64(desckrip.position), 0)
+ s := utf.fileReader.Read(desckrip.size)
+ return s
+}
+
+func (utf *utf8FontFile) setOutTable(name string, data []byte) {
+ if data == nil {
+ return
+ }
+ if name == "head" {
+ data = utf.splice(data, 8, []byte{0, 0, 0, 0})
+ }
+ utf.outTablesData[name] = data
+}
+
+func arrayKeys(arr map[int]string) []int {
+ answer := make([]int, len(arr))
+ i := 0
+ for key := range arr {
+ answer[i] = key
+ i++
+ }
+ return answer
+}
+
+func inArray(s int, arr []int) bool {
+ for _, i := range arr {
+ if s == i {
+ return true
+ }
+ }
+ return false
+}
+
+func (utf *utf8FontFile) parseNAMETable() int {
+ namePosition := utf.SeekTable("name")
+ format := utf.readUint16()
+ if format != 0 {
+ fmt.Printf("Illegal format %d\n", format)
+ return format
+ }
+ nameCount := utf.readUint16()
+ stringDataPosition := namePosition + utf.readUint16()
+ names := map[int]string{1: "", 2: "", 3: "", 4: "", 6: ""}
+ keys := arrayKeys(names)
+ counter := len(names)
+ for i := 0; i < nameCount; i++ {
+ system := utf.readUint16()
+ code := utf.readUint16()
+ local := utf.readUint16()
+ nameID := utf.readUint16()
+ size := utf.readUint16()
+ position := utf.readUint16()
+ if !inArray(nameID, keys) {
+ continue
+ }
+ currentName := ""
+ if system == 3 && code == 1 && local == 0x409 {
+ oldPos := utf.fileReader.readerPosition
+ utf.seek(stringDataPosition + position)
+ if size%2 != 0 {
+ fmt.Printf("name is not binar byte format\n")
+ return format
+ }
+ size /= 2
+ currentName = ""
+ for size > 0 {
+ char := utf.readUint16()
+ currentName += string(rune(char))
+ size--
+ }
+ utf.fileReader.readerPosition = oldPos
+ utf.seek(int(oldPos))
+ } else if system == 1 && code == 0 && local == 0 {
+ oldPos := utf.fileReader.readerPosition
+ currentName = string(utf.getRange(stringDataPosition+position, size))
+ utf.fileReader.readerPosition = oldPos
+ utf.seek(int(oldPos))
+ }
+ if currentName != "" && names[nameID] == "" {
+ names[nameID] = currentName
+ counter--
+ if counter == 0 {
+ break
+ }
+ }
+ }
+ return format
+}
+
+func (utf *utf8FontFile) parseHEADTable() {
+ utf.SeekTable("head")
+ utf.skip(18)
+ utf.fontElementSize = utf.readUint16()
+ scale := 1000.0 / float64(utf.fontElementSize)
+ utf.skip(16)
+ xMin := utf.readInt16()
+ yMin := utf.readInt16()
+ xMax := utf.readInt16()
+ yMax := utf.readInt16()
+ utf.Bbox = fontBoxType{int(float64(xMin) * scale), int(float64(yMin) * scale), int(float64(xMax) * scale), int(float64(yMax) * scale)}
+ utf.skip(3 * 2)
+ _ = utf.readUint16()
+ symbolDataFormat := utf.readUint16()
+ if symbolDataFormat != 0 {
+ fmt.Printf("Unknown symbol data format %d\n", symbolDataFormat)
+ return
+ }
+}
+
+func (utf *utf8FontFile) parseHHEATable() int {
+ metricsCount := 0
+ if _, OK := utf.tableDescriptions["hhea"]; OK {
+ scale := 1000.0 / float64(utf.fontElementSize)
+ utf.SeekTable("hhea")
+ utf.skip(4)
+ hheaAscender := utf.readInt16()
+ hheaDescender := utf.readInt16()
+ utf.Ascent = int(float64(hheaAscender) * scale)
+ utf.Descent = int(float64(hheaDescender) * scale)
+ utf.skip(24)
+ metricDataFormat := utf.readUint16()
+ if metricDataFormat != 0 {
+ fmt.Printf("Unknown horizontal metric data format %d\n", metricDataFormat)
+ return 0
+ }
+ metricsCount = utf.readUint16()
+ if metricsCount == 0 {
+ fmt.Printf("Number of horizontal metrics is 0\n")
+ return 0
+ }
+ }
+ return metricsCount
+}
+
+func (utf *utf8FontFile) parseOS2Table() int {
+ weightType := 0
+ scale := 1000.0 / float64(utf.fontElementSize)
+ if _, OK := utf.tableDescriptions["OS/2"]; OK {
+ utf.SeekTable("OS/2")
+ version := utf.readUint16()
+ utf.skip(2)
+ weightType = utf.readUint16()
+ utf.skip(2)
+ fsType := utf.readUint16()
+ if fsType == 0x0002 || (fsType&0x0300) != 0 {
+ fmt.Printf("ERROR - copyright restrictions.\n")
+ return 0
+ }
+ utf.skip(20)
+ _ = utf.readInt16()
+
+ utf.skip(36)
+ sTypoAscender := utf.readInt16()
+ sTypoDescender := utf.readInt16()
+ if utf.Ascent == 0 {
+ utf.Ascent = int(float64(sTypoAscender) * scale)
+ }
+ if utf.Descent == 0 {
+ utf.Descent = int(float64(sTypoDescender) * scale)
+ }
+ if version > 1 {
+ utf.skip(16)
+ sCapHeight := utf.readInt16()
+ utf.CapHeight = int(float64(sCapHeight) * scale)
+ } else {
+ utf.CapHeight = utf.Ascent
+ }
+ } else {
+ weightType = 500
+ if utf.Ascent == 0 {
+ utf.Ascent = int(float64(utf.Bbox.Ymax) * scale)
+ }
+ if utf.Descent == 0 {
+ utf.Descent = int(float64(utf.Bbox.Ymin) * scale)
+ }
+ utf.CapHeight = utf.Ascent
+ }
+ utf.StemV = 50 + int(math.Pow(float64(weightType)/65.0, 2))
+ return weightType
+}
+
+func (utf *utf8FontFile) parsePOSTTable(weight int) {
+ utf.SeekTable("post")
+ utf.skip(4)
+ utf.ItalicAngle = int(utf.readInt16()) + utf.readUint16()/65536.0
+ scale := 1000.0 / float64(utf.fontElementSize)
+ utf.UnderlinePosition = float64(utf.readInt16()) * scale
+ utf.UnderlineThickness = float64(utf.readInt16()) * scale
+ fixed := utf.readUint32()
+
+ utf.Flags = 4
+
+ if utf.ItalicAngle != 0 {
+ utf.Flags = utf.Flags | 64
+ }
+ if weight >= 600 {
+ utf.Flags = utf.Flags | 262144
+ }
+ if fixed != 0 {
+ utf.Flags = utf.Flags | 1
+ }
+}
+
+func (utf *utf8FontFile) parseCMAPTable(format int) int {
+ cmapPosition := utf.SeekTable("cmap")
+ utf.skip(2)
+ cmapTableCount := utf.readUint16()
+ cidCMAPPosition := 0
+ for i := 0; i < cmapTableCount; i++ {
+ system := utf.readUint16()
+ coded := utf.readUint16()
+ position := utf.readUint32()
+ oldReaderPosition := utf.fileReader.readerPosition
+ if (system == 3 && coded == 1) || system == 0 { // Microsoft, Unicode
+ format = utf.getUint16(cmapPosition + position)
+ if format == 4 {
+ if cidCMAPPosition == 0 {
+ cidCMAPPosition = cmapPosition + position
+ }
+ break
+ }
+ }
+ utf.seek(int(oldReaderPosition))
+ }
+ if cidCMAPPosition == 0 {
+ fmt.Printf("Font does not have cmap for Unicode\n")
+ return cidCMAPPosition
+ }
+ return cidCMAPPosition
+}
+
+func (utf *utf8FontFile) parseTables() {
+ f := utf.parseNAMETable()
+ utf.parseHEADTable()
+ n := utf.parseHHEATable()
+ w := utf.parseOS2Table()
+ utf.parsePOSTTable(w)
+ runeCMAPPosition := utf.parseCMAPTable(f)
+
+ utf.SeekTable("maxp")
+ utf.skip(4)
+ numSymbols := utf.readUint16()
+
+ symbolCharDictionary := make(map[int][]int)
+ charSymbolDictionary := make(map[int]int)
+ utf.generateSCCSDictionaries(runeCMAPPosition, symbolCharDictionary, charSymbolDictionary)
+
+ scale := 1000.0 / float64(utf.fontElementSize)
+ utf.parseHMTXTable(n, numSymbols, symbolCharDictionary, scale)
+}
+
+func (utf *utf8FontFile) generateCMAP() map[int][]int {
+ cmapPosition := utf.SeekTable("cmap")
+ utf.skip(2)
+ cmapTableCount := utf.readUint16()
+ runeCmapPosition := 0
+ for i := 0; i < cmapTableCount; i++ {
+ system := utf.readUint16()
+ coder := utf.readUint16()
+ position := utf.readUint32()
+ oldPosition := utf.fileReader.readerPosition
+ if (system == 3 && coder == 1) || system == 0 {
+ format := utf.getUint16(cmapPosition + position)
+ if format == 4 {
+ runeCmapPosition = cmapPosition + position
+ break
+ }
+ }
+ utf.seek(int(oldPosition))
+ }
+
+ if runeCmapPosition == 0 {
+ fmt.Printf("Font does not have cmap for Unicode\n")
+ return nil
+ }
+
+ symbolCharDictionary := make(map[int][]int)
+ charSymbolDictionary := make(map[int]int)
+ utf.generateSCCSDictionaries(runeCmapPosition, symbolCharDictionary, charSymbolDictionary)
+
+ utf.charSymbolDictionary = charSymbolDictionary
+
+ return symbolCharDictionary
+}
+
+func (utf *utf8FontFile) parseSymbols(usedRunes map[int]int) (map[int]int, map[int]int, map[int]int, []int) {
+ symbolCollection := map[int]int{0: 0}
+ charSymbolPairCollection := make(map[int]int)
+ for _, char := range usedRunes {
+ if _, OK := utf.charSymbolDictionary[char]; OK {
+ symbolCollection[utf.charSymbolDictionary[char]] = char
+ charSymbolPairCollection[char] = utf.charSymbolDictionary[char]
+
+ }
+ utf.LastRune = max(utf.LastRune, char)
+ }
+
+ begin := utf.tableDescriptions["glyf"].position
+
+ symbolArray := make(map[int]int)
+ symbolCollectionKeys := keySortInt(symbolCollection)
+
+ symbolCounter := 0
+ maxRune := 0
+ for _, oldSymbolIndex := range symbolCollectionKeys {
+ maxRune = max(maxRune, symbolCollection[oldSymbolIndex])
+ symbolArray[oldSymbolIndex] = symbolCounter
+ symbolCounter++
+ }
+ charSymbolPairCollectionKeys := keySortInt(charSymbolPairCollection)
+ runeSymbolPairCollection := make(map[int]int)
+ for _, runa := range charSymbolPairCollectionKeys {
+ runeSymbolPairCollection[runa] = symbolArray[charSymbolPairCollection[runa]]
+ }
+ utf.CodeSymbolDictionary = runeSymbolPairCollection
+
+ symbolCollectionKeys = keySortInt(symbolCollection)
+ for _, oldSymbolIndex := range symbolCollectionKeys {
+ _, symbolArray, symbolCollection, symbolCollectionKeys = utf.getSymbols(oldSymbolIndex, &begin, symbolArray, symbolCollection, symbolCollectionKeys)
+ }
+
+ return runeSymbolPairCollection, symbolArray, symbolCollection, symbolCollectionKeys
+}
+
+func (utf *utf8FontFile) generateCMAPTable(cidSymbolPairCollection map[int]int, numSymbols int) []byte {
+ cidSymbolPairCollectionKeys := keySortInt(cidSymbolPairCollection)
+ cidID := 0
+ cidArray := make(map[int][]int)
+ prevCid := -2
+ prevSymbol := -1
+ for _, cid := range cidSymbolPairCollectionKeys {
+ if cid == (prevCid+1) && cidSymbolPairCollection[cid] == (prevSymbol+1) {
+ if n, OK := cidArray[cidID]; !OK || n == nil {
+ cidArray[cidID] = make([]int, 0)
+ }
+ cidArray[cidID] = append(cidArray[cidID], cidSymbolPairCollection[cid])
+ } else {
+ cidID = cid
+ cidArray[cidID] = make([]int, 0)
+ cidArray[cidID] = append(cidArray[cidID], cidSymbolPairCollection[cid])
+ }
+ prevCid = cid
+ prevSymbol = cidSymbolPairCollection[cid]
+ }
+ cidArrayKeys := keySortArrayRangeMap(cidArray)
+ segCount := len(cidArray) + 1
+
+ searchRange := 1
+ entrySelector := 0
+ for searchRange*2 <= segCount {
+ searchRange = searchRange * 2
+ entrySelector = entrySelector + 1
+ }
+ searchRange = searchRange * 2
+ rangeShift := segCount*2 - searchRange
+ length := 16 + (8 * segCount) + (numSymbols + 1)
+ cmap := []int{0, 1, 3, 1, 0, 12, 4, length, 0, segCount * 2, searchRange, entrySelector, rangeShift}
+
+ for _, start := range cidArrayKeys {
+ endCode := start + (len(cidArray[start]) - 1)
+ cmap = append(cmap, endCode)
+ }
+ cmap = append(cmap, 0xFFFF)
+ cmap = append(cmap, 0)
+
+ for _, cidKey := range cidArrayKeys {
+ cmap = append(cmap, cidKey)
+ }
+ cmap = append(cmap, 0xFFFF)
+ for _, cidKey := range cidArrayKeys {
+ idDelta := -(cidKey - cidArray[cidKey][0])
+ cmap = append(cmap, idDelta)
+ }
+ cmap = append(cmap, 1)
+ for range cidArray {
+ cmap = append(cmap, 0)
+
+ }
+ cmap = append(cmap, 0)
+ for _, start := range cidArrayKeys {
+ for _, glidx := range cidArray[start] {
+ cmap = append(cmap, glidx)
+ }
+ }
+ cmap = append(cmap, 0)
+ cmapstr := make([]byte, 0)
+ for _, cm := range cmap {
+ cmapstr = append(cmapstr, packUint16(cm)...)
+ }
+ return cmapstr
+}
+
+//GenerateСutFont fill utf8FontFile from .utf file, only with runes from usedRunes
+func (utf *utf8FontFile) GenerateСutFont(usedRunes map[int]int) []byte {
+ utf.fileReader.readerPosition = 0
+ utf.symbolPosition = make([]int, 0)
+ utf.charSymbolDictionary = make(map[int]int)
+ utf.tableDescriptions = make(map[string]*tableDescription)
+ utf.outTablesData = make(map[string][]byte)
+ utf.Ascent = 0
+ utf.Descent = 0
+ utf.skip(4)
+ utf.LastRune = 0
+ utf.generateTableDescriptions()
+
+ utf.SeekTable("head")
+ utf.skip(50)
+ LocaFormat := utf.readUint16()
+
+ utf.SeekTable("hhea")
+ utf.skip(34)
+ metricsCount := utf.readUint16()
+ oldMetrics := metricsCount
+
+ utf.SeekTable("maxp")
+ utf.skip(4)
+ numSymbols := utf.readUint16()
+
+ symbolCharDictionary := utf.generateCMAP()
+ if symbolCharDictionary == nil {
+ return nil
+ }
+
+ utf.parseHMTXTable(metricsCount, numSymbols, symbolCharDictionary, 1.0)
+
+ utf.parseLOCATable(LocaFormat, numSymbols)
+
+ cidSymbolPairCollection, symbolArray, symbolCollection, symbolCollectionKeys := utf.parseSymbols(usedRunes)
+
+ metricsCount = len(symbolCollection)
+ numSymbols = metricsCount
+
+ utf.setOutTable("name", utf.getTableData("name"))
+ utf.setOutTable("cvt ", utf.getTableData("cvt "))
+ utf.setOutTable("fpgm", utf.getTableData("fpgm"))
+ utf.setOutTable("prep", utf.getTableData("prep"))
+ utf.setOutTable("gasp", utf.getTableData("gasp"))
+
+ postTable := utf.getTableData("post")
+ postTable = append(append([]byte{0x00, 0x03, 0x00, 0x00}, postTable[4:16]...), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
+ utf.setOutTable("post", postTable)
+
+ delete(cidSymbolPairCollection, 0)
+
+ utf.setOutTable("cmap", utf.generateCMAPTable(cidSymbolPairCollection, numSymbols))
+
+ symbolData := utf.getTableData("glyf")
+
+ offsets := make([]int, 0)
+ glyfData := make([]byte, 0)
+ pos := 0
+ hmtxData := make([]byte, 0)
+ utf.symbolData = make(map[int]map[string][]int, 0)
+
+ for _, originalSymbolIdx := range symbolCollectionKeys {
+ hm := utf.getMetrics(oldMetrics, originalSymbolIdx)
+ hmtxData = append(hmtxData, hm...)
+
+ offsets = append(offsets, pos)
+ symbolPos := utf.symbolPosition[originalSymbolIdx]
+ symbolLen := utf.symbolPosition[originalSymbolIdx+1] - symbolPos
+ data := symbolData[symbolPos : symbolPos+symbolLen]
+ var up int
+ if symbolLen > 0 {
+ up = unpackUint16(data[0:2])
+ }
+
+ if symbolLen > 2 && (up&(1<<15)) != 0 {
+ posInSymbol := 10
+ flags := symbolContinue
+ nComponentElements := 0
+ for (flags & symbolContinue) != 0 {
+ nComponentElements++
+ up = unpackUint16(data[posInSymbol : posInSymbol+2])
+ flags = up
+ up = unpackUint16(data[posInSymbol+2 : posInSymbol+4])
+ symbolIdx := up
+ if _, OK := utf.symbolData[originalSymbolIdx]; !OK {
+ utf.symbolData[originalSymbolIdx] = make(map[string][]int)
+ }
+ if _, OK := utf.symbolData[originalSymbolIdx]["compSymbols"]; !OK {
+ utf.symbolData[originalSymbolIdx]["compSymbols"] = make([]int, 0)
+ }
+ utf.symbolData[originalSymbolIdx]["compSymbols"] = append(utf.symbolData[originalSymbolIdx]["compSymbols"], symbolIdx)
+ data = utf.insertUint16(data, posInSymbol+2, symbolArray[symbolIdx])
+ posInSymbol += 4
+ if (flags & symbolWords) != 0 {
+ posInSymbol += 4
+ } else {
+ posInSymbol += 2
+ }
+ if (flags & symbolScale) != 0 {
+ posInSymbol += 2
+ } else if (flags & symbolAllScale) != 0 {
+ posInSymbol += 4
+ } else if (flags & symbol2x2) != 0 {
+ posInSymbol += 8
+ }
+ }
+ }
+
+ glyfData = append(glyfData, data...)
+ pos += symbolLen
+ if pos%4 != 0 {
+ padding := 4 - (pos % 4)
+ glyfData = append(glyfData, make([]byte, padding)...)
+ pos += padding
+ }
+ }
+
+ offsets = append(offsets, pos)
+ utf.setOutTable("glyf", glyfData)
+
+ utf.setOutTable("hmtx", hmtxData)
+
+ locaData := make([]byte, 0)
+ if ((pos + 1) >> 1) > 0xFFFF {
+ LocaFormat = 1
+ for _, offset := range offsets {
+ locaData = append(locaData, packUint32(offset)...)
+ }
+ } else {
+ LocaFormat = 0
+ for _, offset := range offsets {
+ locaData = append(locaData, packUint16(offset/2)...)
+ }
+ }
+ utf.setOutTable("loca", locaData)
+
+ headData := utf.getTableData("head")
+ headData = utf.insertUint16(headData, 50, LocaFormat)
+ utf.setOutTable("head", headData)
+
+ hheaData := utf.getTableData("hhea")
+ hheaData = utf.insertUint16(hheaData, 34, metricsCount)
+ utf.setOutTable("hhea", hheaData)
+
+ maxp := utf.getTableData("maxp")
+ maxp = utf.insertUint16(maxp, 4, numSymbols)
+ utf.setOutTable("maxp", maxp)
+
+ os2Data := utf.getTableData("OS/2")
+ utf.setOutTable("OS/2", os2Data)
+
+ return utf.assembleTables()
+}
+
+func (utf *utf8FontFile) getSymbols(originalSymbolIdx int, start *int, symbolSet map[int]int, SymbolsCollection map[int]int, SymbolsCollectionKeys []int) (*int, map[int]int, map[int]int, []int) {
+ symbolPos := utf.symbolPosition[originalSymbolIdx]
+ symbolSize := utf.symbolPosition[originalSymbolIdx+1] - symbolPos
+ if symbolSize == 0 {
+ return start, symbolSet, SymbolsCollection, SymbolsCollectionKeys
+ }
+ utf.seek(*start + symbolPos)
+
+ lineCount := utf.readInt16()
+
+ if lineCount < 0 {
+ utf.skip(8)
+ flags := symbolContinue
+ for flags&symbolContinue != 0 {
+ flags = utf.readUint16()
+ symbolIndex := utf.readUint16()
+ if _, OK := symbolSet[symbolIndex]; !OK {
+ symbolSet[symbolIndex] = len(SymbolsCollection)
+ SymbolsCollection[symbolIndex] = 1
+ SymbolsCollectionKeys = append(SymbolsCollectionKeys, symbolIndex)
+ }
+ oldPosition, _ := utf.fileReader.seek(0, 1)
+ _, _, _, SymbolsCollectionKeys = utf.getSymbols(symbolIndex, start, symbolSet, SymbolsCollection, SymbolsCollectionKeys)
+ utf.seek(int(oldPosition))
+ if flags&symbolWords != 0 {
+ utf.skip(4)
+ } else {
+ utf.skip(2)
+ }
+ if flags&symbolScale != 0 {
+ utf.skip(2)
+ } else if flags&symbolAllScale != 0 {
+ utf.skip(4)
+ } else if flags&symbol2x2 != 0 {
+ utf.skip(8)
+ }
+ }
+ }
+ return start, symbolSet, SymbolsCollection, SymbolsCollectionKeys
+}
+
+func (utf *utf8FontFile) parseHMTXTable(numberOfHMetrics, numSymbols int, symbolToChar map[int][]int, scale float64) {
+ start := utf.SeekTable("hmtx")
+ arrayWidths := 0
+ widths := 0
+ var arr []int
+ utf.CharWidths = make([]int, 256*256)
+ charCount := 0
+ arr = unpackUint16Array(utf.getRange(start, numberOfHMetrics*4))
+ for symbol := 0; symbol < numberOfHMetrics; symbol++ {
+ arrayWidths = arr[(symbol*2)+1]
+ if _, OK := symbolToChar[symbol]; OK || symbol == 0 {
+
+ if arrayWidths >= (1 << 15) {
+ arrayWidths = 0
+ }
+ if symbol == 0 {
+ utf.DefaultWidth = scale * float64(arrayWidths)
+ continue
+ }
+ for _, char := range symbolToChar[symbol] {
+ if char != 0 && char != 65535 {
+ widths = int(math.Round(scale * float64(arrayWidths)))
+ if widths == 0 {
+ widths = 65535
+ }
+ if char < 196608 {
+ utf.CharWidths[char] = widths
+ charCount++
+ }
+ }
+ }
+ }
+ }
+ data := utf.getRange(start+numberOfHMetrics*4, numSymbols*2)
+ arr = unpackUint16Array(data)
+ diff := numSymbols - numberOfHMetrics
+ for pos := 0; pos < diff; pos++ {
+ symbol := pos + numberOfHMetrics
+ if _, OK := symbolToChar[symbol]; OK {
+ for _, char := range symbolToChar[symbol] {
+ if char != 0 && char != 65535 {
+ widths = int(math.Round(scale * float64(arrayWidths)))
+ if widths == 0 {
+ widths = 65535
+ }
+ if char < 196608 {
+ utf.CharWidths[char] = widths
+ charCount++
+ }
+ }
+ }
+ }
+ }
+ utf.CharWidths[0] = charCount
+}
+
+func (utf *utf8FontFile) getMetrics(metricCount, gid int) []byte {
+ start := utf.SeekTable("hmtx")
+ var metrics []byte
+ if gid < metricCount {
+ utf.seek(start + (gid * 4))
+ metrics = utf.fileReader.Read(4)
+ } else {
+ utf.seek(start + ((metricCount - 1) * 4))
+ metrics = utf.fileReader.Read(2)
+ utf.seek(start + (metricCount * 2) + (gid * 2))
+ metrics = append(metrics, utf.fileReader.Read(2)...)
+ }
+ return metrics
+}
+
+func (utf *utf8FontFile) parseLOCATable(format, numSymbols int) {
+ start := utf.SeekTable("loca")
+ utf.symbolPosition = make([]int, 0)
+ if format == 0 {
+ data := utf.getRange(start, (numSymbols*2)+2)
+ arr := unpackUint16Array(data)
+ for n := 0; n <= numSymbols; n++ {
+ utf.symbolPosition = append(utf.symbolPosition, arr[n+1]*2)
+ }
+ } else if format == 1 {
+ data := utf.getRange(start, (numSymbols*4)+4)
+ arr := unpackUint32Array(data)
+ for n := 0; n <= numSymbols; n++ {
+ utf.symbolPosition = append(utf.symbolPosition, arr[n+1])
+ }
+ } else {
+ fmt.Printf("Unknown loca table format %d\n", format)
+ return
+ }
+}
+
+func (utf *utf8FontFile) generateSCCSDictionaries(runeCmapPosition int, symbolCharDictionary map[int][]int, charSymbolDictionary map[int]int) {
+ maxRune := 0
+ utf.seek(runeCmapPosition + 2)
+ size := utf.readUint16()
+ rim := runeCmapPosition + size
+ utf.skip(2)
+
+ segmentSize := utf.readUint16() / 2
+ utf.skip(6)
+ completers := make([]int, 0)
+ for i := 0; i < segmentSize; i++ {
+ completers = append(completers, utf.readUint16())
+ }
+ utf.skip(2)
+ beginners := make([]int, 0)
+ for i := 0; i < segmentSize; i++ {
+ beginners = append(beginners, utf.readUint16())
+ }
+ sizes := make([]int, 0)
+ for i := 0; i < segmentSize; i++ {
+ sizes = append(sizes, int(utf.readInt16()))
+ }
+ readerPositionStart := utf.fileReader.readerPosition
+ positions := make([]int, 0)
+ for i := 0; i < segmentSize; i++ {
+ positions = append(positions, utf.readUint16())
+ }
+ symbol := 0
+ for n := 0; n < segmentSize; n++ {
+ completePosition := completers[n] + 1
+ for char := beginners[n]; char < completePosition; char++ {
+ if positions[n] == 0 {
+ symbol = (char + sizes[n]) & 0xFFFF
+ } else {
+ position := (char-beginners[n])*2 + positions[n]
+ position = int(readerPositionStart) + 2*n + position
+ if position >= rim {
+ symbol = 0
+ } else {
+ symbol = utf.getUint16(position)
+ if symbol != 0 {
+ symbol = (symbol + sizes[n]) & 0xFFFF
+ }
+ }
+ }
+ charSymbolDictionary[char] = symbol
+ if char < 196608 {
+ maxRune = max(char, maxRune)
+ }
+ symbolCharDictionary[symbol] = append(symbolCharDictionary[symbol], char)
+ }
+ }
+}
+
+func max(i, n int) int {
+ if n > i {
+ return n
+ }
+ return i
+}
+
+func (utf *utf8FontFile) assembleTables() []byte {
+ answer := make([]byte, 0)
+ tablesCount := len(utf.outTablesData)
+ findSize := 1
+ writer := 0
+ for findSize*2 <= tablesCount {
+ findSize = findSize * 2
+ writer = writer + 1
+ }
+ findSize = findSize * 16
+ rOffset := tablesCount*16 - findSize
+
+ answer = append(answer, packHeader(0x00010000, tablesCount, findSize, writer, rOffset)...)
+
+ tables := utf.outTablesData
+ tablesNames := keySortStrings(tables)
+
+ offset := 12 + tablesCount*16
+ begin := 0
+
+ for _, name := range tablesNames {
+ if name == "head" {
+ begin = offset
+ }
+ answer = append(answer, []byte(name)...)
+ checksum := utf.generateChecksum(tables[name])
+ answer = append(answer, pack2Uint16(checksum[0], checksum[1])...)
+ answer = append(answer, pack2Uint32(offset, len(tables[name]))...)
+ paddedLength := (len(tables[name]) + 3) &^ 3
+ offset = offset + paddedLength
+ }
+
+ for _, key := range tablesNames {
+ data := tables[key]
+ data = append(data, []byte{0, 0, 0}...)
+ answer = append(answer, data[:(len(data)&^3)]...)
+ }
+
+ checksum := utf.generateChecksum([]byte(answer))
+ checksum = utf.calcInt32([]int{0xB1B0, 0xAFBA}, checksum)
+ answer = utf.splice(answer, (begin + 8), pack2Uint16(checksum[0], checksum[1]))
+ return answer
+}
+
+func unpackUint16Array(data []byte) []int {
+ answer := make([]int, 1)
+ r := bytes.NewReader(data)
+ bs := make([]byte, 2)
+ var e error
+ var c int
+ c, e = r.Read(bs)
+ for e == nil && c > 0 {
+ answer = append(answer, int(binary.BigEndian.Uint16(bs)))
+ c, e = r.Read(bs)
+ }
+ return answer
+}
+
+func unpackUint32Array(data []byte) []int {
+ answer := make([]int, 1)
+ r := bytes.NewReader(data)
+ bs := make([]byte, 4)
+ var e error
+ var c int
+ c, e = r.Read(bs)
+ for e == nil && c > 0 {
+ answer = append(answer, int(binary.BigEndian.Uint32(bs)))
+ c, e = r.Read(bs)
+ }
+ return answer
+}
+
+func unpackUint16(data []byte) int {
+ return int(binary.BigEndian.Uint16(data))
+}
+
+func packHeader(N uint32, n1, n2, n3, n4 int) []byte {
+ answer := make([]byte, 0)
+ bs4 := make([]byte, 4)
+ binary.BigEndian.PutUint32(bs4, N)
+ answer = append(answer, bs4...)
+ bs := make([]byte, 2)
+ binary.BigEndian.PutUint16(bs, uint16(n1))
+ answer = append(answer, bs...)
+ binary.BigEndian.PutUint16(bs, uint16(n2))
+ answer = append(answer, bs...)
+ binary.BigEndian.PutUint16(bs, uint16(n3))
+ answer = append(answer, bs...)
+ binary.BigEndian.PutUint16(bs, uint16(n4))
+ answer = append(answer, bs...)
+ return answer
+}
+
+func pack2Uint16(n1, n2 int) []byte {
+ answer := make([]byte, 0)
+ bs := make([]byte, 2)
+ binary.BigEndian.PutUint16(bs, uint16(n1))
+ answer = append(answer, bs...)
+ binary.BigEndian.PutUint16(bs, uint16(n2))
+ answer = append(answer, bs...)
+ return answer
+}
+
+func pack2Uint32(n1, n2 int) []byte {
+ answer := make([]byte, 0)
+ bs := make([]byte, 4)
+ binary.BigEndian.PutUint32(bs, uint32(n1))
+ answer = append(answer, bs...)
+ binary.BigEndian.PutUint32(bs, uint32(n2))
+ answer = append(answer, bs...)
+ return answer
+}
+
+func packUint32(n1 int) []byte {
+ bs := make([]byte, 4)
+ binary.BigEndian.PutUint32(bs, uint32(n1))
+ return bs
+}
+
+func packUint16(n1 int) []byte {
+ bs := make([]byte, 2)
+ binary.BigEndian.PutUint16(bs, uint16(n1))
+ return bs
+}
+
+func keySortStrings(s map[string][]byte) []string {
+ keys := make([]string, len(s))
+ i := 0
+ for key := range s {
+ keys[i] = key
+ i++
+ }
+ sort.Strings(keys)
+ return keys
+}
+
+func keySortInt(s map[int]int) []int {
+ keys := make([]int, len(s))
+ i := 0
+ for key := range s {
+ keys[i] = key
+ i++
+ }
+ sort.Ints(keys)
+ return keys
+}
+
+func keySortArrayRangeMap(s map[int][]int) []int {
+ keys := make([]int, len(s))
+ i := 0
+ for key := range s {
+ keys[i] = key
+ i++
+ }
+ sort.Ints(keys)
+ return keys
+}
diff --git a/util.go b/util.go
index 1dad60e..e96cdd8 100644
--- a/util.go
+++ b/util.go
@@ -105,10 +105,16 @@ func sliceUncompress(data []byte) (outData []byte, err error) {
return
}
-// utf8toutf16 converts UTF-8 to UTF-16BE with BOM; from http://www.fpdf.org/
-func utf8toutf16(s string) string {
+// utf8toutf16 converts UTF-8 to UTF-16BE; from http://www.fpdf.org/
+func utf8toutf16(s string, withBOM ...bool) string {
+ bom := true
+ if len(withBOM) > 0 {
+ bom = withBOM[0]
+ }
res := make([]byte, 0, 8)
- res = append(res, 0xFE, 0xFF)
+ if bom {
+ res = append(res, 0xFE, 0xFF)
+ }
nb := len(s)
i := 0
for i < nb {
@@ -314,3 +320,133 @@ func (s *SizeType) ScaleToHeight(height float64) SizeType {
width := s.Wd * height / s.Ht
return SizeType{width, height}
}
+
+//The untypedKeyMap structure and its methods are copyrighted 2019 by Arteom Korotkiy (Gmail: arteomkorotkiy).
+//Imitation of untyped Map Array
+type untypedKeyMap struct {
+ keySet []interface{}
+ valueSet []int
+}
+
+//Get position of key=>value in PHP Array
+func (pa *untypedKeyMap) getIndex(key interface{}) int {
+ if key != nil {
+ for i, mKey := range pa.keySet {
+ if mKey == key {
+ return i
+ }
+ }
+ return -1
+ }
+ return -1
+}
+
+//Put key=>value in PHP Array
+func (pa *untypedKeyMap) put(key interface{}, value int) {
+ if key == nil {
+ i := 0
+ for n := 0; ; n++ {
+ i = pa.getIndex(n)
+ if i < 0 {
+ key = n
+ break
+ }
+ }
+ pa.keySet = append(pa.keySet, key)
+ pa.valueSet = append(pa.valueSet, value)
+ } else {
+ i := pa.getIndex(key)
+ if i < 0 {
+ pa.keySet = append(pa.keySet, key)
+ pa.valueSet = append(pa.valueSet, value)
+ } else {
+ pa.valueSet[i] = value
+ }
+ }
+}
+
+//Delete value in PHP Array
+func (pa *untypedKeyMap) delete(key interface{}) {
+ if pa == nil || pa.keySet == nil || pa.valueSet == nil {
+ return
+ }
+ i := pa.getIndex(key)
+ if i >= 0 {
+ if i == 0 {
+ pa.keySet = pa.keySet[1:]
+ pa.valueSet = pa.valueSet[1:]
+ } else if i == len(pa.keySet)-1 {
+ pa.keySet = pa.keySet[:len(pa.keySet)-1]
+ pa.valueSet = pa.valueSet[:len(pa.valueSet)-1]
+ } else {
+ pa.keySet = append(pa.keySet[:i], pa.keySet[i+1:]...)
+ pa.valueSet = append(pa.valueSet[:i], pa.valueSet[i+1:]...)
+ }
+ }
+}
+
+//Get value from PHP Array
+func (pa *untypedKeyMap) get(key interface{}) int {
+ i := pa.getIndex(key)
+ if i >= 0 {
+ return pa.valueSet[i]
+ }
+ return 0
+}
+
+//Imitation of PHP function pop()
+func (pa *untypedKeyMap) pop() {
+ pa.keySet = pa.keySet[:len(pa.keySet)-1]
+ pa.valueSet = pa.valueSet[:len(pa.valueSet)-1]
+}
+
+//Imitation of PHP function array_merge()
+func arrayMerge(arr1, arr2 *untypedKeyMap) *untypedKeyMap {
+ answer := untypedKeyMap{}
+ if arr1 == nil && arr2 == nil {
+ answer = untypedKeyMap{
+ make([]interface{}, 0),
+ make([]int, 0),
+ }
+ } else if arr2 == nil {
+ answer.keySet = arr1.keySet[:]
+ answer.valueSet = arr1.valueSet[:]
+ } else if arr1 == nil {
+ answer.keySet = arr2.keySet[:]
+ answer.valueSet = arr2.valueSet[:]
+ } else {
+ answer.keySet = arr1.keySet[:]
+ answer.valueSet = arr1.valueSet[:]
+ for i := 0; i < len(arr2.keySet); i++ {
+ if arr2.keySet[i] == "interval" {
+ u := 0
+ u = u + 1
+ if arr1.getIndex("interval") < 0 {
+ answer.put("interval", arr2.valueSet[i])
+ } else {
+
+ u := 0
+ u = u + 1
+ }
+ } else {
+ answer.put(nil, arr2.valueSet[i])
+ }
+ }
+ }
+ return &answer
+}
+
+func remove(arr []int, key int) []int {
+ n := 0
+ for i, mKey := range arr {
+ if mKey == key {
+ n = i
+ }
+ }
+ if n == 0 {
+ return arr[1:]
+ } else if n == len(arr)-1 {
+ return arr[:len(arr)-1]
+ }
+ return append(arr[:n], arr[n+1:]...)
+}