blob: 9c119ee6d225590cbeadad15fee207e0ffa43fb6 [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include "log.h"
#include "lock.h"
#include "file.h"
#include "charset.h"
#include "ttb.h"
#include "ttb_internal.h"
#include "brl_dots.h"
#include "brl_types.h"
#include "prefs.h"
static const unsigned char internalTextTableBytes[] = {
#include "ttb.auto.h"
};
static TextTable internalTextTable = {
.header.bytes = internalTextTableBytes,
.size = 0
};
TextTable *textTable = &internalTextTable;
static LockDescriptor *
getTextTableLock (void) {
static LockDescriptor *lock = NULL;
return getLockDescriptor(&lock, "text-table");
}
void
lockTextTable (void) {
obtainExclusiveLock(getTextTableLock());
}
void
unlockTextTable (void) {
releaseLock(getTextTableLock());
}
static inline const void *
getTextTableItem (TextTable *table, TextTableOffset offset) {
return &table->header.bytes[offset];
}
static inline const UnicodeGroupEntry *
getUnicodeGroupEntry (TextTable *table, wchar_t character) {
TextTableOffset offset = table->header.fields->unicodeGroups[UNICODE_GROUP_NUMBER(character)];
if (offset) return getTextTableItem(table, offset);
return NULL;
}
static inline const UnicodePlaneEntry *
getUnicodePlaneEntry (TextTable *table, wchar_t character) {
const UnicodeGroupEntry *group = getUnicodeGroupEntry(table, character);
if (group) {
TextTableOffset offset = group->planes[UNICODE_PLANE_NUMBER(character)];
if (offset) return getTextTableItem(table, offset);
}
return NULL;
}
static inline const UnicodeRowEntry *
getUnicodeRowEntry (TextTable *table, wchar_t character) {
const UnicodePlaneEntry *plane = getUnicodePlaneEntry(table, character);
if (plane) {
TextTableOffset offset = plane->rows[UNICODE_ROW_NUMBER(character)];
if (offset) return getTextTableItem(table, offset);
}
return NULL;
}
static inline const unsigned char *
getUnicodeCell (TextTable *table, wchar_t character) {
const UnicodeRowEntry *row = getUnicodeRowEntry(table, character);
if (row) {
unsigned int cellNumber = UNICODE_CELL_NUMBER(character);
if (BITMASK_TEST(row->cellDefined, cellNumber)) return &row->cells[cellNumber];
}
return NULL;
}
void
setTryBaseCharacter (TextTable *table, unsigned char yes) {
table->options.tryBaseCharacter = yes;
}
static int
searchTextTableAlias (const void *target, const void *element) {
const wchar_t *reference = target;
const TextTableAliasEntry *alias = element;
if (*reference < alias->from) return -1;
if (*reference > alias->from) return 1;
return 0;
}
const TextTableAliasEntry *
locateTextTableAlias (wchar_t character, const TextTableAliasEntry *array, size_t count) {
const TextTableAliasEntry *alias = bsearch(
&character, array, count, sizeof(*array), searchTextTableAlias
);
if (alias) return alias;
return NULL;
}
static const TextTableAliasEntry *
findTextTableAlias (TextTable *table, wchar_t character) {
const TextTableHeader *header = table->header.fields;
return locateTextTableAlias(character, getTextTableItem(table, header->aliasArray), header->aliasCount);
}
static int
getDotsForAliasedCharacter (TextTable *table, wchar_t *character, unsigned char *dots) {
unsigned int iterationLimit = 0X10;
wchar_t characterEncountered[iterationLimit];
unsigned int iterationNumber = 0;
while (iterationNumber < iterationLimit) {
if (wmemchr(characterEncountered, *character, iterationNumber)) break;
characterEncountered[iterationNumber++] = *character;
const UnicodeRowEntry *row = getUnicodeRowEntry(table, *character);
if (row) {
unsigned int cellNumber = UNICODE_CELL_NUMBER(*character);
if (BITMASK_TEST(row->cellDefined, cellNumber)) {
*dots = row->cells[cellNumber];
return 1;
}
if (BITMASK_TEST(row->cellAliased, cellNumber)) {
const TextTableAliasEntry *alias = findTextTableAlias(table, *character);
if (alias) {
*character = alias->to;
continue;
}
}
}
break;
}
return 0;
}
typedef struct {
TextTable *const table;
unsigned char dots;
} SetBrailleRepresentationData;
static int
setBrailleRepresentation (wchar_t character, void *data) {
SetBrailleRepresentationData *sbr = data;
const unsigned char *cell = getUnicodeCell(sbr->table, character);
if (cell) {
sbr->dots = *cell;
return 1;
}
return 0;
}
unsigned char
convertCharacterToDots (TextTable *table, wchar_t character) {
uint32_t row = character & ~UNICODE_CELL_MASK;
switch (row) {
#if WCHAR_MAX >= UINT16_MAX
case 0XF000: {
wint_t wc = convertCharToWchar(character & UNICODE_CELL_MASK);
if (wc == WEOF) break;
character = wc;
}
/* fall through */
#endif /* WCHAR_MAX >= UINT16_MAX */
default: {
{
unsigned char dots;
if (getDotsForAliasedCharacter(table, &character, &dots)) return dots;
}
if (character == UNICODE_REPLACEMENT_CHARACTER) break;
if (table->options.tryBaseCharacter) {
SetBrailleRepresentationData sbr = {
.table = table,
.dots = 0
};
if (handleBestCharacter(character, setBrailleRepresentation, &sbr)) {
return sbr.dots;
}
}
break;
}
}
if (row == UNICODE_BRAILLE_ROW) {
return character & UNICODE_CELL_MASK;
}
{
const unsigned char *cell = table->cells.replacementCharacter;
if (cell) return *cell;
}
return BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6 | BRL_DOT_7 | BRL_DOT_8;
}
wchar_t
convertDotsToCharacter (TextTable *table, unsigned char dots) {
const TextTableHeader *header = table->header.fields;
if (BITMASK_TEST(header->inputCharacterDefined, dots)) return header->inputCharacters[dots];
return UNICODE_REPLACEMENT_CHARACTER;
}
wchar_t
convertInputToCharacter (unsigned char dots) {
switch (prefs.brailleTypingMode) {
case BRL_TYPING_TEXT:
return convertDotsToCharacter(textTable, dots);
default:
logMessage(LOG_WARNING, "unknown braille typing mode: %u", prefs.brailleTypingMode);
/* fall through */
case BRL_TYPING_DOTS:
return UNICODE_BRAILLE_ROW | dots;
}
}
int
replaceTextTable (const char *directory, const char *name) {
TextTable *newTable = NULL;
if (*name) {
char *path;
if ((path = makeTextTablePath(directory, name))) {
logMessage(LOG_DEBUG, "compiling text table: %s", path);
if (!(newTable = compileTextTable(path))) {
logMessage(LOG_ERR, "%s: %s", gettext("cannot compile text table"), path);
}
free(path);
}
} else {
newTable = &internalTextTable;
}
if (newTable) {
TextTable *oldTable = textTable;
lockTextTable();
textTable = newTable;
unlockTextTable();
destroyTextTable(oldTable);
return 1;
}
logMessage(LOG_ERR, "%s: %s", gettext("cannot load text table"), name);
return 0;
}
size_t
getTextTableRowsMask (TextTable *table, uint8_t *mask, size_t size) {
size_t result = 0;
memset(mask, 0, size);
for (unsigned int groupNumber=0; groupNumber<UNICODE_GROUP_COUNT; groupNumber+=1) {
TextTableOffset groupOffset = table->header.fields->unicodeGroups[groupNumber];
if (groupOffset) {
const UnicodeGroupEntry *group = getTextTableItem(table, groupOffset);
for (unsigned int planeNumber=0; planeNumber<UNICODE_PLANES_PER_GROUP; planeNumber+=1) {
TextTableOffset planeOffset = group->planes[planeNumber];
if (planeOffset) {
const UnicodePlaneEntry *plane = getTextTableItem(table, planeOffset);
for (unsigned int rowNumber=0; rowNumber<UNICODE_ROWS_PER_PLANE; rowNumber+=1) {
TextTableOffset rowOffset = plane->rows[rowNumber];
if (rowOffset) {
uint32_t row = UNICODE_CHARACTER(groupNumber, planeNumber, rowNumber, 0) >> UNICODE_ROW_SHIFT;
uint32_t index = row / 8;
if (index >= size) goto done;
mask[index] |= 1 << (row % 8);
result = index + 1;
}
}
}
}
}
}
done:
return result;
}
int
getTextTableRowCells (TextTable *table, uint32_t rowIndex, uint8_t *cells, uint8_t *defined) {
wchar_t character = rowIndex << UNICODE_ROW_SHIFT;
const UnicodeRowEntry *row = getUnicodeRowEntry(table, character);
if (!row) return 0;
int maskIndex = -1;
uint8_t maskBit = 0;
for (unsigned int cellNumber=0; cellNumber<UNICODE_CELLS_PER_ROW; cellNumber+=1) {
unsigned char *cell = &cells[cellNumber];
*cell = 0;
if (!maskBit) {
defined[++maskIndex] = 0;
maskBit = 1;
}
if (BITMASK_TEST(row->cellDefined, cellNumber)) {
*cell = row->cells[cellNumber];
defined[maskIndex] |= maskBit;
} else if (BITMASK_TEST(row->cellAliased, cellNumber)) {
wchar_t wc = character | (cellNumber << UNICODE_CELL_SHIFT);
unsigned char dots;
if (getDotsForAliasedCharacter(table, &wc, &dots)) {
*cell = dots;
defined[maskIndex] |= maskBit;
}
}
maskBit <<= 1;
}
return 1;
}