/* * Low-level utilities. * * This file is part of abcm2ps. * * Copyright (C) 1998-2007 Jean-François Moine * Adapted from abc2ps, Copyright (C) 1996,1997 Michael Methfessel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include "abcparse.h" #include "abc2ps.h" char tex_buf[TEX_BUF_SZ]; /* result of tex_str() */ int outft = -1; /* last font in the output file */ static char *strop; /* current string output operation */ static float strlw; /* line width */ static float strtw = -1; /* current text width */ static int strns; /* number of spaces (justify) */ static int curft; /* current (wanted) font */ static int defft; /* default font */ static int strtx; /* PostScript text outputing */ /* width of characters according to the encoding */ /* these are the widths for Times-Roman, extracted from the 'a2ps' package */ static short ISOLatin1_w[256] = { 0, 0, 0, 0, 0, 0, 0, 0, /* \002: hyphen in lyrics */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250,333,408,500,500,833,778,333, 333,333,500,564,250,564,250,278, 500,500,500,500,500,500,500,500, 500,500,278,278,564,564,564,444, 921,722,667,667,722,611,556,722, 722,333,389,722,611,889,722,722, 556,722,667,556,611,722,722,944, 722,722,611,333,278,333,469,500, 333,444,500,444,500,444,333,500, 500,278,278,500,278,778,500,500, 500,500,333,389,278,500,500,722, 500,500,444,480,200,480,541, 0, 0,500,500,500, 0, 0, 0, 0, /* \201..\203: sharp, flat and natural signs */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250,333,500,500,500,500,200,500, 333,760,276,500,564,333,760,333, 400,564,300,300,333,500,453,350, 333,278,310,500,750,750,750,444, 722,722,722,722,722,722,889,667, 611,611,611,611,333,333,333,333, 722,722,722,722,722,722,722,564, 722,722,722,722,722,722,556,500, 444,444,444,444,444,444,667,444, 444,444,444,444,278,278,278,278, 500,500,500,500,500,500,500,564, 500,500,500,500,500,500,500,500, }; static short ISOLatin2_w[96] = { 250,500,333,611,500,500,500,500, 333,556,500,500,500,333,611,500, 400,500,333,278,333,500,500,333, 333,389,500,500,500,333,444,500, 500,722,722,500,722,500,500,667, 500,611,500,611,500,333,333,500, 500,500,500,722,722,500,722,564, 500,500,722,500,722,722,500,500, 500,444,444,500,444,500,500,444, 500,444,500,444,500,278,278,500, 500,500,500,500,500,500,500,564, 500,500,500,500,500,500,500,333, }; static short ISOLatin3_w[96] = { 250,500,333,500,500,500,500,500, 333,500,500,500,500,333,760,500, 400,500,300,300,333,500,500,350, 333,278,500,500,500,750,750,500, 722,722,722,722,722,500,500,667, 611,611,611,611,333,333,333,333, 722,722,722,722,722,500,722,564, 500,722,722,722,722,500,500,500, 444,444,444,444,444,500,500,444, 444,444,444,444,278,278,278,278, 500,500,500,500,500,500,500,564, 500,500,500,500,500,500,500,333, }; static short ISOLatin4_w[96] = { 250,500,500,500,500,500,500,500, 333,556,500,500,500,333,611,333, 400,500,333,500,333,500,500,333, 333,389,500,500,500,500,444,500, 500,722,722,722,722,722,889,500, 500,611,500,611,500,333,333,500, 722,500,500,500,722,722,722,564, 722,500,722,722,722,500,500,500, 500,444,444,444,444,444,667,500, 500,444,500,444,500,278,278,500, 500,500,500,500,500,500,500,564, 500,500,500,500,500,500,500,333, }; static short ISOLatin5_w[96] = { 250,333,500,500,500,500,200,500, 333,760,276,500,564,333,760,333, 400,564,300,300,333,500,453,350, 333,278,310,500,750,750,750,444, 722,722,722,722,722,722,889,667, 611,611,611,611,333,333,333,333, 500,722,722,722,722,722,722,564, 722,722,722,722,722,500,500,500, 444,444,444,444,444,444,667,444, 444,444,444,444,278,278,278,278, 500,500,500,500,500,500,500,564, 500,500,500,500,500,278,500,500, }; static short ISOLatin6_w[96] = { 250,500,500,500,500,500,500,500, 333,500,556,500,611,333,500,500, 500,500,500,500,500,500,500,500, 500,500,389,500,444,500,500,500, 500,722,722,722,722,722,889,500, 500,611,500,611,500,333,333,333, 500,500,500,722,722,722,722,500, 722,500,722,722,722,722,556,500, 500,444,444,444,444,444,667,500, 500,444,500,444,500,278,278,278, 500,500,500,500,500,500,500,500, 500,500,500,500,500,500,500,500, }; static short *cw_tb[] = { ISOLatin1_w, /* 0 = ascii */ ISOLatin1_w, ISOLatin2_w - 160, ISOLatin3_w - 160, ISOLatin4_w - 160, ISOLatin5_w - 160, ISOLatin6_w - 160 }; /* escaped character table */ /* adapted from the 'recode' package - first index is 128 + 32 */ static char ISOLatin1_c[] = "NS!!CtPdCuYeBBSE':Co-a<>141234?I" "A!A'A>A?A:AAAEC,E!E'E>E:I!I'I>I:D-N?O!O'O>O?O:*XO/U!U'U>U:Y'THss" "a!a'a>a?a:aaaec,e!e'e>e:i!i'i>i:d-n?o!o'o>o?o:-:o/u!u'u>u:y'thy:"; static char ISOLatin2_c[] = "NSA;'(L/CuLA(A:L'C'C,CDO\"O:*XRa(a:l'c'c,cdo\"o:-:r A:C.C>C,E!E'E>E:I!I'I>I: N?O!O'O>G.O:*XG>U!U'U>U:U(S>ss" "a!a'a> a:c.c>c,e!e'e>e:i!i'i>i: n?o!o'o>g.o:-:g>u!u'u>u:u(s>'."; static char ISOLatin4_c[] = "NSA;kkR,CuI?L,SE':SA?A:AAAEI;CI-D/N,O-K,O>O?O:*XO/U;U'U>U:U?U-ss" "a-a'a>a?a:aaaei;ci-d/n,o-k,o>o?o:-:o/u;u'u>u:u?u-'."; static char ISOLatin5_c[] = "NS!!CtPdCuYeBBSE':Co-a<>141234?I" "A!A'A>A?A:AAAEC,E!E'E>E:I!I'I>I:G(N?O!O'O>O?O:*XO/U!U'U>U:I.S,ss" "a!a'a>a?a:aaaec,e!e'e;e:e.i'i>i-g(n?o!o'o>o?o:-:o/u!u'u>u:i.s,y:"; static char ISOLatin6_c[] = "NSA;E-G,I-I?K,L,N'R,SA?A:AAAEI;CI:D/N,O-O'O>O?O:U?O/U;U'U>U:Y'THU-" "a-a'a>a?a:aaaei;ci:d-n,o-o'o>o?o:u?o/u;u'u>u:y'thu-"; static char *esc_tb[] = { ISOLatin1_c, /* 0 = ascii */ ISOLatin1_c, ISOLatin2_c, ISOLatin3_c, ISOLatin4_c, ISOLatin5_c, ISOLatin6_c }; static struct u_ps { struct u_ps *next; char text[2]; } *user_ps; static char *trim_title(char *p, int first); /* -- print message for internal error and maybe stop -- */ void bug(char *msg, int fatal) { error(1, 0, "This cannot happen!\n" "Internal error: %s.\n", msg); if (fatal) { fprintf(stderr, "Emergency stop.\n\n"); exit(3); } fprintf(stderr, "Trying to continue...\n"); } /* -- print an error message -- */ void error(int sev, /* 0: warning, 1: error */ struct SYMBOL *s, char *fmt, ...) { va_list args; static struct SYMBOL *t; if (t != info['T' - 'A']) { char *p; t = info['T' - 'A']; p = &t->as.text[2]; while (isspace((unsigned char) *p)) p++; fprintf(stderr, " - In tune `%s':\n", p); } fprintf(stderr, sev == 0 ? "Warning " : "Error "); if (s != 0) { fprintf(stderr, "in line %d.%d", s->as.linenum, s->as.colnum); if (showerror) { s->as.flags |= ABC_F_ERROR; showerror++; } } fprintf(stderr, ": "); va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); } /* -- read a number with a unit -- */ float scan_u(char *str) { float a; int nch; if (sscanf(str, "%f%n", &a, &nch) == 1) { if (str[nch] == '\0' || str[nch] == ' ') return a PT; if (!strncasecmp(str + nch, "cm", 2)) return a CM; if (!strncasecmp(str + nch, "in", 2)) return a IN; if (!strncasecmp(str + nch, "pt", 2)) return a PT; } error(1, 0, "\n++++ Unknown unit value \"%s\"", str); return 20 PT; } /* -- capitalize a string -- */ static void cap_str(char *p) { while (*p != '\0') { #if 1 /* pb with toupper - works with ASCII only */ unsigned char c; c = (unsigned char) *p; if ((c >= 'a' && c <= 'z') || (c >= 0xe0 && c <= 0xfe)) *p = c & ~0x20; #else *p = toupper((unsigned char) *p); #endif p++; } } /* -- return the character width -- */ float cwid(unsigned char c) { short *w; unsigned enc; if ((enc = cfmt.encoding) >= sizeof cw_tb / sizeof cw_tb[0]) enc = 0; if (c < 160) /* (0xa0) */ w = ISOLatin1_w; else w = cw_tb[enc]; return (float) w[c] / 1000.; } /* -- change string taking care of some tex-style codes -- */ /* Puts \ in front of ( and ) in case brackets are not balanced, * interprets all ISOLatin1..6 escape sequences as defined in rfc1345. * Returns an estimate of the string width. */ float tex_str(char *s) { char *d, c1, c2, *p_enc, *p; int maxlen, i; float w, swfac; w = 0; d = tex_buf; maxlen = sizeof tex_buf - 1; /* have room for EOS */ if ((i = curft) <= 0) i = defft; swfac = cfmt.font_tb[i].swfac; i = font_enc[cfmt.font_tb[i].fnum]; if ((unsigned) i >= sizeof esc_tb / sizeof esc_tb[0]) i = 0; p_enc = esc_tb[i]; while ((c1 = *s++) != '\0') { switch (c1) { case '\\': /* backslash sequences */ if ((c1 = *s++) == '\0') break; if (c1 == ' ') goto addchar1; if (c1 == 't') { c1 = '\t'; goto addchar1; } if (c1 == '\\' || (c2 = *s) == '\0') { if (--maxlen <= 0) break; *d++ = '\\'; goto addchar1; } /* treat escape with octal value */ if ((unsigned) (c1 - '0') <= 3 && (unsigned) (c2 - '0') <= 7 && (unsigned) (s[1] - '0') <= 7) { #if 1 c1 = ((c1 - '0') << 6) + ((c2 - '0') << 3) + s[1] - '0'; s += 2; goto addchar1; #else if ((maxlen -= 4) <= 0) break; *d++ = '\\'; *d++ = c1; *d++ = c2; *d++ = s[1]; c1 = ((c1 - '0') << 6) + ((c2 - '0') << 3) + s[1] - '0'; w += cwid((unsigned char) c1) * swfac; s += 2; break; #endif } /* convert to rfc1345 */ switch (c1) { case '`': c1 = '!'; break; case '^': c1 = '>'; break; case '~': c1 = '?'; break; case '"': c1 = ':'; break; /* special TeX sequences */ case 'O': c1 = '/'; c2 = 'O'; s--; break; case 'o': c1 = '/'; c2 = 'o'; s--; break; case 'c': if (c2 == 'c' || c2 == 'C') c1 = ','; break; } switch (c2) { case '`': c2 = '!'; break; case '^': c2 = '>'; break; case '~': c2 = '?'; break; case '"': c2 = ':'; break; } for (i = 32 * 3, p = p_enc; --i >= 0; p += 2) { if ((*p == c1 && p[1] == c2) || (*p == c2 && p[1] == c1)) { s++; c1 = (p - p_enc) / 2 + 128 + 32; break; } } if (i < 0) c1 = s[-1]; goto addchar1; case '$': if (isdigit((unsigned char) *s) && (unsigned) (*s - '0') < FONT_UMAX) { i = *s - '0'; if (i == 0) i = defft; swfac = cfmt.font_tb[i].swfac; i = cfmt.font_tb[i].fnum; i = font_enc[i]; if ((unsigned) i >= sizeof esc_tb / sizeof esc_tb[0]) i = 0; p_enc = esc_tb[i]; } else if (*s == '$') { if (--maxlen <= 0) break; *d++ = c1; w += cwid((unsigned char) c1) * swfac; s++; } goto addchar1; case '(': case ')': /* ( ) becomes \( \) */ if (--maxlen <= 0) break; *d++ = '\\'; /* fall thru */ default: /* other characters: pass through */ addchar1: if (--maxlen <= 0) break; *d++ = c1; w += cwid((unsigned char) c1) * swfac; break; } } *d = '\0'; return w; } /* -- set the default font of a string -- */ void str_font(int ft) { curft = defft = ft; } /* -- get the current default font -- */ int get_str_font(void) { return defft; } /* -- output one string -- */ static void str_ft_out1(char *p, int l) { if (curft != outft) { if (strtx) { PUT1(")%s ", strop); strtx = 0; } if (curft == 0) curft = defft; set_font(curft); } if (!strtx) { PUT0("("); strtx = 1; } PUT2("%.*s", l, p); } /* -- output a string and the font changes -- */ static void str_ft_out(char *p, int end) { char *q; q = p; while (*p != '\0') { if (*p == '$') { if (isdigit((unsigned char) p[1]) && (unsigned) (p[1] - '0') < FONT_UMAX) { if (p > q) str_ft_out1(q, p - q); if (curft != p[1] - '0') { curft = p[1] - '0'; if (curft == 0) curft = defft; } p += 2; q = p; continue; } if (p[1] == '$') { str_ft_out1(q, p - q); q = ++p; } } p++; } if (p > q) str_ft_out1(q, p - q); if (end && strtx) { PUT1(")%s ", strop); strtx = 0; } } /* -- output a string, handling the font changes -- */ void str_out(char *p, int action) { if (curft <= 0) /* first call */ curft = defft; /* special case when font change at start of text */ /*---fixme: authorize 2 chars?*/ if (*p == '$' && isdigit((unsigned char) p[1]) && (unsigned) (p[1] - '0') < FONT_UMAX) { if (curft != p[1] - '0') { curft = p[1] - '0'; if (curft == 0) curft = defft; } p += 2; } /* direct output if no font change */ if (strchr(p, '$') == 0) { char *op; set_font(curft); if (action == A_CENTER) op = "c"; else if (action == A_RIGHT) op = "r"; else op = ""; PUT2("(%s)show%s ", p, op); return; } /* if not left aligned, build a PS function */ if (action == A_LEFT) strop = "show"; else { PUT0("/str{"); outft = -1; strop = "strop"; } str_ft_out(p, 1); /* output the string */ /* if not left aligned, call the PS function */ if (action == A_LEFT) return; PUT0("}def\n" "/strop/strw load def/w 0 def str w "); if (action == A_CENTER) PUT0("0.5 mul "); PUT0("neg 0 RM/strop/show load def str "); } /* -- output a string with TeX translation -- */ void put_str(char *str, int action) { tex_str(str); str_out(tex_buf, action); PUT0("\n"); } /* -- output a header information -- */ static void put_inf(struct SYMBOL *s) { char *p; p = s->as.text; if (p[1] == ':') p += 2; while (isspace((unsigned char) *p)) p++; put_str(p, A_LEFT); } /* -- output a header format '111 (222)' -- */ static void put_inf2r(struct SYMBOL *s1, struct SYMBOL *s2, int action) { char buf[256], *p, *q; if (s1 == 0) { s1 = s2; s2 = 0; } p = s1->as.text; if (p[1] == ':') p += 2; while (isspace((unsigned char) *p)) p++; if (s1->as.text[0] == 'T' && s1->as.text[1] == ':') p = trim_title(p, s1 == info['T' - 'A']); if (s2 != 0) { buf[sizeof buf - 1] = '\0'; strncpy(buf, p, sizeof buf - 1); q = buf + strlen(buf); if (q < buf + sizeof buf - 4) { *q++ = ' '; *q++ = '('; p = s2->as.text; if (p[1] == ':') p += 2; while (isspace((unsigned char) *p)) p++; strncpy(q, p, buf + sizeof buf - 2 - q); q += strlen(q); *q++ = ')'; *q = '\0'; } p = buf; } put_str(p, action); } /* -- add text to a block -- */ void add_to_text_block(char *s, int job) { float baseskip, lw; char *p, sep; struct FONTSPEC *f; /* if first line, set the fonts */ if (strtw < 0) { str_font(TEXTFONT); strlw = ((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) - cfmt.leftmargin - cfmt.rightmargin) / cfmt.scale; } if (curft > 0) f = &cfmt.font_tb[curft]; else f = &cfmt.font_tb[defft]; baseskip = f->size * cfmt.lineskipfac; /* follow lines */ if (job == T_LEFT || job == T_CENTER || job == T_RIGHT) { if (*s != '\0') { bskip(baseskip); if (job == T_LEFT) { PUT0("0 0 M "); put_str(s, A_LEFT); } else if (job == T_CENTER) { PUT1("%.1f 0 M ", strlw * 0.5); put_str(s, A_CENTER); } else { PUT1("%.1f 0 M ", strlw); put_str(s, A_RIGHT); } } else bskip(baseskip * 0.5); buffer_eob(); strtw = 0; return; } /* fill or justify lines */ if (strtw < 0) { /* if first line */ curft = defft; bskip(baseskip); PUT0("0 0 M "); if (job == T_FILL) strop = "show"; else { PUT0("/str{"); outft = -1; strop = "strop"; } strns = 0; strtw = 0; } if (*s == '\0') { /* empty line */ if (strtx) { PUT1(")%s", strop); strtx = 0; } if (job == T_JUSTIFY) PUT0("}def\n" "/strop/show load def str\n"); else PUT0("\n"); bskip(f->size * cfmt.lineskipfac * 1.5); buffer_eob(); PUT0("0 0 M "); if (job == T_JUSTIFY) { PUT0("/str{"); outft = -1; } strns = 0; strtw = 0; return; } p = s; for (;;) { while (*p != ' ' && *p != '\0') p++; sep = *p; *p = '\0'; lw = tex_str(s); if (strtw + lw > strlw) { if (strtx) { PUT1(")%s ", strop); strtx = 0; } if (job == T_JUSTIFY) { if (strns == 0) strns = 1; PUT2("}def\n" "/strop/strw load def/w 0 def str" "/w %.1f w sub %d div def" "/strop/jshow load def str ", strlw, strns); strns = 0; } bskip(cfmt.font_tb[curft].size * cfmt.lineskipfac); PUT0("0 0 M "); if (job == T_JUSTIFY) { PUT0("/str{"); outft = -1; } strtw = 0; } if (strtw != 0) { str_ft_out(" ", 0); strtw += cwid(' ') * cfmt.font_tb[curft].swfac; strns++; } str_ft_out(tex_buf, 0); strtw += lw; *p = sep; while (*p == ' ') p++; if (*p == '\0') break; s = p; } } /* -- write a text block -- */ void write_text_block(int job, int abc_state) { if (strtw < 0) return; if (strtx) { PUT1(")%s", strop); strtx = 0; } if (job == T_JUSTIFY) PUT0("}def\n" "/strop/show load def str\n"); else if (job == T_FILL) PUT0("\n"); bskip(cfmt.font_tb[TEXTFONT].size * cfmt.parskipfac); buffer_eob(); /* next line to allow pagebreak after each paragraph */ if (!epsf && abc_state != ABC_S_TUNE) write_buffer(); strtw = -1; } /* -- output a line of words after tune -- */ static int put_wline(char *p, float x, int right) { char *q, *r, sep; while (isspace((unsigned char) *p)) p++; if (*p == '$' && isdigit((unsigned char) p[1]) && (unsigned) (p[1] - '0') < FONT_UMAX) { if (curft != p[1] - '0') { curft = p[1] - '0'; if (curft == 0) curft = defft; } p += 2; } r = 0; q = p; if (isdigit((unsigned char) *p) || p[1] == '.') { while (*p != '\0') { p++; if (*p == ' ' || p[-1] == ':' || p[-1] == '.') break; } r = p; while (*p == ' ') p++; } /* on the left side, permit page break at empty lines or stanza start */ if (!right && (*p == '\0' || r != 0)) buffer_eob(); if (r != 0) { sep = *r; *r = '\0'; PUT1("%.1f 0 M ", x); put_str(q, A_RIGHT); *r = sep; } if (*p != '\0') { PUT1("%.1f 0 M ", x + 5); put_str(p, A_LEFT); } return *p == '\0' && r == 0; } /* -- output the words after tune -- */ void put_words(struct SYMBOL *words) { struct SYMBOL *s, *s_end, *s2; char *p; int i, n, have_text, max2col; float middle; str_font(WORDSFONT); /* see if we may have 2 columns */ middle = 0.5 * ((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) - cfmt.leftmargin - cfmt.rightmargin) / cfmt.scale; max2col = (int) ((middle - 45.) / (cwid('a') * cfmt.font_tb[WORDSFONT].swfac)); n = 0; have_text = 0; for (s = words; s != 0; s = s->next) { p = &s->as.text[2]; while (isspace((unsigned char) *p)) p++; if (strlen(p) > max2col) { n = 0; break; } if (*p == '\0') { if (have_text) { n++; have_text = 0; } } else have_text = 1; } if (n > 0) { n++; n /= 2; i = n; have_text = 0; s_end = words; for (;;) { p = &s_end->as.text[2]; while (isspace((unsigned char) *p)) p++; if (*p == '\0') { if (have_text && --i <= 0) break; have_text = 0; } else have_text = 1; s_end = s_end->next; } s2 = s_end->next; } else { s_end = 0; s2 = 0; } /* output the text */ bskip(cfmt.wordsspace); for (s = words; s != 0 || s2 != 0; ) { bskip(cfmt.lineskipfac * cfmt.font_tb[WORDSFONT].size); if (s != 0) { put_wline(&s->as.text[2], 45., 0); s = s->next; if (s == s_end) s = 0; } if (s2 != 0) { if (put_wline(&s2->as.text[2], 20. + middle, 1)) { if (--n == 0) { if (s != 0) n++; else if (s2->next != 0) { /* center the last words */ /*fixme: should compute the width average.. */ middle *= 0.6; } } } s2 = s2->next; } } buffer_eob(); } /* -- output history -- */ void put_history(void) { struct SYMBOL *s, *s2; float h; bskip(cfmt.textspace); str_font(HISTORYFONT); for (s = info['I' - 'A']; s != 0; s = s->next) { if ((s2 = info[s->as.text[0] - 'A']) == 0) continue; get_str(tex_buf, &s->as.text[1], 256); h = cfmt.font_tb[HISTORYFONT].size * cfmt.lineskipfac; set_font(HISTORYFONT); PUT1("0 0 M(%s)show ", tex_buf); for (;;) { put_inf(s2); if ((s2 = s2->next) == 0) break; bskip(h); PUT0("50 0 M "); } bskip(h * 1.2); buffer_eob(); } } /* -- move trailing "The" to front, set to uppercase letters or add xref -- */ static char *trim_title(char *p, int first) { char *b, *q; int l; static char buf[256]; q = 0; if (cfmt.titletrim) { q = strrchr(p, ','); if (q != 0) { if (q[1] != ' ' || !isupper(q[2]) || strchr(q + 2, ' ') != 0) q = 0; } } if (q == 0 && !cfmt.titlecaps && !(first && cfmt.withxrefs)) return p; /* keep the title as it is */ b = buf; if (first && cfmt.withxrefs) b += sprintf(b, "%s. ", &info['X' - 'A']->as.text[2]); if (q != 0) { strcpy(b, q + 2); b += strlen(q + 2); *b++ = ' '; l = q - p; if (l > buf + sizeof buf - b - 1) l = buf + sizeof buf - b - 1; } else l = buf + sizeof buf - b - 1; strncpy(b, p, l); b[l] = '\0'; if (cfmt.titlecaps) cap_str(buf); return buf; } /* -- write a title -- */ void write_title(struct SYMBOL *s) { char *p; p = &s->as.text[2]; while (isspace((unsigned char) *p)) p++; if (*p == '\0') return; p = trim_title(p, s == info['T' - 'A']); if (s == info['T' - 'A']) { bskip(cfmt.titlespace + cfmt.font_tb[TITLEFONT].size); set_font(TITLEFONT); } else { bskip(cfmt.subtitlespace + cfmt.font_tb[SUBTITLEFONT].size); set_font(SUBTITLEFONT); } if (cfmt.titleleft) PUT0("0 0 M("); else PUT1("%.1f 0 M(", 0.5 * ((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) - cfmt.leftmargin - cfmt.rightmargin) / cfmt.scale); tex_str(p); PUT2("%s)show%s\n", tex_buf, cfmt.titleleft ? "" : "c"); } /* -- write heading with format -- */ static void write_headform(float lwidth) { char *p, *q; struct SYMBOL *s; struct FONTSPEC *f; int align, i, j; float x, y, xa[3], ya[3], sz, yb[3]; char inf_nb[26]; INFO inf_s; char inf_ft[26]; float inf_sz[26]; char fmt[64]; memset(inf_nb, 0, sizeof inf_nb); memset(inf_ft, HISTORYFONT, sizeof inf_ft); inf_ft['A' - 'A'] = INFOFONT; inf_ft['C' - 'A'] = COMPOSERFONT; inf_ft['O' - 'A'] = COMPOSERFONT; inf_ft['P' - 'A'] = PARTSFONT; inf_ft['Q' - 'A'] = TEMPOFONT; inf_ft['R' - 'A'] = INFOFONT; inf_ft['T' - 'A'] = TITLEFONT; inf_ft['X' - 'A'] = TITLEFONT; memcpy(inf_s, info, sizeof inf_s); memset(inf_sz, 0, sizeof inf_sz); inf_sz['A' - 'A'] = cfmt.infospace; inf_sz['C' - 'A'] = cfmt.composerspace; inf_sz['O' - 'A'] = cfmt.composerspace; inf_sz['R' - 'A'] = cfmt.infospace; p = cfmt.titleformat; j = 0; for (;;) { while (isspace((unsigned char) *p)) p++; if (*p == '\0') break; i = *p - 'A'; if ((unsigned) i < 26) { inf_nb[i]++; switch (p[1]) { default: align = A_CENTER; break; case '1': align = A_RIGHT; p++; break; case '-': align = A_LEFT; p++; break; } if (j < sizeof fmt - 4) { fmt[j++] = i; fmt[j++] = align; } } else if (*p == ',') { if (j < sizeof fmt - 3) fmt[j++] = 126; /* next line */ } else if (*p == '+') { if (j > 0 && fmt[j - 1] < 125 && j < sizeof fmt - 3) fmt[j++] = 125; /* concatenate */ /*new fixme: add free text "..." ?*/ } p++; } fmt[j++] = 126; /* newline */ fmt[j] = 127; /* end of format */ ya[0] = ya[1] = ya[2] = cfmt.titlespace;; xa[0] = 0; xa[1] = lwidth * 0.5; xa[2] = lwidth; p = fmt; for (;;) { yb[0] = yb[1] = yb[2] = y = 0; q = p; for (;;) { i = *q++; if (i >= 126) /* if newline */ break; align = *q++; if (yb[align + 1] != 0) continue; s = inf_s[i]; if (s == 0 || inf_nb[i] == 0) continue; j = inf_ft[i]; f = &cfmt.font_tb[j]; sz = f->size * 1.1 + inf_sz[i]; if (y < sz) y = sz; yb[align + 1] = sz; /*fixme:should count the height of the concatenated field*/ if (*q == 125) q++; } for (i = 0; i < 3; i++) ya[i] += y - yb[i]; for (;;) { i = *p++; if (i >= 126) /* if newline */ break; align = *p++; s = inf_s[i]; if (s == 0 || inf_nb[i] == 0) continue; j = inf_ft[i]; str_font(j); x = xa[align + 1]; f = &cfmt.font_tb[j]; sz = f->size * 1.1 + inf_sz[i]; y = ya[align + 1] + sz; PUT2("%.1f %.1f M ", x, -y); if (*p == 125) { /* concatenate */ p++; /*fixme: do it work with different fields*/ if (*p == i && p[1] == align && s->next != 0) { char buf[256], *r; q = s->as.text; if (q[1] == ':') q += 2; while (isspace((unsigned char) *q)) q++; if (i == 'T' - 'A') q = trim_title(q, s == inf_s['T' - 'A']); strncpy(buf, q, sizeof buf - 1); buf[sizeof buf - 1] = '\0'; j = strlen(buf); if (j < sizeof buf - 1) { buf[j] = ' '; buf[j + 1] = '\0'; } s = s->next; q = s->as.text; if (q[1] == ':') q += 2; while (isspace((unsigned char) *q)) q++; if (s->as.text[0] == 'T' && s->as.text[1] == ':') q = trim_title(q, 0); r = buf + strlen(buf); strncpy(r, q, buf + sizeof buf - r - 1); tex_str(buf); str_out(tex_buf, align); PUT0("\n"); inf_nb[i]--; p += 2; } } else if (i == 'Q' - 'A') { /* special case for tempo */ if (align != A_LEFT) { float w; w = tempo_width(s); if (align == A_CENTER) PUT1("-%.1f 0 RM ", w * 0.5); else PUT1("-%.1f 0 RM ", w); } write_tempo(s, 0, 0.75); } else put_inf2r(s, 0, align); if (inf_s[i] == info['T' - 'A']) { inf_ft[i] = SUBTITLEFONT; str_font(SUBTITLEFONT); f = &cfmt.font_tb[SUBTITLEFONT]; inf_sz[i] = cfmt.subtitlespace; sz = f->size * 1.1 + inf_sz[i]; } s = s->next; if (inf_nb[i] == 1) { while (s != 0) { y += sz; PUT2("%.1f %.1f M ", x, -y); put_inf2r(s, 0, align); s = s->next; } } inf_s[i] = s; inf_nb[i]--; ya[align + 1] = y; } if (ya[1] > ya[0]) ya[0] = ya[1]; if (ya[2] > ya[0]) ya[0] = ya[2]; if (*p == 127) { bskip(ya[0]); break; } ya[1] = ya[2] = ya[0]; } } /* -- output the tune heading -- */ void write_heading(struct abctune *t) { struct SYMBOL *s, *rhythm, *area, *author; float lwidth, down1, down2; lwidth = ((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) - cfmt.leftmargin - cfmt.rightmargin) / cfmt.scale; if (cfmt.titleformat != 0) { write_headform(lwidth); bskip(cfmt.musicspace); return; } /* titles */ for (s = info['T' - 'A']; s != 0; s = s->next) write_title(s); /* rhythm, composer, origin */ down1 = cfmt.composerspace + cfmt.font_tb[COMPOSERFONT].size; rhythm = (first_voice->key.bagpipe && !cfmt.infoline) ? info['R' - 'A'] : 0; if (rhythm) { str_font(COMPOSERFONT); PUT1("0 %.1f M ", -(cfmt.composerspace + cfmt.font_tb[COMPOSERFONT].size)); put_inf(rhythm); down1 -= cfmt.font_tb[COMPOSERFONT].size; } area = author = 0; if (t->abc_vers < 2) area = info['A' - 'A']; else author = info['A' - 'A']; if ((s = info['C' - 'A']) != 0 || info['O' - 'A'] || author != 0) { float xcomp; int align; str_font(COMPOSERFONT); bskip(cfmt.composerspace); if (cfmt.aligncomposer < 0) { xcomp = 0; align = A_LEFT; } else if (cfmt.aligncomposer == 0) { xcomp = lwidth * 0.5; align = A_CENTER; } else { xcomp = lwidth; align = A_RIGHT; } down2 = down1; if (author != 0) { for (;;) { bskip(cfmt.font_tb[COMPOSERFONT].size); down2 += cfmt.font_tb[COMPOSERFONT].size; PUT0("0 0 M "); put_inf(author); if ((author = author->next) == 0) break; } } if ((s = info['C' - 'A']) != 0 || info['O' - 'A']) { if (cfmt.aligncomposer >= 0 && down1 != down2) bskip(down1 - down2); for (;;) { bskip(cfmt.font_tb[COMPOSERFONT].size); PUT1("%.1f 0 M ", xcomp); put_inf2r(s, (s == 0 || s->next == 0) ? info['O' - 'A'] : 0, align); if (s == 0) break; if ((s = s->next) == 0) break; down1 += cfmt.font_tb[COMPOSERFONT].size; } if (down2 > down1) bskip(down2 - down1); } rhythm = rhythm ? 0 : info['R' - 'A']; if ((rhythm || area) && cfmt.infoline) { /* if only one of rhythm or area then do not use ()'s * otherwise output 'rhythm (area)' */ str_font(INFOFONT); bskip(cfmt.font_tb[INFOFONT].size + cfmt.infospace); PUT1("%.1f 0 M ", lwidth); put_inf2r(rhythm, area, A_RIGHT); down1 += cfmt.font_tb[INFOFONT].size + cfmt.infospace; } down2 = 0; } else down2 = cfmt.composerspace + cfmt.font_tb[COMPOSERFONT].size; /* parts */ if (info['P' - 'A']) { down1 = cfmt.partsspace + cfmt.font_tb[PARTSFONT].size - down1; if (down1 > 0) down2 += down1; if (down2 > 0.01) bskip(down2); str_font(PARTSFONT); PUT0("0 0 M "); put_inf(info['P' - 'A']); down2 = 0; } bskip(down2 + cfmt.musicspace); } /* -- memorize a PS line -- */ void user_ps_add(char *s) { struct u_ps *t, *r; int l; l = strlen(s); t = (struct u_ps *) malloc(sizeof *user_ps - sizeof user_ps->text + l + 1); strcpy(t->text, s); t->next = 0; if ((r = user_ps) == 0) user_ps = t; else { while (r->next != 0) r = r->next; r->next = t; } } /* -- output the user defined postscript sequences -- */ void user_ps_write(void) { struct u_ps *t, *r; if ((t = user_ps) == 0) return; user_ps = 0; for (;;) { if (t->text[0] == '\001') { /* PS file */ FILE *f; char line[BSIZE]; if ((f = fopen(&t->text[1], "r")) == 0) { error(1, 0, "Cannot open PS file '%s'", &t->text[1]); } else { while (fgets(line, sizeof line, f)) /* copy the file */ fwrite(line, 1, strlen(line), fout); fclose(f); } } else fprintf(fout, "%s\n", t->text); r = t->next; free(t); if ((t = r) == 0) break; } }