Coverage Report

Created: 2026-09-03 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/espeak-ng/src/libespeak-ng/translate.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2005 to 2014 by Jonathan Duddington
3
 * email: jonsd@users.sourceforge.net
4
 * Copyright (C) 2015-2017 Reece H. Dunn
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, see: <http://www.gnu.org/licenses/>.
18
 */
19
20
#include "config.h"
21
22
#include <ctype.h>
23
#include <stdbool.h>
24
#include <stdint.h>
25
#include <stdio.h>
26
#include <stdlib.h>
27
#include <string.h>
28
#include <wchar.h>
29
#include <wctype.h>
30
31
#include <espeak-ng/espeak_ng.h>
32
#include <espeak-ng/speak_lib.h>
33
#include <espeak-ng/encoding.h>
34
35
#include "translate.h"
36
#include "common.h"
37
#include "dictionary.h"           // for TranslateRules, LookupDictList, Cha...
38
#include "phoneme.h"              // for phonSWITCH, PHONEME_TAB, phonPAUSE_...
39
#include "phonemelist.h"          // for MakePhonemeList
40
#include "readclause.h"           // for towlower2, Eof, ReadClause, is_str_...
41
#include "synthdata.h"            // for SelectPhonemeTable, LookupPhonemeTable
42
#include "synthesize.h"           // for PHONEME_LIST2, N_PHONEME_LIST, PHON...
43
#include "ucd/ucd.h"              // for ucd_toupper
44
#include "voice.h"                // for voice, voice_t
45
#include "speech.h"               // for MAKE_MEM_UNDEFINED
46
#include "translateword.h"
47
48
static int CalcWordLength(int source_index, int charix_top, short int *charix, WORD_TAB *words, int word_count);
49
static void CombineFlag(Translator *tr, WORD_TAB *wtab, int wtab_remaining, char *word, int *flags, unsigned char *p, char *word_phonemes);
50
static void SwitchLanguage(char *word, char *word_phonemes);
51
static int TranslateWordWithBounds(Translator *tr, char *word_start, WORD_TAB *wtab, int wtab_remaining, char *word_out, int depth);
52
53
Translator *translator = NULL; // the main translator
54
Translator *translator2 = NULL; // secondary translator for certain words
55
static char translator2_language[20] = { 0 };
56
Translator *translator3 = NULL; // tertiary translator for certain words
57
static char translator3_language[20] = { 0 };
58
59
FILE *f_trans = NULL; // phoneme output text
60
int option_tone_flags = 0; // bit 8=emphasize allcaps, bit 9=emphasize penultimate stress
61
int option_phonemes = 0;
62
int option_phoneme_events = 0;
63
int option_endpause = 0; // suppress pause after end of text
64
int option_capitals = 0;
65
int option_punctuation = 0;
66
int option_sayas = 0;
67
static int option_sayas2 = 0; // used in translate_clause()
68
static int option_emphasis = 0; // 0=normal, 1=normal, 2=weak, 3=moderate, 4=strong
69
int option_ssml = 0;
70
int option_phoneme_input = 0; // allow [[phonemes]] in input
71
int option_wordgap = 0;
72
73
static int count_sayas_digits;
74
int skip_sentences;
75
int skip_words;
76
int skip_characters;
77
char skip_marker[N_MARKER_LENGTH];
78
bool skipping_text; // waiting until word count, sentence count, or named marker is reached
79
int end_character_position;
80
int count_sentences;
81
static int count_words;
82
int clause_start_char;
83
int clause_start_word;
84
static bool new_sentence;
85
static int word_emphasis = 0; // set if emphasis level 3 or 4
86
static int embedded_flag = 0; // there are embedded commands to be applied to the next phoneme, used in TranslateWord2()
87
88
static int max_clause_pause = 0;
89
static bool any_stressed_words;
90
int pre_pause;
91
static ALPHABET *current_alphabet;
92
93
char word_phonemes[N_WORD_PHONEMES]; // a word translated into phoneme codes
94
int n_ph_list2;
95
PHONEME_LIST2 ph_list2[N_PHONEME_LIST]; // first stage of text->phonemes
96
97
wchar_t option_punctlist[N_PUNCTLIST] = { 0 };
98
99
// these are overridden by defaults set in the "speak" file
100
int option_linelength = 0;
101
102
67.3k
#define N_EMBEDDED_LIST  250
103
static int embedded_ix;
104
static int embedded_read;
105
unsigned int embedded_list[N_EMBEDDED_LIST];
106
107
// the source text of a single clause (UTF8 bytes)
108
static char source[N_TR_SOURCE+40]; // extra space for embedded command & voice change info at end
109
110
int n_replace_phonemes;
111
REPLACE_PHONEMES replace_phonemes[N_REPLACE_PHONEMES];
112
113
// other characters which break a word, but don't produce a pause
114
static const unsigned short breaks[] = { '_', 0 };
115
116
void DeleteTranslator(Translator *tr)
117
34.6k
{
118
34.6k
  if (!tr) return;
119
120
34.6k
  if (tr->data_dictlist != NULL)
121
34.6k
    free(tr->data_dictlist);
122
34.6k
  free(tr);
123
34.6k
}
124
125
int lookupwchar(const unsigned short *list, int c)
126
7.81M
{
127
  // Is the character c in the list ?
128
7.81M
  int ix;
129
130
70.2M
  for (ix = 0; list[ix] != 0; ix++) {
131
63.8M
    if (list[ix] == c)
132
1.41M
      return ix+1;
133
63.8M
  }
134
6.40M
  return 0;
135
7.81M
}
136
137
char *strchr_w(const char *s, int c)
138
67.1k
{
139
  // return NULL for any non-ascii character
140
67.1k
  if (c >= 0x80)
141
8.15k
    return NULL;
142
58.9k
  return strchr((char *)s, c); // (char *) is needed for Borland compiler
143
67.1k
}
144
145
static bool ShouldSplitIdeographs(Translator *tr, int c, int prev)
146
709k
{
147
709k
  if (tr->langopts.ideographs && ((c > 0x3040) || (prev > 0x3040)))
148
424
    return true;
149
150
  // A non-ideographic base language may nominate an ideographic alternate
151
  // translator (Arabic uses Mandarin for Han characters). Split that run in
152
  // the same way as the alternate translator would, so every Han character
153
  // reaches its dictionary entry instead of one long foreign-script token.
154
709k
  if (tr->langopts.alt_alphabet != 0) {
155
1.47k
    const ALPHABET *alpha_c = AlphabetFromChar(c);
156
1.47k
    const ALPHABET *alpha_prev = AlphabetFromChar(prev);
157
1.47k
    if (((alpha_c != NULL) && (alpha_c->offset == tr->langopts.alt_alphabet)
158
21
        && (alpha_c->flags & AL_IDEOGRAPHS))
159
1.47k
        || ((alpha_prev != NULL) && (alpha_prev->offset == tr->langopts.alt_alphabet)
160
32
        && (alpha_prev->flags & AL_IDEOGRAPHS)))
161
0
      return true;
162
1.47k
  }
163
709k
  return false;
164
709k
}
165
166
static void SegmentReplacement(Translator *tr, const char *text, char *out, int out_size)
167
16.4k
{
168
  // Apply the word-splitting rules of the clause tokenizer (TranslateClause)
169
  // to dictionary replacement text, so that a $textmode replacement is
170
  // re-translated in the same word units as if it had been normal input:
171
  // for languages with langopts.ideographs each CJK character is a separate
172
  // word (so that hanzi words can match their multi-word *_list entries),
173
  // and a non-alpha mark such as a Thai tone mark terminates a word instead
174
  // of derailing the letter-to-phoneme rules mid-word.  A hyphen between two
175
  // letters also ends a word, as it does in normal input; without this a
176
  // hyphenated replacement name ("Burkina-Faso", "États-Unis") is handed to
177
  // the rules as one token, which stops at the hyphen and drops the rest.
178
  // Other ASCII characters are kept in-word, so that Latin-script
179
  // replacement text such as "cai3hong2" stays a single word.
180
16.4k
  int ix = 0, out_ix = 0;
181
16.4k
  int c = 0;
182
183
262k
  while (text[ix] != 0) {
184
245k
    int prev = c;
185
245k
    int nbytes = utf8_in(&c, &text[ix]);
186
245k
    bool insert_space = false;
187
188
245k
    if (IsAlpha(prev)) {
189
206k
      if (IsAlpha(c)) {
190
167k
        if (ShouldSplitIdeographs(tr, c, prev))
191
194
          insert_space = true; // each ideograph is a separate word
192
167k
      } else if (c == '-') {
193
166
        int next;
194
195
166
        utf8_in(&next, &text[ix + nbytes]);
196
166
        if (IsAlpha(next)) {
197
          // hyphen between two letters: the clause tokenizer turns it
198
          // into a word break, so do the same and drop the hyphen
199
163
          if (out_ix + 2 > out_size)
200
0
            break;
201
163
          out[out_ix++] = ' ';
202
163
          ix += nbytes;
203
163
          c = ' ';
204
163
          continue;
205
163
        }
206
38.4k
      } else if (!IsSpace(c) && (c >= 0x80) && (wcschr(tr->punct_within_word, c) == 0))
207
849
        insert_space = true; // eg. a Thai tone mark ends the word, as in the clause tokenizer
208
206k
    } else if ((prev >= 0x80) && !IsSpace(prev) && IsAlpha(c) && (wcschr(tr->punct_within_word, prev) == 0))
209
611
      insert_space = true; // a letter after such a mark starts a new word
210
211
245k
    if (out_ix + nbytes + 2 > out_size)
212
0
      break;
213
245k
    if (insert_space)
214
1.65k
      out[out_ix++] = ' ';
215
245k
    memcpy(&out[out_ix], &text[ix], nbytes);
216
245k
    out_ix += nbytes;
217
245k
    ix += nbytes;
218
245k
  }
219
16.4k
  out[out_ix] = 0;
220
16.4k
}
221
222
static int TranslateWordWithBounds(Translator *tr, char *word_start, WORD_TAB *wtab, int wtab_remaining, char *word_out, int depth)
223
1.07M
{
224
1.07M
  char words_phonemes[N_WORD_PHONEMES]; // a word translated into phoneme codes
225
1.07M
  char *phonemes = words_phonemes;
226
227
228
1.07M
  int flags = TranslateWord3(tr, word_start, wtab, wtab_remaining, word_out, &any_stressed_words, current_alphabet, word_phonemes, sizeof(word_phonemes));
229
1.07M
  if (flags & FLAG_TEXTMODE && word_out) {
230
    // Ensure that start of word rules match with the replaced text,
231
    // so that emoji and other characters are pronounced correctly.
232
    // The buffer allows for a space inserted after every character of
233
    // the replacement by SegmentReplacement().
234
16.4k
    char word[2+N_WORD_BYTES*2];
235
16.4k
    word[0] = 0;
236
16.4k
    word[1] = ' ';
237
16.4k
    SegmentReplacement(tr, word_out, word+2, sizeof(word)-2);
238
16.4k
    word_out = word+2;
239
240
16.4k
      bool first_word = true;
241
16.4k
      int available = N_WORD_PHONEMES;
242
56.0k
    while (*word_out && available > 1) {
243
39.6k
      int c;
244
39.6k
      int nbytes = utf8_in(&c, word_out);
245
39.6k
      if (iswupper(c)) {
246
798
        char lower_buf[8];
247
798
        int n_lower;
248
249
798
        wtab->flags |= FLAG_FIRST_UPPER;
250
        // towlower2(), not tolower(): the latter only knows ASCII, and
251
        // on a non-ASCII capital (Казахстан, Καζακστάν) it returned a
252
        // value whose UTF-8 form is a different length, which corrupted
253
        // the first letter of the word. Leave the letter alone if its
254
        // lower case form does not fit the same number of bytes.
255
798
        n_lower = utf8_out(towlower2(c, tr), lower_buf);
256
798
        if (n_lower == nbytes)
257
798
          memcpy(word_out, lower_buf, nbytes);
258
38.8k
      } else {
259
38.8k
        wtab->flags &= ~FLAG_FIRST_UPPER;
260
38.8k
      }
261
262
      // dictionary_skipwords is a global variable and TranslateWord3 will reset it to 0 at the beginning.
263
      // However, dictionary_skipwords value is still needed outside this scope.
264
      // So we backup and restore it at the end of this scope.
265
39.6k
      int skipwords = dictionary_skipwords;
266
39.6k
      if (depth < 3) {
267
        // translate through TranslateWordWithBounds so that a replacement
268
        // word which itself has a $textmode entry is expanded too: eg. an
269
        // emoji entry replaces the emoji by hanzi, and the hanzi words map
270
        // to pinyin through *_list. The depth limit guards against an
271
        // entry cycle in the dictionary (a $textmode b, b $textmode a).
272
39.6k
        char word_replacement2[N_WORD_BYTES+1];
273
39.6k
        word_replacement2[0] = 0;
274
39.6k
        TranslateWordWithBounds(tr, word_out, wtab, wtab_remaining, word_replacement2, depth+1);
275
39.6k
      } else
276
0
        TranslateWord3(tr, word_out, wtab, wtab_remaining, NULL, &any_stressed_words, current_alphabet, word_phonemes, sizeof(word_phonemes));
277
278
39.6k
      int n;
279
39.6k
      if (first_word) {
280
16.4k
        n = snprintf(phonemes, available, "%s", word_phonemes);
281
16.4k
        first_word = false;
282
23.1k
      } else {
283
23.1k
        n = snprintf(phonemes, available, "%c%s", phonEND_WORD, word_phonemes);
284
23.1k
      }
285
286
39.6k
      available -= n;
287
39.6k
      phonemes += n;
288
289
      // skip to the next word in a multi-word replacement. Always skip at least one word.
290
79.2k
      for (dictionary_skipwords++; dictionary_skipwords > 0; dictionary_skipwords--) {
291
317k
        while (!isspace(*word_out)) ++word_out;
292
39.6k
        while (isspace(*word_out))  ++word_out;
293
39.6k
      }
294
39.6k
      dictionary_skipwords = skipwords;
295
39.6k
    }
296
297
    // If the list file contains a text replacement to another
298
    // entry in the list file, e.g.:
299
    //     ripost     riposte $text
300
    //     riposte    rI#p0st
301
    // calling it from a prefix or suffix rule such as 'riposted'
302
    // causes word_out[0] to be NULL, as TranslateWord3 has the
303
    // information needed to perform the mapping. In this case,
304
    // no phonemes have been written in this loop and the phonemes
305
    // have been calculated, so don't override them.
306
16.4k
    if (phonemes != words_phonemes) {
307
16.4k
      snprintf(word_phonemes, sizeof(word_phonemes), "%s", words_phonemes);
308
16.4k
    }
309
16.4k
  }
310
1.07M
  return flags;
311
1.07M
}
312
313
int TranslateWord(Translator *tr, char *word_start, WORD_TAB *wtab, char *word_out)
314
18.1k
{
315
18.1k
  return TranslateWordWithBounds(tr, word_start, wtab, 0, word_out, 0);
316
18.1k
}
317
318
static void SetPlist2(PHONEME_LIST2 *p, unsigned char phcode)
319
335k
{
320
335k
  p->phcode = phcode;
321
335k
  p->stresslevel = 0;
322
335k
  p->tone_ph = 0;
323
335k
  p->synthflags = embedded_flag;
324
335k
  p->sourceix = 0;
325
335k
  embedded_flag = 0;
326
335k
}
327
328
static int CountSyllables(unsigned char *phonemes)
329
11
{
330
11
  int count = 0;
331
11
  int phon;
332
110
  while ((phon = *phonemes++) != 0) {
333
99
    if (phoneme_tab[phon]->type == phVOWEL)
334
48
      count++;
335
99
  }
336
11
  return count;
337
11
}
338
339
static void Word_EmbeddedCmd(void)
340
19.2k
{
341
  // Process embedded commands for emphasis, sayas, and break
342
19.2k
  int embedded_cmd;
343
34.3k
  do {
344
34.3k
    embedded_cmd = embedded_list[embedded_read++];
345
34.3k
    int value = embedded_cmd >> 8;
346
347
34.3k
    switch (embedded_cmd & 0x1f)
348
34.3k
    {
349
2.55k
    case EMBED_Y:
350
2.55k
      option_sayas = value;
351
2.55k
      break;
352
353
2.21k
    case EMBED_F:
354
2.21k
      option_emphasis = value;
355
2.21k
      break;
356
357
2.92k
    case EMBED_B:
358
      // break command
359
2.92k
      if (value == 0)
360
414
        pre_pause = 0; // break=none
361
2.51k
      else
362
2.51k
        pre_pause += value;
363
2.92k
      break;
364
34.3k
    }
365
34.3k
  } while (((embedded_cmd & 0x80) == 0) && (embedded_read < embedded_ix));
366
19.2k
}
367
368
static int SetAlternateTranslator(const char *new_language, Translator **translator, char translator_language[20])
369
748k
{
370
  // Set alternate translator to a second language
371
748k
  int new_phoneme_tab;
372
373
748k
  if ((new_phoneme_tab = SelectPhonemeTableName(new_language)) >= 0) {
374
748k
    if ((*translator != NULL) && (strcmp(new_language, translator_language) != 0)) {
375
      // we already have an alternative translator, but not for the required language, delete it
376
25.5k
      DeleteTranslator(*translator);
377
25.5k
      *translator = NULL;
378
25.5k
    }
379
380
748k
    if (*translator == NULL) {
381
25.5k
      *translator = SelectTranslator(new_language);
382
25.5k
      strcpy(translator_language, new_language);
383
384
25.5k
      if (LoadDictionary(*translator, (*translator)->dictionary_name, 0) != 0) {
385
0
        SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table
386
0
        new_phoneme_tab = -1;
387
0
        translator_language[0] = 0;
388
0
      }
389
25.5k
      (*translator)->phoneme_tab_ix = new_phoneme_tab;
390
25.5k
    }
391
748k
  }
392
748k
  if (*translator != NULL)
393
748k
    (*translator)->phonemes_repeat[0] = 0;
394
748k
  return new_phoneme_tab;
395
748k
}
396
397
int SetTranslator2(const char *new_language)
398
163k
{
399
163k
  return SetAlternateTranslator(new_language, &translator2, translator2_language);
400
163k
}
401
402
int SetTranslator3(const char *new_language)
403
584k
{
404
584k
  return SetAlternateTranslator(new_language, &translator3, translator3_language);
405
584k
}
406
407
static int TranslateWord2(Translator *tr, char *word, WORD_TAB *wtab, int wtab_remaining, int pre_pause)
408
986k
{
409
986k
  int flags = 0;
410
986k
  int stress;
411
986k
  int next_stress;
412
986k
  int next_tone = 0;
413
986k
  unsigned char *p;
414
986k
  int srcix;
415
986k
  int found_dict_flag;
416
986k
  unsigned char ph_code;
417
986k
  PHONEME_LIST2 *plist2;
418
986k
  PHONEME_TAB *ph;
419
986k
  int max_stress;
420
986k
  int max_stress_ix = 0;
421
986k
  int prev_vowel = -1;
422
986k
  int pitch_raised = 0;
423
986k
  int switch_phonemes = -1;
424
986k
  bool first_phoneme = true;
425
986k
  int source_ix;
426
986k
  int len;
427
986k
  int bad_phoneme;
428
986k
  int word_flags;
429
986k
  char word_copy[N_WORD_BYTES+1];
430
986k
  char word_replaced[N_WORD_BYTES+1];
431
986k
  char old_dictionary_name[40];
432
433
986k
  len = wtab->length;
434
986k
  if (len > 31) len = 31;
435
986k
  source_ix = (wtab->sourceix & 0x7ff) | (len << 11); // bits 0-10 sourceix, bits 11-15 word length
436
437
986k
  word_flags = wtab[0].flags;
438
986k
  if (word_flags & FLAG_EMBEDDED) {
439
17.8k
    wtab[0].flags &= ~FLAG_EMBEDDED; // clear it in case we call TranslateWord2() again for the same word
440
17.8k
    embedded_flag = SFLAG_EMBEDDED;
441
442
17.8k
    Word_EmbeddedCmd();
443
17.8k
  }
444
445
986k
  if (n_ph_list2 >= N_PHONEME_LIST-2) {
446
    // No room, can't translate anything
447
0
    return 0;
448
0
  }
449
450
986k
  if ((word[0] == 0) || (word_flags & FLAG_DELETE_WORD)) {
451
    // nothing to translate.  Add a dummy phoneme to carry any embedded commands
452
8.07k
    if (embedded_flag) {
453
785
      SetPlist2(&ph_list2[n_ph_list2], phonEND_WORD);
454
785
      ph_list2[n_ph_list2].wordstress = 0;
455
785
      n_ph_list2++;
456
785
      embedded_flag = 0;
457
785
    }
458
8.07k
    word_phonemes[0] = 0;
459
8.07k
    return 0;
460
8.07k
  }
461
462
977k
  if (n_ph_list2 >= N_PHONEME_LIST-7-2) {
463
    // We may require up to 7 phonemes, plus the 2 phonemes from the caller, can't translate safely
464
3.35k
    return 0;
465
3.35k
  }
466
467
  // after a $pause word attribute, ignore a $pause attribute on the next two words
468
974k
  if (tr->prepause_timeout > 0)
469
4.73k
    tr->prepause_timeout--;
470
471
974k
  if ((option_sayas & 0xf0) == 0x10) {
472
3.95k
    if (!(word_flags & FLAG_FIRST_WORD)) {
473
      // SAYAS_CHARS, SAYAS_GLYPHS, or SAYAS_SINGLECHARS.  Pause between each word.
474
3.47k
      pre_pause += 4;
475
3.47k
    }
476
3.95k
  }
477
478
974k
  if (word_flags & FLAG_FIRST_UPPER) {
479
116k
    if ((option_capitals > 2) && (embedded_ix < N_EMBEDDED_LIST-6)) {
480
      // indicate capital letter by raising pitch
481
0
      if (embedded_flag)
482
0
        embedded_list[embedded_ix-1] &= ~0x80; // already embedded command before this word, remove terminator
483
0
      if ((pitch_raised = option_capitals) == 3)
484
0
        pitch_raised = 20; // default pitch raise for capitals
485
0
      embedded_list[embedded_ix++] = EMBED_P+0x40+0x80 + (pitch_raised << 8); // raise pitch
486
0
      embedded_flag = SFLAG_EMBEDDED;
487
0
    }
488
116k
  }
489
490
974k
  p = (unsigned char *)word_phonemes;
491
974k
  if (word_flags & FLAG_PHONEMES) {
492
    // The input is in phoneme mnemonics, not language text
493
494
105k
    if (memcmp(word, "_^_", 3) == 0) {
495
5.51k
      SwitchLanguage(word, word_phonemes);
496
100k
    } else {
497
100k
      EncodePhonemes(word, word_phonemes, &bad_phoneme);
498
100k
    }
499
500
105k
    flags = FLAG_FOUND;
501
869k
  } else {
502
869k
    int c2;
503
869k
    int ix = 0;
504
869k
    int word_copy_len;
505
3.21M
    while (((c2 = word_copy[ix] = word[ix]) != ' ') && (c2 != 0) && (ix < N_WORD_BYTES)) ix++;
506
869k
    word_copy_len = ix;
507
508
869k
    word_replaced[2] = 0;
509
869k
    flags = TranslateWordWithBounds(translator, word, wtab, wtab_remaining, &word_replaced[2], 0);
510
511
869k
    if (flags & FLAG_SPELLWORD) {
512
      // re-translate the word as individual letters, separated by spaces
513
12.6k
      memcpy(word, word_copy, word_copy_len);
514
12.6k
      return flags;
515
12.6k
    }
516
517
856k
    if ((flags & FLAG_COMBINE) && (wtab_remaining > 1) && !(wtab[1].flags & FLAG_PHONEMES)) {
518
111
      CombineFlag(tr, wtab, wtab_remaining, word, &flags, p, word_phonemes);
519
111
    }
520
521
856k
    if (p[0] == phonSWITCH) {
522
130k
      int switch_attempt;
523
130k
      strcpy(old_dictionary_name, dictionary_name);
524
156k
      for (switch_attempt = 0; switch_attempt < 2; switch_attempt++) {
525
        // this word uses a different language
526
143k
        memcpy(word, word_copy, word_copy_len);
527
528
143k
        const char *new_language;
529
143k
        new_language = (char *)(&p[1]);
530
143k
        if (new_language[0] == 0)
531
28.3k
          new_language = ESPEAKNG_DEFAULT_VOICE;
532
533
143k
        switch_phonemes = SetTranslator2(new_language);
534
535
143k
        if (switch_phonemes >= 0) {
536
          // re-translate the word using the new translator
537
143k
          wtab[0].flags |= FLAG_TRANSLATOR2;
538
143k
          if (word_replaced[2] != 0) {
539
0
            word_replaced[0] = 0; // byte before the start of the word
540
0
            word_replaced[1] = ' ';
541
0
            flags = TranslateWordWithBounds(translator2, &word_replaced[1], wtab, wtab_remaining, NULL, 0);
542
0
          } else
543
143k
            flags = TranslateWordWithBounds(translator2, word, wtab, wtab_remaining, &word_replaced[2], 0);
544
143k
        }
545
546
143k
        if (p[0] != phonSWITCH)
547
116k
          break;
548
143k
      }
549
550
130k
      if (p[0] == phonSWITCH)
551
13.3k
        return FLAG_SPELLWORD;
552
553
116k
      if (switch_phonemes < 0) {
554
        // language code is not recognised or 2nd translator won't translate it
555
0
        p[0] = phonSCHWA; // just say something
556
0
        p[1] = phonSCHWA;
557
0
        p[2] = 0;
558
0
      }
559
560
116k
      if (switch_phonemes == -1) {
561
0
        strcpy(dictionary_name, old_dictionary_name);
562
0
        SelectPhonemeTable(voice->phoneme_tab_ix);
563
564
        // leave switch_phonemes set, but use the original phoneme table number.
565
        // This will suppress LOPT_REGRESSIVE_VOICING
566
0
        switch_phonemes = voice->phoneme_tab_ix; // original phoneme table
567
0
      }
568
116k
    }
569
570
842k
    if (!(word_flags & FLAG_HYPHEN)) {
571
834k
      if (flags & FLAG_PAUSE1) {
572
33.0k
        if (pre_pause < 1)
573
29.2k
          pre_pause = 1;
574
33.0k
      }
575
834k
      if ((flags & FLAG_PREPAUSE) && !(word_flags & (FLAG_LAST_WORD | FLAG_FIRST_WORD)) && !(wtab[-1].flags & FLAG_FIRST_WORD) && (tr->prepause_timeout == 0)) {
576
        // the word is marked in the dictionary list with $pause
577
1.65k
        if (pre_pause < 4) pre_pause = 4;
578
1.65k
        tr->prepause_timeout = 3;
579
1.65k
      }
580
834k
    }
581
582
842k
    if ((option_emphasis >= 3) && (pre_pause < 1))
583
6.87k
      pre_pause = 1;
584
842k
  }
585
586
948k
  stress = 0;
587
948k
  next_stress = 1;
588
948k
  srcix = 0;
589
948k
  max_stress = -1;
590
591
948k
  found_dict_flag = 0;
592
948k
  if ((flags & FLAG_FOUND) && !(flags & FLAG_TEXTMODE))
593
327k
    found_dict_flag = SFLAG_DICTIONARY;
594
595
  // Each iteration may require up to 1 phoneme
596
  // and after this loop we may require up to 7 phonemes
597
  // and our caller requires 2 phonemes
598
1.12M
  while ((pre_pause > 0) && (n_ph_list2 < N_PHONEME_LIST-7-2)) {
599
    // add pause phonemes here. Either because of punctuation (brackets or quotes) in the
600
    // text, or because the word is marked in the dictionary lookup as a conjunction
601
173k
    if (pre_pause > 1) {
602
137k
      SetPlist2(&ph_list2[n_ph_list2++], phonPAUSE);
603
137k
      pre_pause -= 2;
604
137k
    } else {
605
36.1k
      SetPlist2(&ph_list2[n_ph_list2++], phonPAUSE_NOLINK);
606
36.1k
      pre_pause--;
607
36.1k
    }
608
173k
    tr->end_stressed_vowel = 0; // forget about the previous word
609
173k
    tr->prev_dict_flags[0] = 0;
610
173k
    tr->prev_dict_flags[1] = 0;
611
173k
  }
612
948k
  plist2 = &ph_list2[n_ph_list2];
613
  // From here we may require up to 4+1+3 phonemes
614
615
  // This may require up to 4 phonemes
616
948k
  if ((option_capitals == 1) && (word_flags & FLAG_FIRST_UPPER)) {
617
0
    SetPlist2(&ph_list2[n_ph_list2++], phonPAUSE_SHORT);
618
0
    SetPlist2(&ph_list2[n_ph_list2++], phonCAPITAL);
619
0
    if ((word_flags & FLAG_ALL_UPPER) && IsAlpha(word[1])) {
620
      // word > 1 letter and all capitals
621
0
      SetPlist2(&ph_list2[n_ph_list2++], phonPAUSE_SHORT);
622
0
      SetPlist2(&ph_list2[n_ph_list2++], phonCAPITAL);
623
0
    }
624
0
  }
625
626
  // This may require up to 1 phoneme
627
948k
  if (switch_phonemes >= 0) {
628
116k
    if ((p[0] == phonPAUSE) && (p[1] == phonSWITCH)) {
629
      // the new word starts with a phoneme table switch, so there's no need to switch before it.
630
12.1k
      if (ph_list2[n_ph_list2-1].phcode == phonSWITCH) {
631
        // previous phoneme is also a phonSWITCH, delete it
632
5.98k
        n_ph_list2--;
633
5.98k
      }
634
104k
    } else {
635
      // this word uses a different phoneme table
636
104k
      if (ph_list2[n_ph_list2-1].phcode == phonSWITCH) {
637
        // previous phoneme is also a phonSWITCH, just change its phoneme table number
638
60.6k
        n_ph_list2--;
639
60.6k
      } else
640
43.8k
        SetPlist2(&ph_list2[n_ph_list2], phonSWITCH);
641
104k
      ph_list2[n_ph_list2++].tone_ph = switch_phonemes; // temporary phoneme table number
642
104k
    }
643
116k
  }
644
645
  // remove initial pause from a word if it follows a hyphen
646
948k
  if ((word_flags & FLAG_HYPHEN) && (phoneme_tab[*p]->type == phPAUSE))
647
2.44k
    p++;
648
649
948k
  if ((p[0] == 0) && (embedded_flag)) {
650
    // no phonemes.  Insert a very short pause to carry an embedded command
651
5.47k
    p[0] = phonPAUSE_VSHORT;
652
5.47k
    p[1] = 0;
653
5.47k
  }
654
655
  // Each iteration may require up to 1 phoneme
656
  // and after this loop we may require up to 3 phonemes
657
  // and our caller requires 2 phonemes
658
7.65M
  while (((ph_code = *p++) != 0) && (n_ph_list2 < N_PHONEME_LIST-3-2)) {
659
6.70M
    if (ph_code == 255)
660
0
      continue; // unknown phoneme
661
662
    // Add the phonemes to the first stage phoneme list (ph_list2)
663
6.70M
    ph = phoneme_tab[ph_code];
664
6.70M
    if (ph == NULL) {
665
6.71k
      printf("Invalid phoneme code %d\n", ph_code);
666
6.71k
      continue;
667
6.71k
    }
668
669
6.70M
    if (ph_code == phonSWITCH) {
670
99.5k
      ph_list2[n_ph_list2].phcode = ph_code;
671
99.5k
      ph_list2[n_ph_list2].stresslevel = 0;
672
99.5k
      ph_list2[n_ph_list2].sourceix = 0;
673
99.5k
      ph_list2[n_ph_list2].synthflags = 0;
674
99.5k
      ph_list2[n_ph_list2++].tone_ph = *p;
675
99.5k
      SelectPhonemeTable(*p);
676
99.5k
      p++;
677
6.60M
    } else if (ph->type == phSTRESS) {
678
      // don't add stress phonemes codes to the list, but give their stress
679
      // value to the next vowel phoneme
680
      // std_length is used to hold stress number or (if >10) a tone number for a tone language
681
1.46M
      if (ph->program == 0)
682
1.42M
        next_stress = ph->std_length;
683
34.8k
      else {
684
        // for tone languages, the tone number for a syllable follows the vowel
685
34.8k
        if (prev_vowel >= 0)
686
34.1k
          ph_list2[prev_vowel].tone_ph = ph_code;
687
682
        else
688
682
          next_tone = ph_code; // no previous vowel, apply to the next vowel
689
34.8k
      }
690
5.13M
    } else if (ph_code == phonSYLLABIC) {
691
      // mark the previous phoneme as a syllabic consonant
692
1.22k
      prev_vowel = n_ph_list2-1;
693
1.22k
      ph_list2[prev_vowel].synthflags |= SFLAG_SYLLABLE;
694
1.22k
      ph_list2[prev_vowel].stresslevel = next_stress;
695
5.13M
    } else if (ph_code == phonLENGTHEN)
696
67.5k
      ph_list2[n_ph_list2-1].synthflags |= SFLAG_LENGTHEN;
697
5.06M
    else if (ph_code == phonEND_WORD) {
698
      // a || symbol in a phoneme string was used to indicate a word boundary
699
      // Don't add this phoneme to the list, but make sure the next phoneme has
700
      // a newword indication
701
256k
      srcix = source_ix+1;
702
4.81M
    } else if (ph_code == phonX1) {
703
      // a language specific action
704
4
        flags |= FLAG_DOUBLING;
705
4.81M
    } else {
706
4.81M
      ph_list2[n_ph_list2].phcode = ph_code;
707
4.81M
      ph_list2[n_ph_list2].tone_ph = 0;
708
4.81M
      ph_list2[n_ph_list2].synthflags = embedded_flag | found_dict_flag;
709
4.81M
      embedded_flag = 0;
710
4.81M
      ph_list2[n_ph_list2].sourceix = srcix;
711
4.81M
      srcix = 0;
712
713
4.81M
      if (ph->type == phVOWEL) {
714
1.89M
        stress = next_stress;
715
1.89M
        next_stress = 1; // default is 'unstressed'
716
717
1.89M
        if (stress >= 4)
718
856k
          any_stressed_words = true;
719
720
1.89M
        if ((prev_vowel >= 0) && (n_ph_list2-1) != prev_vowel)
721
1.13M
          ph_list2[n_ph_list2-1].stresslevel = stress; // set stress for previous consonant
722
723
1.89M
        ph_list2[n_ph_list2].synthflags |= SFLAG_SYLLABLE;
724
1.89M
        prev_vowel = n_ph_list2;
725
726
1.89M
        if (stress > max_stress) {
727
695k
          max_stress = stress;
728
695k
          max_stress_ix = n_ph_list2;
729
695k
        }
730
1.89M
        if (next_tone != 0) {
731
557
          ph_list2[n_ph_list2].tone_ph = next_tone;
732
557
          next_tone = 0;
733
557
        }
734
2.91M
      } else {
735
2.91M
        if (first_phoneme && tr->prev_dict_flags[0] & FLAG_DOUBLING) {
736
            // double the initial consonant if the previous word is marked with a flag
737
2
          ph_list2[n_ph_list2].synthflags |= SFLAG_LENGTHEN;
738
2
        }
739
2.91M
      }
740
741
4.81M
      ph_list2[n_ph_list2].stresslevel = stress;
742
4.81M
      n_ph_list2++;
743
4.81M
      first_phoneme = false;
744
4.81M
    }
745
6.70M
  }
746
  // From here, we may require up to 3 phonemes
747
748
  // This may require up to 1 phoneme
749
948k
  if (word_flags & FLAG_COMMA_AFTER)
750
125
    SetPlist2(&ph_list2[n_ph_list2++], phonPAUSE_CLAUSE);
751
752
  // don't set new-word if there is a hyphen before it
753
948k
  if ((word_flags & FLAG_HYPHEN) == 0)
754
940k
    plist2->sourceix = source_ix;
755
756
948k
  tr->end_stressed_vowel = 0;
757
948k
  if ((stress >= 4) && (phoneme_tab[ph_list2[n_ph_list2-1].phcode]->type == phVOWEL))
758
136k
    tr->end_stressed_vowel = 1; // word ends with a stressed vowel
759
760
  // This may require up to 1 phoneme
761
948k
  if (switch_phonemes >= 0) {
762
    // this word uses a different phoneme table, now switch back
763
116k
    strcpy(dictionary_name, old_dictionary_name);
764
116k
    SelectPhonemeTable(voice->phoneme_tab_ix);
765
116k
    SetPlist2(&ph_list2[n_ph_list2], phonSWITCH);
766
116k
    ph_list2[n_ph_list2++].tone_ph = voice->phoneme_tab_ix; // original phoneme table number
767
116k
  }
768
769
770
  // This may require up to 1 phoneme
771
948k
  if (pitch_raised > 0) {
772
0
    embedded_list[embedded_ix++] = EMBED_P+0x60+0x80 + (pitch_raised << 8); // lower pitch
773
0
    SetPlist2(&ph_list2[n_ph_list2], phonPAUSE_SHORT);
774
0
    ph_list2[n_ph_list2++].synthflags = SFLAG_EMBEDDED;
775
0
  }
776
777
948k
  if (flags & FLAG_STRESS_END2) {
778
    // this's word's stress could be increased later
779
2.21k
    ph_list2[max_stress_ix].synthflags |= SFLAG_PROMOTE_STRESS;
780
2.21k
  }
781
782
948k
  tr->prev_dict_flags[0] = flags;
783
948k
  return flags;
784
974k
}
785
786
static int EmbeddedCommand(unsigned int *source_index_out)
787
67.3k
{
788
  // An embedded command to change the pitch, volume, etc.
789
  // returns number of commands added to embedded_list
790
791
  // pitch,speed,amplitude,expression,reverb,tone,voice,sayas
792
67.3k
  const char *commands = "PSARHTIVYMUBF";
793
67.3k
  int value = -1;
794
67.3k
  int sign = 0;
795
67.3k
  unsigned char c;
796
67.3k
  char *p;
797
67.3k
  int cmd;
798
67.3k
  int source_index = *source_index_out;
799
800
67.3k
  c = source[source_index];
801
67.3k
  if (c == '+') {
802
1.98k
    sign = 0x40;
803
1.98k
    source_index++;
804
65.3k
  } else if (c == '-') {
805
4.11k
    sign = 0x60;
806
4.11k
    source_index++;
807
4.11k
  }
808
809
67.3k
  if (IsDigit09(source[source_index])) {
810
15.6k
    value = atoi(&source[source_index]);
811
55.3k
    while (IsDigit09(source[source_index]))
812
39.6k
      source_index++;
813
15.6k
  }
814
815
67.3k
  c = source[source_index++];
816
67.3k
  if (embedded_ix >= (N_EMBEDDED_LIST - 2))
817
218
    return 0; // list is full
818
819
67.1k
  if ((p = strchr_w(commands, c)) == NULL)
820
30.2k
    return 0;
821
36.8k
  cmd = (p - commands)+1;
822
36.8k
  if (value == -1) {
823
24.0k
    value = embedded_default[cmd];
824
24.0k
    sign = 0;
825
24.0k
  }
826
827
36.8k
  if (cmd == EMBED_Y) {
828
3.53k
    option_sayas2 = value;
829
3.53k
    count_sayas_digits = 0;
830
3.53k
  }
831
36.8k
  if (cmd == EMBED_F) {
832
2.25k
    if (value >= 3)
833
412
      word_emphasis = FLAG_EMPHASIZED;
834
1.84k
    else
835
1.84k
      word_emphasis = 0;
836
2.25k
  }
837
838
36.8k
  embedded_list[embedded_ix++] = cmd + sign + (value << 8);
839
36.8k
  *source_index_out = source_index;
840
36.8k
  return 1;
841
67.1k
}
842
843
static const char *FindReplacementChars(Translator *tr, const char **pfrom, unsigned int c, const char *next, int *ignore_next_n)
844
1.95M
{
845
1.95M
  const char *from = *pfrom;
846
50.6M
  while ( !is_str_totally_null(from, 4) ) {
847
48.6M
    unsigned int fc = 0; // from character
848
48.6M
    unsigned int nc = c; // next character
849
48.6M
    const char *match_next = next;
850
851
48.6M
    *pfrom = from;
852
853
48.6M
    from += utf8_in((int *)&fc, from);
854
48.6M
    if (nc == fc) {
855
66.7k
      if (*from == 0) return from + 1;
856
857
66.7k
      bool matched = true;
858
45.2k
      int nmatched = 0;
859
90.5k
      while (*from != 0) {
860
45.2k
        from += utf8_in((int *)&fc, from);
861
862
45.2k
        match_next += utf8_in((int *)&nc, match_next);
863
45.2k
        nc = towlower2(nc, tr);
864
865
45.2k
        if (nc != fc)
866
44.8k
          matched = false;
867
419
        else
868
419
          nmatched++;
869
45.2k
      }
870
871
45.2k
      if (matched) {
872
419
        *ignore_next_n = nmatched;
873
419
        return from + 1;
874
419
      }
875
45.2k
    }
876
877
    // replacement 'from' string (skip the remaining part, if any)
878
120M
    while (*from != '\0') from++;
879
48.6M
    from++;
880
881
    // replacement 'to' string
882
163M
    while (*from != '\0') from++;
883
48.6M
    from++;
884
48.6M
  }
885
1.92M
  return NULL;
886
1.95M
}
887
888
// handle .replace rule in xx_rules file
889
static int SubstituteChar(Translator *tr, unsigned int c, unsigned int next_in, const char *next, int *insert, int *wordflags)
890
4.03M
{
891
4.03M
  unsigned int new_c, c2 = ' ', c_lower;
892
4.03M
  int upper_case = 0;
893
894
4.03M
  static int ignore_next_n = 0;
895
4.03M
  if (ignore_next_n > 0) {
896
419
    ignore_next_n--;
897
419
    return 8;
898
419
  }
899
900
4.03M
  if (c == 0) return 0;
901
902
4.03M
  const char *from = (const char *)tr->langopts.replace_chars;
903
4.03M
  if (from == NULL)
904
2.08M
    return c;
905
906
  // there is a list of character codes to be substituted with alternative codes
907
908
1.95M
  if (iswupper(c_lower = c)) {
909
161k
    c_lower = towlower2(c, tr);
910
161k
    upper_case = 1;
911
161k
  }
912
913
1.95M
  const char *to = FindReplacementChars(tr, &from, c_lower, next, &ignore_next_n);
914
1.95M
  if (to == NULL)
915
1.92M
    return c; // no substitution
916
917
21.8k
  if (option_phonemes & espeakPHONEMES_TRACE)
918
0
    fprintf(f_trans, "Replace: %s > %s\n", from, to);
919
920
21.8k
  to += utf8_in((int *)&new_c, to);
921
21.8k
  if (*to != 0) {
922
    // there is a second character to be inserted
923
    // don't convert the case of the second character unless the next letter is also upper case
924
352
    to += utf8_in((int *)&c2, to);
925
352
    if (upper_case && iswupper(next_in))
926
5
      c2 = ucd_toupper(c2);
927
352
    *insert = c2;
928
352
  }
929
930
21.8k
  if (upper_case)
931
5.55k
    new_c = ucd_toupper(new_c);
932
933
21.8k
  *wordflags |= FLAG_CHAR_REPLACED;
934
21.8k
  return new_c;
935
1.95M
}
936
937
static int TranslateChar(Translator *tr, char *ptr, int prev_in, unsigned int c, unsigned int next_in, int *insert, int *wordflags)
938
4.04M
{
939
  // To allow language specific examination and replacement of characters
940
941
4.04M
  int code;
942
4.04M
  int next2;
943
944
4.04M
  static const unsigned char hangul_compatibility[0x34] = {
945
4.04M
    0,  0x00, 0x01, 0xaa, 0x02, 0xac, 0xad, 0x03,
946
4.04M
    0x04, 0x05, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb4,
947
4.04M
    0xb6, 0x06, 0x07, 0x08, 0xb9, 0x09, 0x0a, 0xbc,
948
4.04M
    0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x61,
949
4.04M
    0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
950
4.04M
    0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71,
951
4.04M
    0x72, 0x73, 0x74, 0x75
952
4.04M
  };
953
954
  // check for Korean Hangul letters
955
4.04M
  if (((code = c - 0xac00) >= 0) && (c <= 0xd7af)) {
956
    // break a syllable hangul into 2 or 3 individual jamo
957
8.56k
    int initial = (code/28)/21;
958
8.56k
    int medial = (code/28) % 21;
959
8.56k
    int final = code % 28;
960
961
8.56k
    if (initial == 11) {
962
      // null initial
963
4.35k
      c = medial + 0x1161;
964
4.35k
      if (final > 0)
965
4.13k
        *insert = final + 0x11a7;
966
4.35k
    } else {
967
      // extract the initial and insert the remainder with a null initial
968
4.21k
      c = initial + 0x1100;
969
4.21k
      *insert = (11*28*21) + (medial*28) + final + 0xac00;
970
4.21k
    }
971
8.56k
    return c;
972
4.03M
  } else if (((code = c - 0x3130) >= 0) && (code < 0x34)) {
973
    // Hangul compatibility jamo
974
453
    return hangul_compatibility[code] + 0x1100;
975
453
  }
976
977
4.03M
  switch (tr->translator_name)
978
4.03M
  {
979
695
  case L('a', 'f'):
980
49.1k
  case L('n', 'l'):
981
    // look for 'n  and replace by a special character (unicode: schwa)
982
983
49.1k
    if ((c == '\'') && !iswalpha(prev_in)) {
984
409
      utf8_in(&next2, &ptr[1]);
985
986
409
      if (IsSpace(next2)) {
987
236
        if ((next_in == 'n') && (tr->translator_name == L('a', 'f'))) {
988
          // n preceded by either apostrophe or U2019 "right single quotation mark"
989
0
          ptr[0] = ' '; // delete the n
990
0
          return 0x0259; // replace  '  by  unicode schwa character
991
0
        }
992
236
        if ((next_in == 'n') || (next_in == 't')) {
993
          // Dutch, [@n] and [@t]
994
0
          return 0x0259; // replace  '  by  unicode schwa character
995
0
        }
996
236
      }
997
409
    }
998
49.1k
    break;
999
4.03M
  }
1000
  // handle .replace rule in xx_rules file
1001
4.03M
  return SubstituteChar(tr, c, next_in, ptr, insert, wordflags);
1002
4.03M
}
1003
1004
static const char *const UCase_ga[] = { "bp", "bhf", "dt", "gc", "hA", "mb", "nd", "ng", "ts", "tA", "nA", NULL };
1005
1006
static int UpperCaseInWord(Translator *tr, char *word, int c)
1007
16.2k
{
1008
16.2k
  if (tr->translator_name == L('g', 'a')) {
1009
863
    int ix;
1010
863
    const char *p;
1011
1012
10.1k
    for (ix = 0;; ix++) {
1013
10.1k
      int len;
1014
10.1k
      if ((p = UCase_ga[ix]) == NULL)
1015
750
        break;
1016
1017
9.38k
      len = strlen(p);
1018
9.38k
      if ((word[-len] == ' ') && (memcmp(&word[-len+1], p, len-1) == 0)) {
1019
801
        if ((c == p[len-1]) || ((p[len-1] == 'A') && IsVowel(tr, c)))
1020
113
          return 1;
1021
801
      }
1022
9.38k
    }
1023
863
  }
1024
16.0k
  return 0;
1025
16.2k
}
1026
1027
// Same as TranslateClause except we also get the clause terminator used (full stop, comma, etc.).
1028
// Used by espeak_TextToPhonemesWithTerminator.
1029
void TranslateClauseWithTerminator(Translator *tr, int *tone_out, char **voice_change, int *terminator_out)
1030
72.9k
{
1031
72.9k
  int ix;
1032
72.9k
  int c;
1033
72.9k
  int cc = 0;
1034
72.9k
  unsigned int source_index = 0;
1035
72.9k
  int source_index_word = 0;
1036
72.9k
  int prev_in;
1037
72.9k
  int prev_out = ' ';
1038
72.9k
  int prev_in_save = 0;
1039
72.9k
  int next_in;
1040
72.9k
  int next_in_nbytes;
1041
72.9k
  int char_inserted = 0;
1042
72.9k
  int clause_pause;
1043
72.9k
  int pre_pause_add = 0;
1044
72.9k
  int all_upper_case = FLAG_ALL_UPPER;
1045
72.9k
  int alpha_count = 0;
1046
72.9k
  bool finished = false;
1047
72.9k
  bool single_quoted = false;
1048
72.9k
  bool phoneme_mode = false;
1049
72.9k
  int dict_flags = 0; // returned from dictionary lookup
1050
72.9k
  int word_flags; // set here
1051
72.9k
  int next_word_flags;
1052
72.9k
  bool new_sentence2;
1053
72.9k
  int embedded_count = 0;
1054
72.9k
  int letter_count = 0;
1055
72.9k
  bool space_inserted = false;
1056
72.9k
  bool syllable_marked = false;
1057
72.9k
  bool decimal_sep_count = false;
1058
72.9k
  char *word;
1059
72.9k
  char *p;
1060
72.9k
  int j, k;
1061
72.9k
  int n_digits;
1062
72.9k
  int charix_top = 0;
1063
1064
72.9k
  short charix[N_TR_SOURCE+4];
1065
72.9k
  WORD_TAB words[N_CLAUSE_WORDS];
1066
72.9k
  static char voice_change_name[40];
1067
72.9k
  int word_count = 0; // index into words
1068
1069
72.9k
  char sbuf[N_TR_SOURCE];
1070
1071
72.9k
  int terminator;
1072
72.9k
  int tone;
1073
1074
72.9k
  if (tr == NULL)
1075
0
    return;
1076
1077
72.9k
  MAKE_MEM_UNDEFINED(&voice_change_name, sizeof(voice_change_name));
1078
1079
72.9k
  embedded_ix = 0;
1080
72.9k
  embedded_read = 0;
1081
72.9k
  pre_pause = 0;
1082
72.9k
  any_stressed_words = false;
1083
1084
72.9k
  if ((clause_start_char = count_characters) < 0)
1085
2.37k
    clause_start_char = 0;
1086
72.9k
  clause_start_word = count_words + 1;
1087
1088
58.4M
  for (ix = 0; ix < N_TR_SOURCE; ix++)
1089
58.3M
    charix[ix] = 0;
1090
72.9k
  MAKE_MEM_UNDEFINED(&source, sizeof(source));
1091
72.9k
  terminator = ReadClause(tr, source, charix, &charix_top, N_TR_SOURCE, &tone, voice_change_name);
1092
1093
72.9k
  if (terminator_out != NULL) {
1094
0
    *terminator_out = terminator;
1095
0
  }
1096
1097
72.9k
  if (tone_out != NULL) {
1098
72.9k
    if (tone == 0)
1099
71.2k
      *tone_out = (terminator & CLAUSE_INTONATION_TYPE) >> 12; // tone type not overridden in ReadClause, use default
1100
1.62k
    else
1101
1.62k
      *tone_out = tone; // override tone type
1102
72.9k
  }
1103
1104
72.9k
  charix[charix_top+1] = 0;
1105
72.9k
  charix[charix_top+2] = 0x7fff;
1106
72.9k
  charix[charix_top+3] = 0;
1107
1108
72.9k
  clause_pause = (terminator & CLAUSE_PAUSE) * 10; // mS
1109
72.9k
  if (terminator & CLAUSE_PAUSE_LONG)
1110
0
    clause_pause = clause_pause * 32; // pause value is *320mS not *10mS
1111
1112
316k
  for (p = source; *p != 0; p++) {
1113
303k
    if (!isspace2(*p))
1114
59.3k
      break;
1115
303k
  }
1116
72.9k
  if (*p == 0) {
1117
    // No characters except spaces. This is not a sentence.
1118
    // Don't add this pause, just make up the previous pause to this value;
1119
13.5k
    clause_pause -= max_clause_pause;
1120
13.5k
    if (clause_pause < 0)
1121
2.13k
      clause_pause = 0;
1122
1123
13.5k
    if (new_sentence)
1124
6.94k
      terminator |= CLAUSE_TYPE_SENTENCE; // carry forward an end-of-sentence indicator
1125
13.5k
    max_clause_pause += clause_pause;
1126
13.5k
    new_sentence2 = false;
1127
59.3k
  } else {
1128
59.3k
    max_clause_pause = clause_pause;
1129
59.3k
    new_sentence2 = new_sentence;
1130
59.3k
  }
1131
72.9k
  tr->clause_terminator = terminator;
1132
1133
72.9k
  if (new_sentence2) {
1134
40.8k
    count_sentences++;
1135
40.8k
    if (skip_sentences > 0) {
1136
0
      skip_sentences--;
1137
0
      if (skip_sentences == 0)
1138
0
        skipping_text = false;
1139
0
    }
1140
40.8k
  }
1141
1142
72.9k
  MAKE_MEM_UNDEFINED(&ph_list2, sizeof(ph_list2));
1143
72.9k
  memset(&ph_list2[0], 0, sizeof(ph_list2[0]));
1144
72.9k
  ph_list2[0].phcode = phonPAUSE_SHORT;
1145
1146
72.9k
  n_ph_list2 = 1;
1147
72.9k
  tr->prev_last_stress = 0;
1148
72.9k
  tr->prepause_timeout = 0;
1149
72.9k
  tr->expect_verb = 0;
1150
72.9k
  tr->expect_noun = 0;
1151
72.9k
  tr->expect_past = 0;
1152
72.9k
  tr->expect_verb_s = 0;
1153
72.9k
  tr->phonemes_repeat_count = 0;
1154
72.9k
  tr->end_stressed_vowel = 0;
1155
72.9k
  tr->prev_dict_flags[0] = 0;
1156
72.9k
  tr->prev_dict_flags[1] = 0;
1157
1158
72.9k
  word_count = 0;
1159
72.9k
  word_flags = 0;
1160
72.9k
  next_word_flags = 0;
1161
1162
72.9k
  sbuf[0] = 0;
1163
72.9k
  sbuf[1] = ' ';
1164
72.9k
  sbuf[2] = ' ';
1165
72.9k
  ix = 3;
1166
72.9k
  prev_in = ' ';
1167
1168
72.9k
  words[0].start = ix;
1169
72.9k
  words[0].flags = 0;
1170
1171
72.9k
  words[0].length = CalcWordLength(source_index, charix_top, charix, words, 0);
1172
1173
72.9k
  int prev_out2;
1174
5.90M
  while (!finished && (ix < (int)sizeof(sbuf) - 1)) {
1175
5.83M
    prev_out2 = prev_out;
1176
5.83M
    utf8_in2(&prev_out, &sbuf[ix-1], 1);
1177
1178
5.83M
    if (tr->langopts.tone_numbers && IsDigit09(prev_out) && IsAlpha(prev_out2)) {
1179
      // tone numbers can be part of a word, consider them as alphabetic
1180
424
      prev_out = 'a';
1181
424
    }
1182
1183
5.83M
    if (prev_in_save != 0) {
1184
114k
      prev_in = prev_in_save;
1185
114k
      prev_in_save = 0;
1186
5.71M
    } else if (source_index > 0)
1187
5.64M
      utf8_in2(&prev_in, &source[source_index-1], 1);
1188
1189
5.83M
    unsigned int prev_source_index = source_index;
1190
1191
5.83M
    if (char_inserted) {
1192
7.69k
      c = char_inserted;
1193
7.69k
      char_inserted = 0;
1194
5.82M
    } else {
1195
5.82M
      source_index += utf8_in(&cc, &source[source_index]);
1196
5.82M
      c = cc;
1197
5.82M
    }
1198
1199
5.83M
    if (c == 0) {
1200
72.9k
      finished = true;
1201
72.9k
      c = ' ';
1202
72.9k
      next_in = ' ';
1203
72.9k
      next_in_nbytes = 0;
1204
72.9k
    }
1205
5.75M
    else
1206
5.75M
      next_in_nbytes = utf8_in(&next_in, &source[source_index]);
1207
1208
5.83M
    if (c == CTRL_EMBEDDED) {
1209
      // start of embedded command in the text
1210
96.0k
      int srcix = source_index-1;
1211
1212
96.0k
      if (prev_in != ' ') {
1213
28.7k
        c = ' ';
1214
28.7k
        prev_in_save = c;
1215
28.7k
        source_index--;
1216
67.3k
      } else {
1217
67.3k
        embedded_count += EmbeddedCommand(&source_index);
1218
67.3k
        prev_in_save = prev_in;
1219
        // replace the embedded command by spaces
1220
67.3k
        memset(&source[srcix], ' ', source_index-srcix);
1221
67.3k
        source_index = srcix;
1222
67.3k
        continue;
1223
67.3k
      }
1224
96.0k
    }
1225
1226
5.76M
    if ((option_sayas2 == SAYAS_KEY) && (c != ' ')) {
1227
7.57k
      if ((prev_in == ' ') && (next_in == ' '))
1228
434
        option_sayas2 = SAYAS_SINGLE_CHARS; // single character, speak its name
1229
7.57k
      c = towlower2(c, tr);
1230
7.57k
    }
1231
1232
1233
5.76M
    if (phoneme_mode) {
1234
1.45M
      all_upper_case = FLAG_PHONEMES;
1235
1236
1.45M
      if ((c == ']') && (next_in == ']')) {
1237
87.2k
        phoneme_mode = false;
1238
87.2k
        source_index++;
1239
87.2k
        c = ' ';
1240
87.2k
      }
1241
4.30M
    } else if ((option_sayas2 & 0xf0) == SAYAS_DIGITS) {
1242
132k
      if (iswdigit(c)) {
1243
10.9k
        count_sayas_digits++;
1244
10.9k
        if (count_sayas_digits > (option_sayas2 & 0xf)) {
1245
          // break after the specified number of digits
1246
174
          c = ' ';
1247
174
          space_inserted = true;
1248
174
          count_sayas_digits = 0;
1249
174
        }
1250
121k
      } else {
1251
121k
        count_sayas_digits = 0;
1252
121k
        if (iswdigit(prev_out)) {
1253
9.48k
          c = ' ';
1254
9.48k
          space_inserted = true;
1255
9.48k
        }
1256
121k
      }
1257
4.17M
    } else if ((option_sayas2 & 0x10) == 0) {
1258
      // speak as words
1259
1260
4.04M
      if ((c == 0x92) || (c == 0xb4) || (c == 0x2019) || (c == 0x2032))
1261
133
        c = '\''; // 'microsoft' quote or sexed closing single quote, or prime - possibly used as apostrophe
1262
1263
4.04M
      if (((c == 0x2018) || (c == '?')) && IsAlpha(prev_out) && IsAlpha(next_in)) {
1264
        // ? between two letters may be a smart-quote replaced by ?
1265
1.11k
        c = '\'';
1266
1.11k
      }
1267
1268
4.04M
      if (c == CHAR_EMPHASIS) {
1269
        // this character is a marker that the previous word is the focus of the clause
1270
3.07k
        c = ' ';
1271
3.07k
        word_flags |= FLAG_FOCUS;
1272
3.07k
      }
1273
1274
4.04M
      if (c == CHAR_COMMA_BREAK) {
1275
191
        c = ' ';
1276
191
        word_flags |= FLAG_COMMA_AFTER;
1277
191
      }
1278
      // language specific character translations
1279
4.04M
      c = TranslateChar(tr, &source[source_index], prev_in, c, next_in, &char_inserted, &word_flags);
1280
4.04M
      if (c == 8)
1281
3.53k
        continue; // ignore this character
1282
1283
4.04M
      if ((c == 0xfe0e) || (c == 0xfe0f))
1284
27
        continue; // variation selector (text/emoji presentation style), not spoken;
1285
                  // emoji dictionary keys are stripped of these at compile time to match
1286
1287
4.04M
      if (char_inserted)
1288
8.70k
        next_in = char_inserted;
1289
1290
      // allow certain punctuation within a word (usually only apostrophe)
1291
4.04M
      if (!IsAlpha(c) && !IsSpace(c) && (wcschr(tr->punct_within_word, c) == 0)) {
1292
1.36M
        if (IsAlpha(prev_out)) {
1293
111k
          if (tr->langopts.tone_numbers && IsDigit09(c) && !IsDigit09(next_in)) {
1294
            // allow a tone number as part of the word
1295
111k
          } else if ((c == 0x200d) && IsEmoji(prev_out) && IsEmoji(next_in)) {
1296
            // keep U+200D ZERO WIDTH JOINER between two emoji in the word,
1297
            // so the sequence can match a multi-codepoint emoji dictionary
1298
            // entry (e.g. woman + ZWJ + microscope = woman scientist)
1299
111k
          } else if (IsEmojiTag(c) && IsEmoji(prev_out)) {
1300
            // keep the tag characters of an emoji tag sequence in the
1301
            // word, so that it can match its dictionary entry (black
1302
            // flag + the tag letters "gbsct" + cancel tag = flag of
1303
            // Scotland). The following tag characters get here with a
1304
            // tag character as prev_out, which is not IsAlpha, so they
1305
            // are kept without needing a case of their own.
1306
111k
          } else {
1307
111k
            c = ' '; // ensure we have an end-of-word terminator
1308
111k
            space_inserted = true;
1309
111k
          }
1310
111k
        }
1311
1.36M
      }
1312
1313
4.04M
      if (iswdigit(prev_out)) {
1314
292k
        if (!iswdigit(c) && (c != '.') && (c != ',') && (c != ' ')) {
1315
59.8k
          c = ' '; // terminate digit string with a space
1316
59.8k
          space_inserted = true;
1317
59.8k
        }
1318
3.74M
      } else { // Prev output is not digit
1319
3.74M
        if (prev_in == ',') {
1320
          // Workaround for several consecutive commas —
1321
          // replace current character with space
1322
19.2k
          if (c == ',')
1323
1
            c = ' ';
1324
3.72M
        } else {
1325
3.72M
          decimal_sep_count = false;
1326
3.72M
        }
1327
3.74M
      }
1328
1329
4.04M
      if (c == '[') {
1330
101k
        if ((next_in == '\002') || ((next_in == '[') && option_phoneme_input)) {
1331
          //  "[\002" is used internally to start phoneme mode
1332
89.2k
          phoneme_mode = true;
1333
89.2k
          source_index++;
1334
89.2k
          continue;
1335
89.2k
        }
1336
101k
      }
1337
1338
3.95M
      if (IsAlpha(c)) {
1339
955k
        bool emoji_join = false;
1340
955k
        bool emoji_break = false;
1341
1342
955k
        alpha_count++;
1343
955k
        if ((prev_out == 0x200d) && IsEmoji(c)) {
1344
          // U+200D ZERO WIDTH JOINER: continue the emoji sequence only if
1345
          // the current word began with an emoji; a word-initial ZWJ (stray
1346
          // joiner in the input) must not absorb the following emoji, which
1347
          // would prevent its dictionary lookup
1348
2
          int wc_first;
1349
2
          utf8_in(&wc_first, &sbuf[words[word_count].start]);
1350
2
          emoji_join = IsEmoji(wc_first);
1351
955k
        } else if (IsEmoji(c) && IsEmoji(prev_out)) {
1352
          // Two emoji written next to each other. They only belong to the
1353
          // same token when they form one of the sequences that the emoji
1354
          // dictionary has a key for; otherwise each emoji must become its
1355
          // own word, or the whole run fails its dictionary lookup and is
1356
          // spelled out codepoint by codepoint.
1357
984
          if (IsEmojiModifier(c)) {
1358
            // skin tone modifier, part of the preceding emoji
1359
950
          } else if (IsRegionalIndicator(c) && IsRegionalIndicator(prev_out)) {
1360
            // A flag is exactly two regional indicators (e.g. 🇰🇿), so
1361
            // count how many of them the word already holds: an odd
1362
            // count means this one completes a flag, an even count
1363
            // means it starts the next one.
1364
24
            int n_ri = 0;
1365
24
            int j = ix;
1366
1367
54
            while (j > (int)words[word_count].start) {
1368
30
              int wc_ri;
1369
30
              utf8_in2(&wc_ri, &sbuf[j-1], 1);
1370
30
              if (!IsRegionalIndicator(wc_ri))
1371
0
                break;
1372
30
              n_ri++;
1373
30
              j -= 4; // a regional indicator is 4 bytes in UTF-8
1374
30
            }
1375
24
            emoji_break = ((n_ri & 1) == 0);
1376
24
          } else
1377
926
            emoji_break = true;
1378
984
        }
1379
        // Start a new word where an adjacent letter belongs to a script
1380
        // that is foreign to the base language (e.g. Latin glued to
1381
        // Malayalam, as in "BJPയുമായുള്ള"), so each run is translated in
1382
        // its own language rather than the whole token being spelled out
1383
        // letter by letter. Comparing foreign-ness to the base language
1384
        // (rather than raw alphabet identity) keeps the language's own
1385
        // script joined, including ranges outside the alphabet table such
1386
        // as Greek Extended.
1387
955k
        const ALPHABET *alpha_c = AlphabetFromChar(c);
1388
955k
        const ALPHABET *alpha_prev = AlphabetFromChar(prev_out);
1389
955k
        bool c_foreign = (alpha_c != NULL) && (alpha_c->offset != tr->letter_bits_offset);
1390
955k
        bool prev_foreign = (alpha_prev != NULL) && (alpha_prev->offset != tr->letter_bits_offset);
1391
955k
        if (emoji_join) {
1392
          // continuation of a ZWJ emoji sequence: not a word boundary
1393
955k
        } else if (emoji_break || !IsAlpha(prev_out) || ShouldSplitIdeographs(tr, c, prev_out) || (IsEmoji(c) != IsEmoji(prev_out)) || (c_foreign != prev_foreign)) {
1394
425k
          if (wcschr(tr->punct_within_word, prev_out) == 0)
1395
418k
            letter_count = 0; // don't reset count for an apostrophy within a word
1396
1397
425k
          if ((prev_out != ' ') && (wcschr(tr->punct_within_word, prev_out) == 0)) {
1398
            // start of word, insert space if not one there already
1399
105k
            c = ' ';
1400
105k
            space_inserted = true;
1401
1402
105k
            if (!IsBracket(prev_out)) // ?? perhaps only set FLAG_NOSPACE for . - /  (hyphenated words, URLs, etc)
1403
92.5k
              next_word_flags |= FLAG_NOSPACE;
1404
320k
          } else {
1405
320k
            if (iswupper(c))
1406
118k
              word_flags |= FLAG_FIRST_UPPER;
1407
1408
320k
            if ((prev_out == ' ') && iswdigit(sbuf[ix-2]) && !iswdigit(prev_in)) {
1409
              // word, following a number, but with a space between
1410
              // Add an extra space, to distinguish "2 a" from "2a"
1411
12.2k
              sbuf[ix++] = ' ';
1412
12.2k
              words[word_count].start++;
1413
12.2k
            }
1414
320k
          }
1415
425k
        }
1416
1417
955k
        if (c != ' ') {
1418
849k
          letter_count++;
1419
1420
849k
          if (tr->letter_bits_offset > 0) {
1421
330k
            if (((c < 0x250) && (prev_out >= tr->letter_bits_offset)) ||
1422
329k
                ((c >= tr->letter_bits_offset) && (letter_count > 1) && (prev_out < 0x250))) {
1423
              // Don't mix native and Latin characters in the same word
1424
              // Break into separate words
1425
1.89k
              if (IsAlpha(prev_out)) {
1426
1.30k
                c = ' ';
1427
1.30k
                space_inserted = true;
1428
1.30k
                word_flags |= FLAG_HYPHEN_AFTER;
1429
1.30k
                next_word_flags |= FLAG_HYPHEN;
1430
1.30k
              }
1431
1.89k
            }
1432
330k
          }
1433
849k
        }
1434
1435
955k
        if (iswupper(c)) {
1436
318k
          c = towlower2(c, tr);
1437
1438
318k
          if (tr->langopts.param[LOPT_CAPS_IN_WORD]) {
1439
0
            if (syllable_marked == false) {
1440
0
              char_inserted = c;
1441
0
              c = 0x2c8; // stress marker
1442
0
              syllable_marked = true;
1443
0
            }
1444
318k
          } else {
1445
318k
            if (iswlower(prev_in)) {
1446
              // lower case followed by upper case, possibly CamelCase
1447
17.3k
              if ((prev_out != ' ') && UpperCaseInWord(tr, &sbuf[ix], c) == 0)
1448
16.0k
              { // start a new word
1449
16.0k
                c = ' ';
1450
16.0k
                space_inserted = true;
1451
16.0k
                prev_in_save = c;
1452
16.0k
              }
1453
301k
            } else if ((c != ' ') && iswupper(prev_in) && iswlower(next_in)) {
1454
3.52k
              int next2_in;
1455
3.52k
              utf8_in(&next2_in, &source[source_index + next_in_nbytes]);
1456
1457
3.52k
              if ((tr->translator_name == L('n', 'l')) && (letter_count == 2) && (c == 'j') && (prev_in == 'I')) {
1458
                // Dutch words may capitalise initial IJ, don't split
1459
3.52k
              } else if ((prev_out != ' ') && IsAlpha(next2_in)) {
1460
                // changing from upper to lower case, start new word at the last uppercase, if 3 or more letters
1461
2.08k
                c = ' ';
1462
2.08k
                space_inserted = true;
1463
2.08k
                prev_in_save = c;
1464
2.08k
                next_word_flags |= FLAG_NOSPACE;
1465
2.08k
              }
1466
3.52k
            }
1467
318k
          }
1468
636k
        } else {
1469
636k
          if ((all_upper_case) && (letter_count > 2)) {
1470
            // Flag as plural only English
1471
2.96k
            if (tr->translator_name == L('e', 'n') && (c == 's') && (next_in == ' ')) {
1472
28
              c = ' ';
1473
28
              all_upper_case |= FLAG_HAS_PLURAL;
1474
1475
28
              if (sbuf[ix-1] == '\'')
1476
20
                sbuf[ix-1] = ' ';
1477
28
            } else
1478
2.93k
              all_upper_case = 0; // current word contains lower case letters, not "'s"
1479
2.96k
          } else
1480
633k
            all_upper_case = 0;
1481
636k
        }
1482
2.99M
      } else if (c == '-') {
1483
25.6k
        if (!IsSpace(prev_in) && IsAlpha(next_in)) {
1484
7.92k
          if (prev_out != ' ') {
1485
            // previous 'word' not yet ended (not alpha or numeric), start new word now.
1486
671
            c = ' ';
1487
671
            space_inserted = true;
1488
7.25k
          } else {
1489
            // '-' between two letters is a hyphen, treat as a space
1490
7.25k
            word_flags |= FLAG_HYPHEN;
1491
7.25k
            if (word_count > 0)
1492
7.05k
              words[word_count-1].flags |= FLAG_HYPHEN_AFTER;
1493
7.25k
            c = ' ';
1494
7.25k
          }
1495
17.7k
        } else if ((prev_in == ' ') && (next_in == ' ')) {
1496
          // ' - ' dash between two spaces, treat as pause
1497
956
          c = ' ';
1498
956
          pre_pause_add = 4;
1499
16.8k
        } else if (next_in == '-') {
1500
          // double hyphen, treat as pause
1501
5.14k
          source_index++;
1502
5.14k
          c = ' ';
1503
5.14k
          pre_pause_add = 4;
1504
11.6k
        } else if ((prev_out == ' ') && IsAlpha(prev_out2) && !IsAlpha(prev_in)) {
1505
          // insert extra space between a word + space + hyphen, to distinguish 'a -2' from 'a-2'
1506
1.00k
          sbuf[ix++] = ' ';
1507
1.00k
          words[word_count].start++;
1508
1.00k
        }
1509
2.97M
      } else if (c == '.') {
1510
43.5k
        if (prev_out == '.') {
1511
          // multiple dots, separate by spaces. Note >3 dots has been replaced by elipsis
1512
1.41k
          c = ' ';
1513
1.41k
          space_inserted = true;
1514
42.1k
        } else if ((word_count > 0) && !(words[word_count-1].flags & FLAG_NOSPACE) && IsAlpha(prev_in)) {
1515
          // dot after a word, with space following, probably an abbreviation
1516
9.85k
          words[word_count-1].flags |= FLAG_HAS_DOT;
1517
1518
9.85k
          if (IsSpace(next_in) || (next_in == '-'))
1519
4.34k
            c = ' '; // remove the dot if it's followed by a space or hyphen, so that it's not pronounced
1520
9.85k
        }
1521
2.92M
      } else if (c == '\'') {
1522
16.0k
        if (((prev_in == '.' && next_in == 's') || iswalnum(prev_in)) && IsAlpha(next_in)) {
1523
          // between two letters, or in an abbreviation (eg. u.s.a.'s). Consider the apostrophe as part of the word
1524
7.00k
          single_quoted = false;
1525
9.00k
        } else if ((tr->langopts.param[LOPT_APOSTROPHE] & 1) && IsAlpha(next_in))
1526
0
          single_quoted = false; // apostrophe at start of word is part of the word
1527
9.00k
        else if ((tr->langopts.param[LOPT_APOSTROPHE] & 2) && IsAlpha(prev_in))
1528
32
          single_quoted = false; // apostrophe at end of word is part of the word
1529
8.96k
        else if ((wcschr(tr->char_plus_apostrophe, prev_in) != 0) && (prev_out2 == ' ')) {
1530
          // consider single character plus apostrophe as a word
1531
0
          single_quoted = false;
1532
0
          if (next_in == ' ')
1533
0
            source_index++; // skip following space
1534
8.96k
        } else {
1535
8.96k
          if ((prev_out == 's') && (single_quoted == false)) {
1536
            // looks like apostrophe after an 's'
1537
82
            c = ' ';
1538
8.88k
          } else {
1539
8.88k
            if (IsSpace(prev_out))
1540
5.89k
              single_quoted = true;
1541
2.99k
            else
1542
2.99k
              single_quoted = false;
1543
1544
8.88k
            pre_pause_add = 4; // single quote
1545
8.88k
            c = ' ';
1546
8.88k
          }
1547
8.96k
        }
1548
2.91M
      } else if (lookupwchar(breaks, c) != 0)
1549
9.83k
        c = ' '; // various characters to treat as space
1550
2.90M
      else if (iswdigit(c)) {
1551
326k
        if (tr->langopts.tone_numbers && IsAlpha(prev_out) && !IsDigit(next_in)) {
1552
326k
        } else if ((prev_out != ' ') && !iswdigit(prev_out)) {
1553
37.8k
          if ((prev_out != tr->langopts.decimal_sep) || ((decimal_sep_count == true) && (tr->langopts.decimal_sep == ','))) {
1554
32.9k
            c = ' ';
1555
32.9k
            space_inserted = true;
1556
32.9k
          } else
1557
4.85k
            decimal_sep_count = true;
1558
288k
        } else if ((prev_out == ' ') && IsAlpha(prev_out2) && !IsAlpha(prev_in)) {
1559
          // insert extra space between a word and a number, to distinguish 'a 2' from 'a2'
1560
4.14k
          sbuf[ix++] = ' ';
1561
4.14k
          words[word_count].start++;
1562
4.14k
        }
1563
326k
      }
1564
3.95M
    }
1565
1566
5.67M
    if (IsSpace(c)) {
1567
2.24M
      if (prev_out == ' ') {
1568
1.35M
        word_flags |= FLAG_MULTIPLE_SPACES;
1569
1.35M
        continue; // multiple spaces
1570
1.35M
      }
1571
1572
894k
      if ((cc == 0x09) || (cc == 0x0a))
1573
8.36k
        next_word_flags |= FLAG_MULTIPLE_SPACES; // tab or newline, not a simple space
1574
1575
894k
      if (space_inserted) {
1576
        // count the number of characters since the start of the word
1577
340k
        j = 0;
1578
340k
        k = source_index - 1;
1579
1.44M
        while ((k >= source_index_word) && (charix[k] != 0)) {
1580
1.10M
          if (charix[k] > 0) // don't count initial bytes of multi-byte character
1581
801k
            j++;
1582
1.10M
          k--;
1583
1.10M
        }
1584
340k
        words[word_count].length = j;
1585
340k
      }
1586
1587
894k
      source_index_word = source_index;
1588
1589
      // end of 'word'
1590
894k
      sbuf[ix++] = ' ';
1591
1592
894k
      if ((word_count < N_CLAUSE_WORDS-1) && (ix > words[word_count].start)) {
1593
892k
        if (embedded_count > 0) {
1594
          // there are embedded commands before this word
1595
19.6k
          embedded_list[embedded_ix-1] |= 0x80; // terminate list of commands for this word
1596
19.6k
          words[word_count].flags |= FLAG_EMBEDDED;
1597
19.6k
          embedded_count = 0;
1598
19.6k
        }
1599
892k
        if (alpha_count == 0) {
1600
493k
          all_upper_case &= ~FLAG_ALL_UPPER;
1601
493k
        }
1602
892k
        words[word_count].pre_pause = pre_pause;
1603
892k
        words[word_count].flags |= (all_upper_case | word_flags | word_emphasis);
1604
1605
892k
        if (pre_pause > 0) {
1606
          // insert an extra space before the word, to prevent influence from previous word across the pause
1607
53.6k
          for (j = ix; j > words[word_count].start; j--)
1608
42.7k
            sbuf[j] = sbuf[j-1];
1609
10.8k
          sbuf[j] = ' ';
1610
10.8k
          words[word_count].start++;
1611
10.8k
          ix++;
1612
10.8k
        }
1613
1614
892k
        word_count++;
1615
892k
        words[word_count].start = ix;
1616
892k
        words[word_count].flags = 0;
1617
1618
892k
        words[word_count].length = CalcWordLength(source_index, charix_top, charix, words, word_count);
1619
1620
892k
        word_flags = next_word_flags;
1621
892k
        next_word_flags = 0;
1622
892k
        pre_pause = 0;
1623
892k
        all_upper_case = FLAG_ALL_UPPER;
1624
892k
        alpha_count = 0;
1625
892k
        syllable_marked = false;
1626
892k
      }
1627
1628
894k
      if (space_inserted) {
1629
340k
        source_index = prev_source_index; // rewind to the previous character
1630
340k
        char_inserted = 0;
1631
340k
        space_inserted = false;
1632
340k
      }
1633
3.42M
    } else {
1634
3.42M
      if ((ix < (N_TR_SOURCE - 4)))
1635
3.41M
        ix += utf8_out(c, &sbuf[ix]);
1636
3.42M
    }
1637
4.32M
    if (pre_pause_add > pre_pause)
1638
10.6k
      pre_pause = pre_pause_add;
1639
4.32M
    pre_pause_add = 0;
1640
4.32M
  }
1641
1642
72.9k
  if ((word_count == 0) && (embedded_count > 0)) {
1643
    // add a null 'word' to carry the embedded command flag
1644
765
    embedded_list[embedded_ix-1] |= 0x80;
1645
765
    words[word_count].flags |= FLAG_EMBEDDED;
1646
765
    word_count = 1;
1647
765
  }
1648
1649
72.9k
  tr->clause_end = &sbuf[ix-1];
1650
72.9k
  sbuf[ix] = 0;
1651
72.9k
  words[0].pre_pause = 0; // don't add extra pause at beginning of clause
1652
72.9k
  words[word_count].pre_pause = 8;
1653
72.9k
  if (word_count > 0) {
1654
61.6k
    ix = word_count-1;
1655
74.1k
    while ((ix > 0) && (IsBracket(sbuf[words[ix].start])))
1656
12.5k
      ix--; // the last word is a bracket, mark the previous word as last
1657
61.6k
    words[ix].flags |= FLAG_LAST_WORD;
1658
1659
    // FLAG_NOSPACE check to avoid recognizing  .mr  -mr
1660
61.6k
    if ((terminator & CLAUSE_DOT_AFTER_LAST_WORD) && !(words[word_count-1].flags & FLAG_NOSPACE))
1661
4.75k
      words[word_count-1].flags |= FLAG_HAS_DOT;
1662
61.6k
  }
1663
72.9k
  words[0].flags |= FLAG_FIRST_WORD;
1664
1665
  // Each TranslateWord2 may require up to 7 phonemes
1666
  // and after this loop we require 2 phonemes
1667
916k
  for (ix = 0; ix < word_count && (n_ph_list2 < N_PHONEME_LIST-7-2); ix++) {
1668
843k
    int nx;
1669
843k
    int c_temp;
1670
843k
    char *pn;
1671
843k
    char *pw;
1672
843k
    char number_buf[150];
1673
843k
    WORD_TAB num_wtab[N_CLAUSE_WORDS]; // copy of 'words', when splitting numbers into parts
1674
1675
    // start speaking at a specified word position in the text?
1676
843k
    count_words++;
1677
843k
    if (skip_words > 0) {
1678
0
      skip_words--;
1679
0
      if (skip_words == 0)
1680
0
        skipping_text = false;
1681
0
    }
1682
843k
    if (skipping_text)
1683
0
      continue;
1684
1685
843k
    current_alphabet = NULL;
1686
1687
    // digits should have been converted to Latin alphabet ('0' to '9')
1688
843k
    word = pw = &sbuf[words[ix].start];
1689
1690
843k
    if (iswdigit(word[0]) && (tr->langopts.break_numbers != BREAK_THOUSANDS)) {
1691
      // Languages with 100000 numbers.  Remove thousands separators so that we can insert them again later
1692
7.20k
      pn = number_buf;
1693
31.5k
      while (pn < &number_buf[sizeof(number_buf)-20]) {
1694
31.5k
        if (iswdigit(*pw))
1695
24.0k
          *pn++ = *pw++;
1696
7.45k
        else if ((*pw == tr->langopts.thousands_sep) && (pw[1] == ' ')
1697
7.45k
                   && iswdigit(pw[2]) && (pw[3] != ' ') && (pw[4] != ' ')) { // don't allow only 1 or 2 digits in the final part
1698
260
          pw += 2;
1699
260
          ix++; // skip "word"
1700
7.19k
        } else {
1701
7.19k
          nx = pw - word;
1702
7.19k
          memset(word, ' ', nx);
1703
7.19k
          nx = pn - number_buf;
1704
7.19k
          memcpy(word, number_buf, nx);
1705
7.19k
          break;
1706
7.19k
        }
1707
31.5k
      }
1708
7.20k
      pw = word;
1709
7.20k
    }
1710
1711
1.10M
    for (n_digits = 0; iswdigit(word[n_digits]); n_digits++) // count consecutive digits
1712
256k
      ;
1713
1714
843k
    if (n_digits > 4 && n_digits <= 32) {
1715
      // word is entirely digits, insert commas and break into 3 digit "words"
1716
10.2k
      int nw = 0;
1717
10.2k
      int num_wtab_count;
1718
1719
10.2k
      number_buf[0] = ' ';
1720
10.2k
      number_buf[1] = ' ';
1721
10.2k
      number_buf[2] = ' ';
1722
10.2k
      pn = &number_buf[3];
1723
10.2k
      nx = n_digits;
1724
1725
10.2k
      if ((n_digits > tr->langopts.max_digits) || (word[0] == '0'))
1726
1.68k
        words[ix].flags |= FLAG_INDIVIDUAL_DIGITS;
1727
1728
107k
      while (pn < &number_buf[sizeof(number_buf)-20] && nw < N_CLAUSE_WORDS-2) {
1729
107k
        if (!IsDigit09(c = *pw++) && (c != tr->langopts.decimal_sep))
1730
10.2k
          break;
1731
1732
97.1k
        *pn++ = c;
1733
97.1k
        nx--;
1734
97.1k
        if ((nx > 0) && (tr->langopts.break_numbers & (1U << nx))) {
1735
25.1k
          memcpy(&num_wtab[nw++], &words[ix], sizeof(WORD_TAB)); // copy the 'words' entry for each word of numbers
1736
1737
25.1k
          if (tr->langopts.thousands_sep != ' ')
1738
22.8k
            *pn++ = tr->langopts.thousands_sep;
1739
25.1k
          *pn++ = ' ';
1740
1741
25.1k
          if ((words[ix].flags & FLAG_INDIVIDUAL_DIGITS) == 0) {
1742
17.9k
            if (tr->langopts.break_numbers & (1 << (nx-1))) {
1743
              // the next group only has 1 digits, make it three
1744
4
              *pn++ = '0';
1745
4
              *pn++ = '0';
1746
4
            }
1747
17.9k
            if (tr->langopts.break_numbers & (1 << (nx-2))) {
1748
              // the next group only has 2 digits (eg. Indian languages), make it three
1749
1.30k
              *pn++ = '0';
1750
1.30k
            }
1751
17.9k
          }
1752
25.1k
        }
1753
97.1k
      }
1754
10.2k
      pw--;
1755
10.2k
      memcpy(&num_wtab[nw], &words[ix], sizeof(WORD_TAB)*2); // the original number word, and the word after it
1756
10.2k
      num_wtab_count = nw + 2;
1757
1758
35.3k
      for (j = 1; j <= nw; j++)
1759
25.1k
        num_wtab[j].flags &= ~(FLAG_MULTIPLE_SPACES | FLAG_EMBEDDED); // don't use these flags for subsequent parts when splitting a number
1760
1761
      // include the next few characters, in case there are an ordinal indicator or other suffix
1762
10.2k
      strncpy(pn, pw, 16);
1763
10.2k
      pn[16] = 0;
1764
10.2k
      nw = 0;
1765
1766
45.5k
      for (pw = &number_buf[3]; pw < pn && nw < N_CLAUSE_WORDS;) {
1767
        // keep wflags for each part, for FLAG_HYPHEN_AFTER
1768
35.3k
        dict_flags = TranslateWord2(tr, pw, &num_wtab[nw], num_wtab_count - nw, words[ix].pre_pause);
1769
35.3k
        nw++;
1770
156k
        while (pw < pn && *pw++ != ' ')
1771
121k
          ;
1772
35.3k
        words[ix].pre_pause = 0;
1773
35.3k
      }
1774
833k
    } else {
1775
833k
      pre_pause = 0;
1776
1777
833k
      dict_flags = TranslateWord2(tr, word, &words[ix], word_count - ix, words[ix].pre_pause);
1778
1779
833k
      if (pre_pause > words[ix+1].pre_pause) {
1780
60.2k
        words[ix+1].pre_pause = pre_pause;
1781
60.2k
        pre_pause = 0;
1782
60.2k
      }
1783
1784
833k
      if (dict_flags & FLAG_SPELLWORD) {
1785
        // redo the word, speaking single letters
1786
140k
        for (pw = word; *pw != ' ';) {
1787
116k
          memset(number_buf, 0, sizeof(number_buf));
1788
116k
          memset(number_buf+1, ' ', 9);
1789
116k
          nx = utf8_in(&c_temp, pw);
1790
116k
          memcpy(&number_buf[3], pw, nx);
1791
116k
          TranslateWord2(tr, &number_buf[3], &words[ix], word_count - ix, 0);
1792
116k
          pw += nx;
1793
116k
        }
1794
23.2k
      }
1795
1796
833k
      if ((dict_flags & (FLAG_ALLOW_DOT | FLAG_NEEDS_DOT)) && (ix == word_count - 1 - dictionary_skipwords) && (terminator & CLAUSE_DOT_AFTER_LAST_WORD)) {
1797
        // probably an abbreviation such as Mr. or B. rather than end of sentence
1798
202
        clause_pause = 10;
1799
202
        if (tone_out != NULL)
1800
202
          *tone_out = 4;
1801
202
      }
1802
833k
    }
1803
1804
843k
    if (dict_flags & FLAG_SKIPWORDS) {
1805
      // dictionary indicates skip next word(s)
1806
10.2k
      while (dictionary_skipwords > 0) {
1807
7.34k
        if (dictionary_skipwords >= word_count - ix) {
1808
15
          dictionary_skipwords = 0;
1809
15
          break;
1810
15
        }
1811
7.33k
        words[ix+dictionary_skipwords].flags |= FLAG_DELETE_WORD;
1812
7.33k
        dictionary_skipwords--;
1813
7.33k
      }
1814
2.88k
    }
1815
843k
  }
1816
1817
72.9k
  if (embedded_read < embedded_ix) {
1818
    // any embedded commands not yet processed?
1819
1.35k
    Word_EmbeddedCmd();
1820
1.35k
  }
1821
1822
218k
  for (ix = 0; ix < 2; ix++) {
1823
    // terminate the clause with 2 PAUSE phonemes
1824
145k
    PHONEME_LIST2 *p2;
1825
145k
    p2 = &ph_list2[n_ph_list2 + ix];
1826
145k
    p2->phcode = phonPAUSE;
1827
145k
    p2->stresslevel = 0;
1828
145k
    p2->sourceix = source_index;
1829
145k
    p2->synthflags = 0;
1830
145k
  }
1831
72.9k
  n_ph_list2 += 2;
1832
1833
72.9k
  if (Eof() && ((word_count == 0) || (option_endpause == 0)))
1834
2.32k
    clause_pause = 10;
1835
1836
72.9k
  MakePhonemeList(tr, clause_pause, new_sentence2);
1837
72.9k
  phoneme_list[N_PHONEME_LIST].ph = NULL; // recognize end of phoneme_list array, in Generate()
1838
72.9k
  phoneme_list[N_PHONEME_LIST].sourceix = 1;
1839
1840
72.9k
  if (embedded_count) { // ???? is this needed
1841
1.72k
    phoneme_list[n_phoneme_list-2].synthflags = SFLAG_EMBEDDED;
1842
1.72k
    embedded_list[embedded_ix-1] |= 0x80;
1843
1.72k
    embedded_list[embedded_ix] = 0x80;
1844
1.72k
  }
1845
1846
72.9k
  new_sentence = false;
1847
72.9k
  if (terminator & CLAUSE_TYPE_SENTENCE)
1848
47.5k
    new_sentence = true; // next clause is a new sentence
1849
1850
72.9k
  if (voice_change != NULL) {
1851
    // return new voice name if an embedded voice change command terminated the clause
1852
72.9k
    if (terminator & CLAUSE_TYPE_VOICE_CHANGE)
1853
16.7k
      *voice_change = voice_change_name;
1854
56.2k
    else
1855
56.2k
      *voice_change = NULL;
1856
72.9k
  }
1857
72.9k
}
1858
1859
void TranslateClause(Translator *tr, int *tone_out, char **voice_change)
1860
72.9k
{
1861
72.9k
  TranslateClauseWithTerminator(tr, tone_out, voice_change, NULL);
1862
72.9k
}
1863
1864
965k
static int CalcWordLength(int source_index, int charix_top, short int *charix, WORD_TAB *words, int word_count) {
1865
965k
  int j;
1866
965k
  int k;
1867
1868
5.77M
  for (j = source_index; j < charix_top && charix[j] <= 0; j++); // skip blanks
1869
965k
  words[word_count].sourceix = charix[j];
1870
965k
  k = 0;
1871
72.4M
  while (charix[j] != 0) {
1872
    // count the number of characters (excluding multibyte continuation bytes)
1873
71.4M
    if (charix[j++] != -1)
1874
40.6M
      k++;
1875
71.4M
  }
1876
965k
  return k;
1877
965k
  }
