Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/image/font: rendering texts in Arabic #27281

Open
hajimehoshi opened this issue Aug 27, 2018 · 10 comments
Open

x/image/font: rendering texts in Arabic #27281

hajimehoshi opened this issue Aug 27, 2018 · 10 comments
Milestone

Comments

@hajimehoshi
Copy link
Member

@hajimehoshi hajimehoshi commented Aug 27, 2018

What version of Go are you using (go version)?

go version go1.11 darwin/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/hajimehoshi/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/hajimehoshi/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/hajimehoshi/fonttest/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/b7/w11sqqrx7kx6fqfbn24wdsmh0000gn/T/go-build758210799=/tmp/go-build -gno-record-gcc-switches -fno-common"       

What did you do?

I tested to render a text in Arabic language:

package main

import (
        "flag"
        "image"
        "image/color"
        "image/draw"
        "image/png"
        "io/ioutil"
        "os"
        "strings"

        "github.com/golang/freetype/truetype"
        "github.com/pkg/browser"
        "golang.org/x/image/font"
        "golang.org/x/image/math/fixed"
)

const (
        // https://www.unicode.org/udhr/                                                                                                                                                                                                      
        text = "قوقحلاو ةماركلا يف نيواستم ارًارحأ سانلا عيمج دلوي."
)

func run() error {
        const (
                ox = 16
                oy = 16
        )

        width := 640
        height := 16*len(strings.Split(strings.TrimSpace(text), "\n")) + 8

        dst := image.NewRGBA(image.Rect(0, 0, width, height))
        draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.ZP, draw.Src)

        // I tested an arg "/Library/Fonts/Al Nile.ttc" on macOS.                                                                                                                                                                             
        f, err := ioutil.ReadFile(os.Args[1])
        if err != nil {
                return err
        }

        tt, err := truetype.Parse(f)
        if err != nil {
                return err
        }

        face := truetype.NewFace(tt, &truetype.Options{
                Size:    16,
                DPI:     72,
                Hinting: font.HintingFull,
        })

        d := font.Drawer{
                Dst:  dst,
                Src:  image.NewUniform(color.Black),
                Face: face,
                Dot:  fixed.P(ox, oy),
        }

        for _, l := range strings.Split(text, "\n") {
                d.DrawString(l)
                d.Dot.X = fixed.I(ox)
                d.Dot.Y += face.Metrics().Height
        }

        path := "example.png"
        fout, err := os.Create(path)
        if err != nil {
                return err
        }
        defer fout.Close()

        if err := png.Encode(fout, d.Dst); err != nil {
                return err
        }

        if err := browser.OpenFile(path); err != nil {
                return err
        }
        return nil
}

func main() {
        flag.Parse()
        if err := run(); err != nil {
                panic(err)
        }
}

What did you expect to see?

The text is rendered correctly in an image.

What did you see instead?

The text is rendered wrongly:

example

I was also wondering how to render texts in complex languages like Thai or Hindi.

@gopherbot gopherbot added this to the Unreleased milestone Aug 27, 2018
@hajimehoshi hajimehoshi changed the title x/image/font: rendering texts in an RTL language like Arabic x/image/font: rendering texts in Arabic Aug 27, 2018
@agnivade
Copy link
Contributor

@agnivade agnivade commented Aug 28, 2018

/cc @nigeltao

Loading

@nigeltao
Copy link
Contributor

@nigeltao nigeltao commented Aug 29, 2018

Yeah, text shaping isn't implemented yet. There's no Go equivalent of HarfBuzz yet.

That's certainly a bug I'd like to see fixed, but it'd be a lot of work, and I don't have any free time. Sorry.

Loading

@hajimehoshi
Copy link
Member Author

@hajimehoshi hajimehoshi commented Aug 29, 2018

Thanks.

BTW, I was wondering if the current font.Face interface assumes text shaping like Arabic or not. If not, wouldn't this be not only a lack of implementation problem but also an API issue?

Loading

@nigeltao
Copy link
Contributor

@nigeltao nigeltao commented Aug 29, 2018

It would need API design work, which I'd expect to be a lot of work. It's not's just a lack of implementation.

Loading

@sri-shubham
Copy link

@sri-shubham sri-shubham commented Nov 13, 2020

I am facing same issue with Devanagari Fonts; Is there a solution yet?

Loading

@zapkub
Copy link

@zapkub zapkub commented Dec 23, 2020

I am also facing this problem ( with the Thai language ) which needs to have a special rule to adjust the position of the vowel and tone notation.

For reference
https://mirror.las.iastate.edu/tex-archive/language/chinese/CJK/cjk-4.8.4/doc/pdf/c90.pdf

Can anyone suggest to me base way to enhance this? I am about to work on freetype package

here is some POC of vertical adjustment by applying the logic of Thai glyph combination from the reference above

Input is

มั้ย

Before

image

After

image

