golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.1k stars 17.55k forks source link

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

Open hajimehoshi opened 6 years ago

hajimehoshi commented 6 years ago

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.

agnivade commented 6 years ago

/cc @nigeltao

nigeltao commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 3 years ago

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

zapkub commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

AbdullahDiaa commented 3 years ago

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