mirror of
https://github.com/rhasspy/espeak-ng.git
synced 2026-11-02 06:01:53 +00:00
350 lines
7.8 KiB
C
350 lines
7.8 KiB
C
/*
|
||
* Copyright (C) 2005 to 2013 by Jonathan Duddington
|
||
* email: jonsd@users.sourceforge.net
|
||
* Copyright (C) 2013-2017 Reece H. Dunn
|
||
*
|
||
* This program is free software; you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation; either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this program; if not, see: <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#include <ctype.h>
|
||
#include <errno.h>
|
||
#include <locale.h>
|
||
#include <stdbool.h>
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <sys/stat.h>
|
||
#include <wctype.h>
|
||
|
||
#include <espeak-ng/espeak_ng.h>
|
||
#include <espeak-ng/speak_lib.h>
|
||
#include <espeak-ng/encoding.h>
|
||
#include <ucd/ucd.h>
|
||
|
||
#include "common.h"
|
||
#include "translate.h"
|
||
|
||
#pragma GCC visibility push(default)
|
||
|
||
int GetFileLength(const char *filename)
|
||
{
|
||
struct stat statbuf;
|
||
|
||
if (stat(filename, &statbuf) != 0)
|
||
return -errno;
|
||
|
||
if (S_ISDIR(statbuf.st_mode))
|
||
return -EISDIR;
|
||
|
||
return statbuf.st_size;
|
||
}
|
||
|
||
void strncpy0(char *to, const char *from, int size)
|
||
{
|
||
// strcpy with limit, ensures a zero terminator
|
||
strncpy(to, from, size);
|
||
to[size-1] = 0;
|
||
}
|
||
|
||
int utf8_in(int *c, const char *buf)
|
||
{
|
||
/* Read a unicode characater from a UTF8 string
|
||
* Returns the number of UTF8 bytes used.
|
||
* buf: position of buffer is moved, if character is read
|
||
* c: holds UTF-16 representation of multibyte character by
|
||
* skipping UTF-8 header bits of bytes in following way:
|
||
* 2-byte character "ā":
|
||
* hex binary
|
||
* c481 1100010010000001
|
||
* | 11000100 000001
|
||
* V \ \ | |
|
||
* 0101 0000000100000001
|
||
* 3-byte character "ꙅ":
|
||
* ea9985 111010101001100110000101
|
||
* 1010 011001 000101
|
||
* | + +--.\ \ | |
|
||
* V `--. \`. `.| |
|
||
* A645 1010011001000101
|
||
* 4-byte character "𠜎":
|
||
* f0a09c8e 11110000101000001001110010001110
|
||
* V 000 100000 011100 001110
|
||
* 02070e 000000100000011100001110
|
||
*/
|
||
return utf8_in2(c, buf, 0);
|
||
}
|
||
#pragma GCC visibility pop
|
||
|
||
int utf8_out(unsigned int c, char *buf)
|
||
{
|
||
// write a UTF-16 character into a buffer as UTF-8
|
||
// returns the number of bytes written
|
||
|
||
int n_bytes;
|
||
int j;
|
||
int shift;
|
||
static const char unsigned code[4] = { 0, 0xc0, 0xe0, 0xf0 };
|
||
|
||
if (c < 0x80) {
|
||
buf[0] = c;
|
||
return 1;
|
||
}
|
||
if (c >= 0x110000) {
|
||
buf[0] = ' '; // out of range character code
|
||
return 1;
|
||
}
|
||
if (c < 0x0800)
|
||
n_bytes = 1;
|
||
else if (c < 0x10000)
|
||
n_bytes = 2;
|
||
else
|
||
n_bytes = 3;
|
||
|
||
shift = 6*n_bytes;
|
||
buf[0] = code[n_bytes] | (c >> shift);
|
||
for (j = 0; j < n_bytes; j++) {
|
||
shift -= 6;
|
||
buf[j+1] = 0x80 + ((c >> shift) & 0x3f);
|
||
}
|
||
return n_bytes+1;
|
||
}
|
||
|
||
int utf8_in2(int *c, const char *buf, int backwards)
|
||
{
|
||
// Reads a unicode characater from a UTF8 string
|
||
// Returns the number of UTF8 bytes used.
|
||
// c: holds integer representation of multibyte character
|
||
// buf: position of buffer is moved, if character is read
|
||
// backwards: set if we are moving backwards through the UTF8 string
|
||
|
||
int c1;
|
||
int n_bytes;
|
||
static const unsigned char mask[4] = { 0xff, 0x1f, 0x0f, 0x07 };
|
||
|
||
// find the start of the next/previous character
|
||
while ((*buf & 0xc0) == 0x80) {
|
||
// skip over non-initial bytes of a multi-byte utf8 character
|
||
if (backwards)
|
||
buf--;
|
||
else
|
||
buf++;
|
||
}
|
||
|
||
n_bytes = 0;
|
||
|
||
if ((c1 = *buf++) & 0x80) {
|
||
if ((c1 & 0xe0) == 0xc0)
|
||
n_bytes = 1;
|
||
else if ((c1 & 0xf0) == 0xe0)
|
||
n_bytes = 2;
|
||
else if ((c1 & 0xf8) == 0xf0)
|
||
n_bytes = 3;
|
||
|
||
c1 &= mask[n_bytes];
|
||
int ix;
|
||
for (ix = 0; ix < n_bytes; ix++)
|
||
{
|
||
if (!*buf)
|
||
/* Oops, truncated */
|
||
break;
|
||
c1 = (c1 << 6) + (*buf++ & 0x3f);
|
||
}
|
||
n_bytes = ix;
|
||
}
|
||
*c = c1;
|
||
return n_bytes+1;
|
||
}
|
||
|
||
|
||
int IsAlpha(unsigned int c)
|
||
{
|
||
// Replacement for iswalph() which also checks for some in-word symbols
|
||
|
||
static const unsigned short extra_indic_alphas[] = {
|
||
0xa70, 0xa71, // Gurmukhi: tippi, addak
|
||
0
|
||
};
|
||
|
||
if (iswalpha(c))
|
||
return 1;
|
||
|
||
if (c < 0x300)
|
||
return 0;
|
||
|
||
if ((c >= 0x901) && (c <= 0xdf7)) {
|
||
// Indic scripts: Devanagari, Tamil, etc
|
||
if ((c & 0x7f) < 0x64)
|
||
return 1;
|
||
if (lookupwchar(extra_indic_alphas, c) != 0)
|
||
return 1;
|
||
if ((c >= 0xd7a) && (c <= 0xd7f))
|
||
return 1; // malaytalam chillu characters
|
||
|
||
return 0;
|
||
}
|
||
|
||
if ((c >= 0x5b0) && (c <= 0x5c2))
|
||
return 1; // Hebrew vowel marks
|
||
|
||
if (c == 0x0605)
|
||
return 1;
|
||
|
||
if ((c == 0x670) || ((c >= 0x64b) && (c <= 0x65e)))
|
||
return 1; // arabic vowel marks
|
||
|
||
if ((c >= 0x300) && (c <= 0x36f))
|
||
return 1; // combining accents
|
||
|
||
if ((c >= 0xf40) && (c <= 0xfbc))
|
||
return 1; // tibetan
|
||
|
||
if ((c >= 0x1100) && (c <= 0x11ff))
|
||
return 1; // Korean jamo
|
||
|
||
if ((c >= 0x2800) && (c <= 0x28ff))
|
||
return 1; // braille
|
||
|
||
if ((c > 0x3040) && (c <= 0xa700))
|
||
return 1; // Chinese/Japanese. Should never get here, but Mac OS 10.4's iswalpha seems to be broken, so just make sure
|
||
|
||
return 0;
|
||
}
|
||
|
||
// brackets, also 0x2014 to 0x021f which don't need to be in this list
|
||
static const unsigned short brackets[] = {
|
||
'(', ')', '[', ']', '{', '}', '<', '>', '"', '\'', '`',
|
||
0xab, 0xbb, // double angle brackets
|
||
0x300a, 0x300b, // double angle brackets (ideograph)
|
||
0xe000+'<', // private usage area
|
||
0
|
||
};
|
||
|
||
int IsBracket(int c)
|
||
{
|
||
if ((c >= 0x2014) && (c <= 0x201f))
|
||
return 1;
|
||
return lookupwchar(brackets, c);
|
||
}
|
||
|
||
int IsDigit09(unsigned int c)
|
||
{
|
||
if ((c >= '0') && (c <= '9'))
|
||
return 1;
|
||
return 0;
|
||
}
|
||
|
||
int IsDigit(unsigned int c)
|
||
{
|
||
if (iswdigit(c))
|
||
return 1;
|
||
|
||
if ((c >= 0x966) && (c <= 0x96f))
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
int IsSpace(unsigned int c)
|
||
{
|
||
if (c == 0)
|
||
return 0;
|
||
if ((c >= 0x2500) && (c < 0x25a0))
|
||
return 1; // box drawing characters
|
||
if ((c >= 0xfff9) && (c <= 0xffff))
|
||
return 1; // unicode specials
|
||
return iswspace(c);
|
||
}
|
||
|
||
int isspace2(unsigned int c)
|
||
{
|
||
// can't use isspace() because on Windows, isspace(0xe1) gives TRUE !
|
||
if ( ((c & 0xff) == 0) || (c > ' '))
|
||
return 0;
|
||
return 1;
|
||
}
|
||
|
||
int is_str_totally_null(const char* str, int size) {
|
||
// Tests if all bytes of str are null up to size
|
||
// This should never be reimplemented with integers, because
|
||
// this function has to work with unaligned char*
|
||
// (casting to int when unaligned may result in ungaranteed behaviors)
|
||
return (*str == 0 && memcmp(str, str+1, size-1) == 0);
|
||
}
|
||
|
||
int Read4Bytes(FILE *f)
|
||
{
|
||
// Read 4 bytes (least significant first) into a word
|
||
int ix;
|
||
int acc = 0;
|
||
|
||
for (ix = 0; ix < 4; ix++) {
|
||
unsigned char c;
|
||
c = fgetc(f) & 0xff;
|
||
acc += (c << (ix*8));
|
||
}
|
||
return acc;
|
||
}
|
||
|
||
unsigned int StringToWord(const char *string)
|
||
{
|
||
// Pack 4 characters into a word
|
||
int ix;
|
||
unsigned char c;
|
||
unsigned int word;
|
||
|
||
if (string == NULL)
|
||
return 0;
|
||
|
||
word = 0;
|
||
for (ix = 0; ix < 4; ix++) {
|
||
if (string[ix] == 0) break;
|
||
c = string[ix];
|
||
word |= (c << (ix*8));
|
||
}
|
||
return word;
|
||
}
|
||
|
||
int towlower2(unsigned int c, Translator *translator)
|
||
{
|
||
// check for non-standard upper to lower case conversions
|
||
if (c == 'I' && translator->langopts.dotless_i)
|
||
return 0x131; // I -> ı
|
||
|
||
return ucd_tolower(c);
|
||
}
|
||
|
||
static uint32_t espeak_rand_state = 0;
|
||
|
||
long espeak_rand(long min, long max) {
|
||
// Ref: https://github.com/bminor/glibc/blob/glibc-2.36/stdlib/random_r.c#L364
|
||
espeak_rand_state = (((uint64_t)espeak_rand_state * 1103515245) + 12345) % 0x7fffffff;
|
||
long res = (long)espeak_rand_state;
|
||
return (res % (max-min+1))-min;
|
||
}
|
||
|
||
void espeak_srand(long seed) {
|
||
espeak_rand_state = (uint32_t)(seed);
|
||
(void)espeak_rand(0, 1); // Dummy flush a generator
|
||
}
|
||
|
||
#pragma GCC visibility push(default)
|
||
ESPEAK_NG_API espeak_ng_STATUS
|
||
espeak_ng_SetRandSeed(long seed) {
|
||
espeak_srand(seed);
|
||
return ENS_OK;
|
||
}
|
||
#pragma GCC visibility pop
|