diff --git a/cache_content_text.go b/cache_content_text.go index fdc0d755..ada7f0ef 100644 --- a/cache_content_text.go +++ b/cache_content_text.go @@ -6,6 +6,10 @@ import ( "io" "math" "strconv" + + "github.com/go-text/typesetting/language" + "github.com/go-text/typesetting/shaping" + "golang.org/x/image/math/fixed" ) const defaultCoefLineHeight = float64(1) @@ -134,6 +138,8 @@ func FormatFloatTrim(floatval float64) (formatted string) { } func (c *cacheContentText) write(w io.Writer, protection *PDFProtection) error { + // w = io.MultiWriter(w, os.Stdout) + x, err := c.calX() if err != nil { return err @@ -160,35 +166,33 @@ func (c *cacheContentText) write(w io.Writer, protection *PDFProtection) error { if c.txtColorMode == "color" { c.textColor.write(w, protection) } - io.WriteString(w, "[<") - unitsPerEm := int(c.fontSubset.ttfp.UnitsPerEm()) - var leftRune rune - var leftRuneIndex uint - for i, r := range c.text { + shouldKern := c.fontSubset.ttfFontOption.UseKerning - glyphindex, err := c.fontSubset.CharIndex(r) - if err == ErrCharNotFound { - continue - } else if err != nil { - return err + io.WriteString(w, "[") + + if !shouldKern { + io.WriteString(w, "<") + } + + for _, glyph := range getGlyphs(c.fontSubset, c.text, c.fontSize) { + if shouldKern { + fmt.Fprintf(w, "%d", glyph.XAdvance) + io.WriteString(w, "<") } - pairvalPdfUnit := 0 - if i > 0 && c.fontSubset.ttfFontOption.UseKerning { //kerning - pairval := kern(c.fontSubset, leftRune, r, leftRuneIndex, glyphindex) - pairvalPdfUnit = convertTTFUnit2PDFUnit(int(pairval), unitsPerEm) - if pairvalPdfUnit != 0 { - fmt.Fprintf(w, ">%d<", (-1)*pairvalPdfUnit) - } + fmt.Fprintf(w, "%04X", glyph.GlyphID) + + if shouldKern { + io.WriteString(w, ">") } + } - fmt.Fprintf(w, "%04X", glyphindex) - leftRune = r - leftRuneIndex = glyphindex + if !shouldKern { + io.WriteString(w, ">") } - io.WriteString(w, ">] TJ\n") + io.WriteString(w, "] TJ\n") io.WriteString(w, "ET\n") if c.fontStyle&Underline == Underline { @@ -308,39 +312,15 @@ func (c *cacheContentText) createContent() (float64, float64, error) { } func createContent(f *SubsetFontObj, text string, fontSize float64, charSpacing float64, rectangle *Rect) (float64, float64, float64, error) { - unitsPerEm := int(f.ttfp.UnitsPerEm()) - var leftRune rune - var leftRuneIndex uint sumWidth := int(0) //fmt.Printf("unitsPerEm = %d", unitsPerEm) - for i, r := range text { - - glyphindex, err := f.CharIndex(r) - if err == ErrCharNotFound { - continue - } else if err != nil { - return 0, 0, 0, err - } - - pairvalPdfUnit := 0 - if i > 0 && f.ttfFontOption.UseKerning { //kerning - pairval := kern(f, leftRune, r, leftRuneIndex, glyphindex) - pairvalPdfUnit = convertTTFUnit2PDFUnit(int(pairval), unitsPerEm) - } - - width, err := f.CharWidth(r) - if err != nil { - return 0, 0, 0, err - } - + for _, glyph := range getGlyphs(f, text, fontSize) { unitsPerPt := float64(unitsPerEm) / fontSize spaceWidthInPt := unitsPerPt * charSpacing spaceWidthPdfUnit := convertTTFUnit2PDFUnit(int(spaceWidthInPt), unitsPerEm) - sumWidth += int(width) + int(pairvalPdfUnit) + spaceWidthPdfUnit - leftRune = r - leftRuneIndex = glyphindex + sumWidth += int(glyph.Width) + spaceWidthPdfUnit } cellWidthPdfUnit := float64(0) @@ -358,25 +338,22 @@ func createContent(f *SubsetFontObj, text string, fontSize float64, charSpacing return cellWidthPdfUnit, cellHeightPdfUnit, textWidthPdfUnit, nil } -func kern(f *SubsetFontObj, leftRune rune, rightRune rune, leftIndex uint, rightIndex uint) int16 { - - pairVal := int16(0) - if haveKerning, kval := f.KernValueByLeft(leftIndex); haveKerning { - if ok, v := kval.ValueByRight(rightIndex); ok { - pairVal = v - } - } - - if f.funcKernOverride != nil { - pairVal = f.funcKernOverride( - leftRune, - rightRune, - leftIndex, - rightIndex, - pairVal, - ) - } - return pairVal +func getGlyphs(f *SubsetFontObj, text string, fontSize float64) []shaping.Glyph { + face := f.ttfFontOption.face + runeText := []rune(text) + shaper := shaping.HarfbuzzShaper{} + output := shaper.Shape( + shaping.Input{ + Text: runeText, + RunStart: 0, + RunEnd: len(runeText), + Face: face, + Size: fixed.Int26_6(fontSize), + Script: language.LookupScript(runeText[0]), + }, + ) + + return output.Glyphs } // CacheContent Export cacheContent diff --git a/go.mod b/go.mod index d47fe88e..54969afb 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,8 @@ module github.com/signintech/gopdf go 1.13 -require github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 +require ( + github.com/go-text/typesetting v0.1.1 + github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 + golang.org/x/image v0.17.0 +) diff --git a/go.sum b/go.sum index 581584f2..34d9e15e 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,78 @@ +github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo= +github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI= +github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY= +github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 h1:zyWXQ6vu27ETMpYsEMAsisQ+GqJ4e1TPvSNfdOPF0no= github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg= +golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= +golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= +golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/gopdf.go b/gopdf.go index 1f768a4a..b9cafca9 100644 --- a/gopdf.go +++ b/gopdf.go @@ -18,6 +18,7 @@ import ( "errors" + "github.com/go-text/typesetting/font" "github.com/phpdave11/gofpdi" ) @@ -1724,8 +1725,15 @@ func (gp *GoPdf) AddTTFFontWithOption(family string, ttfpath string, option TtfO if err != nil { return err } - rd := bytes.NewReader(data) - return gp.AddTTFFontByReaderWithOption(family, rd, option) + + face, err := font.ParseTTF(bytes.NewReader(data)) + if err != nil { + return err + } + + option.face = face + + return gp.AddTTFFontByReaderWithOption(family, bytes.NewReader(data), option) } // AddTTFFont : add font file diff --git a/test/pagination/page_test.go b/test/pagination/page_test.go index 4f5fce66..7710c333 100644 --- a/test/pagination/page_test.go +++ b/test/pagination/page_test.go @@ -238,3 +238,30 @@ func TestLineBreak(t *testing.T) { log.Fatalln(err) } } + +func TestHindiRendering(t *testing.T) { + var err error + pdf := &gopdf.GoPdf{} + pageSize := *gopdf.PageSizeA4 + pdf.Start(gopdf.Config{PageSize: pageSize}) + pdf.AddPage() + + err = pdf.AddTTFFontWithOption( + "mangal", + "../res/MangalRegular.ttf", + gopdf.TtfOption{UseKerning: true}, + ) + if err != nil { + log.Print(err.Error()) + return + } + + err = pdf.SetFont("mangal", "", 14) + if err != nil { + log.Print(err.Error()) + return + } + + pdf.Cell(nil, "नमस्ते") + pdf.WritePdf("page_hindi_rendering.pdf") +} diff --git a/test/res/MangalRegular.ttf b/test/res/MangalRegular.ttf new file mode 100644 index 00000000..aa345d15 Binary files /dev/null and b/test/res/MangalRegular.ttf differ diff --git a/ttf_option.go b/ttf_option.go index 60aebfe1..2e6bbe55 100644 --- a/ttf_option.go +++ b/ttf_option.go @@ -1,7 +1,10 @@ package gopdf +import "github.com/go-text/typesetting/font" + // TtfOption font option type TtfOption struct { + face font.Face UseKerning bool Style int //Regular|Bold|Italic OnGlyphNotFound func(r rune) //Called when a glyph cannot be found, just for debugging