Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ doc/body.md
doc/index.html
doc/index.html.ok
coverage.html
.vscode

# macOS
.DS_Store
36 changes: 15 additions & 21 deletions fpdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import (
"strconv"
"strings"
"time"

"github.com/hmmftg/goarabic"
)

var gl struct {
Expand Down Expand Up @@ -2426,17 +2428,19 @@ func (f *Fpdf) Bookmark(txtStr string, level int, y float64) {
f.outlines = append(f.outlines, outlineType{text: txtStr, level: level, y: y, p: f.PageNo(), prev: -1, last: -1, next: -1, first: -1})
}

// rtl: make direction of text rtl
func rtl(str string) string {
return goarabic.FixArabic(str)
}

Comment thread
hmmftg marked this conversation as resolved.
// Text prints a character string. The origin (x, y) is on the left of the
// first character at the baseline. This method permits a string to be placed
// precisely on the page, but it is usually easier to use Cell(), MultiCell()
// or Write() which are the standard methods to print text.
func (f *Fpdf) Text(x, y float64, txtStr string) {
var txt2 string
if f.isCurrentUTF8 {
if f.isRTL {
txtStr = reverseText(txtStr)
x -= f.GetStringWidth(txtStr)
}
x -= f.GetStringWidth(txtStr)
txt2 = f.escape(utf8toutf16(txtStr, false))
for _, uni := range txtStr {
f.currentFont.usedRunes[int(uni)] = int(uni)
Expand Down Expand Up @@ -2546,6 +2550,10 @@ func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
return
}

if f.isRTL {
txtStr = rtl(txtStr)
}

borderStr = strings.ToUpper(borderStr)
k := f.k
if f.y+h > f.pageBreakTrigger && !f.inHeader && !f.inFooter && f.acceptPageBreak() {
Expand Down Expand Up @@ -2647,9 +2655,6 @@ func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
}
//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 = reverseText(txtStr)
}
wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
for _, uni := range txtStr {
f.currentFont.usedRunes[int(uni)] = int(uni)
Expand All @@ -2672,9 +2677,6 @@ func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
} else {
var txt2 string
if f.isCurrentUTF8 {
if f.isRTL {
txtStr = reverseText(txtStr)
}
txt2 = f.escape(utf8toutf16(txtStr, false))
for _, uni := range txtStr {
f.currentFont.usedRunes[int(uni)] = int(uni)
Expand Down Expand Up @@ -2720,17 +2722,6 @@ func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
}
}

// Revert string to use in RTL languages
func reverseText(text string) string {
oldText := []rune(text)
newText := make([]rune, len(oldText))
length := len(oldText) - 1
for i, r := range oldText {
newText[length-i] = r
}
return string(newText)
}

// Cell is a simpler version of CellFormat with no fill, border, links or
// special alignment. The Cell_strikeout() example demonstrates this method.
func (f *Fpdf) Cell(w, h float64, txtStr string) {
Expand Down Expand Up @@ -2834,6 +2825,9 @@ func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill
w = f.w - f.rMargin - f.x
}
wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))

f.isRTL = isRTL(txtStr)

s := strings.Replace(txtStr, "\r", "", -1)
srune := []rune(s)

Expand Down
40 changes: 39 additions & 1 deletion fpdf_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2429,6 +2429,44 @@ func ExampleFpdf_AddUTF8Font() {
// Successfully generated pdf/Fpdf_AddUTF8Font.pdf
}

// ExampleFpdf_RTL demonstrates how use rtl mode
func ExampleFpdf_RTL() {
var fileStr string
var txtStr []byte
var err error

pdf := fpdf.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.ttf"))
pdf.AddUTF8Font("dejavu", "BI", example.FontFile("DejaVuSansCondensed-Bold.ttf"))

fileStr = example.Filename("Fpdf_RTL")
txtStr, err = os.ReadFile(example.TextFile("rtl-test.txt"))
if err == nil {
pdf.SetFont("dejavu", "B", 17)
pdf.MultiCell(100, 8, "تست لغات عربی و فارسی وسط چین", "1", "C", false)
pdf.SetFont("dejavu", "", 14)
pdf.MultiCell(100, 5, string(txtStr), "1", "C", false)
pdf.SetFont("dejavu", "B", 17)
pdf.MultiCell(100, 8, "تست لغات عربی و فارسی راست چین", "1", "R", false)
pdf.SetFont("dejavu", "", 14)
pdf.MultiCell(100, 5, string(txtStr), "1", "R", false)
pdf.SetFont("dejavu", "B", 17)
pdf.MultiCell(100, 8, "تست لغات عربی و فارسی چپ چین", "1", "L", false)
pdf.SetFont("dejavu", "", 14)
pdf.MultiCell(100, 5, string(txtStr), "1", "L", false)
pdf.Ln(15)
pdf.OutputFileAndClose(fileStr)
}
example.SummaryCompare(err, fileStr)
// Output:
// Successfully generated pdf/Fpdf_RTL.pdf
}

// ExampleUTF8CutFont demonstrates how generate a TrueType font subset.
func ExampleUTF8CutFont() {
var pdfFileStr, fullFontFileStr, subFontFileStr string
Expand Down Expand Up @@ -2463,7 +2501,7 @@ func ExampleUTF8CutFont() {
pdf.AddPage()
pdf.AddUTF8Font("calligra", "", subFontFileStr)
pdf.SetFont("calligra", "", fontHt)
write("cabbed")
write("cabbed vwxyz")
write("vwxyz")
pdf.SetFont("courier", "", fontHt)
writeSize(fullFontFileStr)
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ retract (

require (
github.com/boombuler/barcode v1.0.1
github.com/hmmftg/goarabic v0.0.0-20230523174344-63f61ec6e505
github.com/phpdave11/gofpdi v1.0.13
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245
golang.org/x/image v0.6.0
golang.org/x/image v0.7.0
golang.org/x/text v0.9.0
)

require github.com/pkg/errors v0.9.1 // indirect
9 changes: 6 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyX
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hmmftg/goarabic v0.0.0-20230523174344-63f61ec6e505 h1:YOMZSJmlAfUryfZvkzq0m7cw1cA0xrV5ORRzUL8N0Vg=
github.com/hmmftg/goarabic v0.0.0-20230523174344-63f61ec6e505/go.mod h1:72JQM/NN64op0MoliuRYr9tsrDAgeyXfYHMneegbeks=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
Expand All @@ -22,8 +24,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
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/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
Expand All @@ -46,7 +48,8 @@ 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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/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=
Expand Down
Binary file added pdf/reference/ExampleFpdf_RTL.pdf
Binary file not shown.
Binary file modified pdf/reference/Fpdf_AddUTF8Font.pdf
Binary file not shown.
Binary file added pdf/reference/Fpdf_RTL.pdf
Binary file not shown.
47 changes: 47 additions & 0 deletions rtl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2013-2014 Kurt Jung (Gmail: kurt.w.jung)
*
* 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 fpdf
Comment thread
hmmftg marked this conversation as resolved.

var registeredIsRtl func(string) bool = nil

func RegisterIsRtl(method func(string) bool) {
registeredIsRtl = method
}

// IsRtl checks if the text has rtl direction
func isRTL(text string) bool {
if registeredIsRtl != nil {
return registeredIsRtl(text)
}
if len(text) == 0 {
return false
}
r := []rune(text)
//Ranges are taken from : https://stackoverflow.com/questions/12006095/javascript-how-to-check-if-character-is-rtl
if r[0] >= 0x0591 && 0x07FF >= r[0] {
return true
}
if r[0] >= 0xFB1D && 0xFDFD >= r[0] {
return true
}
if r[0] >= 0xFE70 && 0xFEFC >= r[0] {
return true
}
if r[0] == 0x200F || r[0] == 0x202B || r[0] == 0x202E {
return true
}
return false
}
14 changes: 14 additions & 0 deletions text/rtl-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
فارسی با انگلیسی:
سلام abcdefghijklmnopqrstuvwwxyz خوبی xml چرا swf

العربی:
أكل بعض أكثر من هذه الكعك الفرنسي لينة وشرب بعض الشاي.

فارسی:
مقداری دیگر از این نان های نرم فرانسوی بخورید و کمی چای بنوشید.

فارسی با اعداد:
سلام ۱۲۳ خوبی ۲۴۲۳ چرا 123

english:
sample english text همراه arabic لغت inside
37 changes: 9 additions & 28 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"os"
"path/filepath"
"strings"

"golang.org/x/text/encoding/unicode"
)

func must(n int, err error) {
Expand Down Expand Up @@ -78,36 +80,15 @@ func utf8toutf16(s string, withBOM ...bool) string {
if len(withBOM) > 0 {
bom = withBOM[0]
}
res := make([]byte, 0, 8)

bomState := unicode.IgnoreBOM
if bom {
res = append(res, 0xFE, 0xFF)
bomState = unicode.UseBOM
}
nb := len(s)
i := 0
for i < nb {
c1 := byte(s[i])
i++
switch {
case c1 >= 224:
// 3-byte character
c2 := byte(s[i])
i++
c3 := byte(s[i])
i++
res = append(res, ((c1&0x0F)<<4)+((c2&0x3C)>>2),
((c2&0x03)<<6)+(c3&0x3F))
case c1 >= 192:
// 2-byte character
c2 := byte(s[i])
i++
res = append(res, ((c1 & 0x1C) >> 2),
((c1&0x03)<<6)+(c2&0x3F))
default:
// Single-byte character
res = append(res, 0, c1)
}
}
return string(res)

enc := unicode.UTF16(unicode.BigEndian, bomState).NewEncoder()
u, _ := enc.String(s)
return string(u)
}

// intIf returns a if cnd is true, otherwise b
Expand Down