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
Labels
FeatureRequest help wanted NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@hajimehoshi
Copy link
Member

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

/cc @nigeltao

@nigeltao
Copy link
Contributor

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.

@hajimehoshi
Copy link
Member Author

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?

@nigeltao
Copy link
Contributor

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.

@sri-shubham
Copy link

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

@zapkub
Copy link

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
			}

		}

@mrg0lden
Copy link

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.

@nigeltao
Copy link
Contributor

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.

@AbdullahDiaa
Copy link

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

@AbdullahDiaa
Copy link

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)
	}
}

@seankhliao seankhliao added help wanted NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. FeatureRequest labels Aug 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
FeatureRequest help wanted NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

9 participants