1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4
5 #include <string.h>
6 #include <stdlib.h>
7
8 #include "evas_common_private.h"
9 #include "evas_bidi_utils.h"
10
11 #include "evas_font_private.h"
12
13 #ifdef BIDI_SUPPORT
14 #include <fribidi.h>
15 /**
16 * @internal
17 * @addtogroup Evas_Utils
18 *
19 * @{
20 */
21 /**
22 * @internal
23 * @addtogroup Evas_BiDi
24 *
25 * @{
26 */
27
28 /**
29 * @internal
30 * @def _SAFE_FREE(x)
31 * checks if x is not NULL, if it's not, it's freed and set to NULL.
32 */
33 #define _SAFE_FREE(x) \
34 do { \
35 if (x) \
36 { \
37 free(x); \
38 x = NULL; \
39 } \
40 } while(0)
41
42 #if SIZEOF_FRIBIDICHAR != SIZEOF_EINA_UNICODE
43 # define EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
44 #endif
45
46 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
47 /* Convert bidichar to eina_unicode assume both are valid pointers */
48 static Eina_Unicode *
_evas_bidi_fribidichar_to_unicode(Eina_Unicode * dest,const FriBidiChar * src)49 _evas_bidi_fribidichar_to_unicode(Eina_Unicode *dest, const FriBidiChar *src)
50 {
51 Eina_Unicode *ret = dest;
52
53 if ((!src) || (!dest)) return NULL;
54 while (*src) *dest++ = *src++;
55 *dest = 0;
56 return ret;
57 }
58
59 /* Convert eina_unicode to bidi_char assume both are valid pointers */
60 static FriBidiChar *
_evas_bidi_unicode_to_fribidichar(FriBidiChar * dest,const Eina_Unicode * src)61 _evas_bidi_unicode_to_fribidichar(FriBidiChar *dest, const Eina_Unicode *src)
62 {
63 FriBidiChar *ret = dest;
64
65 if ((!src) || (!dest)) return NULL;
66 while (*src) *dest++ = *src++;
67 *dest = 0;
68 return ret;
69 }
70 #endif
71
72 /**
73 * @internal
74 * Checks if the string has RTL characters.
75 *
76 * @param str The string to be checked
77 * @return #EINA_TRUE if true, #EINA_FALSE otherwise.
78 */
79 Eina_Bool
evas_bidi_is_rtl_str(const Eina_Unicode * str)80 evas_bidi_is_rtl_str(const Eina_Unicode *str)
81 {
82 EvasBiDiCharType type;
83
84 if (!str)
85 return EINA_FALSE;
86
87 for ( ; *str ; str++)
88 {
89 type = fribidi_get_bidi_type((FriBidiChar) *str);
90 if (FRIBIDI_IS_RTL(type))
91 {
92 return EINA_TRUE;
93 }
94 }
95 return EINA_FALSE;
96 }
97
98 /**
99 * @internal
100 * Shapes the string ustr according to the bidi properties.
101 *
102 * @param str The string to shape
103 * @param bidi_props the bidi props to shaped according.
104 * @param start the start of the string to shape (offset in bidi_props)
105 * @param len the length of th string.
106 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
107 */
108 EAPI Eina_Bool
evas_bidi_shape_string(Eina_Unicode * eina_ustr,const Evas_BiDi_Paragraph_Props * bidi_props,size_t start,size_t len)109 evas_bidi_shape_string(Eina_Unicode *eina_ustr, const Evas_BiDi_Paragraph_Props *bidi_props, size_t start, size_t len)
110 {
111 FriBidiChar *ustr, *base_ustr = NULL;
112
113 if (!bidi_props)
114 return EINA_FALSE;
115
116 /* The size of fribidichar is different than eina_unicode, convert */
117 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
118 base_ustr = ustr = calloc(len + 1, sizeof(FriBidiChar));
119 ustr = _evas_bidi_unicode_to_fribidichar(ustr, eina_ustr);
120 #else
121 (void) base_ustr;
122 ustr = (FriBidiChar *) eina_ustr;
123 #endif
124
125
126 EvasBiDiJoiningType *join_types = NULL;
127 join_types = (EvasBiDiJoiningType *) malloc(sizeof(EvasBiDiJoiningType) * len);
128 if (!join_types)
129 {
130 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
131 if (base_ustr) free(base_ustr);
132 #endif
133 return EINA_FALSE;
134 }
135 fribidi_get_joining_types(ustr, len, join_types);
136
137 fribidi_join_arabic(bidi_props->char_types + start, len,
138 bidi_props->embedding_levels + start, join_types);
139
140
141 /* Actually modify the string */
142 fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
143 bidi_props->embedding_levels + start, len, join_types, ustr);
144
145 if (join_types) free(join_types);
146
147 /* Convert back */
148 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
149 _evas_bidi_fribidichar_to_unicode(eina_ustr, ustr);
150 if (base_ustr) free(base_ustr);
151 #endif
152 return EINA_TRUE;
153 }
154
155 /**
156 * @internal
157 * Return a -1 terminated array of the indexes of the delimiters (passed in
158 * delim) found in the string. This result should be used with par_props_get.
159 *
160 * @param str The string to parse
161 * @param delim a list of delimiters to work with.
162 * @return returns a -1 terminated array of indexes according to positions of the delimiters found. NULL if there were none.
163 */
164 int *
evas_bidi_segment_idxs_get(const Eina_Unicode * str,const char * delim)165 evas_bidi_segment_idxs_get(const Eina_Unicode *str, const char *delim)
166 {
167 Eina_Unicode *udelim;
168 const Eina_Unicode *str_base = str;
169 int *ret, *tmp_ret;
170 int ret_idx = 0, ret_len = 10; /* arbitrary choice */
171 udelim = eina_unicode_utf8_to_unicode(delim, NULL);
172 ret = malloc(ret_len * sizeof(int));
173 for ( ; *str ; str++)
174 {
175 const Eina_Unicode *del;
176 for (del = udelim ; *del ; del++)
177 {
178 if (*str == *del)
179 {
180 if (ret_idx >= ret_len)
181 {
182 /* arbitrary choice */
183 ret_len += 20;
184 tmp_ret = realloc(ret, ret_len * sizeof(int));
185 if (!tmp_ret)
186 {
187 free(ret);
188 free(udelim);
189 return NULL;
190 }
191 ret = tmp_ret;
192 }
193 ret[ret_idx++] = str - str_base;
194 break;
195 }
196 }
197 }
198 free(udelim);
199
200 /* If no indexes were found return NULL */
201 if (ret_idx == 0)
202 {
203 free(ret);
204 return NULL;
205 }
206
207 ret[ret_idx] = -1;
208 tmp_ret = realloc(ret, (ret_idx + 1) * sizeof(int));
209
210 return (tmp_ret) ? tmp_ret : ret;
211 }
212
213 /**
214 * @internal
215 * Allocates bidi properties according to ustr. First checks to see if the
216 * passed has rtl chars, if not, it returns NULL.
217 *
218 * Assumes all the segment_idxs are either -1 or legal, and > 0 indexes.
219 * Also assumes that the characters at the override points are of weak/neutral
220 * bidi type, otherwise unexpected results may occur.
221 *
222 * @param ustr The string to update according to.
223 * @param len The length of the string
224 * @param segment_idxs A -1 terminated array of points to start a new bidi analysis at (used for section high level bidi overrides). - NULL means none.
225 * @param base_bidi The base BiDi direction of paragraph.
226 * @return returns allocated paragraph props on success, NULL otherwise.
227 */
228 Evas_BiDi_Paragraph_Props *
evas_bidi_paragraph_props_get(const Eina_Unicode * eina_ustr,size_t len,int * segment_idxs,EvasBiDiParType base_bidi)229 evas_bidi_paragraph_props_get(const Eina_Unicode *eina_ustr, size_t len,
230 int *segment_idxs, EvasBiDiParType base_bidi)
231 {
232 Evas_BiDi_Paragraph_Props *bidi_props = NULL;
233 EvasBiDiCharType *char_types = NULL;
234 EvasBiDiLevel *embedding_levels = NULL;
235 const FriBidiChar *ustr;
236 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
237 FriBidiChar *base_ustr = NULL;
238 #endif
239 EvasBiDiLevel ret_level = 0;
240 #if FRIBIDI_MAJOR_VERSION >= 1
241 EvasBiDiBracketType *bracket_types = NULL;
242 #endif
243
244 if (!eina_ustr)
245 return NULL;
246
247 /* No need to handle bidi */
248 if (!evas_bidi_is_rtl_str(eina_ustr) &&
249 (base_bidi != EVAS_BIDI_PARAGRAPH_RTL))
250 {
251 goto cleanup;
252 }
253
254 len = eina_unicode_strlen(eina_ustr);
255 /* The size of fribidichar s different than eina_unicode, convert */
256 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
257 base_ustr = calloc(len + 1, sizeof(FriBidiChar));
258 base_ustr = _evas_bidi_unicode_to_fribidichar(base_ustr, eina_ustr);
259 ustr = base_ustr;
260 #else
261 ustr = (const FriBidiChar *) eina_ustr;
262 #endif
263
264 bidi_props = evas_bidi_paragraph_props_new();
265 bidi_props->direction = base_bidi;
266
267 /* Prep work for reordering */
268 char_types = (EvasBiDiCharType *) malloc(sizeof(EvasBiDiCharType) * len);
269 if (!char_types)
270 {
271 goto cleanup;
272 }
273 fribidi_get_bidi_types(ustr, len, char_types);
274
275 #if FRIBIDI_MAJOR_VERSION >= 1
276 bracket_types = (EvasBiDiBracketType *) malloc(sizeof(EvasBiDiBracketType) * len);
277 if (!bracket_types)
278 {
279 goto cleanup;
280 }
281 fribidi_get_bracket_types(ustr, len, char_types, bracket_types);
282 #endif
283
284 embedding_levels = (EvasBiDiLevel *)malloc(sizeof(EvasBiDiLevel) * len);
285 if (!embedding_levels)
286 {
287 goto cleanup;
288 }
289
290 if (segment_idxs)
291 {
292 size_t pos = 0;
293 int *itr;
294 EvasBiDiLevel base_level = 0;
295 EvasBiDiParType direction;
296
297 for (itr = segment_idxs ; *itr > 0 ; itr++)
298 {
299 direction = base_bidi;
300 #if FRIBIDI_MAJOR_VERSION >= 1
301 ret_level = fribidi_get_par_embedding_levels_ex(char_types + pos,
302 bracket_types,
303 *itr - pos,
304 &direction,
305 embedding_levels + pos);
306 #else
307 ret_level = fribidi_get_par_embedding_levels(char_types + pos,
308 *itr - pos,
309 &direction,
310 embedding_levels + pos);
311 #endif
312 if (!ret_level)
313 {
314 goto cleanup;
315 }
316
317 /* Only on the first run */
318 if (itr == segment_idxs)
319 {
320 bidi_props->direction = direction;
321 /* adjust base_level to be 1 for rtl paragraphs, and 0 for
322 * ltr paragraphs. */
323 base_level =
324 EVAS_BIDI_PARAGRAPH_DIRECTION_IS_RTL(bidi_props) ? 1 : 0;
325 }
326
327 /* We want those chars at the override points to be on the base
328 * level and we also remove -2 cause we later increment them,
329 * just for simpler code paths */
330 embedding_levels[*itr] = base_level - 2;
331 pos = *itr + 1;
332 }
333
334 direction = base_bidi;
335 #if FRIBIDI_MAJOR_VERSION >= 1
336 ret_level = fribidi_get_par_embedding_levels_ex(char_types + pos,
337 bracket_types,
338 len - pos,
339 &direction,
340 embedding_levels + pos);
341 #else
342 ret_level = fribidi_get_par_embedding_levels(char_types + pos,
343 len - pos,
344 &direction,
345 embedding_levels + pos);
346 #endif
347 if (!ret_level)
348 {
349 goto cleanup;
350 }
351
352 /* Increment all levels by 2 to emulate embedding. */
353 {
354 EvasBiDiLevel *bitr = embedding_levels, *end;
355 end = bitr + len;
356 for ( ; bitr < end ; bitr++)
357 {
358 *bitr += 2;
359 }
360 }
361 }
362 else
363 {
364 #if FRIBIDI_MAJOR_VERSION >= 1
365 ret_level = fribidi_get_par_embedding_levels_ex(char_types,
366 bracket_types,
367 len,
368 &bidi_props->direction,
369 embedding_levels);
370 #else
371 ret_level = fribidi_get_par_embedding_levels(char_types,
372 len,
373 &bidi_props->direction,
374 embedding_levels);
375 #endif
376 if (!ret_level)
377 {
378 goto cleanup;
379 }
380 }
381
382
383 /* clean up */
384 if (bidi_props->embedding_levels)
385 {
386 free(bidi_props->embedding_levels);
387 }
388 bidi_props->embedding_levels = embedding_levels;
389
390 /* clean up */
391
392 if (bidi_props->char_types)
393 {
394 free(bidi_props->char_types);
395 }
396 bidi_props->char_types = char_types;
397 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
398 if (base_ustr) free(base_ustr);
399 #endif
400 #if FRIBIDI_MAJOR_VERSION >= 1
401 /* Currently, bracket_types is not reused in other places. */
402 if (bracket_types) free(bracket_types);
403 #endif
404
405 return bidi_props;
406
407 /* Cleanup */
408 cleanup:
409 if (char_types) free(char_types);
410 #if FRIBIDI_MAJOR_VERSION >= 1
411 if (bracket_types) free(bracket_types);
412 #endif
413 if (embedding_levels) free(embedding_levels);
414 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
415 if (base_ustr) free(base_ustr);
416 #endif
417 if (bidi_props) evas_bidi_paragraph_props_unref(bidi_props); /* Clean up the bidi props */
418 return NULL;
419 }
420
421 /**
422 * @internal
423 * Copies dst to src and refs (doesn't copy) the paragraph props.
424 *
425 * @param src the props to copy
426 * @param dst the props to copy to.
427 */
428 void
evas_bidi_props_copy_and_ref(const Evas_BiDi_Props * src,Evas_BiDi_Props * dst)429 evas_bidi_props_copy_and_ref(const Evas_BiDi_Props *src, Evas_BiDi_Props *dst)
430 {
431 dst->dir = src->dir;
432 }
433
434 /**
435 * @internal
436 * Reorders ustr according to the bidi props.
437 *
438 * @param ustr the string to reorder. - Null is ok, will just populate the map.
439 * @param start the start of the line
440 * @param len the length of the line
441 * @param props the paragraph props to reorder according to
442 * @param _v_to_l The visual to logical map to populate - if NULL it won't populate it.
443 * @return #EINA_FALSE on success, #EINA_TRUE on error.
444 */
445 Eina_Bool
evas_bidi_props_reorder_line(Eina_Unicode * eina_ustr,size_t start,size_t len,const Evas_BiDi_Paragraph_Props * props,EvasBiDiStrIndex ** _v_to_l)446 evas_bidi_props_reorder_line(Eina_Unicode *eina_ustr, size_t start, size_t len, const Evas_BiDi_Paragraph_Props *props, EvasBiDiStrIndex **_v_to_l)
447 {
448 EvasBiDiStrIndex *v_to_l = NULL;
449 FriBidiChar *ustr = NULL;
450 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
451 FriBidiChar *base_ustr = NULL;
452 #endif
453
454 if (!props)
455 return EINA_FALSE;
456
457 if (eina_ustr)
458 {
459 /* The size of fribidichar is different than eina_unicode, convert */
460 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
461 base_ustr = ustr = calloc(len + 1, sizeof(FriBidiChar));
462 ustr = _evas_bidi_unicode_to_fribidichar(ustr, eina_ustr);
463 #else
464 ustr = (FriBidiChar *) eina_ustr;
465 #endif
466 }
467
468
469 if (_v_to_l) {
470 size_t i;
471 v_to_l = *_v_to_l = calloc(len, sizeof(EvasBiDiStrIndex));
472 if (!v_to_l)
473 {
474 goto error;
475 }
476 /* init the array for fribidi */
477 for (i = 0 ; i < len ; i++)
478 {
479 v_to_l[i] = i;
480 }
481 }
482
483 {
484 EvasBiDiLevel *emb_lvl;
485 emb_lvl = malloc((start + len) * sizeof(EvasBiDiLevel));
486 memcpy(emb_lvl, props->embedding_levels,
487 (start + len) * sizeof(EvasBiDiLevel));
488 /* We pass v_to_l - start, because fribidi assumes start is the offset
489 * from the start of v_to_l as well, not just the props. */
490 if (!fribidi_reorder_line (FRIBIDI_FLAGS_DEFAULT, props->char_types,
491 len, start, props->direction, emb_lvl, ustr, v_to_l - start))
492 {
493 free(emb_lvl);
494 goto error;
495 }
496 free(emb_lvl);
497 }
498
499
500 /* The size of fribidichar is different than eina_unicode, convert */
501 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
502 _evas_bidi_fribidichar_to_unicode(eina_ustr, base_ustr);
503 free(base_ustr);
504 #endif
505 return EINA_FALSE;
506 /* ERROR HANDLING */
507 error:
508 #ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
509 if (base_ustr) free(base_ustr);
510 #endif
511 _SAFE_FREE(v_to_l);
512 return EINA_TRUE;
513 }
514
515 /**
516 * @internal
517 * Returns the end of the current run of text
518 *
519 * @param bidi_props the paragraph properties
520 * @param start where to start looking from
521 * @param len the length of the string
522 * @return the position of the end of the run (offset from
523 * bidi_props->props->start), 0 when there is no end (i.e all the text)
524 */
525 int
evas_bidi_end_of_run_get(const Evas_BiDi_Paragraph_Props * bidi_props,size_t start,int len)526 evas_bidi_end_of_run_get(const Evas_BiDi_Paragraph_Props *bidi_props,
527 size_t start, int len)
528 {
529 EvasBiDiLevel *i;
530 EvasBiDiLevel base;
531
532 if (!bidi_props || (len <= 0))
533 return 0;
534
535 i = bidi_props->embedding_levels + start;
536 base = *i;
537 for ( ; (len > 0) && (base == *i) ; len--, i++)
538 ;
539
540 if (len == 0)
541 {
542 return 0;
543 }
544 return i - (bidi_props->embedding_levels + start);
545 }
546
547 /**
548 * @internal
549 * Returns the visual string index from the logical string index.
550 *
551 * @param v_to_l the visual to logical map
552 * @param len the length of the map.
553 * @param position the position to convert.
554 * @return on success the visual position, on failure the same position.
555 */
556 EvasBiDiStrIndex
evas_bidi_position_logical_to_visual(EvasBiDiStrIndex * v_to_l,int len,EvasBiDiStrIndex position)557 evas_bidi_position_logical_to_visual(EvasBiDiStrIndex *v_to_l, int len, EvasBiDiStrIndex position)
558 {
559 int i;
560 EvasBiDiStrIndex *ind;
561 if (position >= len || !v_to_l)
562 return position;
563
564 for (i = 0, ind = v_to_l ; i < len ; i++, ind++)
565 {
566 if (*ind == position)
567 {
568 return i;
569 }
570 }
571 return position;
572 }
573
574 /**
575 * @internal
576 * Returns the reversed pos of the index.
577 *
578 * @param dir the direction of the string
579 * @param len the length of the map.
580 * @param position the position to convert.
581 * @return on success the visual position, on failure the same position.
582 */
583 EvasBiDiStrIndex
evas_bidi_position_reverse(const Evas_BiDi_Props * props,int len,EvasBiDiStrIndex position)584 evas_bidi_position_reverse(const Evas_BiDi_Props *props, int len, EvasBiDiStrIndex position)
585 {
586 if (!props || position >= len)
587 return position;
588
589 return (props->dir == EVAS_BIDI_DIRECTION_RTL) ? (len - 1) - position : position;
590 }
591
592 /**
593 * @internal
594 * Checks if the char is rtl oriented. I.e even a neutral char can become rtl
595 * if surrounded by rtl chars.
596 *
597 * @param bidi_props The bidi paragraph properties
598 * @param start the base position
599 * @param index the offset from the base position.
600 * @return #EINA_TRUE if true, #EINA_FALSE otherwise.
601 */
602 Eina_Bool
evas_bidi_is_rtl_char(const Evas_BiDi_Paragraph_Props * bidi_props,size_t start,EvasBiDiStrIndex ind)603 evas_bidi_is_rtl_char(const Evas_BiDi_Paragraph_Props *bidi_props, size_t start, EvasBiDiStrIndex ind)
604 {
605 if(!bidi_props || ind < 0)
606 return EINA_FALSE;
607 return (FRIBIDI_IS_RTL(
608 bidi_props->embedding_levels[ind + start]))
609 ? EINA_TRUE : EINA_FALSE;
610 }
611
612 Evas_BiDi_Paragraph_Props *
evas_bidi_paragraph_props_new(void)613 evas_bidi_paragraph_props_new(void)
614 {
615 Evas_BiDi_Paragraph_Props *ret;
616 ret = calloc(1, sizeof(Evas_BiDi_Paragraph_Props));
617 ret->direction = EVAS_BIDI_PARAGRAPH_NEUTRAL;
618 ret->refcount = 1;
619
620 return ret;
621 }
622
623 /**
624 * @internal
625 * Refs the bidi props.
626 *
627 * @param bidi_props the props to ref.
628 */
629 Evas_BiDi_Paragraph_Props *
evas_bidi_paragraph_props_ref(Evas_BiDi_Paragraph_Props * bidi_props)630 evas_bidi_paragraph_props_ref(Evas_BiDi_Paragraph_Props *bidi_props)
631 {
632 if (!bidi_props) return NULL;
633 BIDILOCK();
634
635 bidi_props->refcount++;
636 BIDIUNLOCK();
637 return bidi_props;
638 }
639
640 /**
641 * @internal
642 * Unrefs and potentially frees the props.
643 *
644 * @param bidi_props the properties to unref
645 */
646 void
evas_bidi_paragraph_props_unref(Evas_BiDi_Paragraph_Props * bidi_props)647 evas_bidi_paragraph_props_unref(Evas_BiDi_Paragraph_Props *bidi_props)
648 {
649 if (!bidi_props) return;
650 BIDILOCK();
651
652 if (--bidi_props->refcount == 0)
653 {
654 evas_bidi_paragraph_props_clean(bidi_props);
655 free(bidi_props);
656 }
657 BIDIUNLOCK();
658 }
659
660
661 /**
662 * @internal
663 * Cleans the paragraph properties.
664 *
665 * @param bidi_props the properties to clean.
666 */
667 void
evas_bidi_paragraph_props_clean(Evas_BiDi_Paragraph_Props * bidi_props)668 evas_bidi_paragraph_props_clean(Evas_BiDi_Paragraph_Props *bidi_props)
669 {
670 _SAFE_FREE(bidi_props->embedding_levels);
671 _SAFE_FREE(bidi_props->char_types);
672 }
673
674 /**
675 * @internal
676 * Cleans the bidi properties.
677 *
678 * @param bidi_props the properties to clean.
679 */
680 void
evas_bidi_props_clean(Evas_BiDi_Props * bidi_props)681 evas_bidi_props_clean(Evas_BiDi_Props *bidi_props)
682 {
683 if (!bidi_props) return;
684 bidi_props->dir = EVAS_BIDI_DIRECTION_NEUTRAL;
685 }
686 /**
687 * @}
688 */
689 /**
690 * @}
691 */
692 #endif
693
694 #if 0
695 /* Good for debugging */
696 static void
697 dump_levels(Eina_Unicode *ustr, EvasBiDiLevel *emb)
698 {
699 for ( ; *ustr ; ustr++, emb++)
700 {
701 printf("%lc %d\n", *ustr, *emb);
702 }
703 }
704 #endif
705
706