1878
1879
111
static void CombineFlag(Translator *tr, WORD_TAB *wtab, int wtab_remaining, char *word, int *flags, unsigned char *p, char *word_phonemes) {
1880
  // combine a preposition with the following word
1881
1882
1883
111
  int sylimit; // max. number of syllables in a word to be combined with a preceding preposition
1884
111
  sylimit = tr->langopts.param[LOPT_COMBINE_WORDS];
1885
1886
1887
111
  char *p2;
1888
111
  p2 = word;
1889
223
  while (*p2 != ' ') p2++;
1890
1891
111
  bool ok = true;
1892
111
  int c_word2;
1893
1894
111
  if (wtab_remaining <= 1)
1895
0
    ok = false;
1896
1897
111
  utf8_in(&c_word2, p2+1); // first character of the next word;
1898
1899
111
  if (!iswalpha(c_word2))
1900
42
    ok = false;
1901
1902
111
  int flags2[2];
1903
111
    flags2[0] = 0;
1904
1905
1906
111
  if (ok) {
1907
69
    char ph_buf[N_WORD_PHONEMES];
1908
69
    strcpy(ph_buf, word_phonemes);
1909
1910
69
    flags2[0] = TranslateWordWithBounds(tr, p2+1, wtab+1, wtab_remaining-1, NULL, 0);
1911
69
    if ((flags2[0] & FLAG_WAS_UNPRONOUNCABLE) || (word_phonemes[0] == phonSWITCH))
1912
29
      ok = false;
1913
1914
69
    if ((sylimit & 0x100) && ((flags2[0] & FLAG_ALT_TRANS) == 0)) {
1915
      // only if the second word has $alt attribute
1916
0
      ok = false;
1917
0
    }
1918
1919
69
    if ((sylimit & 0x200) && (wtab_remaining > 1) && ((wtab+1)->flags & FLAG_LAST_WORD)) {
1920
      // not if the next word is end-of-sentence
1921
0
      ok = false;
1922
0
    }
1923
1924
69
    if (ok == false)
1925
29
      strcpy(word_phonemes, ph_buf);
1926
69
  }
1927
1928
111
  if (ok) {
1929
40
    *p2 = '-'; // replace next space by hyphen
1930
40
    wtab[0].flags &= ~FLAG_ALL_UPPER; // prevent it being considered an abbreviation
1931
40
    *flags = TranslateWordWithBounds(translator, word, wtab, wtab_remaining, NULL, 0); // translate the combined word
1932
40
    if ((sylimit > 0) && (CountSyllables(p) > (sylimit & 0x1f))) {
1933
      // revert to separate words
1934
6
      *p2 = ' ';
1935
6
      *flags = TranslateWordWithBounds(translator, word, wtab, wtab_remaining, NULL, 0);
1936
34
    } else {
1937
34
      if (*flags == 0)
1938
30
        *flags = flags2[0]; // no flags for the combined word, so use flags from the second word eg. lang-hu "nem december 7-e"
1939
34
      *flags |= FLAG_SKIPWORDS;
1940
34
      dictionary_skipwords = 1;
1941
34
    }
1942
40
  }
1943
111
}
1944
1945
5.51k
static void SwitchLanguage(char *word, char *word_phonemes) {
1946
5.51k
  char lang_name[12];
1947
5.51k
  int ix;
1948
1949
5.51k
  word += 3;
1950
1951
16.6k
  for (ix = 0;;) {
1952
16.6k
    int  c1;
1953
16.6k
    c1 = *word++;
1954
16.6k
    if ((c1 == ' ') || (c1 == 0))
1955
5.51k
      break;
1956
11.1k
    lang_name[ix++] = tolower(c1);
1957
11.1k
  }
1958
5.51k
  lang_name[ix] = 0;
1959
1960
5.51k
  if ((ix = LookupPhonemeTable(lang_name)) > 0) {
1961
5.07k
    SelectPhonemeTable(ix);
1962
5.07k
    word_phonemes[0] = phonSWITCH;
1963
5.07k
    word_phonemes[1] = ix;
1964
5.07k
    word_phonemes[2] = 0;
1965
5.07k
  }
1966
5.51k
}
1967
1968
void InitText(int control)
1969
2.37k
{
1970
2.37k
  count_sentences = 0;
1971
2.37k
  count_words = 0;
1972
2.37k
  end_character_position = 0;
1973
2.37k
  skip_sentences = 0;
1974
2.37k
  skip_marker[0] = 0;
1975
2.37k
  skip_words = 0;
1976
2.37k
  skip_characters = 0;
1977
2.37k
  skipping_text = false;
1978
2.37k
  new_sentence = true;
1979
1980
2.37k
  option_sayas = 0;
1981
2.37k
  option_sayas2 = 0;
1982
2.37k
  option_emphasis = 0;
1983
2.37k
  word_emphasis = 0;
1984
2.37k
  embedded_flag = 0;
1985
1986
2.37k
  InitText2();
1987
1988
2.37k
  if ((control & espeakKEEP_NAMEDATA) == 0)
1989
2.37k
    InitNamedata();
1990
2.37k
}