eSpeak NG is an open source speech synthesizer that supports more than hundred languages and accents.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

numbers.cpp 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. /***************************************************************************
  2. * Copyright (C) 2005 to 2007 by Jonathan Duddington *
  3. * email: [email protected] *
  4. * *
  5. * This program is free software; you can redistribute it and/or modify *
  6. * it under the terms of the GNU General Public License as published by *
  7. * the Free Software Foundation; either version 3 of the License, or *
  8. * (at your option) any later version. *
  9. * *
  10. * This program is distributed in the hope that it will be useful, *
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  13. * GNU General Public License for more details. *
  14. * *
  15. * You should have received a copy of the GNU General Public License *
  16. * along with this program; if not, see: *
  17. * <http://www.gnu.org/licenses/>. *
  18. ***************************************************************************/
  19. #include "StdAfx.h"
  20. #include <stdio.h>
  21. #include <ctype.h>
  22. #include <stdlib.h>
  23. #include <string.h>
  24. #include <wctype.h>
  25. #include <wchar.h>
  26. #include "speak_lib.h"
  27. #include "speech.h"
  28. #include "phoneme.h"
  29. #include "synthesize.h"
  30. #include "voice.h"
  31. #include "translate.h"
  32. void Translator::LookupLetter(unsigned int letter, int next_byte, char *ph_buf1)
  33. {//=============================================================================
  34. int len;
  35. unsigned char *p;
  36. static char single_letter[10] = {0,0};
  37. char ph_stress[2];
  38. char ph_buf3[30];
  39. ph_buf1[0] = 0;
  40. len = utf8_out(letter,&single_letter[2]);
  41. single_letter[len+2] = ' ';
  42. if(next_byte == -1)
  43. {
  44. // speaking normal text, not individual characters
  45. if(Lookup(&single_letter[2],ph_buf1) != 0)
  46. return;
  47. single_letter[1] = '_';
  48. if(Lookup(&single_letter[1],ph_buf3) != 0)
  49. return; // the character is specified as _* so ignore it when speaking normal text
  50. // check whether this character is specified for English
  51. SetTranslator2("en");
  52. if(translator2->Lookup(&single_letter[2], ph_buf3) != 0)
  53. {
  54. // yes, switch to English and re-translate the word
  55. sprintf(ph_buf1,"%c",phonSWITCH);
  56. }
  57. SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table
  58. return;
  59. }
  60. if((letter <= 32) || iswspace(letter))
  61. {
  62. // lookup space as _&32 etc.
  63. sprintf(&single_letter[1],"_#%d ",letter);
  64. Lookup(&single_letter[1],ph_buf1);
  65. return;
  66. }
  67. if(next_byte != ' ')
  68. next_byte = RULE_SPELLING;
  69. single_letter[3+len] = next_byte; // follow by space-space if the end of the word, or space-0x31
  70. single_letter[1] = '_';
  71. if(Lookup(&single_letter[1],ph_buf3) == 0)
  72. {
  73. single_letter[1] = ' ';
  74. if(Lookup(&single_letter[2],ph_buf3) == 0)
  75. {
  76. TranslateRules(&single_letter[2], ph_buf3, sizeof(ph_buf3), NULL,0,0);
  77. }
  78. }
  79. if(ph_buf3[0] == 0)
  80. {
  81. ph_buf1[0] = 0;
  82. return;
  83. }
  84. // at a stress marker at the start of the letter name, unless one is already marked
  85. ph_stress[0] = phonSTRESS_P;
  86. ph_stress[1] = 0;
  87. for(p=(unsigned char *)ph_buf3; *p != 0; p++)
  88. {
  89. if(phoneme_tab[*p]->type == phSTRESS)
  90. ph_stress[0] = 0; // stress is already marked
  91. }
  92. sprintf(ph_buf1,"%s%s",ph_stress,ph_buf3);
  93. }
  94. int Translator::TranslateLetter(char *word, char *phonemes, int control, int word_length)
  95. {//======================================================================================
  96. // get pronunciation for an isolated letter
  97. // return number of bytes used by the letter
  98. // control 2=say-as glyphs, 3-say-as chars
  99. int n_bytes;
  100. int letter;
  101. int len;
  102. char *p2;
  103. char *pbuf;
  104. char capital[20];
  105. char ph_buf[60];
  106. char ph_buf2[60];
  107. char hexbuf[6];
  108. ph_buf[0] = 0;
  109. capital[0] = 0;
  110. n_bytes = utf8_in(&letter,word,0);
  111. if((letter & 0xfff00) == 0x0e000)
  112. {
  113. letter &= 0xff; // uncode private usage area
  114. }
  115. if(control > 2)
  116. {
  117. // include CAPITAL information
  118. if(iswupper(letter))
  119. {
  120. Lookup("_cap",capital);
  121. }
  122. }
  123. letter = towlower2(letter);
  124. LookupLetter(letter, word[n_bytes], ph_buf);
  125. if(ph_buf[0] == phonSWITCH)
  126. {
  127. strcpy(phonemes,ph_buf);
  128. return(0);
  129. }
  130. if((ph_buf[0] == 0) && (word_length == 1) && (translator_name != L('e','n')))
  131. {
  132. // speak as English, check whether there is a translation for this character
  133. SetTranslator2("en");
  134. translator2->LookupLetter(letter, word[n_bytes], ph_buf);
  135. SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table
  136. if(ph_buf[0] != 0)
  137. {
  138. sprintf(phonemes,"%c",phonSWITCH);
  139. return(0);
  140. }
  141. }
  142. if(ph_buf[0] == 0)
  143. {
  144. // character name not found
  145. if(iswalpha(letter))
  146. Lookup("_?A",ph_buf);
  147. if((ph_buf[0]==0) && !iswspace(letter))
  148. Lookup("_??",ph_buf);
  149. if((control==4) && (ph_buf[0] != 0))
  150. {
  151. // speak the hexadecimal number of the character code
  152. sprintf(hexbuf,"%x",letter);
  153. pbuf = ph_buf;
  154. for(p2 = hexbuf; *p2 != 0; p2++)
  155. {
  156. pbuf += strlen(pbuf);
  157. LookupLetter(*p2, 0, pbuf);
  158. }
  159. }
  160. }
  161. len = strlen(phonemes);
  162. sprintf(ph_buf2,"%c%s%s",0xff,capital,ph_buf); // the 0xff marker will be removed or replaced in SetSpellingStress()
  163. if((len + strlen(ph_buf2)) < N_WORD_PHONEMES)
  164. {
  165. strcpy(&phonemes[len],ph_buf2);
  166. }
  167. return(n_bytes);
  168. } // end of TranslateLetter
  169. void Translator::SetSpellingStress(char *phonemes, int control, int n_chars)
  170. {//=========================================================================
  171. // Individual letter names, reduce the stress of some.
  172. int ix;
  173. unsigned int c;
  174. int n_stress=0;
  175. int count;
  176. unsigned char buf[N_WORD_PHONEMES];
  177. for(ix=0; (c = phonemes[ix]) != 0; ix++)
  178. {
  179. if(c == phonSTRESS_P)
  180. {
  181. n_stress++;
  182. }
  183. buf[ix] = c;
  184. }
  185. buf[ix] = 0;
  186. count = 0;
  187. for(ix=0; (c = buf[ix]) != 0; ix++)
  188. {
  189. if((c == phonSTRESS_P) && (n_chars > 1))
  190. {
  191. count++;
  192. if(langopts.spelling_stress == 1)
  193. {
  194. // stress on initial letter when spelling
  195. if(count > 1)
  196. c = phonSTRESS_3;
  197. }
  198. else
  199. {
  200. if(count != n_stress)
  201. {
  202. if(((count % 3) != 0) || (count == n_stress-1))
  203. c = phonSTRESS_3; // reduce to secondary stress
  204. }
  205. }
  206. }
  207. else
  208. if(c == 0xff)
  209. {
  210. if((control < 2) || (ix==0))
  211. continue; // don't insert pauses
  212. if(control == 4)
  213. c = phonPAUSE; // pause after each character
  214. if(((count % 3) == 0) || (control == 4))
  215. c = phonPAUSE_SHORT; // pause following a primary stress
  216. else
  217. continue; // remove marker
  218. }
  219. *phonemes++ = c;
  220. }
  221. if(control >= 2)
  222. *phonemes++ = phonPAUSE_NOLINK;
  223. *phonemes = 0;
  224. } // end of SetSpellingStress
  225. int Translator::TranslateRoman(char *word, char *ph_out)
  226. {//=====================================================
  227. int c;
  228. char *p;
  229. const char *p2;
  230. int acc;
  231. int prev;
  232. int value;
  233. int subtract;
  234. int repeat = 0;
  235. unsigned int flags;
  236. char number_chars[N_WORD_BYTES];
  237. static const char *roman_numbers = "ixcmvld";
  238. static int roman_values[] = {1,10,100,1000,5,50,500};
  239. acc = 0;
  240. prev = 0;
  241. subtract = 0x7fff;
  242. while((c = *word++) != ' ')
  243. {
  244. if((p2 = strchr(roman_numbers,c)) == NULL)
  245. return(0);
  246. value = roman_values[p2 - roman_numbers];
  247. if(value == prev)
  248. {
  249. repeat++;
  250. if(repeat >= 3)
  251. return(0);
  252. }
  253. else
  254. repeat = 0;
  255. if((prev==5) || (prev==50) || (prev==500))
  256. {
  257. if(value >= prev)
  258. return(0);
  259. }
  260. if((prev != 0) && (prev < value))
  261. {
  262. if(((acc % 10) != 0) || ((prev*10) < value))
  263. return(0);
  264. subtract = prev;
  265. value -= subtract;
  266. }
  267. else
  268. if(value >= subtract)
  269. return(0);
  270. else
  271. acc += prev;
  272. prev = value;
  273. }
  274. acc += prev;
  275. if(acc < 2)
  276. return(0);
  277. if(acc > langopts.max_roman)
  278. return(0);
  279. Lookup("_roman",ph_out); // precede by "roman" if _rom is defined in *_list
  280. p = &ph_out[strlen(ph_out)];
  281. sprintf(number_chars," %d ",acc);
  282. TranslateNumber(&number_chars[1],p,&flags,0);
  283. return(1);
  284. } // end of TranslateRoman
  285. int Translator::LookupNum2(int value, int control, char *ph_out)
  286. {//=============================================================
  287. // Lookup a 2 digit number
  288. // control bit 0: use special form of '1'
  289. // control bit 2: use feminine form of '2'
  290. int found;
  291. int ix;
  292. int units;
  293. int used_and=0;
  294. int next_phtype;
  295. char string[12]; // for looking up entries in de_list
  296. char ph_tens[50];
  297. char ph_digits[50];
  298. char ph_and[12];
  299. if((value == 1) && (control & 1))
  300. {
  301. if(Lookup("_1a",ph_out) != 0)
  302. return(0);
  303. }
  304. // is there a special pronunciation for this 2-digit number
  305. found = 0;
  306. if(control & 4)
  307. {
  308. sprintf(string,"_%df",value);
  309. found = Lookup(string,ph_digits);
  310. }
  311. if(found == 0)
  312. {
  313. sprintf(string,"_%d",value);
  314. found = Lookup(string,ph_digits);
  315. }
  316. // no, speak as tens+units
  317. if((control & 2) && (value < 10))
  318. {
  319. // speak leading zero
  320. Lookup("_0",ph_tens);
  321. }
  322. else
  323. {
  324. if(found)
  325. {
  326. strcpy(ph_out,ph_digits);
  327. return(0);
  328. }
  329. if((value % 10) == 0)
  330. {
  331. sprintf(string,"_%d0",value / 10);
  332. found = Lookup(string,ph_tens);
  333. }
  334. if(!found)
  335. {
  336. sprintf(string,"_%dX",value / 10);
  337. Lookup(string,ph_tens);
  338. }
  339. if((value % 10) == 0)
  340. {
  341. strcpy(ph_out,ph_tens);
  342. return(0);
  343. }
  344. found = 0;
  345. units = (value % 10);
  346. if(control & 4)
  347. {
  348. // is there a variant form of this number?
  349. sprintf(string,"_%df",units);
  350. found = Lookup(string,ph_digits);
  351. }
  352. if(found == 0)
  353. {
  354. sprintf(string,"_%d",units);
  355. Lookup(string,ph_digits);
  356. }
  357. }
  358. if(langopts.numbers & 0x30)
  359. {
  360. Lookup("_0and",ph_and);
  361. if(langopts.numbers & 0x10)
  362. sprintf(ph_out,"%s%s%s",ph_digits,ph_and,ph_tens);
  363. else
  364. sprintf(ph_out,"%s%s%s",ph_tens,ph_and,ph_digits);
  365. used_and = 1;
  366. }
  367. else
  368. {
  369. if(langopts.numbers & 0x200)
  370. {
  371. // remove vowel from the end of tens if units starts with a vowel (LANG=Italian)
  372. if((ix = strlen(ph_tens)-1) >= 0)
  373. {
  374. if((next_phtype = phoneme_tab[(unsigned int)(ph_digits[0])]->type) == phSTRESS)
  375. next_phtype = phoneme_tab[(unsigned int)(ph_digits[1])]->type;
  376. if((phoneme_tab[(unsigned int)(ph_tens[ix])]->type == phVOWEL) && (next_phtype == phVOWEL))
  377. ph_tens[ix] = 0;
  378. }
  379. }
  380. sprintf(ph_out,"%s%s",ph_tens,ph_digits);
  381. }
  382. if(langopts.numbers & 0x100)
  383. {
  384. // only one primary stress
  385. found = 0;
  386. for(ix=strlen(ph_out)-1; ix>=0; ix--)
  387. {
  388. if(ph_out[ix] == phonSTRESS_P)
  389. {
  390. if(found)
  391. ph_out[ix] = phonSTRESS_3;
  392. else
  393. found = 1;
  394. }
  395. }
  396. }
  397. return(used_and);
  398. } // end of LookupNum2
  399. int Translator::LookupNum3(int value, char *ph_out, int suppress_null, int thousandplex, int prev_thousands)
  400. {//=========================================================================================================
  401. // Translate a 3 digit number
  402. int found;
  403. int hundreds;
  404. int x;
  405. char string[12]; // for looking up entries in **_list
  406. char buf1[100];
  407. char buf2[100];
  408. char ph_100[20];
  409. char ph_10T[20];
  410. char ph_digits[50];
  411. char ph_thousands[50];
  412. char ph_hundred_and[12];
  413. char ph_thousand_and[12];
  414. hundreds = value / 100;
  415. buf1[0] = 0;
  416. if(hundreds > 0)
  417. {
  418. ph_thousands[0] = 0;
  419. ph_thousand_and[0] = 0;
  420. Lookup("_0C",ph_100);
  421. if((hundreds >= 10) && (((langopts.numbers & 0x0800) == 0) || (hundreds != 19)))
  422. {
  423. ph_digits[0] = 0;
  424. if(LookupThousands(hundreds / 10, thousandplex+1, ph_10T) == 0)
  425. {
  426. x = 0;
  427. if(langopts.numbers2 & (1 << (thousandplex+1)))
  428. x = 4;
  429. LookupNum2(hundreds/10, x, ph_digits);
  430. }
  431. sprintf(ph_thousands,"%s%s%c",ph_digits,ph_10T,phonPAUSE_NOLINK);
  432. hundreds %= 10;
  433. if(hundreds == 0)
  434. ph_100[0] = 0;
  435. suppress_null = 1;
  436. }
  437. ph_digits[0] = 0;
  438. if(hundreds > 0)
  439. {
  440. if((langopts.numbers & 0x100000) && (prev_thousands || (ph_thousands[0] != 0)))
  441. {
  442. Lookup("_0and",ph_thousand_and);
  443. }
  444. suppress_null = 1;
  445. found = 0;
  446. if((value % 1000) == 100)
  447. {
  448. // is there a special pronunciation for exactly 100 ?
  449. found = Lookup("_1C0",ph_digits);
  450. }
  451. if(!found)
  452. {
  453. sprintf(string,"_%dC",hundreds);
  454. found = Lookup(string,ph_digits); // is there a specific pronunciation for n-hundred ?
  455. }
  456. if(found)
  457. {
  458. ph_100[0] = 0;
  459. }
  460. else
  461. {
  462. if((hundreds > 1) || ((langopts.numbers & 0x400) == 0))
  463. {
  464. LookupNum2(hundreds,0,ph_digits);
  465. }
  466. }
  467. }
  468. sprintf(buf1,"%s%s%s%s",ph_thousands,ph_thousand_and,ph_digits,ph_100);
  469. }
  470. ph_hundred_and[0] = 0;
  471. if((langopts.numbers & 0x40) && ((value % 100) != 0))
  472. {
  473. if((value > 100) || (prev_thousands && (thousandplex==0)))
  474. {
  475. Lookup("_0and",ph_hundred_and);
  476. }
  477. }
  478. buf2[0] = 0;
  479. value = value % 100;
  480. if(value == 0)
  481. {
  482. if(suppress_null == 0)
  483. Lookup("_0",buf2);
  484. }
  485. else
  486. {
  487. x = 0;
  488. if(thousandplex==0)
  489. x = 1; // allow "eins" for 1 rather than "ein"
  490. else
  491. {
  492. if(langopts.numbers2 & (1 << thousandplex))
  493. x = 4; // use variant (feminine) for before thousands and millions
  494. }
  495. if(LookupNum2(value,x,buf2) != 0)
  496. {
  497. if(langopts.numbers & 0x80)
  498. ph_hundred_and[0] = 0; // don't put 'and' after 'hundred' if there's 'and' between tens and units
  499. }
  500. }
  501. sprintf(ph_out,"%s%s%s",buf1,ph_hundred_and,buf2);
  502. return(0);
  503. } // end of LookupNum3
  504. static const char *M_Variant(int value)
  505. {//====================================
  506. // returns M, or perhaps MA for some cases
  507. if(((value % 100)>20) || ((value % 100)<10)) // but not teens, 10 to 19
  508. {
  509. if ((translator->langopts.numbers2 & 0x40) &&
  510. ((value % 10)>=2) &&
  511. ((value % 10)<=4))
  512. {
  513. // for Polish language - two forms of plural!
  514. return("0MA");
  515. }
  516. if((translator->langopts.numbers2 & 0x80) &&
  517. ((value % 10)==1))
  518. {
  519. return("1MA");
  520. }
  521. }
  522. return("0M");
  523. }
  524. int Translator::LookupThousands(int value, int thousandplex, char *ph_out)
  525. {//=======================================================================
  526. int found;
  527. char string[12];
  528. char ph_of[12];
  529. char ph_thousands[40];
  530. ph_of[0] = 0;
  531. // first look fora match with the exact value of thousands
  532. sprintf(string,"_%dM%d",value,thousandplex);
  533. if((found = Lookup(string,ph_thousands)) == 0)
  534. {
  535. if((value % 100) >= 20)
  536. {
  537. Lookup("_0of",ph_of);
  538. }
  539. sprintf(string,"_%s%d",M_Variant(value),thousandplex);
  540. if(Lookup(string,ph_thousands) == 0)
  541. {
  542. // repeat "thousand" if higher order names are not available
  543. sprintf(string,"_%dM1",value);
  544. if((found = Lookup(string,ph_thousands)) == 0)
  545. Lookup("_0M1",ph_thousands);
  546. }
  547. }
  548. sprintf(ph_out,"%s%s",ph_of,ph_thousands);
  549. return(found);
  550. }
  551. int Translator::TranslateNumber_1(char *word, char *ph_out, unsigned int *flags, int wflags)
  552. {//=========================================================================================
  553. // Number translation with various options
  554. // the "word" may be up to 4 digits
  555. // "words" of 3 digits may be preceded by another number "word" for thousands or millions
  556. int n_digits;
  557. int value;
  558. int ix;
  559. unsigned char c;
  560. int suppress_null = 0;
  561. int decimal_point = 0;
  562. int thousandplex = 0;
  563. int thousands_inc = 0;
  564. int prev_thousands = 0;
  565. int this_value;
  566. static int prev_value;
  567. int decimal_count;
  568. int max_decimal_count;
  569. char string[12]; // for looking up entries in de_list
  570. char buf1[100];
  571. char ph_append[50];
  572. char ph_buf[200];
  573. char ph_buf2[50];
  574. static const char str_pause[2] = {phonPAUSE_NOLINK,0};
  575. for(ix=0; isdigit(word[ix]); ix++) ;
  576. n_digits = ix;
  577. value = this_value = atoi(word);
  578. ph_append[0] = 0;
  579. ph_buf2[0] = 0;
  580. // is there a previous thousands part (as a previous "word") ?
  581. if((n_digits == 3) && (word[-2] == langopts.thousands_sep) && isdigit(word[-3]))
  582. {
  583. prev_thousands = 1;
  584. }
  585. else
  586. if((langopts.thousands_sep == ' ') || (langopts.numbers & 0x1000))
  587. {
  588. // thousands groups can be separated by spaces
  589. if((n_digits == 3) && isdigit(word[-2]))
  590. {
  591. prev_thousands = 1;
  592. }
  593. }
  594. if((word[0] == '0') && (prev_thousands == 0) && (word[1] != langopts.decimal_sep))
  595. {
  596. if((n_digits == 2) && (word[3] == ':') && isdigit(word[5]) && isspace(word[7]))
  597. {
  598. // looks like a time 02:30, omit the leading zero
  599. }
  600. else
  601. {
  602. return(0); // number string with leading zero, speak as individual digits
  603. }
  604. }
  605. if((langopts.numbers & 0x1000) && (word[n_digits] == ' '))
  606. thousands_inc = 1;
  607. else
  608. if(word[n_digits] == langopts.thousands_sep)
  609. thousands_inc = 2;
  610. if(thousands_inc > 0)
  611. {
  612. // if the following "words" are three-digit groups, count them and add
  613. // a "thousand"/"million" suffix to this one
  614. ix = n_digits + thousands_inc;
  615. while(isdigit(word[ix]) && isdigit(word[ix+1]) && isdigit(word[ix+2]))
  616. {
  617. thousandplex++;
  618. if(word[ix+3] == langopts.thousands_sep)
  619. ix += (3 + thousands_inc);
  620. else
  621. break;
  622. }
  623. }
  624. if((value == 0) && prev_thousands)
  625. {
  626. suppress_null = 1;
  627. }
  628. if((word[n_digits] == langopts.decimal_sep) && isdigit(word[n_digits+1]))
  629. {
  630. // this "word" ends with a decimal point
  631. Lookup("_dpt",ph_append);
  632. decimal_point = 1;
  633. }
  634. else
  635. if(suppress_null == 0)
  636. {
  637. if(thousands_inc > 0)
  638. {
  639. if((thousandplex > 0) && (value < 1000))
  640. {
  641. if(langopts.numbers2 & 0x100)
  642. {
  643. if((thousandplex == 1) && (value >= 100))
  644. {
  645. // special word for 100,000's
  646. char ph_buf3[20];
  647. sprintf(string,"_%dL",value / 100);
  648. if(Lookup(string,ph_buf2) == 0)
  649. {
  650. LookupNum2(value/100,0,ph_buf2);
  651. Lookup("_0L",ph_buf3);
  652. strcat(ph_buf2,ph_buf3);
  653. }
  654. value %= 100;
  655. if(value == 0)
  656. suppress_null = 1;
  657. }
  658. }
  659. if((suppress_null == 0) && (LookupThousands(value,thousandplex,ph_append)))
  660. {
  661. // found an exact match for N thousand
  662. value = 0;
  663. suppress_null = 1;
  664. }
  665. }
  666. }
  667. }
  668. else
  669. if((thousandplex > 1) && prev_thousands && (prev_value > 0))
  670. {
  671. sprintf(string,"_%s%d",M_Variant(value),thousandplex+1);
  672. if(Lookup(string,buf1)==0)
  673. {
  674. // speak this thousandplex if there was no word for the previous thousandplex
  675. sprintf(string,"_0M%d",thousandplex);
  676. Lookup(string,ph_append);
  677. }
  678. }
  679. if((ph_append[0] == 0) && (word[n_digits] == '.') && (thousandplex == 0))
  680. {
  681. Lookup("_.",ph_append);
  682. }
  683. LookupNum3(value, ph_buf, suppress_null, thousandplex, prev_thousands);
  684. sprintf(ph_out,"%s%s%s",ph_buf2,ph_buf,ph_append);
  685. while(decimal_point)
  686. {
  687. n_digits++;
  688. decimal_count = 0;
  689. while(isdigit(word[n_digits+decimal_count]))
  690. decimal_count++;
  691. if(decimal_count > 1)
  692. {
  693. max_decimal_count = 2;
  694. switch(langopts.numbers & 0xe000)
  695. {
  696. case 0x8000:
  697. max_decimal_count = 5;
  698. case 0x4000:
  699. // French/Polish decimal fraction
  700. while(word[n_digits] == '0')
  701. {
  702. Lookup("_0",buf1);
  703. strcat(ph_out,buf1);
  704. decimal_count--;
  705. n_digits++;
  706. }
  707. if(decimal_count <= max_decimal_count)
  708. {
  709. LookupNum3(atoi(&word[n_digits]),buf1,0,0,0);
  710. strcat(ph_out,buf1);
  711. n_digits += decimal_count;
  712. }
  713. break;
  714. case 0x2000:
  715. // Italian decimal fractions
  716. if((decimal_count < 4) || ((decimal_count==4) && (word[n_digits] != '0')))
  717. {
  718. LookupNum3(atoi(&word[n_digits]),buf1,0,0,0);
  719. strcat(ph_out,buf1);
  720. if(word[n_digits]=='0')
  721. {
  722. // decimal part has leading zeros, so add a "hundredths" or "thousandths" suffix
  723. sprintf(string,"_0Z%d",decimal_count);
  724. Lookup(string,buf1);
  725. strcat(ph_out,buf1);
  726. }
  727. n_digits += decimal_count;
  728. }
  729. break;
  730. case 0x6000:
  731. // Romanian decimal fractions
  732. if((decimal_count <= 4) && (word[n_digits] != '0'))
  733. {
  734. LookupNum3(atoi(&word[n_digits]),buf1,0,0,0);
  735. strcat(ph_out,buf1);
  736. n_digits += decimal_count;
  737. }
  738. break;
  739. }
  740. }
  741. while(isdigit(c = word[n_digits]) && (strlen(ph_out) < (N_WORD_PHONEMES - 10)))
  742. {
  743. value = word[n_digits++] - '0';
  744. LookupNum2(value, 1, buf1);
  745. strcat(ph_out,buf1);
  746. }
  747. // something after the decimal part ?
  748. if(Lookup("_dpt2",buf1))
  749. strcat(ph_out,buf1);
  750. if(c == langopts.decimal_sep)
  751. {
  752. Lookup("_dpt",buf1);
  753. strcat(ph_out,buf1);
  754. }
  755. else
  756. {
  757. decimal_point = 0;
  758. }
  759. }
  760. if((ph_out[0] != 0) && (ph_out[0] != phonSWITCH))
  761. {
  762. int next_char;
  763. char *p;
  764. p = &word[n_digits+1];
  765. p += utf8_in(&next_char,p,0);
  766. if((langopts.numbers & NUM_NOPAUSE) && (next_char == ' '))
  767. utf8_in(&next_char,p,0);
  768. if(!iswalpha(next_char))
  769. strcat(ph_out,str_pause); // don't add pause for 100s, 6th, etc.
  770. }
  771. *flags = FLAG_FOUND;
  772. prev_value = this_value;
  773. return(1);
  774. } // end of TranslateNumber_1
  775. int Translator::TranslateNumber(char *word1, char *ph_out, unsigned int *flags, int wflags)
  776. {//=======================================================================================
  777. if(option_sayas == SAYAS_DIGITS1)
  778. return(0); // speak digits individually
  779. if((langopts.numbers & 0x3) == 1)
  780. return(TranslateNumber_1(word1,ph_out,flags,wflags));
  781. return(0);
  782. } // end of TranslateNumber