summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarkFreedman <misterdark@mail.ru>2019-04-19 17:00:39 +0300
committerDarkFreedman <misterdark@mail.ru>2019-04-19 17:00:39 +0300
commit0d6a38d49d631da305a6f17704f06f35c36cbddc (patch)
tree343d52bffa64a5055f4d90abf23a3aafbf4b69ce
parent5262aa851a9a9faf10c562b221986ac190099efc (diff)
Added full support for UTF-8 font. With MIT license.
-rw-r--r--README.md16
-rw-r--r--def.go34
-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.go784
-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.go1124
-rw-r--r--util.go141
15 files changed, 2020 insertions, 154 deletions
diff --git a/README.md b/README.md
index 54c4d55..860d524 100644
--- a/README.md
+++ b/README.md
@@ -28,14 +28,14 @@ text, drawing and images.
* 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.
-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
+gofpdf supports UTF-8 fonts.
+
+Also, support is provided to automatically translate
UTF-8 runes to code page encodings for languages that have fewer than 256
glyphs.
@@ -135,7 +135,9 @@ for all examples.
Nothing special is required to use the standard PDF fonts (courier, helvetica,
times, zapfdingbats) in your documents other than calling SetFont().
-In order to use a different TrueType or Type1 font, you will need to generate a
+You should use AddUTF8Font or AddUTF8FontFromBytes to add UTF-8 TTF font.
+
+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
@@ -225,12 +227,10 @@ 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.
+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.
diff --git a/def.go b/def.go
index fcc9237..4c46d03 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 {
@@ -495,6 +496,7 @@ 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
page int // current page number
n int // current object number
offsets []int // array of object offsets
@@ -682,20 +684,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
@@ -715,7 +719,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 bd4aa4e..1400a85 100644
--- a/fpdf.go
+++ b/fpdf.go
@@ -915,14 +915,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 +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("<</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")
}
}
{
@@ -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("<</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
@@ -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)
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..ad9f924
--- /dev/null
+++ b/utf8fontfile.go
@@ -0,0 +1,1124 @@
+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
+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..1a776e8 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,132 @@ func (s *SizeType) ScaleToHeight(height float64) SizeType {
width := s.Wd * height / s.Ht
return SizeType{width, height}
}
+
+//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:]...)
+}