Files
cthulhu/internal/braille/translate_native.go

118 lines
3.5 KiB
Go

//go:build brlapi && cgo
package braille
/*
#cgo pkg-config: liblouis
#include <liblouis/liblouis.h>
#include <stdlib.h>
*/
import "C"
import (
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"unsafe"
)
// translate retains liblouis's output-to-input positions so a routing key
// addresses the original text rather than a cell in the shortened line.
func translate(text, table string, cursor int, expand bool) (string, []int, int, error) {
if table == "" {
table = defaultContractionTable()
}
cTable := C.CString(table)
defer C.free(unsafe.Pointer(cTable))
if C.lou_checkTable(cTable) == 0 {
return "", nil, 0, fmt.Errorf("invalid liblouis table %q", table)
}
source := []rune(text)
in := make([]C.widechar, len(source))
for index, value := range source {
in[index] = C.widechar(value)
}
// Supply a bounded output buffer; translation fails if a table needs more.
out := make([]C.widechar, len(source)*8+32)
inputPos := make([]C.int, len(out))
outputPos := make([]C.int, len(in))
inLength, outLength := C.int(len(in)), C.int(len(out))
cursorPos := C.int(cursor)
mode := C.int(C.dotsIO)
if expand && cursor >= 0 && cursor < len(source) && source[cursor] != ' ' {
mode |= C.compbrlAtCursor
}
if C.lou_translate(cTable, &in[0], &inLength, &out[0], &outLength, nil, nil,
&outputPos[0], &inputPos[0], &cursorPos, mode) == 0 {
return "", nil, 0, errors.New("liblouis translation failed")
}
if int(inLength) != len(in) || outLength < 0 || int(outLength) > len(out) {
return "", nil, 0, errors.New("liblouis returned invalid translation lengths")
}
result := make([]rune, int(outLength))
positions := make([]int, int(outLength))
for index := range result {
if out[index]&0x8000 == 0 || out[index]&^C.widechar(0x80ff) != 0 {
return "", nil, 0, errors.New("liblouis returned invalid braille dots")
}
result[index] = 0x2800 + rune(out[index]&0xff)
positions[index] = int(inputPos[index])
if positions[index] < 0 || positions[index] >= len(source) {
return "", nil, 0, errors.New("liblouis returned invalid position map")
}
}
if cursor == len(source) {
cursorPos = outLength
}
if cursor < 0 {
cursorPos = -1
}
if cursorPos < -1 || cursorPos > outLength {
return "", nil, 0, errors.New("liblouis returned invalid cursor")
}
return string(result), positions, int(cursorPos), nil
}
func defaultContractionTable() string {
locale := ""
for _, name := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
if value := os.Getenv(name); value != "" {
locale = value
break
}
}
locale = strings.ToLower(strings.ReplaceAll(strings.Split(strings.Split(locale, ".")[0], "@")[0], "_", "-"))
if locale == "" || locale == "c" || locale == "posix" {
locale = "en-us"
}
entries, err := os.ReadDir("/usr/share/liblouis/tables")
if err != nil {
return "en-us-g2.ctb"
}
var candidates []string
for _, entry := range entries {
name := strings.ToLower(entry.Name())
if entry.IsDir() || !strings.HasPrefix(name, locale+"-") ||
!(strings.HasSuffix(name, ".ctb") || strings.HasSuffix(name, ".utb")) ||
strings.Contains(name, "interline") || strings.Contains(name, "mathtext") {
continue
}
candidates = append(candidates, entry.Name())
}
sort.Strings(candidates)
for _, grade := range []string{"g2", "g1", "comp6", "comp8"} {
for _, name := range candidates {
if strings.Contains(name, grade) {
return filepath.Join("/usr/share/liblouis/tables", name)
}
}
}
if len(candidates) > 0 {
return filepath.Join("/usr/share/liblouis/tables", candidates[0])
}
return "en-us-g2.ctb"
}