/* * 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-2018 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.com/ * * This software is maintained by Dave Mielke . */ #include "prologue.h" #include #include "log.h" #include "ctb_translate.h" #include "ttb.h" #include "prefs.h" #ifdef HAVE_ICU #include #include static int nextBaseCharacter (const UChar **current, const UChar *end) { do { if (*current == end) return 0; } while (u_getCombiningClass(*(*current)++)); return 1; } static int normalizeText ( BrailleContractionData *bcd, const wchar_t *begin, const wchar_t *end, wchar_t *buffer, size_t *length, unsigned int *map ) { const size_t size = end - begin; UChar source[size]; UChar target[size]; int32_t count; { const wchar_t *wc = begin; UChar *uc = source; while (wc < end) { *uc++ = *wc++; } } { UErrorCode error = U_ZERO_ERROR; count = unorm_normalize(source, ARRAY_COUNT(source), UNORM_NFC, 0, target, ARRAY_COUNT(target), &error); if (!U_SUCCESS(error)) return 0; } if (count == size) { if (memcmp(source, target, (count * sizeof(source[0]))) == 0) { return 0; } } { const UChar *src = source; const UChar *srcEnd = src + ARRAY_COUNT(source); const UChar *trg = target; const UChar *trgEnd = target + count; wchar_t *out = buffer; while (trg < trgEnd) { if (!nextBaseCharacter(&src, srcEnd)) return 0; *map++ = src - source - 1; *out++ = *trg++; } if (nextBaseCharacter(&src, srcEnd)) return 0; *map = src - source; } *length = count; return 1; } #else /* HAVE_ICU */ static int normalizeText ( BrailleContractionData *bcd, const wchar_t *begin, const wchar_t *end, wchar_t *buffer, size_t *length, unsigned int *map ) { return 0; } #endif /* HAVE_ICU */ CharacterEntry * getCharacterEntry (BrailleContractionData *bcd, wchar_t character) { int first = 0; int last = bcd->table->characters.count - 1; while (first <= last) { int current = (first + last) / 2; CharacterEntry *entry = &bcd->table->characters.array[current]; if (entry->value < character) { first = current + 1; } else if (entry->value > character) { last = current - 1; } else { return entry; } } if (bcd->table->characters.count == bcd->table->characters.size) { int newSize = bcd->table->characters.size; newSize = newSize? newSize<<1: 0X80; { CharacterEntry *newArray = realloc(bcd->table->characters.array, (newSize * sizeof(*newArray))); if (!newArray) { logMallocError(); return NULL; } bcd->table->characters.array = newArray; bcd->table->characters.size = newSize; } } memmove(&bcd->table->characters.array[first+1], &bcd->table->characters.array[first], (bcd->table->characters.count - first) * sizeof(*bcd->table->characters.array)); bcd->table->characters.count += 1; { CharacterEntry *entry = &bcd->table->characters.array[first]; memset(entry, 0, sizeof(*entry)); entry->value = entry->uppercase = entry->lowercase = character; if (iswspace(character)) { entry->attributes |= CTC_Space; } else if (iswalpha(character)) { entry->attributes |= CTC_Letter; if (iswupper(character)) { entry->attributes |= CTC_UpperCase; entry->lowercase = towlower(character); } if (iswlower(character)) { entry->attributes |= CTC_LowerCase; entry->uppercase = towupper(character); } } else if (iswdigit(character)) { entry->attributes |= CTC_Digit; } else if (iswpunct(character)) { entry->attributes |= CTC_Punctuation; } bcd->table->translationMethods->finishCharacterEntry(bcd, entry); return entry; } } static inline int makeCachedCursorOffset (BrailleContractionData *bcd) { return bcd->input.cursor? (bcd->input.cursor - bcd->input.begin): CTB_NO_CURSOR; } static int checkCache (BrailleContractionData *bcd) { if (!bcd->table->cache.input.characters) return 0; if (!bcd->table->cache.output.cells) return 0; if (bcd->input.offsets && !bcd->table->cache.offsets.count) return 0; if (bcd->table->cache.output.maximum != getOutputCount(bcd)) return 0; if (bcd->table->cache.cursorOffset != makeCachedCursorOffset(bcd)) return 0; if (bcd->table->cache.expandCurrentWord != prefs.expandCurrentWord) return 0; if (bcd->table->cache.capitalizationMode != prefs.capitalizationMode) return 0; { unsigned int count = getInputCount(bcd); if (bcd->table->cache.input.count != count) return 0; if (wmemcmp(bcd->input.begin, bcd->table->cache.input.characters, count) != 0) return 0; } return 1; } static void updateCache (BrailleContractionData *bcd) { { unsigned int count = getInputCount(bcd); if (count > bcd->table->cache.input.size) { unsigned int newSize = count | 0X7F; wchar_t *newCharacters = malloc(ARRAY_SIZE(newCharacters, newSize)); if (!newCharacters) { logMallocError(); bcd->table->cache.input.count = 0; goto inputDone; } if (bcd->table->cache.input.characters) free(bcd->table->cache.input.characters); bcd->table->cache.input.characters = newCharacters; bcd->table->cache.input.size = newSize; } wmemcpy(bcd->table->cache.input.characters, bcd->input.begin, count); bcd->table->cache.input.count = count; bcd->table->cache.input.consumed = getInputConsumed(bcd); } inputDone: { unsigned int count = getOutputConsumed(bcd); if (count > bcd->table->cache.output.size) { unsigned int newSize = count | 0X7F; unsigned char *newCells = malloc(ARRAY_SIZE(newCells, newSize)); if (!newCells) { logMallocError(); bcd->table->cache.output.count = 0; goto outputDone; } if (bcd->table->cache.output.cells) free(bcd->table->cache.output.cells); bcd->table->cache.output.cells = newCells; bcd->table->cache.output.size = newSize; } memcpy(bcd->table->cache.output.cells, bcd->output.begin, count); bcd->table->cache.output.count = count; bcd->table->cache.output.maximum = getOutputCount(bcd); } outputDone: if (bcd->input.offsets) { unsigned int count = getInputCount(bcd); if (count > bcd->table->cache.offsets.size) { unsigned int newSize = count | 0X7F; int *newArray = malloc(ARRAY_SIZE(newArray, newSize)); if (!newArray) { logMallocError(); bcd->table->cache.offsets.count = 0; goto offsetsDone; } if (bcd->table->cache.offsets.array) free(bcd->table->cache.offsets.array); bcd->table->cache.offsets.array = newArray; bcd->table->cache.offsets.size = newSize; } memcpy(bcd->table->cache.offsets.array, bcd->input.offsets, ARRAY_SIZE(bcd->input.offsets, count)); bcd->table->cache.offsets.count = count; } else { bcd->table->cache.offsets.count = 0; } offsetsDone: bcd->table->cache.cursorOffset = makeCachedCursorOffset(bcd); bcd->table->cache.expandCurrentWord = prefs.expandCurrentWord; bcd->table->cache.capitalizationMode = prefs.capitalizationMode; } void contractText ( ContractionTable *contractionTable, const wchar_t *inputBuffer, int *inputLength, BYTE *outputBuffer, int *outputLength, int *offsetsMap, const int cursorOffset ) { BrailleContractionData bcd = { .table = contractionTable, .input = { .begin = inputBuffer, .current = inputBuffer, .end = inputBuffer + *inputLength, .cursor = (cursorOffset == CTB_NO_CURSOR)? NULL: &inputBuffer[cursorOffset], .offsets = offsetsMap }, .output = { .begin = outputBuffer, .end = outputBuffer + *outputLength, .current = outputBuffer } }; if (checkCache(&bcd)) { bcd.input.current = bcd.input.begin + bcd.table->cache.input.consumed; if (bcd.input.offsets) { memcpy(bcd.input.offsets, bcd.table->cache.offsets.array, ARRAY_SIZE(bcd.input.offsets, bcd.table->cache.offsets.count)); } bcd.output.current = bcd.output.begin + bcd.table->cache.output.count; memcpy(bcd.output.begin, bcd.table->cache.output.cells, ARRAY_SIZE(bcd.output.begin, bcd.table->cache.output.count)); } else { int contracted; { const size_t size = getInputCount(&bcd); wchar_t buffer[size]; unsigned int map[size + 1]; size_t length; if (normalizeText(&bcd, bcd.input.begin, bcd.input.end, buffer, &length, map)) { const wchar_t *oldBegin = bcd.input.begin; const wchar_t *oldEnd = bcd.input.end; bcd.input.begin = buffer; bcd.input.current = bcd.input.begin + (bcd.input.current - oldBegin); bcd.input.end = bcd.input.begin + length; if (bcd.input.cursor) { ptrdiff_t offset = bcd.input.cursor - oldBegin; unsigned int mapIndex; bcd.input.cursor = NULL; for (mapIndex=0; mapIndex<=length; mapIndex+=1) { unsigned int mappedIndex = map[mapIndex]; if (mappedIndex > offset) break; bcd.input.cursor = &bcd.input.begin[mappedIndex]; } } contracted = contractionTable->translationMethods->contractText(&bcd); if (bcd.input.offsets) { size_t mapIndex = length; size_t offsetsIndex = oldEnd - oldBegin; while (mapIndex > 0) { unsigned int mappedIndex = map[--mapIndex]; int offset = bcd.input.offsets[mapIndex]; if (offset != CTB_NO_OFFSET) { while (--offsetsIndex > mappedIndex) bcd.input.offsets[offsetsIndex] = CTB_NO_OFFSET; bcd.input.offsets[offsetsIndex] = offset; } } while (offsetsIndex > 0) bcd.input.offsets[--offsetsIndex] = CTB_NO_OFFSET; } bcd.input.begin = oldBegin; bcd.input.current = bcd.input.begin + map[bcd.input.current - buffer]; bcd.input.end = oldEnd; } else { contracted = contractionTable->translationMethods->contractText(&bcd); } } if (!contracted) { bcd.input.current = bcd.input.begin; bcd.output.current = bcd.output.begin; while ((bcd.input.current < bcd.input.end) && (bcd.output.current < bcd.output.end)) { setOffset(&bcd); *bcd.output.current++ = convertCharacterToDots(textTable, *bcd.input.current++); } } if (bcd.input.current < bcd.input.end) { const wchar_t *srcorig = bcd.input.current; int done = 1; setOffset(&bcd); while (1) { if (done && !testCurrent(&bcd, CTC_Space)) { done = 0; if (!bcd.input.cursor || (bcd.input.cursor < srcorig) || (bcd.input.cursor >= bcd.input.current)) { setOffset(&bcd); srcorig = bcd.input.current; } } if (++bcd.input.current == bcd.input.end) break; clearOffset(&bcd); } if (!done) bcd.input.current = srcorig; } updateCache(&bcd); } *inputLength = getInputConsumed(&bcd); *outputLength = getOutputConsumed(&bcd); }