From ab27ea1804ac68017cbe5b80722ffa853c8c699e Mon Sep 17 00:00:00 2001 From: Benoit KUGLER Date: Tue, 2 Dec 2025 10:31:27 +0100 Subject: [PATCH 1/2] [render] use LineWrapper to compute bidi visual index; also set the overall direction of the string --- go.mod | 6 +++--- go.sum | 42 ++++++++++++++++++++++++++++++------- render.go | 14 +++++++++++++ render_test.go | 25 ++++++++++++++++++++++ testdata/mixed_ltr_rtl.png | Bin 0 -> 12389 bytes 5 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 testdata/mixed_ltr_rtl.png diff --git a/go.mod b/go.mod index 365d5f8..c4948d7 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module github.com/go-text/render go 1.19 require ( - github.com/go-text/typesetting v0.2.0 - github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 + github.com/go-text/typesetting v0.3.0 + github.com/go-text/typesetting-utils v0.0.0-20250620161931-017769003e3c github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef golang.org/x/image v0.23.0 ) require ( - golang.org/x/net v0.9.0 // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/text v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index b4b89b5..d886b3e 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,8 @@ -github.com/go-text/typesetting v0.2.0 h1:fbzsgbmk04KiWtE+c3ZD4W2nmCRzBqrqQOvYlwAOdho= -github.com/go-text/typesetting v0.2.0/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I= -github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY= -github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4= +github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY= +github.com/go-text/typesetting-utils v0.0.0-20250620161931-017769003e3c h1:EkVgKYSt3+6Y2FdS5TDAe8WRSVpFzWaMoSk0KVzVy2c= +github.com/go-text/typesetting-utils v0.0.0-20250620161931-017769003e3c/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= @@ -9,39 +10,64 @@ github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+g 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.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= 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 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -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 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +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/sync v0.10.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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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/render.go b/render.go index 9671330..3435514 100644 --- a/render.go +++ b/render.go @@ -1,6 +1,7 @@ package render import ( + "fmt" "image/color" "image/draw" "math" @@ -28,6 +29,7 @@ type Renderer struct { segmenter shaping.Segmenter shaper shaping.HarfbuzzShaper + wrapper shaping.LineWrapper filler *rasterx.Filler fillerScale float32 } @@ -51,6 +53,18 @@ func (r *Renderer) shape(str string, face *font.Face) (_ shaping.Line, ascent in ascent = a } } + + // // overall direction of the text, deduced from the first runes + // direction := line[0].Direction + // r.wrapper.Prepare(shaping.WrapConfig{Direction: direction}, text, shaping.NewSliceIterator(line)) + // wrapped, _ := r.wrapper.WrapNextLine(math.MaxInt) + // line = wrapped.Line + + // // sort the line by visual order + // sort.Slice(line, func(i, j int) bool { return line[i].VisualIndex < line[j].VisualIndex }) + + fmt.Println(line, ascent) + return line, ascent } diff --git a/render_test.go b/render_test.go index a8e99a5..f7066a2 100644 --- a/render_test.go +++ b/render_test.go @@ -171,5 +171,30 @@ func TestBitmapBaseline(t *testing.T) { if !bytes.Equal(pngBytes.Bytes(), reference) { t.Error("unexpected image output") } +} + +func TestMixedLTR_RTL(t *testing.T) { + s := "وَرَسُولِهِ وَإِنْ تُبْتُمْ فَلَكُمْ رُءُوسُ hello أَمْوَالِكُمْ لَا تَظْلِمُونَ وَلَا تُظْلَمُونَ ( 279 ) وَإِنْ كَانَ ذُو عُسْرَةٍ فَنَظِرَةٌ إِلَى مَيْسَرَةٍ" + + data, _ := ot.Files.ReadFile("common/NotoSansArabic.ttf") + face, _ := font.ParseTTF(bytes.NewReader(data)) + + img := image.NewNRGBA(image.Rect(0, 0, 800, 40)) + draw.Draw(img, img.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src) + + r := render.Renderer{FontSize: 15, Color: color.Black} + r.DrawString(s, img, face) + + // w, _ := os.Create("testdata/mixed_ltr_rtl.png") + // _ = png.Encode(w, img) + // w.Close() + + // compare against the reference + var pngBytes bytes.Buffer + png.Encode(&pngBytes, img) + reference, _ := os.ReadFile("testdata/mixed_ltr_rtl.png") + if !bytes.Equal(pngBytes.Bytes(), reference) { + t.Error("unexpected image output") + } } diff --git a/testdata/mixed_ltr_rtl.png b/testdata/mixed_ltr_rtl.png new file mode 100644 index 0000000000000000000000000000000000000000..0b44d14d95788f346b211861a6680b7981588287 GIT binary patch literal 12389 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 Zqdy)vYHCd2zYh>-p4C;&QML*B{{a5JP$K{U literal 0 HcmV?d00001 From dbef80dfee8ab4f5de676d255e0b5604365b6928 Mon Sep 17 00:00:00 2001 From: Benoit KUGLER Date: Tue, 2 Dec 2025 10:36:49 +0100 Subject: [PATCH 2/2] apply changes --- render.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/render.go b/render.go index 3435514..406245b 100644 --- a/render.go +++ b/render.go @@ -1,10 +1,10 @@ package render import ( - "fmt" "image/color" "image/draw" "math" + "sort" "github.com/go-text/typesetting/font" "github.com/go-text/typesetting/font/opentype" @@ -54,16 +54,14 @@ func (r *Renderer) shape(str string, face *font.Face) (_ shaping.Line, ascent in } } - // // overall direction of the text, deduced from the first runes - // direction := line[0].Direction - // r.wrapper.Prepare(shaping.WrapConfig{Direction: direction}, text, shaping.NewSliceIterator(line)) - // wrapped, _ := r.wrapper.WrapNextLine(math.MaxInt) - // line = wrapped.Line + // overall direction of the text, deduced from the first runes + direction := line[0].Direction + r.wrapper.Prepare(shaping.WrapConfig{Direction: direction}, text, shaping.NewSliceIterator(line)) + wrapped, _ := r.wrapper.WrapNextLine(math.MaxInt) + line = wrapped.Line - // // sort the line by visual order - // sort.Slice(line, func(i, j int) bool { return line[i].VisualIndex < line[j].VisualIndex }) - - fmt.Println(line, ascent) + // sort the line by visual order + sort.Slice(line, func(i, j int) bool { return line[i].VisualIndex < line[j].VisualIndex }) return line, ascent }