Commit | Line | Data |
---|---|---|
69695f33 MW |
1 | |
2 | /* Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 by Arkkra Enterprises */ | |
3 | /* All rights reserved */ | |
4 | ||
5 | /* functions related to characters: information about the size and shape of | |
6 | * characters to be printed, to initialize the internal tables | |
7 | * to tell how big each character is, etc. | |
8 | */ | |
9 | ||
10 | #include <string.h> | |
11 | #include "defines.h" | |
12 | #include "structs.h" | |
13 | #include "globals.h" | |
14 | ||
15 | ||
16 | /* code for invalid music character */ | |
17 | #define BAD_CHAR '\377' | |
18 | /* code for invalid number in user input string */ | |
19 | #define BAD_NUMBER -30000 | |
20 | ||
21 | /* machine-generated sorted list for translating | |
22 | * music character names to internal code numbers. */ | |
23 | /* The +1 is because there is an "end-of-list" entry with charname == 0 */ | |
24 | extern struct SPECCHAR Mus_char_table[NUM_MFONTS][CHARS_IN_FONT+1]; | |
25 | #ifdef EXTCHAR | |
26 | extern struct SPECCHAR Ext_char_table[]; | |
27 | #endif | |
28 | ||
29 | ||
30 | /* save information about characters in string as we go, in order to be | |
31 | * able to backspace back over them */ | |
32 | struct BACKSPACEINFO { | |
33 | char code; | |
34 | char font; | |
35 | }; | |
36 | ||
37 | ||
38 | #ifndef __STDC__ | |
39 | extern char *bsearch(); /* binary search library function */ | |
40 | #endif | |
41 | extern long strtol(); | |
42 | ||
43 | /* static functions */ | |
44 | static char *get_font P((char *string, int *font_p, int prev_font, char *fname, | |
45 | int lineno)); | |
46 | static char *get_num P((char *string, int *num_p)); | |
47 | static int sc_compare P((const void *item1_p, const void *item2_p)); | |
48 | #ifdef EXTCHAR | |
49 | static unsigned char ext_name2num P((char *name)); | |
50 | #endif | |
51 | static int starts_piled P((char *string, int *font_p, int *size_p, | |
52 | char **pile_start_p_p)); | |
53 | static int str_cmd P((char *str, int *size_p, int *in_pile_p)); | |
54 | static int get_accidental P((unsigned char *str, char *accidental_p, | |
55 | int *acc_size_p, int trans_natural, int *escaped_p)); | |
56 | static int add_accidental P((char *buff, int acc_character, int acc_size, | |
57 | int escaped)); | |
58 | static int dim_tri P((unsigned char *str, char *replacement, | |
59 | int size, int is_chord)); | |
60 | static int smallsize P((int size)); | |
61 | static int accsize P((int size)); | |
62 | \f | |
63 | ||
64 | /* return the height (in inches) of a character of specified font and size */ | |
65 | ||
66 | double | |
67 | height(font, size, ch) | |
68 | ||
69 | int font; | |
70 | int size; | |
71 | int ch; /* which character */ | |
72 | ||
73 | { | |
74 | int chval; | |
75 | ||
76 | chval = ch & 0xff; | |
77 | ||
78 | /* control characters have no height */ | |
79 | if (chval < FIRST_CHAR) { | |
80 | return(0.0); | |
81 | } | |
82 | ||
83 | return((Fontinfo[ font_index(font) ].ch_height[ CHAR_INDEX(chval) ] | |
84 | / FONTFACTOR) * ((float)size / (float)DFLT_SIZE) ); | |
85 | } | |
86 | \f | |
87 | ||
88 | /* return the width (in inches) of a character of specified font and size */ | |
89 | ||
90 | double | |
91 | width(font, size, ch) | |
92 | ||
93 | int font; | |
94 | int size; | |
95 | int ch; /* which character */ | |
96 | ||
97 | { | |
98 | int chval; | |
99 | ||
100 | chval = ch & 0xff; | |
101 | ||
102 | /* control characters have no width */ | |
103 | if (chval < FIRST_CHAR) { | |
104 | return(0.0); | |
105 | } | |
106 | ||
107 | return((Fontinfo[ font_index(font) ].ch_width[ CHAR_INDEX(chval) ] | |
108 | / FONTFACTOR) * ((float)size / (float)DFLT_SIZE) ); | |
109 | } | |
110 | \f | |
111 | ||
112 | /* return the ascent (in inches) of a character of specified font and size */ | |
113 | ||
114 | double | |
115 | ascent(font, size, ch) | |
116 | ||
117 | int font; | |
118 | int size; | |
119 | int ch; /* which character */ | |
120 | ||
121 | { | |
122 | int chval; | |
123 | ||
124 | chval = ch & 0xff; | |
125 | ||
126 | /* control characters have no ascent */ | |
127 | if (chval < FIRST_CHAR) { | |
128 | return(0.0); | |
129 | } | |
130 | ||
131 | return((Fontinfo[ font_index(font) ].ch_ascent[ CHAR_INDEX(chval) ] | |
132 | / FONTFACTOR) * ((float) size / (float)DFLT_SIZE) ); | |
133 | } | |
134 | \f | |
135 | ||
136 | /* return the descent (in inches) of a character of specified font and size */ | |
137 | ||
138 | double | |
139 | descent(font, size, ch) | |
140 | ||
141 | int font; | |
142 | int size; | |
143 | int ch; /* which character */ | |
144 | ||
145 | { | |
146 | return ( height(font, size, ch) - ascent(font, size, ch) ); | |
147 | } | |
148 | \f | |
149 | ||
150 | /* given a user input string, normalize it. This means: | |
151 | * Put the default font in [0] and default size in [1] of the string. | |
152 | * Change backslashed things to internal format. Each starts with a | |
153 | * hyper-ASCII code byte and is followed by one or more data bytes. | |
154 | * Note that in all cases in internal format is no longer than the | |
155 | * incoming format. | |
156 | * Change any \f(XX) to STR_FONT font_number | |
157 | * Change any \s(NN) to STR_SIZE actual_size | |
158 | * Note that NN might have a sign to indicate relative size change. | |
159 | * Change any \v(NN) to STR_VERTICAL vertical_offset | |
160 | * Note that NN might have a sign to indicate direction, | |
161 | * negative means downward. | |
162 | * Change any \/ to STR_SLASH | |
163 | * Change any \| to STR_L_ALIGN (piled mode only) | |
164 | * Change any \^ to STR_C_ALIGN (piled mode only) | |
165 | * Change any backslashed space to space when in piled mode | |
166 | * Change any space to newline while in piled mode | |
167 | * Change \(xxxx) to STR_MUS_CHAR size mus_char_code | |
168 | * Change \% to STR_PAGENUM % | |
169 | * Change \# to STR_NUMPAGES # | |
170 | * Change \n to newline | |
171 | * Change \b to STR_BACKSPACE n | |
172 | * where n is how much to back up for the | |
173 | * default size, in BACKSP_FACTORths of an inch | |
174 | * Change backslashed backslash or double quote to just be themselves. | |
175 | * Reject any other control characters or illegal backslash escapes. | |
176 | * The string is null-terminated. | |
177 | * | |
178 | * The normalized string is put back into the original string buffer | |
179 | * that was passed in, and a pointer to it is returned. | |
180 | * | |
181 | * Note that some functions in lyrics.c, and prntdata.c | |
182 | * also have knowledge of the escape conventions, | |
183 | * so if these change, check there too. But it is intended | |
184 | * that all the rest of the code gets at strings indirectly | |
185 | * via functions in this file, so the details can be hidden here. | |
186 | */ | |
187 | ||
188 | char * | |
189 | fix_string(string, font, size, fname, lineno) | |
190 | ||
191 | char *string; /* original string */ | |
192 | int font; /* default font for string */ | |
193 | int size; /* default size for string */ | |
194 | char *fname; /* file name, for error messages */ | |
195 | int lineno; /* input line number, for error messages */ | |
196 | ||
197 | { | |
198 | char *tmpbuff; /* for normalized string */ | |
199 | int leng; /* strlen(string) + 1 */ | |
200 | char *inp_p, *out_p; /* walk thru orig & normalized string */ | |
201 | int nsize; /* new size */ | |
202 | int prevsize; /* previous size */ | |
203 | int msize; /* size for music character */ | |
204 | int vert; /* argument to \v without sign */ | |
205 | int vertval = 0; /* signed argument to \v */ | |
206 | int has_vertical = NO; /* YES if \v or pile found */ | |
207 | int has_newline = NO; /* YES if \n somewhere in string */ | |
208 | int pile_mode = NO; | |
209 | int align_points = 0; /* how many aligments points found */ | |
210 | int error; /* YES if have found an error */ | |
211 | char spec_name[100], *sc_p; /* name of special music character, or | |
212 | * extended character set character */ | |
213 | unsigned char extchar; /* value for extended character */ | |
214 | unsigned char muschar; /* value for music character */ | |
215 | int now_font; /* current font */ | |
216 | int newfont; /* proposed new font */ | |
217 | int prevfont; /* previous font */ | |
218 | int mfont; /* music font */ | |
219 | struct BACKSPACEINFO *backspaceinfo; /* font/size for backspacing */ | |
220 | int backspaceindex = 0; /* index into backspaceinfo */ | |
221 | float backup; /* backspace distance in inches | |
222 | * for default size */ | |
223 | int backupval; /* value to store for backspace | |
224 | * distance */ | |
225 | ||
226 | ||
227 | /* fill in default font and size */ | |
228 | string[0] = (char) font; | |
229 | if (rangecheck(size, MINSIZE, MAXSIZE, "size") == NO) { | |
230 | size = MAXSIZE; | |
231 | } | |
232 | string[1] = (char) size; | |
233 | now_font = prevfont = font; | |
234 | prevsize = size; | |
235 | ||
236 | leng = strlen(string) + 1; | |
237 | MALLOCA(char, tmpbuff, leng); | |
238 | MALLOC(BACKSPACEINFO, backspaceinfo, leng); | |
239 | /* walk through incoming string, creating normalized string */ | |
240 | for (error = NO, out_p = tmpbuff + 2, inp_p = string + 2; | |
241 | (error == NO) && (*inp_p != '\0'); | |
242 | inp_p++, out_p++) { | |
243 | ||
244 | /* handle backslashed stuff */ | |
245 | if (*inp_p == '\\') { | |
246 | ||
247 | /* skip past the backslash */ | |
248 | inp_p++; | |
249 | ||
250 | switch( *inp_p) { | |
251 | ||
252 | case '\n': | |
253 | /* ignore the backslashed newline */ | |
254 | out_p--; | |
255 | break; | |
256 | ||
257 | case '\r': | |
258 | if (*(inp_p) == '\n') { | |
259 | inp_p++; | |
260 | } | |
261 | out_p--; | |
262 | break; | |
263 | ||
264 | case 'f': | |
265 | /* font change */ | |
266 | inp_p = get_font(++inp_p, &newfont, prevfont, | |
267 | fname, lineno); | |
268 | if (newfont == FONT_UNKNOWN) { | |
269 | error = YES; | |
270 | } | |
271 | else { | |
272 | *out_p++ = (char) STR_FONT; | |
273 | *out_p = (char) newfont; | |
274 | prevfont = now_font; | |
275 | now_font = newfont; | |
276 | } | |
277 | break; | |
278 | ||
279 | case 's': | |
280 | /* size change */ | |
281 | if (*++inp_p == '(') { | |
282 | switch (*++inp_p) { | |
283 | case '+': | |
284 | inp_p = get_num(++inp_p, &nsize); | |
285 | if (nsize > 0) { | |
286 | nsize += size; | |
287 | } | |
288 | break; | |
289 | case '-': | |
290 | inp_p = get_num(++inp_p, &nsize); | |
291 | if (nsize > 0) { | |
292 | nsize = size - nsize; | |
293 | } | |
294 | break; | |
295 | case 'P': | |
296 | if (strncmp(inp_p, "PV)", 3) == 0) { | |
297 | nsize = prevsize; | |
298 | inp_p += 2; | |
299 | } | |
300 | else { | |
301 | nsize = BAD_NUMBER; | |
302 | } | |
303 | break; | |
304 | case 'p': | |
305 | if (strncmp(inp_p, "previous)", 9) == 0) { | |
306 | nsize = prevsize; | |
307 | inp_p += 8; | |
308 | } | |
309 | else { | |
310 | nsize = BAD_NUMBER; | |
311 | } | |
312 | break; | |
313 | default: | |
314 | inp_p = get_num(inp_p, &nsize); | |
315 | break; | |
316 | } | |
317 | } | |
318 | else { | |
319 | nsize = BAD_NUMBER; | |
320 | } | |
321 | ||
322 | /* if got valid size, store it */ | |
323 | if (nsize == BAD_NUMBER) { | |
324 | l_yyerror(fname, lineno, | |
325 | "Invalid format for size value"); | |
326 | error = YES; | |
327 | } | |
328 | else if (rangecheck(nsize, MINSIZE, | |
329 | MAXSIZE, "size") == YES) { | |
330 | *out_p++ = (char) STR_SIZE; | |
331 | *out_p = (char) nsize; | |
332 | /* save new size */ | |
333 | prevsize = size; | |
334 | size = nsize; | |
335 | } | |
336 | else { | |
337 | error = YES; | |
338 | } | |
339 | ||
340 | break; | |
341 | ||
342 | case 'v': | |
343 | /* vertical motion */ | |
344 | if (*++inp_p == '(') { | |
345 | switch (*++inp_p) { | |
346 | case '-': | |
347 | inp_p = get_num(++inp_p, &vert); | |
348 | if (vert >= 0) { | |
349 | vertval = -vert; | |
350 | } | |
351 | break; | |
352 | ||
353 | case '+': | |
354 | ++inp_p; | |
355 | /* fall through */ | |
356 | default: | |
357 | inp_p = get_num(inp_p, &vert); | |
358 | if (vert >= 0) { | |
359 | vertval = vert; | |
360 | } | |
361 | break; | |
362 | } | |
363 | } | |
364 | else { | |
365 | vert = BAD_NUMBER; | |
366 | } | |
367 | ||
368 | if (vert == BAD_NUMBER) { | |
369 | l_yyerror(fname, lineno, | |
370 | "Invalid format for vertical motion value"); | |
371 | error = YES; | |
372 | } | |
373 | else if (rangecheck(vertval, -100, 100, | |
374 | "vertical") == YES) { | |
375 | /* if motion is zero, don't even bother | |
376 | * to save it, else do */ | |
377 | if (vertval != 0) { | |
378 | /* convert percentage to | |
379 | * STR_VERTICAL units */ | |
380 | if (vertval > 0) { | |
381 | vertval = vertval * | |
382 | MAXVERTICAL/100; | |
383 | } | |
384 | else { | |
385 | vertval = -vertval * | |
386 | MINVERTICAL/100; | |
387 | } | |
388 | *out_p++ = (char) STR_VERTICAL; | |
389 | *out_p = (char) ENCODE_VERT( | |
390 | vertval ); | |
391 | } | |
392 | } | |
393 | else { | |
394 | error = YES; | |
395 | } | |
396 | ||
397 | /* we don't allow backspacing to something | |
398 | * before a vertical motion--this is almost | |
399 | * like a newline. */ | |
400 | backspaceindex = 0; | |
401 | ||
402 | has_vertical = YES; | |
403 | ||
404 | break; | |
405 | ||
406 | case ':': | |
407 | /* If this begins a pile, and the next thing | |
408 | * in input ends the pile, just ignore them | |
409 | * both to keep things simpler later. */ | |
410 | if (pile_mode == NO && *(inp_p+1) == '\\' | |
411 | && *(inp_p+2) == ':') { | |
412 | inp_p += 2; | |
413 | /* no output character */ | |
414 | out_p--; | |
415 | } | |
416 | else { | |
417 | *out_p = (char) STR_PILE; | |
418 | has_vertical = YES; | |
419 | pile_mode = (pile_mode == YES ? NO : YES); | |
420 | } | |
421 | align_points = 0; | |
422 | break; | |
423 | ||
424 | case '|': | |
425 | case '^': | |
426 | if (pile_mode == NO) { | |
427 | l_yyerror(fname, lineno, | |
428 | "alignment point only allowed in piled mode"); | |
429 | *out_p = *inp_p; | |
430 | } | |
431 | ||
432 | else if (++align_points > 1) { | |
433 | l_yyerror(fname, lineno, | |
434 | "only one alignment point allowed per line"); | |
435 | *out_p = *inp_p; | |
436 | } | |
437 | ||
438 | else if (*inp_p == '^') { | |
439 | int next_ch; | |
440 | *out_p = (char) STR_C_ALIGN; | |
441 | next_ch = *(inp_p+1) & 0xff; | |
442 | /* it's too much trouble to handle | |
443 | * things like font changes between | |
444 | * the \^ and the character that | |
445 | * will be allowed, so disallow them, | |
446 | * since user can easily put them | |
447 | * before the \^ anyway. */ | |
448 | if ( (IS_STR_COMMAND(next_ch) | |
449 | && next_ch != STR_MUS_CHAR) | |
450 | || *(inp_p+1) == ' ' | |
451 | || iscntrl(*(inp_p+1)) ) { | |
452 | l_yyerror(fname, lineno, | |
453 | "\\^ must be followed by normal character"); | |
454 | } | |
455 | } | |
456 | else { | |
457 | *out_p = (char) STR_L_ALIGN; | |
458 | } | |
459 | has_vertical = YES; | |
460 | break; | |
461 | ||
462 | case ' ': | |
463 | if (pile_mode == NO) { | |
464 | l_yyerror(fname, lineno, | |
465 | "backslashed space only allowed in piled mode"); | |
466 | } | |
467 | *out_p = ' '; | |
468 | break; | |
469 | ||
470 | case '/': | |
471 | /* This is only allowed after one | |
472 | * or more digits */ | |
473 | if ( inp_p - string < 4 || | |
474 | ! isdigit( *(inp_p - 2)) ) { | |
475 | l_yyerror(fname, lineno, | |
476 | "slash can only be used after digit(s)"); | |
477 | } | |
478 | *out_p = (char) STR_SLASH; | |
479 | break; | |
480 | case '\\': | |
481 | case '"': | |
482 | /* real backslash or embedded quote, copy it */ | |
483 | backspaceinfo[backspaceindex].code = *inp_p; | |
484 | backspaceinfo[backspaceindex++].font | |
485 | = (char) now_font; | |
486 | *out_p = *inp_p; | |
487 | break; | |
488 | ||
489 | case '(': | |
490 | /* special music character or extended | |
491 | * character set character */ | |
492 | /* make copy of name */ | |
493 | for ( sc_p = spec_name, inp_p++; | |
494 | *inp_p != ')' && *inp_p != '\0'; | |
495 | sc_p++, inp_p++) { | |
496 | *sc_p = *inp_p; | |
497 | } | |
498 | *sc_p = '\0'; | |
499 | ||
500 | #ifdef EXTCHAR | |
501 | /* first see if it is a character in the | |
502 | * extended character set */ | |
503 | if ((extchar = ext_name2num(spec_name)) | |
504 | != (unsigned char) BAD_CHAR) { | |
505 | /* temporarily change to the extended | |
506 | * character set font that corresponds | |
507 | * to the current normal ASCII font, | |
508 | * and output the extended character | |
509 | * set code for the desired character. | |
510 | * Then go back to original font */ | |
511 | *out_p++ = (char) STR_FONT; | |
512 | *out_p++ = (char) | |
513 | (now_font + EXT_FONT_OFFSET); | |
514 | *out_p++ = extchar; | |
515 | *out_p++ = (char) STR_FONT; | |
516 | *out_p = (char) now_font; | |
517 | backspaceinfo[backspaceindex].code | |
518 | = extchar; | |
519 | backspaceinfo[backspaceindex++].font | |
520 | = now_font + EXT_FONT_OFFSET; | |
521 | ||
522 | /* mark that this extended character | |
523 | * set font has been used */ | |
524 | Font_used[now_font + EXT_FONT_OFFSET] = YES; | |
525 | ||
526 | break; | |
527 | } | |
528 | #endif | |
529 | /* look up music character with this name */ | |
530 | msize = size; | |
531 | if ((muschar = mc_name2num(spec_name, fname, | |
532 | lineno, &msize, &mfont)) | |
533 | != (unsigned char) BAD_CHAR) { | |
534 | *out_p++ = (char) mfont2str(mfont); | |
535 | *out_p++ = (char) msize; | |
536 | *out_p = muschar; | |
537 | backspaceinfo[backspaceindex].code | |
538 | = muschar; | |
539 | backspaceinfo[backspaceindex++].font | |
540 | = FONT_MUSIC; | |
541 | } | |
542 | break; | |
543 | ||
544 | case '[': | |
545 | /* start of boxed text. We only allow this at | |
546 | * the beginning of a string */ | |
547 | if (inp_p != string + 3) { | |
548 | l_yyerror(fname, lineno, | |
549 | "\\[ only allowed at beginning of string"); | |
550 | error = YES; | |
551 | } | |
552 | else { | |
553 | *out_p = (char) STR_BOX; | |
554 | } | |
555 | break; | |
556 | ||
557 | case ']': | |
558 | /* end of boxed text. Only allowed at end of | |
559 | * string, and only if the string began | |
560 | * with a box start. */ | |
561 | if (*(inp_p + 1) != '\0') { | |
562 | l_yyerror(fname, lineno, | |
563 | "\\] only allowed at end of string"); | |
564 | error = YES; | |
565 | } | |
566 | else if (IS_BOXED(tmpbuff) == NO) { | |
567 | l_yyerror(fname, lineno, | |
568 | "no matching \\[ for \\]"); | |
569 | error = YES; | |
570 | } | |
571 | else { | |
572 | *out_p = (char) STR_BOX_END; | |
573 | } | |
574 | break; | |
575 | ||
576 | case '{': | |
577 | /* start of circled text. We only allow this at | |
578 | * the beginning of a string */ | |
579 | if (inp_p != string + 3) { | |
580 | l_yyerror(fname, lineno, | |
581 | "\\{ only allowed at beginning of string"); | |
582 | error = YES; | |
583 | } | |
584 | else { | |
585 | *out_p = (char) STR_CIR; | |
586 | } | |
587 | break; | |
588 | ||
589 | case '}': | |
590 | /* end of circled text. Only allowed at end of | |
591 | * string, and only if the string began | |
592 | * with a circle start. */ | |
593 | if (*(inp_p + 1) != '\0') { | |
594 | l_yyerror(fname, lineno, | |
595 | "\\} only allowed at end of string"); | |
596 | error = YES; | |
597 | } | |
598 | else if (IS_CIRCLED(tmpbuff) == NO) { | |
599 | l_yyerror(fname, lineno, | |
600 | "no matching \\{ for \\}"); | |
601 | error = YES; | |
602 | } | |
603 | else { | |
604 | *out_p = (char) STR_CIR_END; | |
605 | } | |
606 | break; | |
607 | ||
608 | case '%': | |
609 | /* too hard to deal with inside a pile... */ | |
610 | if (pile_mode == YES) { | |
611 | l_yyerror(fname, lineno, | |
612 | "\\%c not allowed inside a pile\n", '%'); | |
613 | } | |
614 | ||
615 | /* page number -- change to STR_PAGENUM-% */ | |
616 | *out_p++ = (char) STR_PAGENUM; | |
617 | *out_p = '%'; | |
618 | /* we really don't know at this point how far | |
619 | * to backspace over pagenum because we don't | |
620 | * know yet how many digits it is, etc, so we | |
621 | * punt and just use the % character | |
622 | * for width */ | |
623 | backspaceinfo[backspaceindex].code = '%'; | |
624 | backspaceinfo[backspaceindex++].font | |
625 | = (char) now_font; | |
626 | break; | |
627 | ||
628 | case '#': | |
629 | /* code basically the same as for % */ | |
630 | if (pile_mode == YES) { | |
631 | l_yyerror(fname, lineno, | |
632 | "\\# not allowed inside a pile\n"); | |
633 | } | |
634 | ||
635 | /* number of pages -- change to STR_NUMPAGES-# */ | |
636 | *out_p++ = (char) STR_NUMPAGES; | |
637 | *out_p = '#'; | |
638 | /* We really don't know at this point how far | |
639 | * to backspace, because we don't know yet | |
640 | * how many digits it is, etc, so we punt | |
641 | * and just use the # character for width. */ | |
642 | backspaceinfo[backspaceindex].code = '#'; | |
643 | backspaceinfo[backspaceindex++].font | |
644 | = (char) now_font; | |
645 | break; | |
646 | ||
647 | case 'n': | |
648 | /* newline */ | |
649 | *out_p = '\n'; | |
650 | /* can't back up to previous line */ | |
651 | backspaceindex = 0; | |
652 | has_newline = YES; | |
653 | break; | |
654 | ||
655 | case 'b': | |
656 | /* can't back up past beginning of string */ | |
657 | if (backspaceindex == 0) { | |
658 | if (has_newline == YES || has_vertical == YES) { | |
659 | l_yyerror(fname, lineno, | |
660 | "can't backspace before newline or vertical motion"); | |
661 | } | |
662 | else { | |
663 | l_yyerror(fname, lineno, | |
664 | "can't backspace before beginning of line"); | |
665 | } | |
666 | error = YES; | |
667 | } | |
668 | else { | |
669 | backspaceindex--; | |
670 | backup = width(backspaceinfo | |
671 | [backspaceindex].font, | |
672 | DFLT_SIZE, backspaceinfo | |
673 | [backspaceindex].code); | |
674 | *out_p++ = (char) STR_BACKSPACE; | |
675 | /* calculate backup value to store */ | |
676 | backupval = (int) (backup * BACKSP_FACTOR); | |
677 | if (backupval < 1) { | |
678 | backupval = 1; | |
679 | } | |
680 | else if (backupval > 127) { | |
681 | backupval = 127; | |
682 | } | |
683 | *out_p = (char) backupval; | |
684 | } | |
685 | break; | |
686 | ||
687 | default: | |
688 | yyerror("illegal \\ escape"); | |
689 | error = YES; | |
690 | break; | |
691 | } | |
692 | } | |
693 | ||
694 | else if (iscntrl(*inp_p) ) { | |
695 | if (*inp_p == '\n') { | |
696 | backspaceindex = 0; | |
697 | has_newline = YES; | |
698 | *out_p = *inp_p; | |
699 | } | |
700 | else if (*inp_p == '\r' && *(inp_p+1) == '\n') { | |
701 | /* ignore DOS's extraneous \r */ | |
702 | out_p--; | |
703 | } | |
704 | else { | |
705 | /* We don't support any other control | |
706 | * characters, but just convert others to | |
707 | * space and continue. That way user at least | |
708 | * gets something. Tab is something user may | |
709 | * expect to work, so we give a more clear | |
710 | * and specific error for that. | |
711 | */ | |
712 | l_warning(fname, lineno, | |
713 | "unsupported control character '\\0%o' %sin string replaced with space", | |
714 | *inp_p, *inp_p =='\t' ? "(tab) ": ""); | |
715 | *out_p = ' '; | |
716 | } | |
717 | } | |
718 | else if (pile_mode == YES && *inp_p == ' ') { | |
719 | /* in piled mode, space means move down for next | |
720 | * item in pile. */ | |
721 | *out_p = '\n'; | |
722 | ||
723 | align_points = 0; | |
724 | backspaceindex = 0; | |
725 | } | |
726 | else { | |
727 | /* normal character -- copy as is */ | |
728 | *out_p = *inp_p; | |
729 | backspaceinfo[backspaceindex].code = *inp_p; | |
730 | backspaceinfo[backspaceindex++].font = (char) now_font; | |
731 | } | |
732 | } | |
733 | /* If we got an error, we would not have put anything into the | |
734 | * final output position before incrementing out_p in the 'for' loop, | |
735 | * so compensate, so we don't leave a garbage character. */ | |
736 | if (error == YES) { | |
737 | out_p--; | |
738 | } | |
739 | *out_p = '\0'; | |
740 | ||
741 | if (error == NO && IS_BOXED(tmpbuff) == YES && | |
742 | (*(out_p - 1) & 0xff) != (STR_BOX_END & 0xff)) { | |
743 | l_yyerror(fname, lineno, "no matching \\] for \\["); | |
744 | } | |
745 | ||
746 | if (error == NO && IS_CIRCLED(tmpbuff) == YES && | |
747 | (*(out_p - 1) & 0xff) != (STR_CIR_END & 0xff)) { | |
748 | l_yyerror(fname, lineno, "no matching \\} for \\{"); | |
749 | } | |
750 | ||
751 | /* to keep things simple, we don't allow | |
752 | * mixing newlines with vertical motion */ | |
753 | if (has_vertical == YES && has_newline == YES) { | |
754 | l_yyerror(fname, lineno, | |
755 | "can't have newline in same string with vertical motion or alignment"); | |
756 | } | |
757 | ||
758 | /* now copy normalized string back onto original */ | |
759 | (void) strcpy(string + 2, tmpbuff + 2); | |
760 | FREE(tmpbuff); | |
761 | FREE(backspaceinfo); | |
762 | return(string); | |
763 | } | |
764 | \f | |
765 | ||
766 | /* given pointer into a string, read a font name exclosed in parentheses. | |
767 | * Return the corresponding font number, or | |
768 | * FONT_UNKNOWN if name is invalid. Return pointer to last character | |
769 | * processed in string */ | |
770 | ||
771 | static char * | |
772 | get_font(string, font_p, prev_font, fname, lineno) | |
773 | ||
774 | char *string; /* get font from this string */ | |
775 | int *font_p; /* return new font via this pointer */ | |
776 | int prev_font; /* previous font */ | |
777 | char *fname; /* file name for errors */ | |
778 | int lineno; /* line number, for error messages */ | |
779 | ||
780 | { | |
781 | char fontname[BUFSIZ]; | |
782 | int font = FONT_UNKNOWN; | |
783 | char *endparen; /* where ')' is in string */ | |
784 | int length; /* of font name */ | |
785 | ||
786 | ||
787 | if (*string == '(') { | |
788 | string++; | |
789 | if ((endparen = strchr(string, ')')) != (char *) 0) { | |
790 | length = endparen - string; | |
791 | (void) strncpy(fontname, string, (unsigned) length); | |
792 | fontname[length] = '\0'; | |
793 | string += length; | |
794 | if (strcmp(fontname, "PV") == 0 | |
795 | || strcmp(fontname, "previous") == 0) { | |
796 | /* special case of "previous" font */ | |
797 | font = prev_font; | |
798 | } | |
799 | else { | |
800 | font = lookup_font(fontname); | |
801 | } | |
802 | } | |
803 | } | |
804 | ||
805 | *font_p = font; | |
806 | if (font == FONT_UNKNOWN) { | |
807 | l_yyerror(fname, lineno, "unknown font specified"); | |
808 | } | |
809 | return(string); | |
810 | } | |
811 | \f | |
812 | ||
813 | /* given a pointer into a string, get a number followed by close parenthesis. | |
814 | * Return the number via pointer, or BAD_NUMBER on error. | |
815 | * Return pointer to the last character processed | |
816 | * in the incoming string */ | |
817 | ||
818 | static char * | |
819 | get_num(string, num_p) | |
820 | ||
821 | char *string; /* get number from this string */ | |
822 | int *num_p; /* return number via this pointer, or -1 on error */ | |
823 | ||
824 | { | |
825 | if (isdigit(*string)) { | |
826 | *num_p = strtol(string, &string, 10); | |
827 | if (*string != ')') { | |
828 | *num_p = BAD_NUMBER; | |
829 | } | |
830 | } | |
831 | else { | |
832 | *num_p = BAD_NUMBER; | |
833 | } | |
834 | return(string); | |
835 | } | |
836 | \f | |
837 | ||
838 | /* compare the charname fields of 2 SPECCHAR structs and return | |
839 | * their proper order, for comparison by bsearch() */ | |
840 | ||
841 | static int | |
842 | sc_compare(item1_p, item2_p) | |
843 | ||
844 | #ifdef __STDC__ | |
845 | const void *item1_p; /* there are really struct SPECCHAR *, but bsearch | |
846 | * passes them as char * and we have to | |
847 | * cast appropriately */ | |
848 | const void *item2_p; | |
849 | #else | |
850 | char *item1_p; /* there are really struct SPECCHAR *, but bsearch passes them | |
851 | * as char * and we have to cast appropriately */ | |
852 | char *item2_p; | |
853 | #endif | |
854 | ||
855 | { | |
856 | return(strcmp( ((struct SPECCHAR *)(item1_p))->charname, | |
857 | ((struct SPECCHAR *)(item2_p))->charname)); | |
858 | } | |
859 | \f | |
860 | ||
861 | /* given the name of a music character, return its code number. | |
862 | * If the name is not a valid name, return BAD_CHAR. | |
863 | * Just do a binary search in the name-to-code translation table. | |
864 | */ | |
865 | ||
866 | unsigned char | |
867 | mc_name2num(name, fname, lineno, size_p, font_p) | |
868 | ||
869 | char *name; /* name for a music character */ | |
870 | char *fname; /* file name for error messages */ | |
871 | int lineno; /* input line number for error messages */ | |
872 | int *size_p; /* points to current size, proper size for music character | |
873 | * is returned through here */ | |
874 | int *font_p; /* FONT_MUSIC* is returned here */ | |
875 | ||
876 | { | |
877 | struct SPECCHAR *info_p;/* translation entry for the given name */ | |
878 | struct SPECCHAR key; /* what to look for */ | |
879 | int f; /* font index */ | |
880 | static unsigned int numch[NUM_MFONTS]; /* how many items in font */ | |
881 | ||
882 | ||
883 | /* first time through, find size of name-to-code table */ | |
884 | if (numch[0] == 0) { | |
885 | for (f = 0; f < NUM_MFONTS; f++) { | |
886 | for ( ; Mus_char_table[f][numch[f]].charname != (char *)0; | |
887 | (numch[f])++) { | |
888 | ; | |
889 | } | |
890 | } | |
891 | } | |
892 | ||
893 | /* check for "small" characters */ | |
894 | if (name[0] == 's' && name[1] == 'm') { | |
895 | key.charname = name + 2; | |
896 | *size_p = smallsize(*size_p); | |
897 | } | |
898 | else { | |
899 | key.charname = name; | |
900 | } | |
901 | ||
902 | /* do binary search for code */ | |
903 | for (f = 0; f < NUM_MFONTS; f++) { | |
904 | if ((info_p = (struct SPECCHAR *) bsearch((char *) &key, Mus_char_table[f], | |
905 | numch[f], sizeof(struct SPECCHAR), sc_compare)) | |
906 | != (struct SPECCHAR *) 0) { | |
907 | *font_p = FONT_MUSIC + f; | |
908 | return( (unsigned char) info_p->code); | |
909 | } | |
910 | } | |
911 | ||
912 | l_yyerror(fname, lineno, "unknown music character '%s'", name); | |
913 | *font_p = FONT_MUSIC; | |
914 | return( (unsigned char) BAD_CHAR); | |
915 | } | |
916 | #ifdef EXTCHAR | |
917 | \f | |
918 | ||
919 | /* given the name of an extended character set character, | |
920 | * return its code number. | |
921 | * If the name is not a valid name, return BAD_CHAR. | |
922 | * Just do a binary search in the name-to-code translation table. | |
923 | */ | |
924 | ||
925 | static unsigned char | |
926 | ext_name2num(name) | |
927 | ||
928 | char *name; /* name for an extended character set character */ | |
929 | ||
930 | { | |
931 | struct SPECCHAR *info_p;/* translation entry for the given name */ | |
932 | struct SPECCHAR key; /* what to look for */ | |
933 | static unsigned int numch = 0; /* how many items in xlation table */ | |
934 | char shortcut[12]; /* full name of shortcutted character */ | |
935 | ||
936 | ||
937 | /* find size of name-to-code table */ | |
938 | if (numch == 0) { | |
939 | for ( ; Ext_char_table[numch].charname != (char *) 0; | |
940 | numch++) { | |
941 | ; | |
942 | } | |
943 | } | |
944 | ||
945 | key.charname = name; | |
946 | ||
947 | /* allow some shortcuts for common diacritical marks. A letter | |
948 | * followed by one of '`^~:/,vo represents acute, grave, circumflex, | |
949 | * tilde, dieresis, slash, cedilla, caron, and ring. | |
950 | * And as a special case, ss represents germandbls */ | |
951 | if (strlen(name) == 2 && isalpha(name[0])) { | |
952 | switch (name[1]) { | |
953 | case '\'': | |
954 | (void) sprintf(shortcut, "%cacute", name[0]); | |
955 | key.charname = shortcut; | |
956 | break; | |
957 | case '`': | |
958 | (void) sprintf(shortcut, "%cgrave", name[0]); | |
959 | key.charname = shortcut; | |
960 | break; | |
961 | case '^': | |
962 | (void) sprintf(shortcut, "%ccircumflex", name[0]); | |
963 | key.charname = shortcut; | |
964 | break; | |
965 | case '~': | |
966 | (void) sprintf(shortcut, "%ctilde", name[0]); | |
967 | key.charname = shortcut; | |
968 | break; | |
969 | case ':': | |
970 | (void) sprintf(shortcut, "%cdieresis", name[0]); | |
971 | key.charname = shortcut; | |
972 | break; | |
973 | case '/': | |
974 | (void) sprintf(shortcut, "%cslash", name[0]); | |
975 | key.charname = shortcut; | |
976 | break; | |
977 | case ',': | |
978 | (void) sprintf(shortcut, "%ccedilla", name[0]); | |
979 | key.charname = shortcut; | |
980 | break; | |
981 | case 'v': | |
982 | (void) sprintf(shortcut, "%ccaron", name[0]); | |
983 | key.charname = shortcut; | |
984 | break; | |
985 | case 'o': | |
986 | (void) sprintf(shortcut, "%cring", name[0]); | |
987 | key.charname = shortcut; | |
988 | break; | |
989 | case 's': | |
990 | if (name[0] == 's') { | |
991 | (void) sprintf(shortcut, "germandbls"); | |
992 | key.charname = shortcut; | |
993 | } | |
994 | break; | |
995 | default: | |
996 | /* not a special shortcut, leave as is */ | |
997 | break; | |
998 | } | |
999 | } | |
1000 | /* Some more special case shortcuts: `` and '' are shortcuts for | |
1001 | * quotedblleft and quotedblright, and << and >> for guillemots */ | |
1002 | if (strcmp(name, "``") == 0) { | |
1003 | key.charname = "quotedblleft"; | |
1004 | } | |
1005 | else if (strcmp(name, "''") == 0) { | |
1006 | key.charname = "quotedblright"; | |
1007 | } | |
1008 | else if (strcmp(name, "<<") == 0) { | |
1009 | key.charname = "guillemotleft"; | |
1010 | } | |
1011 | else if (strcmp(name, ">>") == 0) { | |
1012 | key.charname = "guillemotright"; | |
1013 | } | |
1014 | ||
1015 | /* do binary search for code */ | |
1016 | if ((info_p = (struct SPECCHAR *) bsearch((char *) &key, Ext_char_table, | |
1017 | numch, sizeof(struct SPECCHAR), sc_compare)) | |
1018 | != (struct SPECCHAR *) 0) { | |
1019 | return( (unsigned char) info_p->code); | |
1020 | } | |
1021 | ||
1022 | else { | |
1023 | /* don't do error message here, because it could just be a | |
1024 | * music character rather than an extended character set char */ | |
1025 | return( (unsigned char) BAD_CHAR); | |
1026 | } | |
1027 | } | |
1028 | #endif | |
1029 | \f | |
1030 | ||
1031 | /* given the C_XXX code value for a music character, return the | |
1032 | * user name for the character. The first time this function gets | |
1033 | * called it sets up a translation array. Then it can just look up | |
1034 | * the name by using the code as an index into the array */ | |
1035 | ||
1036 | char * | |
1037 | mc_num2name(code, font) | |
1038 | ||
1039 | int code; /* the code for the music character */ | |
1040 | int font; /* FONT_MUSIC* */ | |
1041 | ||
1042 | { | |
1043 | static int xlate_tbl[NUM_MFONTS][CHARS_IN_FONT + FIRST_CHAR]; | |
1044 | /* translate music char #define | |
1045 | * values to offset in Mus_char_table | |
1046 | * array */ | |
1047 | int f; /* font index */ | |
1048 | static int called = NO; /* boolean, YES if this function | |
1049 | * has been called before */ | |
1050 | register int numch; /* how many music characters to do */ | |
1051 | ||
1052 | ||
1053 | if (called == NO) { | |
1054 | called = YES; | |
1055 | /* first time. need to build table */ | |
1056 | ||
1057 | /* For each item in the Mus_char_table, fill in the | |
1058 | * element of the xlate_tbl array with its offset, | |
1059 | * or fill in -1 if no valid character with that code. */ | |
1060 | for (f = 0; f < NUM_MFONTS; f++) { | |
1061 | for ( numch = 0; numch < CHARS_IN_FONT + FIRST_CHAR; numch++) { | |
1062 | xlate_tbl[f][numch] = -1; | |
1063 | } | |
1064 | } | |
1065 | for (f = 0; f < NUM_MFONTS; f++) { | |
1066 | for ( numch = 0; numch < CHARS_IN_FONT + FIRST_CHAR; numch++) { | |
1067 | if (Mus_char_table[f][numch].charname != 0) { | |
1068 | xlate_tbl [f] [ Mus_char_table[f][numch].code & 0xff ] = | |
1069 | numch; | |
1070 | } | |
1071 | else { | |
1072 | break; | |
1073 | } | |
1074 | } | |
1075 | } | |
1076 | } | |
1077 | ||
1078 | /* now we just look up the name */ | |
1079 | if ((numch = xlate_tbl[font - FONT_MUSIC][code & 0xff]) < 0) { | |
1080 | pfatal("bad music character [%d][%d] in mc_num2name", | |
1081 | font - FONT_MUSIC, code & 0xff); | |
1082 | } | |
1083 | ||
1084 | return( Mus_char_table[font - FONT_MUSIC][numch].charname ); | |
1085 | } | |
1086 | #ifdef EXTCHAR | |
1087 | \f | |
1088 | ||
1089 | /* given the C_XXX code value for an extended character set char, return the | |
1090 | * user name for the character. The first time this function gets | |
1091 | * called it sets up a translation array. Then it can just look up | |
1092 | * the name by using the code as an index into the array */ | |
1093 | ||
1094 | char * | |
1095 | ext_num2name(code) | |
1096 | ||
1097 | int code; /* the code for the extended character set character */ | |
1098 | ||
1099 | { | |
1100 | static int xlate_tbl[CHARS_IN_FONT + FIRST_CHAR]; | |
1101 | /* translate extended char | |
1102 | * #define values to offset in | |
1103 | * Ext_char_table array */ | |
1104 | static int called = NO; /* boolean, YES if this function | |
1105 | * has been called before */ | |
1106 | register int numch; /* how many extended characters to do */ | |
1107 | ||
1108 | ||
1109 | if (called == NO) { | |
1110 | called = YES; | |
1111 | /* first time. need to build table */ | |
1112 | ||
1113 | /* initialize table to have nothing set */ | |
1114 | for ( numch = 0; numch < CHARS_IN_FONT + FIRST_CHAR; numch++) { | |
1115 | xlate_tbl[numch] = -1; | |
1116 | } | |
1117 | ||
1118 | /* for each item in the Ext_char_table, fill in the | |
1119 | * element of the xlate_tbl array with its offset */ | |
1120 | for (numch = 0; Ext_char_table[numch].charname != (char *) 0; | |
1121 | numch++) { | |
1122 | xlate_tbl [ Ext_char_table[numch].code & 0xff ] = | |
1123 | numch; | |
1124 | } | |
1125 | } | |
1126 | ||
1127 | /* now we just look up the name */ | |
1128 | if ((numch = xlate_tbl[code & 0xff]) < 0) { | |
1129 | pfatal("bad extended character set character (%d) in ext_num2name", code & 0xff); | |
1130 | } | |
1131 | ||
1132 | return( Ext_char_table[numch].charname ); | |
1133 | } | |
1134 | #endif | |
1135 | \f | |
1136 | ||
1137 | /* return YES if string passed in consists solely of a music symbol, otherwise | |
1138 | * return NO */ | |
1139 | ||
1140 | int | |
1141 | is_music_symbol(str) | |
1142 | ||
1143 | char *str; /* which string to check */ | |
1144 | ||
1145 | { | |
1146 | char *string; | |
1147 | int font; | |
1148 | int size; | |
1149 | ||
1150 | ||
1151 | if (str == (char *) 0) { | |
1152 | return(NO); | |
1153 | } | |
1154 | ||
1155 | font = str[0]; | |
1156 | size = str[1]; | |
1157 | string = str + 2; | |
1158 | ||
1159 | /* has to be music char followed by null to be YES */ | |
1160 | if (next_str_char(&string, &font, &size) == '\0') { | |
1161 | return(NO); | |
1162 | } | |
1163 | if ( ! IS_MUSIC_FONT(font)) { | |
1164 | return(NO); | |
1165 | } | |
1166 | if (next_str_char(&string, &font, &size) == '\0') { | |
1167 | return(YES); | |
1168 | } | |
1169 | return(NO); | |
1170 | } | |
1171 | \f | |
1172 | ||
1173 | /* return the ascent of a string in inches. This is the largest ascent of any | |
1174 | * character in the string */ | |
1175 | ||
1176 | double | |
1177 | strascent(str) | |
1178 | ||
1179 | char *str; /* which string to process */ | |
1180 | ||
1181 | { | |
1182 | float max_ascent, a; /* tallest and current ascent */ | |
1183 | char *s; /* to walk through string */ | |
1184 | int font, size, code; | |
1185 | int textfont; | |
1186 | double vertical, horizontal; | |
1187 | float baseline_offset; /* to account for vertical motion */ | |
1188 | int in_pile; | |
1189 | int only_mus_sym; /* YES if string consists solely | |
1190 | * of a music char */ | |
1191 | ||
1192 | ||
1193 | if (str == (char *) 0) { | |
1194 | return(0.0); | |
1195 | } | |
1196 | ||
1197 | only_mus_sym = is_music_symbol(str); | |
1198 | ||
1199 | /* first 2 bytes are font and size. */ | |
1200 | font = str[0]; | |
1201 | size = str[1]; | |
1202 | ||
1203 | /* Walk through the string. */ | |
1204 | for (max_ascent = 0.0, baseline_offset = 0.0, s = str + 2; | |
1205 | (code = nxt_str_char(&s, &font, &size, &textfont, | |
1206 | &vertical, &horizontal, &in_pile, NO)) > 0; ) { | |
1207 | ||
1208 | /* A newline goes to following line, so we probably won't | |
1209 | * get any higher ascent than we have so far, but if | |
1210 | * user gives enough vertical motion, we might, so continue. */ | |
1211 | if (code == '\n') { | |
1212 | baseline_offset -= fontheight(font, size); | |
1213 | } | |
1214 | ||
1215 | /* adjust for any vertical motion */ | |
1216 | if (vertical != 0.0) { | |
1217 | baseline_offset += vertical; | |
1218 | } | |
1219 | ||
1220 | /* music characters inside strings get moved up to the baseline, | |
1221 | * so use their height as ascent. | |
1222 | * Regular characters use the | |
1223 | * ascent of the character */ | |
1224 | if ((IS_MUSIC_FONT(font)) && (only_mus_sym == NO)) { | |
1225 | a = height(font, size, code); | |
1226 | } | |
1227 | else { | |
1228 | a = ascent(font, size, code); | |
1229 | } | |
1230 | a += baseline_offset; | |
1231 | ||
1232 | /* if tallest seen save this height */ | |
1233 | if (a > max_ascent) { | |
1234 | max_ascent = a; | |
1235 | } | |
1236 | } | |
1237 | ||
1238 | /* if boxed, allow space for that */ | |
1239 | if (IS_BOXED(str) == YES) { | |
1240 | max_ascent += 2.5 * STDPAD; | |
1241 | } | |
1242 | /* similarly, allow space for circle */ | |
1243 | if (IS_CIRCLED(str) == YES) { | |
1244 | float ascent_adjust; | |
1245 | max_ascent += circled_dimensions(str, (float *) 0, (float *) 0, | |
1246 | &ascent_adjust, (float *) 0); | |
1247 | max_ascent += ascent_adjust; | |
1248 | } | |
1249 | return(max_ascent); | |
1250 | } | |
1251 | \f | |
1252 | ||
1253 | /* return the descent of a string in inches. This is the largest descent of any | |
1254 | * character in the string */ | |
1255 | ||
1256 | double | |
1257 | strdescent(str) | |
1258 | ||
1259 | char *str; /* which string to process */ | |
1260 | ||
1261 | { | |
1262 | float max_descent, d; /* largest and current descent */ | |
1263 | float line_descent; /* descent caused by newlines */ | |
1264 | double vertical, horizontal; | |
1265 | int in_pile; | |
1266 | char *s; /* to walk through string */ | |
1267 | int font, size, code; | |
1268 | int textfont; | |
1269 | int only_mus_sym; /* YES if string consists solely | |
1270 | * of a music char */ | |
1271 | ||
1272 | ||
1273 | if (str == (char *) 0) { | |
1274 | return(0.0); | |
1275 | } | |
1276 | ||
1277 | only_mus_sym = is_music_symbol(str); | |
1278 | ||
1279 | /* first 2 bytes are font and size. */ | |
1280 | font = str[0]; | |
1281 | size = str[1]; | |
1282 | ||
1283 | /* walk through the string. */ | |
1284 | for (max_descent = line_descent = 0.0, s = str + 2; | |
1285 | (code = nxt_str_char(&s, &font, &size, &textfont, | |
1286 | &vertical, &horizontal, &in_pile, NO)) > 0 | |
1287 | || vertical != 0.0; ) { | |
1288 | ||
1289 | /* Adjust for vertical motion. Since line_descent is | |
1290 | * measured downward and vertical is upward, have to | |
1291 | * substract the vertical, then adjust max_descent | |
1292 | * to compensate. */ | |
1293 | if (vertical != 0.0) { | |
1294 | line_descent -= vertical; | |
1295 | max_descent += vertical; | |
1296 | if (code == 0) { | |
1297 | /* motion only */ | |
1298 | continue; | |
1299 | } | |
1300 | } | |
1301 | ||
1302 | if (code == '\n') { | |
1303 | /* at newline, descent goes down to next baseline, | |
1304 | * which will be down from current baseline | |
1305 | * by height of font */ | |
1306 | line_descent += fontheight(font, size); | |
1307 | max_descent = 0.0; | |
1308 | continue; | |
1309 | } | |
1310 | ||
1311 | /* music characters inside strings get moved up to the | |
1312 | * baseline, so have no descent. */ | |
1313 | if ( ! (IS_MUSIC_FONT(font)) || (only_mus_sym == YES)) { | |
1314 | d = descent(font, size, code); | |
1315 | } | |
1316 | else { | |
1317 | d = 0.0; | |
1318 | } | |
1319 | ||
1320 | /* if largest descent seen, save this descent */ | |
1321 | if (d > max_descent) { | |
1322 | max_descent = d; | |
1323 | } | |
1324 | } | |
1325 | ||
1326 | /* if boxed, allow space for that */ | |
1327 | if (IS_BOXED(str) == YES) { | |
1328 | max_descent += 3.5 * STDPAD; | |
1329 | } | |
1330 | /* similarly, allow space for circle */ | |
1331 | if (IS_CIRCLED(str) == YES) { | |
1332 | max_descent += circled_dimensions(str, (float *) 0, (float *) 0, | |
1333 | (float *) 0, (float *) 0); | |
1334 | } | |
1335 | return(max_descent + line_descent); | |
1336 | } | |
1337 | \f | |
1338 | ||
1339 | /* return the height of a string in inches. This is the maximum ascent plus the | |
1340 | * maximum descent */ | |
1341 | ||
1342 | double | |
1343 | strheight(str) | |
1344 | ||
1345 | char *str; /* which string to process */ | |
1346 | { | |
1347 | /* Since letters may not | |
1348 | * align because of ascent/descent, we get the tallest extent | |
1349 | * by adding the largest ascent to the largest descent */ | |
1350 | return( strascent(str) + strdescent(str)); | |
1351 | } | |
1352 | \f | |
1353 | ||
1354 | /* return the width of a string. This is the sum of the widths of the | |
1355 | * individual characters in the string */ | |
1356 | ||
1357 | double | |
1358 | strwidth(str) | |
1359 | char *str; | |
1360 | { | |
1361 | float tot_width; | |
1362 | float widest_line; /* for multi-line strings */ | |
1363 | float curr_width; | |
1364 | double horizontal, vertical; | |
1365 | int was_in_pile; /* if in pile last time through loop */ | |
1366 | int in_pile_now; /* if current character is inside a pile */ | |
1367 | char *s; /* to walk through string */ | |
1368 | int font, size, code; | |
1369 | int textfont; | |
1370 | ||
1371 | ||
1372 | if (str == (char *) 0) { | |
1373 | return(0.0); | |
1374 | } | |
1375 | ||
1376 | /* first 2 bytes are font and size. */ | |
1377 | font = str[0]; | |
1378 | size = str[1]; | |
1379 | ||
1380 | /* walk through string */ | |
1381 | was_in_pile = NO; | |
1382 | for (curr_width = tot_width = widest_line = 0.0, s = str + 2; | |
1383 | (code = nxt_str_char(&s, &font, &size, &textfont, | |
1384 | &vertical, &horizontal, &in_pile_now, NO)) > 0; | |
1385 | was_in_pile = in_pile_now) { | |
1386 | ||
1387 | /* Piles are handled specially. As soon as we enter a pile, | |
1388 | * we call the function to get its entire width. Then for | |
1389 | * the rest of the pile, we just skip past everything */ | |
1390 | if (in_pile_now == YES) { | |
1391 | if (was_in_pile == NO) { | |
1392 | curr_width += pile_width(); | |
1393 | if (curr_width > tot_width) { | |
1394 | tot_width = curr_width; | |
1395 | } | |
1396 | } | |
1397 | continue; | |
1398 | } | |
1399 | ||
1400 | /* the horizontal movement coming out of a pile doesn't count, | |
1401 | * since it was included in the pile, otherwise it does */ | |
1402 | if (was_in_pile == NO) { | |
1403 | curr_width += horizontal; | |
1404 | } | |
1405 | if (curr_width > tot_width) { | |
1406 | tot_width = curr_width; | |
1407 | } | |
1408 | ||
1409 | if (code == '\n') { | |
1410 | /* keep track of width line of multi-line string */ | |
1411 | if (tot_width > widest_line) { | |
1412 | widest_line = tot_width; | |
1413 | } | |
1414 | tot_width = 0.0; | |
1415 | curr_width = 0.0; | |
1416 | continue; | |
1417 | } | |
1418 | ||
1419 | if (code == '\b') { | |
1420 | /* backspace */ | |
1421 | tot_width -= backsp_width(size); | |
1422 | curr_width -= backsp_width(size); | |
1423 | continue; | |
1424 | } | |
1425 | ||
1426 | /* If we have the special "page number" character, | |
1427 | * or special "total number of pages" character, | |
1428 | * we deal with that here. */ | |
1429 | if ( (code == '%' || code == '#') && (s > str + 3) | |
1430 | && ( (*(s-2) & 0xff) == STR_PAGENUM | |
1431 | || (*(s-2) & 0xff) == STR_NUMPAGES) ) { | |
1432 | ||
1433 | char pgnumbuff[8], *pgnum_p; | |
1434 | ||
1435 | /* convert page number to a string and | |
1436 | * add the width of each character in | |
1437 | * that string. */ | |
1438 | (void) sprintf(pgnumbuff, "%d", | |
1439 | code == '%' ? Pagenum : Last_pagenum); | |
1440 | ||
1441 | for ( pgnum_p = pgnumbuff; *pgnum_p != '\0'; | |
1442 | pgnum_p++) { | |
1443 | curr_width += width(font, size, *pgnum_p); | |
1444 | } | |
1445 | } | |
1446 | ||
1447 | else { | |
1448 | /* Oh good. This is a normal case. Just add | |
1449 | * width of this character to width so far */ | |
1450 | curr_width += width(font, size, code); | |
1451 | } | |
1452 | ||
1453 | if (curr_width > tot_width) { | |
1454 | tot_width = curr_width; | |
1455 | } | |
1456 | } | |
1457 | if (tot_width < widest_line) { | |
1458 | tot_width = widest_line; | |
1459 | } | |
1460 | /* if string is boxed, allow space for the box */ | |
1461 | if (IS_BOXED(str) == YES) { | |
1462 | tot_width += 6.0 * STDPAD; | |
1463 | } | |
1464 | /* similarly, allow space for circled */ | |
1465 | if (IS_CIRCLED(str) == YES) { | |
1466 | (void) circled_dimensions(str, (float *) 0, &tot_width, | |
1467 | (float *) 0, (float *) 0); | |
1468 | } | |
1469 | return(tot_width); | |
1470 | } | |
1471 | \f | |
1472 | ||
1473 | /* Return the width to the "anchor" point of a string. For most strings, | |
1474 | * this will be half the width of the first character. But for a string | |
1475 | * that begins with things piled atop one another, it is the alignment point. | |
1476 | * And for boxed or circled strings, the box or circle must be considered. | |
1477 | */ | |
1478 | ||
1479 | double | |
1480 | left_width(string) | |
1481 | ||
1482 | char *string; | |
1483 | ||
1484 | { | |
1485 | int font; | |
1486 | int size; | |
1487 | char *pile_start_p; /* where pile begins, if any */ | |
1488 | ||
1489 | if (starts_piled(string, &font, &size, &pile_start_p) == YES) { | |
1490 | return(align_distance(pile_start_p, font, size)); | |
1491 | } | |
1492 | else { | |
1493 | int ch; | |
1494 | float extra; /* space for box or circle, if any */ | |
1495 | ||
1496 | /* For boxed or circled strings, | |
1497 | * the space for the box or circle must be added in */ | |
1498 | if (IS_BOXED(string) == YES) { | |
1499 | extra = 3.5 * STDPAD; | |
1500 | } | |
1501 | else if (IS_CIRCLED(string) == YES) { | |
1502 | (void) circled_dimensions(string, (float *) 0, | |
1503 | (float *) 0, (float *) 0, &extra); | |
1504 | } | |
1505 | else { | |
1506 | extra = 0.0; | |
1507 | } | |
1508 | ||
1509 | /* Get half the width of the first character in the string */ | |
1510 | font = *string++; | |
1511 | size = *string++; | |
1512 | ch = next_str_char(&string, &font, &size); | |
1513 | return(width(font, size, ch) / 2.0 + extra); | |
1514 | } | |
1515 | } | |
1516 | \f | |
1517 | ||
1518 | /* If string begins with piled text, return YES, otherwise NO, | |
1519 | * If YES, also return via pointers the start of the pile and the | |
1520 | * font and size at that point. */ | |
1521 | ||
1522 | static int | |
1523 | starts_piled(string, font_p, size_p, pile_start_p_p) | |
1524 | ||
1525 | char *string; | |
1526 | int *font_p; | |
1527 | int *size_p; | |
1528 | char **pile_start_p_p; | |
1529 | ||
1530 | { | |
1531 | *font_p = *string++; | |
1532 | *size_p = *string++; | |
1533 | ||
1534 | /* walk through string, skipping any leading box/size/font */ | |
1535 | for ( ; *string != '\0'; string++) { | |
1536 | if (IS_STR_COMMAND(*string)) { | |
1537 | switch(*string & 0xff) { | |
1538 | ||
1539 | case STR_FONT: | |
1540 | *font_p = *(++string); | |
1541 | break; | |
1542 | ||
1543 | case STR_SIZE: | |
1544 | *size_p = *(++string); | |
1545 | break; | |
1546 | ||
1547 | case STR_BOX: | |
1548 | case STR_CIR: | |
1549 | break; | |
1550 | ||
1551 | case STR_PILE: | |
1552 | /* The first thing we found that was not to be | |
1553 | * ignored is the beginning of a pile */ | |
1554 | *pile_start_p_p = string; | |
1555 | return(YES); | |
1556 | ||
1557 | default: | |
1558 | return(NO); | |
1559 | } | |
1560 | } | |
1561 | else { | |
1562 | break; | |
1563 | } | |
1564 | } | |
1565 | return(NO); | |
1566 | } | |
1567 | \f | |
1568 | ||
1569 | /* given a string representing a chord mark, transpose it. For each letter | |
1570 | * 'A' to 'G' optionally followed by an accidental, call function to | |
1571 | * get transposed value. Build new string with transposed values. Free up | |
1572 | * the old string and return the new one. Also, if the accidental was | |
1573 | * of the form &, #, x, or && instead of \(smflat) etc, change to proper | |
1574 | * music symbol. Also handles translation of o, o/ and ^ to dim, halfdim, | |
1575 | * and triangle symbols, and does translation of unescaped accidentals. */ | |
1576 | ||
1577 | char * | |
1578 | tranchstr(chordstring, staffno) | |
1579 | ||
1580 | char *chordstring; /* untransposed string */ | |
1581 | int staffno; /* which staff it is associated with */ | |
1582 | /* A staffno of -1 means no transpose, just translate */ | |
1583 | ||
1584 | { | |
1585 | char tmpbuff[128]; /* temporary copy of transposed string */ | |
1586 | char replacement[4]; /* for dim/halfdim/triangle */ | |
1587 | short i; /* index into tmpbuff */ | |
1588 | unsigned char *str; /* walk through chordstring */ | |
1589 | char *transposed; /* new version of letter[accidental] */ | |
1590 | char tranbuff[4]; /* to point 'transposed' at if not really | |
1591 | * transposing */ | |
1592 | char letter; /* A to G */ | |
1593 | char accidental; | |
1594 | int escaped; /* YES is accidental was escaped */ | |
1595 | char literal_accidental; /* what would normally be translated */ | |
1596 | int nprocessed; /* how many character processed by subroutine */ | |
1597 | char *newstring; /* final copy of transposed string */ | |
1598 | int n; | |
1599 | int size; | |
1600 | int in_pile; /* YES if inside a pile */ | |
1601 | int acc_size; /* size for accidentals */ | |
1602 | ||
1603 | ||
1604 | /* get font/size info */ | |
1605 | tmpbuff[0] = chordstring[0]; | |
1606 | tmpbuff[1] = chordstring[1]; | |
1607 | size = chordstring[1]; | |
1608 | in_pile = NO; | |
1609 | str = (unsigned char *) (chordstring + 2); | |
1610 | literal_accidental = '\0'; /* avoids bogus "used before set" warning */ | |
1611 | ||
1612 | /* walk through original string */ | |
1613 | for (i = 2; *str != '\0'; str++) { | |
1614 | ||
1615 | /* Be safe. Bail out a little before we reach end, | |
1616 | * because some things take several bytes, | |
1617 | * and it's easiest to just check once per loop. */ | |
1618 | if (i > sizeof(tmpbuff) - 8) { | |
1619 | ufatal("chord string too long: '%s'", chordstring + 2); | |
1620 | } | |
1621 | ||
1622 | acc_size = accsize(size); | |
1623 | ||
1624 | /* If a STR_*, deal with that */ | |
1625 | if ((n = str_cmd((char *) str, &size, &in_pile)) > 0) { | |
1626 | strncpy(tmpbuff + i, (char *) str, (unsigned) n); | |
1627 | i += n; | |
1628 | str += n - 1; | |
1629 | } | |
1630 | ||
1631 | /* handle backslashed o and ^ */ | |
1632 | else if (*str == '\\' && ( *(str+1) == 'o' || *(str+1) == '^' ) ) { | |
1633 | str++; | |
1634 | tmpbuff[i++] = *str; | |
1635 | } | |
1636 | ||
1637 | else if (*str >= 'A' && *str <= 'G') { | |
1638 | ||
1639 | /* Aha! Something to transpose. */ | |
1640 | letter = *str; | |
1641 | ||
1642 | str += get_accidental( (unsigned char *) (str + 1), | |
1643 | &accidental, &acc_size, NO, &escaped); | |
1644 | if (escaped == YES) { | |
1645 | /* not *really* an accidental, so save to | |
1646 | * print later. */ | |
1647 | literal_accidental = accidental; | |
1648 | accidental = '\0'; | |
1649 | } | |
1650 | if (staffno == -1) { | |
1651 | /* not to be transposed, so make a string | |
1652 | * that would be like what tranchnote() would | |
1653 | * return, but with no transposition. */ | |
1654 | tranbuff[0] = letter; | |
1655 | tranbuff[1] = accidental; | |
1656 | tranbuff[2] = '\0'; | |
1657 | transposed = tranbuff; | |
1658 | } | |
1659 | else { | |
1660 | /* get the transposed value */ | |
1661 | transposed = tranchnote(letter, accidental, staffno); | |
1662 | } | |
1663 | ||
1664 | /* put transposed letter into output */ | |
1665 | tmpbuff[i++] = *transposed; | |
1666 | ||
1667 | /* now add accidental if any */ | |
1668 | i += add_accidental(tmpbuff + i, (int) *++transposed, | |
1669 | acc_size, NO); | |
1670 | ||
1671 | /* add on any escaped pseudo-accidental */ | |
1672 | if (escaped == YES) { | |
1673 | i += add_accidental(tmpbuff + i, | |
1674 | (int) literal_accidental, | |
1675 | acc_size, YES); | |
1676 | escaped = NO; | |
1677 | } | |
1678 | ||
1679 | /* handle dim/halfdim/triangle transformations */ | |
1680 | if ((n = dim_tri(str + 1, replacement, size, YES)) > 0) { | |
1681 | strcpy(tmpbuff + i, replacement); | |
1682 | i += strlen(replacement); | |
1683 | str += n; | |
1684 | } | |
1685 | } | |
1686 | else { | |
1687 | /* Originally we only translated things like # and & | |
1688 | * in chords to musical accidental symbols if they | |
1689 | * immediately followed a letter A-G. But due to | |
1690 | * popular demand, they are now translated everywhere, | |
1691 | * unless escaped. */ | |
1692 | nprocessed = get_accidental( (unsigned char *) str, | |
1693 | &accidental, &acc_size, NO, &escaped); | |
1694 | if (nprocessed > 0) { | |
1695 | i += add_accidental(tmpbuff + i, | |
1696 | (int) accidental, | |
1697 | acc_size, escaped); | |
1698 | /* the -1 is because str will get incremented | |
1699 | * at the top of the 'for' */ | |
1700 | str += nprocessed - 1; | |
1701 | } | |
1702 | else { | |
1703 | /* something boring. Just copy */ | |
1704 | tmpbuff[i++] = *str; | |
1705 | } | |
1706 | } | |
1707 | } | |
1708 | ||
1709 | /* need to make permanent copy of new string */ | |
1710 | tmpbuff[i++] = '\0'; | |
1711 | MALLOCA(char, newstring, i + 1); | |
1712 | (void) memcpy(newstring, tmpbuff, (unsigned) i); | |
1713 | ||
1714 | /* free original version */ | |
1715 | FREE(chordstring); | |
1716 | ||
1717 | /* return new, transposed version */ | |
1718 | return(newstring); | |
1719 | } | |
1720 | \f | |
1721 | ||
1722 | /* If there is a STR_* command in chord/analysis/figbass, return how | |
1723 | * many characters long it is. Also update the size if the | |
1724 | * command was one to change size, and update pile status if necessary. */ | |
1725 | ||
1726 | static int | |
1727 | str_cmd(str, size_p, in_pile_p) | |
1728 | ||
1729 | char *str; /* check string starting here */ | |
1730 | int *size_p; | |
1731 | int *in_pile_p; /* YES if in pile, may be updated */ | |
1732 | ||
1733 | { | |
1734 | if (IS_STR_COMMAND(*str)) { | |
1735 | switch(*str & 0xff) { | |
1736 | ||
1737 | case STR_SIZE: | |
1738 | /* update size */ | |
1739 | *size_p = *(str + 1); | |
1740 | /* command plus 1 argument byte */ | |
1741 | return(2); | |
1742 | ||
1743 | case STR_PAGENUM: | |
1744 | case STR_NUMPAGES: | |
1745 | case STR_FONT: | |
1746 | case STR_BACKSPACE: | |
1747 | case STR_VERTICAL: | |
1748 | /* command plus 1 argument byte */ | |
1749 | return(2); | |
1750 | ||
1751 | case STR_MUS_CHAR: | |
1752 | /* command plus 2 argument bytes */ | |
1753 | return(3); | |
1754 | ||
1755 | case STR_PILE: | |
1756 | /* entering/leaving a pile alters the size */ | |
1757 | *size_p = pile_size(*size_p, *in_pile_p); | |
1758 | *in_pile_p = (*in_pile_p ? NO : YES); | |
1759 | break; | |
1760 | ||
1761 | default: | |
1762 | /* others have no argument bytes */ | |
1763 | return(1); | |
1764 | } | |
1765 | } | |
1766 | return(0); | |
1767 | } | |
1768 | \f | |
1769 | ||
1770 | /* Check the first character of the given string to see if it is an accidental | |
1771 | * or something that should be translated to an accidental (# & x && and | |
1772 | * maybe n). If so, fill in the accidental. If the accidental was specified | |
1773 | * via a STR_MUS_CHAR, also update the accidental size. | |
1774 | * If no accidental, accidental_p will will filled in | |
1775 | * with '\0'. In any case return how many bytes were processed. | |
1776 | */ | |
1777 | ||
1778 | static int | |
1779 | get_accidental(string, accidental_p, acc_size_p, trans_natural, escaped_p) | |
1780 | ||
1781 | unsigned char *string; /* check this for an accidental */ | |
1782 | char *accidental_p; /* return the accidental here, or \0 if none */ | |
1783 | int *acc_size_p; /* return the accidental size here */ | |
1784 | int trans_natural; /* if YES, translate n to natural, else leave as n */ | |
1785 | int *escaped_p; /* Return value: YES if the symbol was backslashed */ | |
1786 | ||
1787 | { | |
1788 | unsigned char *str_p; | |
1789 | ||
1790 | str_p = string; | |
1791 | ||
1792 | /* assume no accidental */ | |
1793 | *accidental_p = '\0'; | |
1794 | ||
1795 | /* check if escaped */ | |
1796 | if (*str_p == '\\') { | |
1797 | *escaped_p = YES; | |
1798 | str_p++; | |
1799 | } | |
1800 | else { | |
1801 | *escaped_p = NO; | |
1802 | } | |
1803 | ||
1804 | /* See if the following character is an accidental */ | |
1805 | switch (*str_p) { | |
1806 | ||
1807 | case '#': | |
1808 | case 'x': | |
1809 | *accidental_p = *str_p++; | |
1810 | break; | |
1811 | case '&': | |
1812 | *accidental_p = *str_p++; | |
1813 | /* have to peek ahead to check for double flat, | |
1814 | * but not if escaped, so person can get a literal | |
1815 | * ampersand followed by a flat. */ | |
1816 | if (*escaped_p == NO && *str_p == '&') { | |
1817 | /* double flat is 'B' internally */ | |
1818 | *accidental_p = 'B'; | |
1819 | str_p++; | |
1820 | } | |
1821 | break; | |
1822 | ||
1823 | case 'n': | |
1824 | /* naturals are not translated in chords, but are | |
1825 | * in analysis and figbass */ | |
1826 | if (trans_natural == YES) { | |
1827 | *accidental_p = *str_p++; | |
1828 | } | |
1829 | break; | |
1830 | ||
1831 | case STR_MUS_CHAR: | |
1832 | if (*escaped_p == YES) { | |
1833 | break; | |
1834 | } | |
1835 | /* Check if user put in \(flat) or something | |
1836 | * similar. If so, use that. */ | |
1837 | switch (*(str_p + 2)) { | |
1838 | case C_FLAT: | |
1839 | *acc_size_p = *(str_p + 1); | |
1840 | *accidental_p = '&'; | |
1841 | str_p += 3; | |
1842 | break; | |
1843 | ||
1844 | case C_SHARP: | |
1845 | *acc_size_p = *(str_p + 1); | |
1846 | *accidental_p = '#'; | |
1847 | str_p += 3; | |
1848 | break; | |
1849 | ||
1850 | case C_DBLFLAT: | |
1851 | *acc_size_p = *(str_p + 1); | |
1852 | *accidental_p = 'B'; | |
1853 | str_p += 3; | |
1854 | break; | |
1855 | ||
1856 | case C_DBLSHARP: | |
1857 | *acc_size_p = *(str_p + 1); | |
1858 | *accidental_p = 'x'; | |
1859 | str_p += 3; | |
1860 | break; | |
1861 | ||
1862 | case C_NAT: | |
1863 | /* Always translate the natural symbol, | |
1864 | * even when trans_natural is NO. That really | |
1865 | * applies just to the use of 'n' which is | |
1866 | * likely to be wanted as a real n, whereas | |
1867 | * a music symbol natural is unambiguous. */ | |
1868 | *acc_size_p = *(str_p + 1); | |
1869 | *accidental_p = 'n'; | |
1870 | str_p += 3; | |
1871 | break; | |
1872 | ||
1873 | default: | |
1874 | /* false alarm. Some other | |
1875 | * music character. */ | |
1876 | break; | |
1877 | } | |
1878 | break; | |
1879 | ||
1880 | default: | |
1881 | /* nothing special */ | |
1882 | break; | |
1883 | } | |
1884 | ||
1885 | /* If all we saw was a backslash, | |
1886 | * then there wasn't really an accidental */ | |
1887 | if (*escaped_p == YES && str_p == string + 1) { | |
1888 | *escaped_p = NO; | |
1889 | str_p = string; | |
1890 | } | |
1891 | ||
1892 | return(str_p - string); | |
1893 | } | |
1894 | \f | |
1895 | ||
1896 | /* Write the given accidental in the given size to the given string. | |
1897 | * Return how many bytes were added. */ | |
1898 | ||
1899 | static int | |
1900 | add_accidental(buff, acc_character, acc_size, escaped) | |
1901 | ||
1902 | char *buff; /* write into this buffer */ | |
1903 | int acc_character; /* write this accidental */ | |
1904 | int acc_size; /* make accidental this big */ | |
1905 | int escaped; /* if YES, was escaped, so not really an accidental; | |
1906 | * print it as a normal character */ | |
1907 | ||
1908 | { | |
1909 | if (acc_character != '\0') { | |
1910 | ||
1911 | /* if escaped, just treat like normal character. */ | |
1912 | if (escaped == YES) { | |
1913 | buff[0] = acc_character; | |
1914 | return(1); | |
1915 | } | |
1916 | ||
1917 | /* sharps and naturals are tall enough that they can | |
1918 | * make things not line up, so move them down some */ | |
1919 | if (acc_character == '#' || acc_character == 'n') { | |
1920 | buff[0] = (char) STR_VERTICAL; | |
1921 | buff[1] = (char) ENCODE_VERT(-4); | |
1922 | buff += 2; | |
1923 | } | |
1924 | /* has accidental. Add STR_MUS_CHAR-size-code */ | |
1925 | buff[0] = (char) STR_MUS_CHAR; | |
1926 | ||
1927 | /* double sharp is special. It is too small, | |
1928 | * so make it bigger */ | |
1929 | if (acc_character == 'x') { | |
1930 | acc_size = (int) ( (float) acc_size | |
1931 | * 1.25); | |
1932 | } | |
1933 | buff[1] = (char) acc_size; | |
1934 | ||
1935 | /* use accidental of appropriate type */ | |
1936 | switch (acc_character) { | |
1937 | ||
1938 | case '#': | |
1939 | buff[2] = C_SHARP; | |
1940 | break; | |
1941 | ||
1942 | case '&': | |
1943 | buff[2] = C_FLAT; | |
1944 | break; | |
1945 | ||
1946 | case 'x': | |
1947 | buff[2] = C_DBLSHARP; | |
1948 | break; | |
1949 | ||
1950 | case 'B': | |
1951 | buff[2] = C_DBLFLAT; | |
1952 | break; | |
1953 | ||
1954 | case 'n': | |
1955 | buff[2] = C_NAT; | |
1956 | break; | |
1957 | ||
1958 | default: | |
1959 | pfatal("illegal accidental on transposed chord"); | |
1960 | break; | |
1961 | } | |
1962 | if (acc_character == '#' || acc_character == 'n') { | |
1963 | buff[3] = (char) STR_VERTICAL; | |
1964 | buff[4] = (char) ENCODE_VERT(4); | |
1965 | /* We added 3 bytes for the accidental, plus | |
1966 | * 2 bytes before and after for vertical motion. */ | |
1967 | return(7); | |
1968 | } | |
1969 | else { | |
1970 | return(3); /* we added 3 bytes */ | |
1971 | } | |
1972 | } | |
1973 | ||
1974 | return (0); | |
1975 | } | |
1976 | \f | |
1977 | ||
1978 | /* In chords and such, "o" becomes \(dim), "o/" becomes \(halfdim) | |
1979 | * unless followed by [A-G] in which case it becomes "\(dim)/", | |
1980 | * and "^" becomes \(triangle). Return number of characters processed. | |
1981 | */ | |
1982 | ||
1983 | static int | |
1984 | dim_tri(str_p, replacement, size, is_chord) | |
1985 | ||
1986 | unsigned char *str_p; /* check string at this point */ | |
1987 | char *replacement; /* return the replacement in this buffer, | |
1988 | * which needs to be at least 4 bytes long */ | |
1989 | int size; /* use this size for music character */ | |
1990 | int is_chord; /* YES for chord, NO for analysis/figbass */ | |
1991 | ||
1992 | { | |
1993 | if (*str_p == '^') { | |
1994 | replacement[0] = (char) STR_MUS_CHAR; | |
1995 | replacement[1] = size; | |
1996 | replacement[2] = C_TRIANGLE; | |
1997 | replacement[3] = '\0'; | |
1998 | return(1); | |
1999 | } | |
2000 | else if (*str_p == 'o') { | |
2001 | replacement[0] = (char) STR_MUS_CHAR; | |
2002 | replacement[1] = size; | |
2003 | replacement[3] = '\0'; | |
2004 | if ( *(str_p+1) == '/' && (is_chord == NO || | |
2005 | (*(str_p+2) < 'A' || *(str_p+2) > 'G'))) { | |
2006 | replacement[2] = C_HALFDIM; | |
2007 | return(2); | |
2008 | } | |
2009 | else { | |
2010 | replacement[2] = C_DIM; | |
2011 | return(1); | |
2012 | } | |
2013 | } | |
2014 | return(0); | |
2015 | } | |
2016 | \f | |
2017 | ||
2018 | /* Given a string for analysis or figbass, transform the accidentals | |
2019 | * & # && x n to their music characters. | |
2020 | */ | |
2021 | ||
2022 | char * | |
2023 | acc_trans(string) | |
2024 | ||
2025 | char *string; | |
2026 | ||
2027 | { | |
2028 | char buffer[128]; /* output buffer for transformed string */ | |
2029 | char *out_p; /* current location in output buffer */ | |
2030 | char replacement[4]; /* space for dim, halfdim, etc */ | |
2031 | int n; | |
2032 | int size, acc_size; | |
2033 | char accidental; /* #, &, x, etc */ | |
2034 | int escaped; /* YES is accidental was escaped */ | |
2035 | int in_pile; /* YES if inside a pile */ | |
2036 | ||
2037 | ||
2038 | buffer[0] = string[0]; | |
2039 | buffer[1] = string[1]; | |
2040 | size = string[1]; | |
2041 | in_pile = NO; | |
2042 | ||
2043 | /* walk through string, transforming any accidentals along the way */ | |
2044 | for ( string += 2, out_p = buffer + 2; *string != '\0'; ) { | |
2045 | /* Be safe. Bail out a little before we reach end, | |
2046 | * because some things take several bytes, | |
2047 | * and it's easiest to just check once per loop. */ | |
2048 | if (out_p - buffer > sizeof(buffer) - 8) { | |
2049 | l_ufatal(Curr_filename, yylineno, | |
2050 | "analysis or figbass string too long"); | |
2051 | } | |
2052 | ||
2053 | acc_size = accsize(size); | |
2054 | if ((n = get_accidental((unsigned char *) string, | |
2055 | &accidental, &acc_size, YES, &escaped)) > 0 ) { | |
2056 | out_p += add_accidental(out_p, (int) accidental, | |
2057 | acc_size, escaped); | |
2058 | string += n; | |
2059 | } | |
2060 | else if (*string == '\\' && ( *(string+1) == 'o' || *(string+1) == '^') ) { | |
2061 | *out_p++ = *++string; | |
2062 | string++; | |
2063 | } | |
2064 | else if ((n = dim_tri((unsigned char *) string, replacement, | |
2065 | size, NO)) > 0) { | |
2066 | strcpy(out_p, replacement); | |
2067 | out_p += strlen(replacement); | |
2068 | string += n; | |
2069 | } | |
2070 | else if ((n = str_cmd(string, &size, &in_pile)) > 0) { | |
2071 | strncpy(out_p, string, (unsigned) n); | |
2072 | out_p += n; | |
2073 | string += n; | |
2074 | } | |
2075 | else { | |
2076 | *out_p++ = *string++; | |
2077 | } | |
2078 | } | |
2079 | *out_p = '\0'; | |
2080 | ||
2081 | return(copy_string(buffer + 2, buffer[0], buffer[1])); | |
2082 | } | |
2083 | \f | |
2084 | /* Given a chord, analysis or figbass string, | |
2085 | * transform according to their special rules: | |
2086 | * - : gets translated to \: and vice-versa | |
2087 | * - figbass starts in piled mode | |
2088 | * - in figbass, a / gets translated to \/ and vice-versa | |
2089 | * This string will be in half transformed state: the first 2 bytes | |
2090 | * are font/size, but the rest is still all ASCII, not internal format. | |
2091 | */ | |
2092 | ||
2093 | char * | |
2094 | modify_chstr(string, modifier) | |
2095 | ||
2096 | char *string; | |
2097 | int modifier; | |
2098 | ||
2099 | { | |
2100 | int length; /* of modified string */ | |
2101 | char *s; /* walk through string */ | |
2102 | char *newstring; | |
2103 | char *new_p; /* walk through newstring */ | |
2104 | int need_new; /* if we need to make a new string */ | |
2105 | ||
2106 | ||
2107 | length = strlen(string); | |
2108 | if (modifier == TM_FIGBASS) { | |
2109 | /* We'll need two extra bytes for | |
2110 | * the leading \: for pile mode. */ | |
2111 | length += 2; | |
2112 | need_new = YES; | |
2113 | } | |
2114 | else { | |
2115 | /* Only need a new string if the original has colons, | |
2116 | * so assume for now we won't need a new string */ | |
2117 | need_new = NO; | |
2118 | } | |
2119 | ||
2120 | /* Figure out how much space we'll need for the modified string. | |
2121 | * Any unbackslashed colons will take up an extra byte once | |
2122 | * we backslash it. But any backslashed one will take up one | |
2123 | * less when we unescape it. Similar for slashes in figbass. */ | |
2124 | for (s = string + 2; *s != '\0'; s++) { | |
2125 | if (*s == ':') { | |
2126 | length++; | |
2127 | need_new = YES; | |
2128 | } | |
2129 | else if (modifier == TM_FIGBASS && *s == '/') { | |
2130 | /* o/ means half diminished so that doesn't count */ | |
2131 | if (s > string + 2 && *(s-1) == 'o') { | |
2132 | continue; | |
2133 | } | |
2134 | length++; | |
2135 | need_new = YES; | |
2136 | } | |
2137 | else if (*s == '\\') { | |
2138 | s++; | |
2139 | /* things that occur inside \(...) don't count */ | |
2140 | if (*s == '(') { | |
2141 | for (s++; *s != '\0' && *s != ')'; s++) { | |
2142 | ; | |
2143 | } | |
2144 | /* If no closing parenthesis, return as is; | |
2145 | * later code will catch that */ | |
2146 | if (*s == '\0') { | |
2147 | return(string); | |
2148 | } | |
2149 | } | |
2150 | else if (*s == ':') { | |
2151 | length--; | |
2152 | need_new = YES; | |
2153 | } | |
2154 | else if (modifier == TM_FIGBASS && *s == '/') { | |
2155 | length--; | |
2156 | need_new = YES; | |
2157 | } | |
2158 | } | |
2159 | } | |
2160 | ||
2161 | /* If string is okay as is, we are done here */ | |
2162 | if (need_new == NO) { | |
2163 | return(string); | |
2164 | } | |
2165 | ||
2166 | /* get enough space for new string */ | |
2167 | MALLOCA(char, newstring, length + 1); | |
2168 | ||
2169 | /* copy font/size */ | |
2170 | newstring[0] = string[0]; | |
2171 | newstring[1] = string[1]; | |
2172 | ||
2173 | new_p = newstring + 2; | |
2174 | s = string + 2; | |
2175 | if (modifier == TM_FIGBASS) { | |
2176 | /* add \: but after box, if any */ | |
2177 | if (string[2] == '\\' && string[3] == '[') { | |
2178 | *new_p++ = *s++; | |
2179 | *new_p++ = *s++; | |
2180 | } | |
2181 | *new_p++ = '\\'; | |
2182 | *new_p++ = ':'; | |
2183 | } | |
2184 | ||
2185 | /* walk through rest of string, copying, but transforming | |
2186 | * any slashes and colons along the way */ | |
2187 | for ( ; *s != '\0'; s++, new_p++) { | |
2188 | ||
2189 | /* handle colons */ | |
2190 | if (*s == ':') { | |
2191 | /* add a backslash */ | |
2192 | *new_p++ = '\\'; | |
2193 | } | |
2194 | else if (*s == '\\' && *(s+1) == ':') { | |
2195 | /* skip past the backslash */ | |
2196 | s++; | |
2197 | } | |
2198 | ||
2199 | /* handle slashes in figbass */ | |
2200 | else if (modifier == TM_FIGBASS) { | |
2201 | if (*s == '/') { | |
2202 | /* o/ means half diminished | |
2203 | * so that doesn't count */ | |
2204 | if (s <= string + 2 || *(s-1) != 'o') { | |
2205 | /* add a backslash */ | |
2206 | *new_p++ = '\\'; | |
2207 | } | |
2208 | } | |
2209 | else if (*s == '\\' && *(s+1) == '/') { | |
2210 | /* skip past the backslash */ | |
2211 | s++; | |
2212 | } | |
2213 | } | |
2214 | ||
2215 | /* copy from original string to new one */ | |
2216 | *new_p = *s; | |
2217 | } | |
2218 | ||
2219 | /* original is now no longer needed */ | |
2220 | FREE(string); | |
2221 | ||
2222 | /* terminate and return the modified string */ | |
2223 | *new_p = '\0'; | |
2224 | return(newstring); | |
2225 | } | |
2226 | \f | |
2227 | ||
2228 | /* given an integer point size, return the integer point size appropriate | |
2229 | * for a "small" version. This is SM_FACTOR times the size, rounded, but | |
2230 | * not less than 1. */ | |
2231 | ||
2232 | static int | |
2233 | smallsize(size) | |
2234 | ||
2235 | int size; | |
2236 | ||
2237 | { | |
2238 | size = (int) ( (float) size * SM_FACTOR); | |
2239 | if (size < 1) { | |
2240 | size = 1; | |
2241 | } | |
2242 | return(size); | |
2243 | } | |
2244 | \f | |
2245 | ||
2246 | /* accidentals in chords need to be scaled. Given a size, return the size | |
2247 | * that an accidental should be. This is 60% of given size, rounded to | |
2248 | * an integer, but no smaller than 1. */ | |
2249 | ||
2250 | static int | |
2251 | accsize(size) | |
2252 | ||
2253 | int size; | |
2254 | ||
2255 | { | |
2256 | size = (int) ( (float) size * 0.6); | |
2257 | if (size < 1) { | |
2258 | size = 1; | |
2259 | } | |
2260 | return(size); | |
2261 | } | |
2262 | \f | |
2263 | ||
2264 | /* return which character to use for rest, based on basictime */ | |
2265 | ||
2266 | int | |
2267 | restchar(basictime) | |
2268 | ||
2269 | int basictime; | |
2270 | ||
2271 | { | |
2272 | if (basictime < -1) { | |
2273 | pfatal("tried to get rest character for multirest"); | |
2274 | /*NOTREACHED*/ | |
2275 | return(0); | |
2276 | } | |
2277 | ||
2278 | else if (basictime == -1) { | |
2279 | /* quad rest */ | |
2280 | return (C_QWHREST); | |
2281 | } | |
2282 | ||
2283 | else if (basictime == 0) { | |
2284 | /* double whole rest */ | |
2285 | return (C_DWHREST); | |
2286 | } | |
2287 | ||
2288 | else { | |
2289 | /* other non-multirest */ | |
2290 | return (Resttab [ drmo(basictime) ] ); | |
2291 | } | |
2292 | } | |
2293 | \f | |
2294 | ||
2295 | /* return YES if given font is an italic font (includes boldital too) */ | |
2296 | ||
2297 | int | |
2298 | is_ital_font(font) | |
2299 | ||
2300 | int font; | |
2301 | ||
2302 | { | |
2303 | return(Fontinfo[ font_index(font) ].is_ital); | |
2304 | } | |
2305 | \f | |
2306 | ||
2307 | /* given a string, return, via pointers the font and size in effect at the | |
2308 | * end of the string */ | |
2309 | ||
2310 | void | |
2311 | end_fontsize(str, font_p, size_p) | |
2312 | ||
2313 | char *str; /* check this string */ | |
2314 | int *font_p; /* return font at end of str via this pointer */ | |
2315 | int *size_p; /* return size at end of str via this pointer */ | |
2316 | ||
2317 | { | |
2318 | if (str == (char *) 0) { | |
2319 | /* empty string, use defaults */ | |
2320 | *font_p = FONT_TR; | |
2321 | *size_p = DFLT_SIZE; | |
2322 | return; | |
2323 | } | |
2324 | ||
2325 | /* find the font/size in effect at end of given string */ | |
2326 | *font_p = *str++; | |
2327 | *size_p = *str++; | |
2328 | while (next_str_char(&str, font_p, size_p) != '\0') { | |
2329 | ; | |
2330 | } | |
2331 | } | |
2332 | \f | |
2333 | ||
2334 | /* given a string, return a string made up of a dash in the font and size | |
2335 | * of the end of the given string. However, if the string ends with a ~ or _ | |
2336 | * return a string containing that instead */ | |
2337 | ||
2338 | char * | |
2339 | dashstr(str) | |
2340 | ||
2341 | char *str; /* return dash with same font/size as end of this string */ | |
2342 | ||
2343 | { | |
2344 | int font, size; | |
2345 | char *newstring; | |
2346 | int ch; /* character to use */ | |
2347 | ||
2348 | ||
2349 | end_fontsize(str, &font, &size); | |
2350 | ch = last_char(str); | |
2351 | if (ch != '~' && ch != '_') { | |
2352 | ch = '-'; | |
2353 | } | |
2354 | ||
2355 | /* allocate space for dash string and fill it in */ | |
2356 | MALLOCA(char, newstring, 4); | |
2357 | newstring[0] = (char) font; | |
2358 | newstring[1] = (char) size; | |
2359 | newstring[2] = (char) ch; | |
2360 | newstring[3] = '\0'; | |
2361 | return(newstring); | |
2362 | } | |
2363 | \f | |
2364 | ||
2365 | /* Given an internal format string, create an ASCII-only string. Flags | |
2366 | * tell how complete a conversion to do. If verbose is YES, try to convert | |
2367 | * everything back to user's original input, otherwise ignore special things | |
2368 | * other than music characters, extended characters, and backspace. | |
2369 | * If pagenum is YES, interpolate the current page number rather than using %. | |
2370 | * | |
2371 | * Recreating the original user string is not perfect, but is usually right. | |
2372 | * Where there are shortcuts, we can't tell if user used them or not. | |
2373 | * Extended characters are output by name even if user put them in as single | |
2374 | * Latin-1 characters. But we couldn't use the Latin-1 hyper-ASCII in midi | |
2375 | * anyway, because they have high bit set. | |
2376 | * | |
2377 | * Returns the ASCII-ized string, which is stored in an area that will get | |
2378 | * overwritten on subsequent calls, so if caller needs a permanent copy, | |
2379 | * they have to make it themselves. | |
2380 | */ | |
2381 | ||
2382 | /* This is how much to malloc at a time to hold the ASCII-ized string */ | |
2383 | #define ASCII_BSIZE 512 | |
2384 | ||
2385 | char * | |
2386 | ascii_str(string, verbose, pagenum, textmod) | |
2387 | ||
2388 | char *string; /* internal format string to convert */ | |
2389 | int verbose; /* If YES, try to reproduce user's original input */ | |
2390 | int pagenum; /* YES (interpolate number for \%) or NO (leave \% as is) */ | |
2391 | int textmod; /* TM_ value */ | |
2392 | ||
2393 | { | |
2394 | static char *buff = 0; /* for ASCII-ized string */ | |
2395 | static unsigned buff_length = 0;/* how much is malloc-ed */ | |
2396 | int i; /* index into ASCII-ized string */ | |
2397 | char *musname; /* music character name */ | |
2398 | int in_pile = NO; | |
2399 | char *str; /* walk through string */ | |
2400 | int musfont; /* FONT_MUSIC* */ | |
2401 | ||
2402 | ||
2403 | /* first time, get some space */ | |
2404 | if (buff_length == 0) { | |
2405 | buff_length = ASCII_BSIZE; | |
2406 | MALLOCA(char, buff, buff_length); | |
2407 | } | |
2408 | ||
2409 | /* walk through string */ | |
2410 | i = 0; | |
2411 | /* special case: normally we implicitly begin a figbass with a | |
2412 | * pile start, but if users cancels that, it won't be there */ | |
2413 | if (textmod == TM_FIGBASS && | |
2414 | (((unsigned char) *(string+2)) & 0xff) != STR_PILE) { | |
2415 | buff[i++] = ':'; | |
2416 | } | |
2417 | for (str = string + 2; *str != '\0'; str++) { | |
2418 | switch ( ((unsigned char) *str) & 0xff) { | |
2419 | ||
2420 | case STR_FONT: | |
2421 | str++; | |
2422 | #ifdef EXTCHAR | |
2423 | if ( (int) *str > EXT_FONT_OFFSET) { | |
2424 | str++; | |
2425 | /* translate to Mup name */ | |
2426 | (void) sprintf(buff + i, "\\(%s)", | |
2427 | ext_num2name((int) *str)); | |
2428 | while (buff[i] != '\0') { | |
2429 | i++; | |
2430 | } | |
2431 | /* skip past the return to original font */ | |
2432 | str += 2; | |
2433 | } | |
2434 | else if (verbose == YES) { | |
2435 | #else | |
2436 | if (verbose == YES) { | |
2437 | #endif | |
2438 | (void) sprintf(buff + i, "\\f(%s)", | |
2439 | fontnum2name((int) *str)); | |
2440 | while (buff[i] != '\0') { | |
2441 | i++; | |
2442 | } | |
2443 | } | |
2444 | break; | |
2445 | ||
2446 | case STR_SIZE: | |
2447 | str++; | |
2448 | if (verbose == YES) { | |
2449 | (void) sprintf(buff + i, "\\s(%d)", (int) *str); | |
2450 | while (buff[i] != '\0') { | |
2451 | i++; | |
2452 | } | |
2453 | } | |
2454 | break; | |
2455 | ||
2456 | case STR_VERTICAL: | |
2457 | str++; | |
2458 | if (verbose == YES) { | |
2459 | (void) sprintf(buff + i, "\\v(%d)", | |
2460 | DECODE_VERT((int) *str) * 100 | |
2461 | / MAXVERTICAL); | |
2462 | while (buff[i] != '\0') { | |
2463 | i++; | |
2464 | } | |
2465 | } | |
2466 | break; | |
2467 | ||
2468 | case STR_MUS_CHAR: | |
2469 | case STR_MUS_CHAR2: | |
2470 | musfont = str2mfont( ((unsigned char) *str) & 0xff); | |
2471 | ||
2472 | /* skip past the size byte, | |
2473 | * and on to the character code. */ | |
2474 | str += 2; | |
2475 | /* In chordlike stuffs, we translate things like | |
2476 | * # and &&, so translate them back. It's possible | |
2477 | * the user used the names explicitly rather than us | |
2478 | * translating, in which case this won't be | |
2479 | * strictly what they put in, but it will be | |
2480 | * consistent, so that a caller of this function | |
2481 | * can easily sort or compare values | |
2482 | * without having to know (for example) | |
2483 | * that '#' and \(smsharp) are the same thing. */ | |
2484 | musname = 0; | |
2485 | if (IS_CHORDLIKE(textmod) == YES | |
2486 | && musfont == FONT_MUSIC) { | |
2487 | switch( ((unsigned char) *str) & 0xff) { | |
2488 | case C_SHARP: | |
2489 | musname = "#"; | |
2490 | break; | |
2491 | case C_FLAT: | |
2492 | musname = "&"; | |
2493 | break; | |
2494 | case C_DBLSHARP: | |
2495 | musname = "x"; | |
2496 | break; | |
2497 | case C_DBLFLAT: | |
2498 | musname = "&&"; | |
2499 | break; | |
2500 | case C_NAT: | |
2501 | if (textmod != TM_CHORD) { | |
2502 | musname = "n"; | |
2503 | } | |
2504 | break; | |
2505 | case C_DIM: | |
2506 | musname = "o"; | |
2507 | break; | |
2508 | case C_HALFDIM: | |
2509 | musname = "o/"; | |
2510 | break; | |
2511 | case C_TRIANGLE: | |
2512 | musname = "^"; | |
2513 | break; | |
2514 | default: | |
2515 | break; | |
2516 | } | |
2517 | } | |
2518 | if (musname != 0) { | |
2519 | (void) sprintf(buff + i, musname); | |
2520 | } | |
2521 | else { | |
2522 | (void) sprintf(buff + i, "\\(%s)", | |
2523 | mc_num2name((int) *str, musfont)); | |
2524 | } | |
2525 | while (buff[i] != '\0') { | |
2526 | i++; | |
2527 | } | |
2528 | ||
2529 | break; | |
2530 | ||
2531 | case STR_BACKSPACE: | |
2532 | if (verbose == YES) { | |
2533 | buff[i++] = '\\'; | |
2534 | buff[i++] = 'b'; | |
2535 | } | |
2536 | /* ignore this and following char */ | |
2537 | str++; | |
2538 | break; | |
2539 | ||
2540 | case STR_PRE: | |
2541 | case STR_PST: | |
2542 | if (verbose == YES) { | |
2543 | buff[i++] = '<'; | |
2544 | } | |
2545 | break; | |
2546 | ||
2547 | case STR_U_PRE: | |
2548 | case STR_U_PST: | |
2549 | if (verbose == YES) { | |
2550 | buff[i++] = '<'; | |
2551 | buff[i++] = '^'; | |
2552 | } | |
2553 | break; | |
2554 | ||
2555 | case STR_PRE_END: | |
2556 | case STR_PST_END: | |
2557 | if (verbose == YES) { | |
2558 | buff[i++] = '>'; | |
2559 | } | |
2560 | break; | |
2561 | ||
2562 | case STR_BOX: | |
2563 | if (verbose == YES) { | |
2564 | buff[i++] = '\\'; | |
2565 | buff[i++] = '['; | |
2566 | } | |
2567 | break; | |
2568 | ||
2569 | case STR_BOX_END: | |
2570 | if (verbose == YES) { | |
2571 | buff[i++] = '\\'; | |
2572 | buff[i++] = ']'; | |
2573 | } | |
2574 | break; | |
2575 | ||
2576 | case STR_CIR: | |
2577 | if (verbose == YES) { | |
2578 | buff[i++] = '\\'; | |
2579 | buff[i++] = '{'; | |
2580 | } | |
2581 | break; | |
2582 | ||
2583 | case STR_CIR_END: | |
2584 | if (verbose == YES) { | |
2585 | buff[i++] = '\\'; | |
2586 | buff[i++] = '}'; | |
2587 | } | |
2588 | break; | |
2589 | ||
2590 | case STR_C_ALIGN: | |
2591 | if (verbose == YES) { | |
2592 | buff[i++] = '\\'; | |
2593 | buff[i++] = '^'; | |
2594 | } | |
2595 | break; | |
2596 | ||
2597 | case STR_L_ALIGN: | |
2598 | if (verbose == YES) { | |
2599 | buff[i++] = '\\'; | |
2600 | buff[i++] = '|'; | |
2601 | } | |
2602 | break; | |
2603 | ||
2604 | case STR_PILE: | |
2605 | if (verbose == YES) { | |
2606 | /* On figbass, we implictly add a pile start */ | |
2607 | if (textmod == TM_FIGBASS && string + 2 == str) { | |
2608 | ; | |
2609 | } | |
2610 | /* if this is at the end of a padded string, | |
2611 | * there is a high probability it is one | |
2612 | * we added implicitly, so skip it */ | |
2613 | else if (in_pile == YES && *(str+1) == ' ' && | |
2614 | *(str+2) == '\0') { | |
2615 | ; | |
2616 | } | |
2617 | else { | |
2618 | /* in chordlike things, user didn't | |
2619 | * use a backslash, else they did */ | |
2620 | if (IS_CHORDLIKE(textmod) == NO) { | |
2621 | buff[i++] = '\\'; | |
2622 | } | |
2623 | buff[i++] = ':'; | |
2624 | } | |
2625 | } | |
2626 | /* keep track of toggle state */ | |
2627 | in_pile = (in_pile == YES ? NO : YES); | |
2628 | break; | |
2629 | ||
2630 | case STR_SLASH: | |
2631 | if (verbose == YES && textmod != TM_FIGBASS) { | |
2632 | buff[i++] = '\\'; | |
2633 | } | |
2634 | buff[i++] = '/'; | |
2635 | break; | |
2636 | ||
2637 | case STR_PAGENUM: | |
2638 | case STR_NUMPAGES: | |
2639 | if (pagenum == YES) { | |
2640 | /* Write page number and update length. | |
2641 | * Actually, we don't have the correct values | |
2642 | * for this until late in program execution, | |
2643 | * and for MIDI, there are no pages at all, | |
2644 | * and this can be called from MIDI, so | |
2645 | * this is probably not really very useful, | |
2646 | * but this is the best we can do... */ | |
2647 | (void) sprintf(buff + i, "%d", | |
2648 | (((unsigned char) *str) & 0xff) | |
2649 | == STR_PAGENUM ? | |
2650 | Pagenum : Last_pagenum); | |
2651 | while (buff[i] != '\0') { | |
2652 | i++; | |
2653 | } | |
2654 | } | |
2655 | else { | |
2656 | buff[i++] = '\\'; | |
2657 | buff[i++] = *(str+1); | |
2658 | } | |
2659 | str++; | |
2660 | break; | |
2661 | ||
2662 | case '\\': | |
2663 | buff[i++] = '\\'; | |
2664 | buff[i++] = '\\'; | |
2665 | break; | |
2666 | ||
2667 | default: | |
2668 | if (*str == '\n') { | |
2669 | if (in_pile == YES) { | |
2670 | if ( *(str+1) != '\0') { | |
2671 | buff[i++] = ' '; | |
2672 | } | |
2673 | } | |
2674 | else { | |
2675 | buff[i++] = '\\'; | |
2676 | buff[i++] = 'n'; | |
2677 | } | |
2678 | } | |
2679 | else if (IS_CHORDLIKE(textmod) == YES && *str == ':') { | |
2680 | buff[i++] = '\\'; | |
2681 | buff[i++] = ':'; | |
2682 | } | |
2683 | else if (textmod == TM_FIGBASS && *str == '/') { | |
2684 | buff[i++] = '\\'; | |
2685 | buff[i++] = '/'; | |
2686 | } | |
2687 | else if (*str == ' ' && *(str+1) == '\0') { | |
2688 | /* This is probably a space padding | |
2689 | * that we added implicitly, | |
2690 | * so don't print it. If this is | |
2691 | * called on a 'with' item or 'print' item | |
2692 | * where user explicitly added a space, | |
2693 | * this will strip that off, which, strictly | |
2694 | * speaking, it shouldn't. But that would | |
2695 | * only be for debugging anyway, and a | |
2696 | * strange case, so don't worry about it. */ | |
2697 | ; | |
2698 | } | |
2699 | else { | |
2700 | /* ordinary character */ | |
2701 | buff[i++] = *str; | |
2702 | } | |
2703 | } | |
2704 | ||
2705 | /* If running low on space, get some more. Could probably | |
2706 | * just truncate, since this is used for things like error | |
2707 | * messages, but alloc-ing more is easy enough. */ | |
2708 | if (i > buff_length - 20) { | |
2709 | buff_length += ASCII_BSIZE; | |
2710 | REALLOCA(char, buff, buff_length); | |
2711 | } | |
2712 | } | |
2713 | buff[i++] = '\0'; | |
2714 | ||
2715 | return(buff); | |
2716 | } | |
2717 | \f | |
2718 | ||
2719 | /* | |
2720 | * Given a text string and a maximum desired width, try adding newlines at | |
2721 | * white space to bring the width down under the desired width. If that's | |
2722 | * not possible, do the best we can. Return pointer to the possibly | |
2723 | * altered string. | |
2724 | */ | |
2725 | ||
2726 | char * | |
2727 | split_string(string, desired_width) | |
2728 | ||
2729 | char *string; | |
2730 | double desired_width; | |
2731 | ||
2732 | { | |
2733 | char *last_white_p; /* where last white space was */ | |
2734 | char *curr_white_p; /* white space we're dealing with now */ | |
2735 | char *str; /* to walk through string */ | |
2736 | double proposed_width; /* width of string so far */ | |
2737 | int font, size; | |
2738 | int c; /* the current character in string */ | |
2739 | int save_c; /* temporary copy of c */ | |
2740 | int save_str; /* temporary copy of character from string */ | |
2741 | ||
2742 | ||
2743 | /* Piles are incompatible with newlines, so we don't want to | |
2744 | * even attempt to split a string with a pile in it. */ | |
2745 | for (str = string + 2; *str != '\0'; str++) { | |
2746 | if ((*str & 0xff) == STR_PILE) { | |
2747 | /* string has a pile, so return it as is */ | |
2748 | return(string); | |
2749 | } | |
2750 | } | |
2751 | ||
2752 | /* Go through the string, until we hit white space. */ | |
2753 | last_white_p = (char *) 0; | |
2754 | font = string[0]; | |
2755 | size = string[1]; | |
2756 | str = string + 2; | |
2757 | while ((c = next_str_char(&str, &font, &size)) != '\0') { | |
2758 | ||
2759 | /* Are we at white space? */ | |
2760 | if ( ! IS_MUSIC_FONT(font) && (c == ' ' || c == '\t')) { | |
2761 | ||
2762 | /* Temporarily replace with newline, and terminate | |
2763 | * to get width so far if we were to add a newline */ | |
2764 | curr_white_p = str - 1; | |
2765 | save_c = c; | |
2766 | save_str = *str; | |
2767 | *curr_white_p = '\n'; | |
2768 | *str = '\0'; | |
2769 | proposed_width = strwidth(string); | |
2770 | *curr_white_p = save_c; | |
2771 | *str = save_str; | |
2772 | ||
2773 | if (proposed_width > desired_width) { | |
2774 | if (last_white_p != (char *) 0) { | |
2775 | /* reduce the width of the string by | |
2776 | * changing the most recent white space | |
2777 | * to a newline */ | |
2778 | *last_white_p = '\n'; | |
2779 | ||
2780 | /* if the overall string is now short | |
2781 | * enough, we are done */ | |
2782 | if (strwidth(string) <= desired_width) { | |
2783 | return(string); | |
2784 | } | |
2785 | last_white_p = curr_white_p; | |
2786 | } | |
2787 | else { | |
2788 | /* No previous white space, so we | |
2789 | * can't make it short enough. So change | |
2790 | * this current white space to a | |
2791 | * newline, since that's the best we | |
2792 | * can do. But also set the desired | |
2793 | * width to our current width, | |
2794 | * because we know we're | |
2795 | * going to have to be at least this | |
2796 | * wide anyway, so we might as well use | |
2797 | * this much space on future lines */ | |
2798 | *curr_white_p = '\n'; | |
2799 | desired_width = proposed_width; | |
2800 | ||
2801 | /* no longer have a previous | |
2802 | * white space on the current line, | |
2803 | * because we just started a new | |
2804 | * line */ | |
2805 | last_white_p = (char *) 0; | |
2806 | } | |
2807 | ||
2808 | } | |
2809 | else { | |
2810 | /* not too wide yet. Remember where this white | |
2811 | * space is, in case the next word makes us | |
2812 | * too wide and we have to change it to a | |
2813 | * newline */ | |
2814 | last_white_p = curr_white_p; | |
2815 | } | |
2816 | } | |
2817 | } | |
2818 | ||
2819 | /* If last word went over the edge, move to next line if possible. */ | |
2820 | if (strwidth(string) > desired_width && last_white_p != (char *) 0) { | |
2821 | *last_white_p = '\n'; | |
2822 | } | |
2823 | ||
2824 | /* Return the (possibly altered) string */ | |
2825 | return(string); | |
2826 | } | |
2827 | \f | |
2828 | ||
2829 | /* Given a point size and an adjustment factor, return a new point size. | |
2830 | * If size would be less than MINSIZE, return MINSIZE. | |
2831 | * If it would be greater than MAXSIZE, print error and return MAXSIZE. | |
2832 | * Since we only use integer sizes, there may be some roundoff error. | |
2833 | * While it would be possible to dream up a pathological case | |
2834 | * where this roundout might be big enough to notice, | |
2835 | * for any sane scenario you would probably need | |
2836 | * an extremely high resolution printer and a microscope to notice. | |
2837 | */ | |
2838 | ||
2839 | int | |
2840 | adj_size(size, scale_factor, filename, lineno) | |
2841 | ||
2842 | int size; /* original point size */ | |
2843 | double scale_factor; /* multiply original size by this factor */ | |
2844 | char *filename; /* filename and lineno are for error messages */ | |
2845 | int lineno; | |
2846 | ||
2847 | { | |
2848 | size = (int) ((double) size * scale_factor + 0.5); | |
2849 | if (size < MINSIZE) { | |
2850 | return(MINSIZE); | |
2851 | } | |
2852 | if (size > MAXSIZE) { | |
2853 | l_warning(filename, lineno, | |
2854 | "Adjusted size of string would be bigger than %d", MAXSIZE); | |
2855 | return(MAXSIZE); | |
2856 | } | |
2857 | return(size); | |
2858 | } | |
2859 | \f | |
2860 | ||
2861 | /* Given a string that is in internal format, and a scale factor by which to | |
2862 | * resize that string, adjust all size bytes in the string. | |
2863 | */ | |
2864 | ||
2865 | char * | |
2866 | resize_string(string, scale_factor, filename, lineno) | |
2867 | ||
2868 | char *string; /* this is the string to adjust */ | |
2869 | double scale_factor; /* adjust sizes in string by this factor */ | |
2870 | char *filename; /* for error messages */ | |
2871 | int lineno; /* for error messages */ | |
2872 | ||
2873 | { | |
2874 | char *s; /* to walk through string */ | |
2875 | ||
2876 | ||
2877 | /* if string is empty, nothing to do */ | |
2878 | if (string == (char *) 0 || *string == '\0') { | |
2879 | return(string); | |
2880 | } | |
2881 | ||
2882 | /* if factor is sufficiently close to 1.0 that it's very clear | |
2883 | * we won't be making any changes (since we only use integer | |
2884 | * point sizes), don't bother */ | |
2885 | if ( fabs( (double) (scale_factor - 1.0)) < 0.01) { | |
2886 | return(string); | |
2887 | } | |
2888 | ||
2889 | /* second byte is size byte, so adjust that */ | |
2890 | string[1] = (char) adj_size( (int) string[1], scale_factor, | |
2891 | filename, lineno); | |
2892 | ||
2893 | /* Go through the string. For each size byte, replace it with an | |
2894 | * adjusted size. Size bytes occur immediately after STR_SIZE | |
2895 | * and STR_MUS_CHAR commands. Everything else can get copied as | |
2896 | * is: STR_BACKSPACE is in terms of the default size, so it is | |
2897 | * unaffected by this resizing, and the other special string commands | |
2898 | * are unrelated to size and thus unaffected. */ | |
2899 | for (s = string + 2; *s != '\0'; s++) { | |
2900 | switch ( (unsigned char) *s ) { | |
2901 | case STR_SIZE: | |
2902 | case STR_MUS_CHAR: | |
2903 | s++; | |
2904 | *s = (char) adj_size( (int) *s, scale_factor, | |
2905 | filename, lineno); | |
2906 | break; | |
2907 | default: | |
2908 | break; | |
2909 | } | |
2910 | } | |
2911 | ||
2912 | return(string); | |
2913 | } | |
2914 | \f | |
2915 | ||
2916 | /* Given a circled string, return how much to add to its ascent and | |
2917 | * descent to give room for the circle. If pointer arguments are non-zero, | |
2918 | * return additional values via those pointers. | |
2919 | */ | |
2920 | ||
2921 | double | |
2922 | circled_dimensions(str, height_p, width_p, ascent_adjust, x_offset_p) | |
2923 | ||
2924 | char *str; /* a circled string */ | |
2925 | float *height_p; /* if non-zero, return circled height here */ | |
2926 | float *width_p; /* if non-zero, return circled width here */ | |
2927 | float *ascent_adjust; /* if non-zero, return amount we added to | |
2928 | * ascent to bring up to minimum height */ | |
2929 | float *x_offset_p; /* if non-zero, return where to print the | |
2930 | * actual string relative to circle edge */ | |
2931 | ||
2932 | { | |
2933 | int font, size; | |
2934 | float min_height; | |
2935 | float adjust; /* amount to bring up to min height */ | |
2936 | float uncirc_height, uncirc_width;/* dimensions of uncircled str */ | |
2937 | float circ_height; /* height including circle */ | |
2938 | float circ_width; /* width including circle */ | |
2939 | float circ_extra; /* how much to add to top and | |
2940 | * bottom to allow space for circle */ | |
2941 | ||
2942 | ||
2943 | /* temporarily make the string uncircled */ | |
2944 | size = str[2] = str[1]; | |
2945 | font = str[1] = str[0]; | |
2946 | /* Note that there is at least one circumstance (in split_string()) | |
2947 | * where a circled string is temporarily lacking the trailing END_CIR, | |
2948 | * and strheight and strwidth don't need it, so we don't need | |
2949 | * to blank that out. */ | |
2950 | ||
2951 | /* get the dimensions of the uncircled version */ | |
2952 | uncirc_height = strheight(str+1); | |
2953 | uncirc_width = strwidth(str+1); | |
2954 | ||
2955 | /* put the circle back */ | |
2956 | str[1] = str[2]; | |
2957 | str[2] = (char) STR_CIR; | |
2958 | ||
2959 | /* If string is unusually short vertically, treat as at least as tall | |
2960 | * as the font's ascent. That way if there are a bunch of | |
2961 | * circled things and one is tiny, like a dot, that circle | |
2962 | * won't be vastly smaller than the others. */ | |
2963 | min_height = fontascent(font, size); | |
2964 | if (uncirc_height < min_height) { | |
2965 | adjust = min_height - uncirc_height; | |
2966 | uncirc_height = min_height; | |
2967 | } | |
2968 | else { | |
2969 | adjust = 0.0; | |
2970 | } | |
2971 | ||
2972 | /* Allow 25% of the height above and below as space for the circle. */ | |
2973 | circ_extra = 0.25 * uncirc_height; | |
2974 | circ_height = 2.0 * circ_extra + uncirc_height; | |
2975 | ||
2976 | /* If width is up to 110% of the height, use the circled | |
2977 | * height as the circled width as well. */ | |
2978 | if (uncirc_width <= 1.1 * uncirc_height) { | |
2979 | circ_width = circ_height; | |
2980 | } | |
2981 | else { | |
2982 | /* make a little taller to compensate for the width */ | |
2983 | circ_extra += circ_height * .03 * (uncirc_width / uncirc_height); | |
2984 | circ_height = 2.0 * circ_extra + uncirc_height; | |
2985 | ||
2986 | /* Use 50% of the circled height as the amount to add | |
2987 | * to the width, 25% on each end. */ | |
2988 | circ_width = uncirc_width + 0.5 * circ_height; | |
2989 | } | |
2990 | if (height_p != 0) { | |
2991 | *height_p = circ_height; | |
2992 | } | |
2993 | if (width_p != 0) { | |
2994 | *width_p = circ_width; | |
2995 | } | |
2996 | if (x_offset_p != 0) { | |
2997 | *x_offset_p = (circ_width - uncirc_width) / 2.0; | |
2998 | } | |
2999 | if (ascent_adjust != 0) { | |
3000 | *ascent_adjust = adjust; | |
3001 | } | |
3002 | ||
3003 | return(circ_extra); | |
3004 | } | |
3005 | \f | |
3006 | ||
3007 | /* Return proper version of rehearsal mark string, based on staff number. | |
3008 | * It may be circled, boxed, or plain. If circled or boxed, a new string | |
3009 | * is returned. If plain, the string is returned as is. | |
3010 | */ | |
3011 | ||
3012 | char * | |
3013 | get_reh_string(string, staffnum) | |
3014 | ||
3015 | char *string; /* the plain rehearsal mark string */ | |
3016 | int staffnum; /* which staff it is for */ | |
3017 | ||
3018 | { | |
3019 | char reh_buffer[100]; /* if not okay as it is, copy is put here */ | |
3020 | int style; | |
3021 | ||
3022 | style = svpath(staffnum, REHSTYLE)->rehstyle; | |
3023 | ||
3024 | if (style == RS_PLAIN) { | |
3025 | return(string); | |
3026 | } | |
3027 | ||
3028 | if (strlen(string) + 3 > sizeof(reh_buffer)) { | |
3029 | /* Usually reh marks are very short, | |
3030 | * so if this one is really long, too bad. | |
3031 | */ | |
3032 | ufatal("rehearsal mark is too long"); | |
3033 | } | |
3034 | ||
3035 | (void) sprintf(reh_buffer, "%c%s%c", | |
3036 | style == RS_CIRCLED ? STR_CIR : STR_BOX, | |
3037 | string + 2, | |
3038 | style == RS_CIRCLED ? STR_CIR_END : STR_BOX_END); | |
3039 | return(copy_string(reh_buffer, string[0], string[1])); | |
3040 | } | |
3041 | \f | |
3042 | ||
3043 | /* Map STR_MUS_CHAR* to FONT_MUSIC* */ | |
3044 | ||
3045 | int | |
3046 | str2mfont(str) | |
3047 | ||
3048 | int str; /* STR_MUS_CHAR* */ | |
3049 | ||
3050 | { | |
3051 | switch (str) { | |
3052 | case STR_MUS_CHAR: | |
3053 | return(FONT_MUSIC); | |
3054 | case STR_MUS_CHAR2: | |
3055 | return(FONT_MUSIC2); | |
3056 | default: | |
3057 | pfatal("impossible str 0x%x in str2mfont", str); | |
3058 | /*NOTREACHED*/ | |
3059 | return(FONT_MUSIC); | |
3060 | } | |
3061 | } | |
3062 | ||
3063 | /* Map FONT_MUSIC* to STR_MUS_CHAR* */ | |
3064 | ||
3065 | int | |
3066 | mfont2str(mfont) | |
3067 | ||
3068 | int mfont; /* FONT_MUSIC* */ | |
3069 | ||
3070 | { | |
3071 | switch (mfont) { | |
3072 | case FONT_MUSIC: | |
3073 | return(STR_MUS_CHAR); | |
3074 | case FONT_MUSIC2: | |
3075 | return(STR_MUS_CHAR2); | |
3076 | default: | |
3077 | pfatal("impossible mfont %d in mfont2str", mfont); | |
3078 | /*NOTREACHED*/ | |
3079 | return(STR_MUS_CHAR); | |
3080 | } | |
3081 | } |