blob: f0c597f981b391554871a82027a7f2c7a5003905 [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 <ctype.h>
#include "log.h"
#include "strfmt.h"
#include "alert.h"
#include "brl_cmds.h"
#include "unicode.h"
#include "ascii.h"
#include "scr.h"
#include "core.h"
void
alertLineSkipped (unsigned int *count) {
const unsigned int interval = 4;
if (!*count) {
alert(ALERT_SKIP_FIRST);
} else if (*count <= interval) {
alert(ALERT_SKIP_ONE);
} else if (!(*count % interval)) {
alert(ALERT_SKIP_SEVERAL);
}
*count += 1;
}
int
isTextOffset (int arg, int *first, int *last, int relaxed) {
int y = arg / brl.textColumns;
if (y >= brl.textRows) return 0;
if ((ses->winy + y) >= scr.rows) return 0;
int x = arg % brl.textColumns;
if (x < textStart) return 0;
if ((x -= textStart) >= textCount) return 0;
if (isContracted) {
BrailleRowDescriptor *brd = getBrailleRowDescriptor(y);
if (!brd) return 0;
int *offsets = brd->contracted.offsets.array;
if (!offsets) return 0;
int start = 0;
int end = 0;
{
int textIndex = 0;
while (textIndex < brd->contracted.length) {
int cellIndex = offsets[textIndex];
if (cellIndex != CTB_NO_OFFSET) {
if (cellIndex > x) {
end = textIndex - 1;
break;
}
start = textIndex;
}
textIndex += 1;
}
if (textIndex == brd->contracted.length) end = textIndex - 1;
}
if (first) *first = start;
if (last) *last = end;
} else {
if ((ses->winx + x) >= scr.cols) {
if (!relaxed) return 0;
x = scr.cols - ses->winx - 1;
}
if (prefs.wordWrap) {
int length = getWordWrapLength(ses->winy, ses->winx, textCount);
if (length > textCount) length = textCount;
if (x >= length) x = length - 1;
}
if (first) *first = x;
if (last) *last = x;
}
return 1;
}
int
getCharacterCoordinates (int arg, int *row, int *first, int *last, int relaxed) {
if (arg == BRL_MSK_ARG) {
if (!SCR_CURSOR_OK()) return 0;
*row = scr.posy;
if (first) *first = scr.posx;
if (last) *last = scr.posx;
} else {
if (!isTextOffset(arg, first, last, relaxed)) return 0;
if (row) *row = ses->winy;
if (first) *first += ses->winx;
if (last) *last += ses->winx;
}
return 1;
}
static ScreenCharacter
getScreenCharacter (int column, int row) {
ScreenCharacter character;
readScreen(column, row, 1, 1, &character);
return character;
}
STR_BEGIN_FORMATTER(formatCharacterDescription, int column, int row)
ScreenCharacter character = getScreenCharacter(column, row);
{
char name[0X40];
if (getCharacterName(character.text, name, sizeof(name))) {
{
size_t length = strlen(name);
for (int i=0; i<length; i+=1) name[i] = tolower(name[i]);
}
STR_PRINTF(" %s: ", name);
}
}
{
uint32_t text = character.text;
STR_PRINTF("U+%04" PRIX32 " (%" PRIu32 "):", text, text);
}
{
STR_PRINTF(" ");
static const char *const colours[] = {
/* */ strtext("black"),
/* B */ strtext("blue"),
/* G */ strtext("green"),
/* GB */ strtext("cyan"),
/* R */ strtext("red"),
/* R B */ strtext("magenta"),
/* RG */ strtext("brown"),
/* RGB */ strtext("light grey"),
/* L */ strtext("dark grey"),
/* L B */ strtext("light blue"),
/* L G */ strtext("light green"),
/* L GB */ strtext("light cyan"),
/* LR */ strtext("light red"),
/* LR B */ strtext("light magenta"),
/* LRG */ strtext("yellow"),
/* LRGB */ strtext("white")
};
unsigned char attributes = character.attributes;
const char *foreground = gettext(colours[attributes & SCR_MASK_FG]);
const char *background = gettext(colours[(attributes & SCR_MASK_BG) >> 4]);
// xgettext: This phrase describes the colour of a character on the screen.
// xgettext: %1$s is the (already translated) foreground colour.
// xgettext: %2$s is the (already translated) background colour.
STR_PRINTF(gettext("%1$s on %2$s"), foreground, background);
}
if (character.attributes & SCR_ATTR_BLINK) {
STR_PRINTF(" %s", gettext("blinking"));
}
STR_END_FORMATTER
static const char *const phoneticWords[] = {
[' '] = "space",
['a'] = "alpha",
['b'] = "bravo",
['c'] = "charlie",
['d'] = "delta",
['e'] = "echo",
['f'] = "foxtrot",
['g'] = "golf",
['h'] = "hotel",
['i'] = "india",
['j'] = "juliet",
['k'] = "kilo",
['l'] = "lima",
['m'] = "mike",
['n'] = "november",
['o'] = "oscar",
['p'] = "papa",
['q'] = "quebec",
['r'] = "romeo",
['s'] = "sierra",
['t'] = "tango",
['u'] = "uniform",
['v'] = "victor",
['w'] = "whiskey",
['x'] = "x-ray",
['y'] = "yankee",
['z'] = "zulu",
['0'] = "zero",
['1'] = "one",
['2'] = "two",
['3'] = "three",
['4'] = "four",
['5'] = "five",
['6'] = "six",
['7'] = "seven",
['8'] = "eight",
['9'] = "nine",
['+'] = "plus",
['='] = "equals",
['<'] = "less than",
['>'] = "greater than",
['('] = "left parenthesis",
[')'] = "right parenthesis",
['['] = "left bracket",
[']'] = "right bracket",
['{'] = "left brace",
['}'] = "right brace",
['"'] = "quote",
['\''] = "apostrophe",
[','] = "comma",
[';'] = "semicolon",
[':'] = "colon",
['.'] = "period",
['!'] = "exclamation",
['?'] = "question",
['`'] = "grave",
['~'] = "tilde",
['@'] = "at",
['#'] = "number",
['$'] = "dollar",
['%'] = "percent",
['^'] = "circumflex",
['&'] = "ampersand",
['*'] = "asterisk",
['-'] = "dash",
['_'] = "underscore",
['/'] = "slash",
['\\'] = "backslash",
['|'] = "vertical bar",
[ASCII_NUL] = "null",
[ASCII_SOH] = "start of header",
[ASCII_STX] = "start of text",
[ASCII_ETX] = "end of text",
[ASCII_EOT] = "end of transmission",
[ASCII_ENQ] = "enquiry",
[ASCII_ACK] = "acknowledgement",
[ASCII_BEL] = "bell",
[ASCII_BS] = "backspace",
[ASCII_HT] = "horizontal tab",
[ASCII_LF] = "line feed",
[ASCII_VT] = "vertical tab",
[ASCII_FF] = "form feed",
[ASCII_CR] = "carriage return",
[ASCII_SO] = "shift out",
[ASCII_SI] = "shift in",
[ASCII_DLE] = "data link escape",
[ASCII_DC1] = "device control one",
[ASCII_DC2] = "device control two",
[ASCII_DC3] = "device control three",
[ASCII_DC4] = "device control four",
[ASCII_NAK] = "negative acknowledgement",
[ASCII_SYN] = "synchronous idle",
[ASCII_ETB] = "end of transmission block",
[ASCII_CAN] = "cancel",
[ASCII_EM] = "end of medium",
[ASCII_SUB] = "substitute",
[ASCII_ESC] = "escape",
[ASCII_FS] = "file separator",
[ASCII_GS] = "group separator",
[ASCII_RS] = "record separator",
[ASCII_US] = "unit separator",
[ASCII_DEL] = "delete",
};
static const char *
getPhoneticWord (wchar_t character) {
if (character >= ARRAY_COUNT(phoneticWords)) return NULL;
return phoneticWords[character];
}
STR_BEGIN_FORMATTER(formatPhoneticPhrase, int column, int row)
wchar_t character = getScreenCharacter(column, row).text;
wchar_t characters[0X10];
size_t characterCount = decomposeCharacter(character, characters, ARRAY_COUNT(characters));
if (!characterCount) {
characters[0] = character;
characterCount = 1;
}
for (unsigned int characterIndex=0; characterIndex<characterCount; characterIndex+=1) {
if (characterIndex > 0) {
STR_PRINTF("%s ", ((characterIndex == 1)? " with": ","));
}
character = characters[characterIndex];
const char *word = getPhoneticWord(character);
char nameBuffer[0X40];
if (!word) {
if (iswupper(character)) {
wchar_t lowercase = towlower(character);
word = getPhoneticWord(lowercase);
if (word) {
STR_PRINTF("cap ");
character = lowercase;
}
}
}
if (!word) {
if (getCharacterName(character, nameBuffer, sizeof(nameBuffer))) {
word = nameBuffer;
{
char *byte = nameBuffer;
while (*byte) {
*byte = tolower((unsigned char)*byte);
byte += 1;
}
}
{
const char *space = strchr(word, ' ');
if (space) {
size_t length = space - word + 1;
if (memcmp(word, "combining ", length) == 0) word += length;
}
}
}
}
if (word) {
if (STR_LENGTH > 0) STR_PRINTF(" ");
STR_PRINTF("%s", word);
}
}
STR_END_FORMATTER