//go:build brlapi && cgo package braille /* #cgo pkg-config: liblouis #include #include */ 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" }