func (c *Context) DrawString(s string, p fixed.Point26_6) (fixed.Point26_6, error) {
	if c.f == nil {
		return fixed.Point26_6{}, errors.New("freetype: DrawText called with a nil font")
	}
	var prevrune rune
	prev, hasPrev := truetype.Index(0), false
	for _, rune := range s {
		index := c.f.Index(rune)
		if hasPrev {
			kern := c.f.Kern(c.scale, prev, index)
			if c.hinting != font.HintingNone {
				kern = (kern + 32) &^ 63
			}
			p.X += kern

                         // classify glyph type and apply combination 
			// adjust vertical position for thai character
			if thLigatureClass(prevrune).isUpper() && thLigatureClass(rune).isTop() {
				index = index + 1
			}

		}

Loading

@mrg0lden
Copy link

@mrg0lden mrg0lden commented Apr 17, 2021

What could be done to move this issue? There's an Arabic shaping package (partial shaping*) goarabic. I'm not really familiar with font shaping, etc, but it works

This is a demo example using x/image/font/opentype and goarabic, replace the font file name.

package main

import (
	"image"
	"image/draw"
	"image/png"
	"io"
	"os"

	"github.com/salsowelim/goarabic"
	"golang.org/x/image/font"
	"golang.org/x/image/font/opentype"
	"golang.org/x/image/math/fixed"
)

func main() {
	file, err := os.Open("font-file-name-here")
	defer file.Close()
	if err != nil {
		panic(err)
	}

	fileBytes, err := io.ReadAll(file)

	parsedFont, err := opentype.Parse(fileBytes)
	if err != nil {
		panic(err)
	}

	fontface, err := opentype.NewFace(parsedFont, &opentype.FaceOptions{
		Size:    40,
		DPI:     72,
		Hinting: font.HintingFull,
	})

	if err != nil {
		panic(err)
	}

	dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
	draw.Draw(dst, dst.Bounds(), image.White, image.Point{}, draw.Src)

	drawer := &font.Drawer{
		Dst:  dst,
		Src:  image.Black,
		Face: fontface,
	}

	str := "بسم الله الرحمن الرحيم"

	str = goarabic.ToGlyph(str)

	str = goarabic.Reverse(str)

	drawer.Dot = fixed.P(20, 80)
	drawer.DrawString(str)

	out, err := os.Create("out.png")
	if err != nil {
		panic(err)
	}

	defer func() {
		err = out.Close()
		if err != nil {
			panic(err)
		}
	}()

	err = png.Encode(out, dst)
	if err != nil {
		panic(err)
	}

}

*From my experience, it works as it should, but it's noted by the author that some cases aren't handled yet.

Loading

@nigeltao
Copy link
Contributor

@nigeltao nigeltao commented Apr 22, 2021

What could be done to move this issue?

I don't have an easy answer. The hard bit is designing a shaping API (for all scripts, not just Arabic and not just Thai), and I don't have much spare time to review any API design proposals, let alone work on implementation. I'm also hesitant to land an API if it's difficult to modify later without breaking people.

The best way forward would be for someone to fork this package and experiment with their own shaping API design and implementation. If it looks like it solves the problem well, we can talk about rolling into x/image/font per se.

Loading

@AbdullahDiaa
Copy link

@AbdullahDiaa AbdullahDiaa commented Jun 16, 2021

Here's a library to reshape arabic text for rendering on images:
https://github.com/AbdullahDiaa/ar-golang

Loading

@AbdullahDiaa
Copy link

@AbdullahDiaa AbdullahDiaa commented Jun 17, 2021

Output:
image

Sample code:

package main

import (
	"image"
	"image/color"
	"image/png"
	"io/ioutil"
	"log"
	"os"

	"github.com/abdullahdiaa/garabic"
	"golang.org/x/image/font"
	"golang.org/x/image/font/opentype"
	"golang.org/x/image/math/fixed"
)

func addLabel(img *image.RGBA, x, y int, label string) {
	//Load font file
	//You can download amiri font from this link: https://fonts.google.com/specimen/Amiri?preview.text=%D8%A8%D9%90%D8%A7%D9%84%D8%B9%D9%8E%D8%B1%D9%8E%D8%A8%D9%90%D9%91%D9%8A&preview.text_type=custom#standard-styles
	b, err := ioutil.ReadFile("Amiri-Regular.ttf")
	if err != nil {
		log.Println(err)
		return
	}

	ttf, err := opentype.Parse(b)
	if err != nil {
		log.Println(err)
		return
	}
	//Create Font.Face from font
	face, err := opentype.NewFace(ttf, &opentype.FaceOptions{
		Size:    26,
		DPI:     72,
		Hinting: font.HintingNone,
	})

	d := &font.Drawer{
		Dst:  img,
		Src:  image.NewUniform(color.RGBA{120, 157, 243, 255}),
		Face: face,
		Dot:  fixed.P(x, y),
	}

	d.DrawString(label)
}

func main() {
	img := image.NewRGBA(image.Rect(0, 0, 700, 70))
	addLabel(img, 40, 40, garabic.Shape("قِفا نَبكِ مِن ذِكرى حَبيبٍ وَمَنزِلِ   ****   بِسِقطِ اللِوى بَينَ الدَخولِ فَحَومَلِ"))

	f, err := os.Create("printed_arabic_text.png")
	if err != nil {
		panic(err)
	}
	defer f.Close()
	if err := png.Encode(f, img); err != nil {
		panic(err)
	}
}

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
8 participants