lorid

convert chordpro to pdf
git clone git://git.relim.de/lorid.git
Log | Files | Refs | README | LICENSE

commit ec7555903f9aa96f70cad05acd9c05e9a7098259
parent a4368b630d09cf6b5ba2631a93ce1f314d0387f4
Author: nibo <nibo@relim.de>
Date:   Sun,  6 Oct 2024 11:05:06 +0200

Speed up enum to string conversion

Instead of having a switch statement for
getting the string represenation for an
enum now there is a character array for
each enum with the elements ordered the
same way as the enum variants. Then you
can access the string like this:
enum_variants[ENUM_VARIANT] => "ENUM_VARIANT"

Diffstat:
Mchordpro.c | 703+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mchordpro.h | 118++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mconfig.c | 47+++++++++++++++++++++++------------------------
Mtodo | 2+-
4 files changed, 492 insertions(+), 378 deletions(-)

diff --git a/chordpro.c b/chordpro.c @@ -29,12 +29,11 @@ static const char *metadata_directives[] = { static const char *formatting_directives[] = { "comment", "c", "highlight", "comment_italic", "ci", - "comment_box", "cb", "image", NULL + "comment_box", "cb", NULL }; -static const char *preamble_directives[] = { - "new_song", "ns", NULL -}; +static const char *image_directives[] = { "image", NULL }; +static const char *preamble_directives[] = { "new_song", "ns", NULL }; static const char *font_directives[] = { "chordfont", "cf", "chordsize", "cs", "chordcolour", @@ -55,6 +54,26 @@ static const char *output_directives[] = { "new_page", "np", "column_break", "colb", NULL }; +static const char *font_families[] = { "normal", "sans", "serif", "monospace", "empty" }; +static const char *font_styles[] = { "normal", "oblique", "italic", "empty" }; +static const char *font_weights[] = { "normal", "bold", "empty" }; +static const char *line_styles[] = { "single", "double", "none", "empty" }; +static const char *chord_qualifiers[] = { "", "m", "", "+", "°" }; + +static const char *state_enums[] = { + "STATE_LYRICS", + "STATE_DIRECTIVE_NAME", + "STATE_DIRECTIVE_VALUE", + "STATE_CHORD", + "STATE_ANNOTATION", + "STATE_MARKUP_TAG_BEGIN", + "STATE_MARKUP_TAG_END", + "STATE_MARKUP_TAG", + "STATE_MARKUP_ATTR_NAME", + "STATE_MARKUP_ATTR_VALUE", + "STATE_COMMENT" +}; + struct StyleProperty default_style_properties[18] = { { SF_CHORD, SPT_FONT, { .font_name = NULL } }, { SF_CHORD, SPT_SIZE, { .font_size = EMPTY } }, @@ -163,21 +182,65 @@ static int *g_transpose = NULL; #ifdef DEBUG -static const char *cho_debug_chord_qualifier_to_string(enum ChordQualifier qual) -{ - switch (qual) { - case CQ_MIN: - return "CQ_MIN"; - case CQ_MAJ: - return "CQ_MAJ"; - case CQ_AUG: - return "CQ_AUG"; - case CQ_DIM: - return "CQ_DIM"; - default: - return "CQ_EMPTY"; - } -} +static char *chord_qualifier_enums[] = { + "CQ_EMPTY", + "CQ_MIN", + "CQ_MAJ", + "CQ_AUG", + "CQ_DIM" +}; + +const char *directive_type_enums[] = { + "DT_EMPTY", + "DT_ENVIRONMENT", + "DT_METADATA", + "DT_FORMATTING", + "DT_IMAGE", + "DT_PREAMBLE", + "DT_FONT", + "DT_CHORD", + "DT_OUTPUT", + "DT_CUSTOM" +}; + +const char *section_type_enums[] = { + "ST_EMPTY", + "ST_NEWSONG", + "ST_CHORUS", + "ST_VERSE", + "ST_BRIDGE", + "ST_TAB", + "ST_GRID", + "ST_CUSTOM" +}; + +const char *position_enums[] = { + "POS_EMPTY", + "POS_START", + "POS_END" +}; + +const char *song_fragment_type_enums[] = { + "SF_EMPTY", + "SF_CHORD", + "SF_ANNOT", + "SF_CHORUS", + "SF_FOOTER", + "SF_GRID", + "SF_TAB", + "SF_TOC", + "SF_TEXT", + "SF_TITLE", + "SF_SUBTITLE", + "SF_LABEL" +}; + +const char *style_property_type_enums[] = { + "SPT_EMPTY", + "SPT_FONT", + "SPT_SIZE", + "SPT_COLOR" +}; static void cho_debug_chord_print(struct ChoChord *chord) { @@ -189,7 +252,7 @@ static void cho_debug_chord_print(struct ChoChord *chord) } else { printf("root: 'NULL'\n"); } - printf("qual: '%s'\n", cho_debug_chord_qualifier_to_string(chord->qual)); + printf("qual: '%s'\n", chord_qualifier_enums[chord->qual]); if (chord->ext) { printf("ext: '%s'\n", chord->ext); } else { @@ -205,35 +268,6 @@ static void cho_debug_chord_print(struct ChoChord *chord) #endif /* DEBUG */ -static const char *cho_state_to_string(enum State state) -{ - switch (state) { - case STATE_LYRICS: - return "STATE_LYRICS"; - case STATE_DIRECTIVE_NAME: - return "STATE_DIRECTIVE_NAME"; - case STATE_DIRECTIVE_VALUE: - return "STATE_DIRECTIVE_VALUE"; - case STATE_CHORD: - return "STATE_CHORD"; - case STATE_ANNOTATION: - return "STATE_ANNOTATION"; - case STATE_MARKUP_TAG_BEGIN: - return "STATE_MARKUP_TAG_BEGIN"; - case STATE_MARKUP_TAG_END: - return "STATE_MARKUP_TAG_END"; - case STATE_MARKUP_TAG: - return "STATE_MARKUP_TAG"; - case STATE_MARKUP_ATTR_NAME: - return "STATE_MARKUP_ATTR_NAME"; - case STATE_MARKUP_ATTR_VALUE: - return "STATE_MARKUP_ATTR_VALUE"; - case STATE_COMMENT: - return "STATE_COMMENT"; - } - return ""; -} - static inline bool is_whitespace(char c) { if ( @@ -475,13 +509,13 @@ void cho_fonts_free(struct Font **fonts) enum FontFamily cho_font_family_parse(const char *str) { - if (strcasecmp(str, "sans") == 0) { + if (!strcmp(str, font_families[FF_SANS])) { return FF_SANS; - } else if (strcasecmp(str, "serif") == 0) { + } else if (!strcmp(str, font_families[FF_SERIF])) { return FF_SERIF; - } else if (strcasecmp(str, "monospace") == 0) { + } else if (!strcmp(str, font_families[FF_MONOSPACE])) { return FF_MONOSPACE; - } else if (strcasecmp(str, "normal") == 0) { + } else if (!strcmp(str, font_families[FF_NORMAL])) { return FF_NORMAL; } else { return FF_EMPTY; @@ -490,25 +524,16 @@ enum FontFamily cho_font_family_parse(const char *str) const char *cho_font_family_to_config_string(enum FontFamily font_family) { - switch (font_family) { - case FF_SANS: - return "sans"; - case FF_SERIF: - return "serif"; - case FF_MONOSPACE: - return "monospace"; - default: - return "normal"; - } + return font_families[font_family]; } enum FontStyle cho_font_style_parse(const char *str) { - if (strcasecmp(str, "italic") == 0) { + if (!strcmp(str, font_styles[FS_ITALIC])) { return FS_ITALIC; - } else if (strcasecmp(str, "oblique") == 0) { + } else if (!strcmp(str, font_styles[FS_OBLIQUE])) { return FS_OBLIQUE; - } else if (strcasecmp(str, "normal") == 0) { + } else if (!strcmp(str, font_styles[FS_ROMAN])) { return FS_ROMAN; } else { return FS_EMPTY; @@ -517,11 +542,7 @@ enum FontStyle cho_font_style_parse(const char *str) const char *cho_font_style_to_config_string(enum FontStyle style) { - if (style == FS_ITALIC) - return "italic"; - if (style == FS_OBLIQUE) - return "oblique"; - return "normal"; + return font_styles[style]; } enum FontWeight cho_font_weight_parse(const char *str) @@ -537,9 +558,7 @@ enum FontWeight cho_font_weight_parse(const char *str) const char *cho_font_weight_to_config_string(enum FontWeight weight) { - if (weight == FW_BOLD) - return "bold"; - return "normal"; + return font_weights[weight]; } void cho_font_print_as_toml(struct Font *font, const char *section) @@ -583,16 +602,66 @@ enum LineStyle cho_linestyle_parse(const char *str) const char *cho_linestyle_to_config_string(enum LineStyle linestyle) { - switch (linestyle) { - case LS_SINGLE: - return "single"; - case LS_DOUBLE: - return "double"; - default: - return "none"; + return line_styles[linestyle]; +} + +#ifdef DEBUG + +static void cho_debug_the_default_style_properties(void) +{ + unsigned int i; + for (i = 0; i<LENGTH(default_style_properties); i++) { + printf( + "%s %s ", + song_fragment_type_enums[default_style_properties[i].ftype], + style_property_type_enums[default_style_properties[i].type] + ); + switch (default_style_properties[i].type) { + case SPT_FONT: + if (default_style_properties[i].u.font_name) + printf("%s\n", default_style_properties[i].u.font_name); + else + printf("NULL\n"); + break; + case SPT_SIZE: + printf("%.1f\n", default_style_properties[i].u.font_size); + break; + case SPT_COLOR: + if (default_style_properties[i].u.foreground_color) + printf("%s\n", cho_rgbcolor_to_string(default_style_properties[i].u.foreground_color)); + else + printf("NULL\n"); + break; + default: + printf("Invalid StylePropertyType value '%d'.\n", default_style_properties[i].type); + } } } +void cho_debug_style_print(struct Style *style) +{ + printf("---- BEGIN STYLE ----\n"); + cho_font_print(style->font); + printf("foreground_color: %s\n", cho_rgbcolor_to_string(style->foreground_color)); + printf("background_color: %s\n", cho_rgbcolor_to_string(style->background_color)); + printf("underline_style: %s\n", cho_linestyle_to_config_string(style->underline_style)); + printf("underline_color: %s\n", cho_rgbcolor_to_string(style->underline_color)); + printf("overline_style: %s\n", cho_linestyle_to_config_string(style->overline_style)); + printf("overline_color: %s\n", cho_rgbcolor_to_string(style->overline_color)); + printf("strikethrough: %d\n", style->strikethrough); + printf("strikethrough_color: %s\n", cho_rgbcolor_to_string(style->strikethrough_color)); + printf("boxed: %d\n", style->boxed); + printf("boxed_color: %s\n", cho_rgbcolor_to_string(style->boxed_color)); + printf("rise: %f\n", style->rise); + if (style->href) + printf("href: %s\n", style->href); + else + printf("href: NULL\n"); + printf("---- END STYLE ------\n\n"); +} + +#endif /* DEBUG */ + static bool cho_style_property_apply_default(enum SongFragmentType current_ftype, enum StylePropertyType ptype, struct Style *style) { unsigned int i; @@ -1286,12 +1355,6 @@ static struct Style *cho_tag_style_inherit(struct Tag **tags, int prev_index) return NULL; } -static void cho_directive_free(struct ChoDirective *directive) -{ - cho_style_free(directive->style); - free(directive); -} - static struct ChoMetadata *cho_metadata_new(void) { struct ChoMetadata *meta = malloc(sizeof(struct ChoMetadata)); @@ -1539,20 +1602,6 @@ static char *cho_chord_qualifier_strip(const char *str) return strdup(str); } -static const char *cho_chord_qualifier_to_string(enum ChordQualifier qual) -{ - switch (qual) { - case CQ_MIN: - return "m"; - case CQ_AUG: - return "+"; - case CQ_DIM: - return "°"; - default: - return ""; - } -} - static int cho_chord_qualifier_and_extension_parse(const char *str, struct ChoChord *chord) { int i; @@ -1674,7 +1723,7 @@ char *cho_chord_name_generate(struct ChoChord *chord) name[n] = chord->root[i]; n++; } - const char *qual = cho_chord_qualifier_to_string(chord->qual); + const char *qual = chord_qualifiers[chord->qual]; name_len += strlen(qual); name = realloc(name, name_len * sizeof(char)); for (i = 0; qual[i]; i++) { @@ -1866,6 +1915,134 @@ void cho_songs_free(struct ChoSong **songs) free(songs); } +static struct ChoImage *cho_image_new(void) +{ + struct ChoImage *image = malloc(sizeof(struct ChoImage)); + image->id = NULL; + image->src = NULL; + image->width = EMPTY; + image->height = EMPTY; + image->scale = EMPTY; + image->align = A_CENTER; + image->border = EMPTY; + image->spread_space = EMPTY; + image->href = NULL; + image->x = EMPTY; + image->y = EMPTY; + image->anchor = AN_FLOAT; + image->dx = EMPTY; + image->dy = EMPTY; + image->w = EMPTY; + image->h = EMPTY; + return image; +} + +static void cho_image_free(struct ChoImage *image) +{ + free(image->id); + free(image->src); + free(image->href); + free(image); +} + +enum OptionState { + OS_NAME, + OS_VALUE +}; + +static struct ChoImage *cho_image_directive_parse(const char *str, size_t line_number) +{ + struct ChoImage *image = cho_image_new(); + int i = 0; + while (is_whitespace(str[i])) { + i++; + } + char c; + enum OptionState state = OS_NAME; + enum AttrValueSyntax avs = AVS_NO; + char name[6+1]; + char value[URL_MAX_LEN+1]; + int n = 0; + int v = 0; + memset(name, 0, sizeof(name)); + memset(value, 0, sizeof(value)); + for (; str[i] != 0; i++) { + c = str[i]; + switch (state) { + case OS_NAME: + if (is_whitespace(c)) { + if (n == 0) { + break; + } else { + name[n] = 0; + fprintf(stderr, "ERR: Option with name '%s' in image directive in line '%ld' has no value.\n", name, line_number); + return NULL; + } + } + if (c == '=') { + name[n] = 0; + memset(name, 0, n); + n = 0; + state = OS_VALUE; + break; + } + if (n > 5) { + fprintf(stderr, "ERR: Option name in image directive in line '%ld' is too long.\n", line_number); + return NULL; + } + name[n] = c; + n++; + break; + case OS_VALUE: + if (avs == AVS_NO) { + if (is_whitespace(c)) { + fprintf(stderr, "ERR: Whitespace character after equals sign in image directive in line '%ld' is invalid.\n", line_number); + return NULL; + } + if (c == '\'') { + avs = AVS_APOSTROPHE; + } else if (c == '"') { + avs = AVS_QUOTATION_MARK; + } else { + avs = AVS_UNQUOTED; + value[v] = c; + v++; + } + break; + } + if (c == '\n') { + fprintf(stderr, "ERR: Newline character inside an option value in image directive in line '%ld' is invalid.\n", line_number); + return NULL; + } + if ( + (avs == AVS_APOSTROPHE && c == '\'') || + (avs == AVS_QUOTATION_MARK && c == '"') || + (avs == AVS_UNQUOTED && (c == ' ' || c == '\t')) + ) { + value[v] = 0; + printf("value: '%s'\n", value); + memset(value, 0, v); + v = 0; + avs = AVS_NO; + state = OS_NAME; + break; + } + value[v] = c; + v++; + break; + } + } + if (avs == AVS_UNQUOTED) { + value[v] = 0; + printf("value: '%s'\n", value); + } + return image; +} + +/* static struct ChoImage *cho_image_tag_parse(const char *str) +{ +} */ + static struct ChoDirective *cho_directive_new(void) { struct ChoDirective *directive = malloc(sizeof(struct ChoDirective)); @@ -1879,13 +2056,19 @@ static struct ChoDirective *cho_directive_new(void) return directive; } +static void cho_directive_free(struct ChoDirective *directive) +{ + cho_style_free(directive->style); + free(directive); +} + static struct ChoDirective *cho_directive_parse(const char *name) { struct ChoDirective *directive = cho_directive_new(); int i = 0; if ( - strcmp(name, environment_directives[0]) == 0 || - strcmp(name, environment_directives[1]) == 0 + strcmp(name, environment_directives[START_OF_CHORUS]) == 0 || + strcmp(name, environment_directives[SOC]) == 0 ) { directive->dtype = DT_ENVIRONMENT; directive->position = POS_START; @@ -1893,8 +2076,8 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_CHORUS; goto END; } else if ( - strcmp(name, environment_directives[2]) == 0 || - strcmp(name, environment_directives[3]) == 0 + strcmp(name, environment_directives[END_OF_CHORUS]) == 0 || + strcmp(name, environment_directives[EOC]) == 0 ) { directive->dtype = DT_ENVIRONMENT; directive->position = POS_END; @@ -1902,8 +2085,8 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_TEXT; goto END; } else if ( - strcmp(name, environment_directives[5]) == 0 || - strcmp(name, environment_directives[6]) == 0 + strcmp(name, environment_directives[START_OF_VERSE]) == 0 || + strcmp(name, environment_directives[SOV]) == 0 ) { directive->dtype = DT_ENVIRONMENT; directive->position = POS_START; @@ -1911,8 +2094,8 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_TEXT; goto END; } else if ( - strcmp(name, environment_directives[7]) == 0 || - strcmp(name, environment_directives[8]) == 0 + strcmp(name, environment_directives[END_OF_VERSE]) == 0 || + strcmp(name, environment_directives[EOV]) == 0 ) { directive->dtype = DT_ENVIRONMENT; directive->position = POS_END; @@ -1920,8 +2103,8 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_TEXT; goto END; } else if ( - strcmp(name, environment_directives[9]) == 0 || - strcmp(name, environment_directives[10]) == 0 + strcmp(name, environment_directives[START_OF_BRIDGE]) == 0 || + strcmp(name, environment_directives[SOB]) == 0 ) { directive->dtype = DT_ENVIRONMENT; directive->position = POS_START; @@ -1929,8 +2112,8 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_TEXT; goto END; } else if ( - strcmp(name, environment_directives[11]) == 0 || - strcmp(name, environment_directives[12]) == 0 + strcmp(name, environment_directives[END_OF_BRIDGE]) == 0 || + strcmp(name, environment_directives[EOB]) == 0 ) { directive->dtype = DT_ENVIRONMENT; directive->position = POS_END; @@ -1938,8 +2121,8 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_TEXT; goto END; } else if ( - strcmp(name, environment_directives[13]) == 0 || - strcmp(name, environment_directives[14]) == 0 + strcmp(name, environment_directives[START_OF_TAB]) == 0 || + strcmp(name, environment_directives[SOT]) == 0 ) { directive->dtype = DT_ENVIRONMENT; directive->position = POS_START; @@ -1947,8 +2130,8 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_TAB; goto END; } else if ( - strcmp(name, environment_directives[15]) == 0 || - strcmp(name, environment_directives[16]) == 0 + strcmp(name, environment_directives[END_OF_TAB]) == 0 || + strcmp(name, environment_directives[EOT]) == 0 ) { directive->dtype = DT_ENVIRONMENT; directive->position = POS_END; @@ -1956,8 +2139,8 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_TEXT; goto END; } else if ( - strcmp(name, environment_directives[17]) == 0 || - strcmp(name, environment_directives[18]) == 0 + strcmp(name, environment_directives[START_OF_GRID]) == 0 || + strcmp(name, environment_directives[SOG]) == 0 ) { directive->dtype = DT_ENVIRONMENT; directive->position = POS_START; @@ -1965,8 +2148,8 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_GRID; goto END; } else if ( - strcmp(name, environment_directives[19]) == 0 || - strcmp(name, environment_directives[20]) == 0 + strcmp(name, environment_directives[END_OF_GRID]) == 0 || + strcmp(name, environment_directives[EOG]) == 0 ) { directive->dtype = DT_ENVIRONMENT; directive->position = POS_END; @@ -1983,28 +2166,32 @@ static struct ChoDirective *cho_directive_parse(const char *name) } i = 0; if ( - strcmp(formatting_directives[0], name) == 0 || - strcmp(formatting_directives[1], name) == 0 || - strcmp(formatting_directives[2], name) == 0 + strcmp(formatting_directives[COMMENT], name) == 0 || + strcmp(formatting_directives[C], name) == 0 || + strcmp(formatting_directives[HIGHLIGHT], name) == 0 ) { directive->style->background_color = cho_rgbcolor_new(228, 228, 228); directive->dtype = DT_FORMATTING; goto END; } else if ( - strcmp(formatting_directives[3], name) == 0 || - strcmp(formatting_directives[4], name) == 0 + strcmp(formatting_directives[COMMENT_ITALIC], name) == 0 || + strcmp(formatting_directives[CI], name) == 0 ) { directive->style->font->style = FS_ITALIC; directive->dtype = DT_FORMATTING; goto END; } else if ( - strcmp(formatting_directives[5], name) == 0 || - strcmp(formatting_directives[6], name) == 0 + strcmp(formatting_directives[COMMENT_BOX], name) == 0 || + strcmp(formatting_directives[CB], name) == 0 ) { directive->style->boxed = true; directive->dtype = DT_FORMATTING; goto END; } + if (strcmp(image_directives[IMAGE], name) == 0) { + directive->dtype = DT_IMAGE; + goto END; + } while (preamble_directives[i] != NULL) { if (strcmp(preamble_directives[i], name) == 0) { directive->dtype = DT_PREAMBLE; @@ -2014,122 +2201,122 @@ static struct ChoDirective *cho_directive_parse(const char *name) i++; } if ( - strcmp(font_directives[0], name) == 0 || - strcmp(font_directives[1], name) == 0 + strcmp(font_directives[CHORDFONT], name) == 0 || + strcmp(font_directives[CF], name) == 0 ) { directive->dtype = DT_FONT; directive->sprop = SPT_FONT; directive->ftype = SF_CHORD; goto END; } else if ( - strcmp(font_directives[2], name) == 0 || - strcmp(font_directives[3], name) == 0 + strcmp(font_directives[CHORDSIZE], name) == 0 || + strcmp(font_directives[CS], name) == 0 ) { directive->dtype = DT_FONT; directive->sprop = SPT_SIZE; directive->ftype = SF_CHORD; goto END; - } else if (strcmp(font_directives[4], name) == 0) { + } else if (strcmp(font_directives[CHORDCOLOR], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_COLOR; directive->ftype = SF_CHORD; goto END; - } else if (strcmp(font_directives[5], name) == 0) { + } else if (strcmp(font_directives[CHORUSFONT], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_FONT; directive->ftype = SF_CHORUS; goto END; - } else if (strcmp(font_directives[6], name) == 0) { + } else if (strcmp(font_directives[CHORUSSIZE], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_SIZE; directive->ftype = SF_CHORUS; goto END; - } else if (strcmp(font_directives[7], name) == 0) { + } else if (strcmp(font_directives[CHORUSCOLOR], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_COLOR; directive->ftype = SF_CHORUS; goto END; - } else if (strcmp(font_directives[8], name) == 0) { + } else if (strcmp(font_directives[GRIDFONT], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_FONT; directive->ftype = SF_GRID; goto END; - } else if (strcmp(font_directives[9], name) == 0) { + } else if (strcmp(font_directives[GRIDSIZE], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_SIZE; directive->ftype = SF_GRID; goto END; - } else if (strcmp(font_directives[10], name) == 0) { + } else if (strcmp(font_directives[GRIDCOLOR], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_COLOR; directive->ftype = SF_GRID; goto END; - } else if (strcmp(font_directives[11], name) == 0) { + } else if (strcmp(font_directives[TABFONT], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_FONT; directive->ftype = SF_TAB; goto END; - } else if (strcmp(font_directives[12], name) == 0) { + } else if (strcmp(font_directives[TABSIZE], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_SIZE; directive->ftype = SF_TAB; goto END; - } else if (strcmp(font_directives[13], name) == 0) { + } else if (strcmp(font_directives[TABCOLOR], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_COLOR; directive->ftype = SF_TAB; goto END; } else if ( - strcmp(font_directives[14], name) == 0 || - strcmp(font_directives[15], name) == 0 + strcmp(font_directives[TEXTFONT], name) == 0 || + strcmp(font_directives[TF], name) == 0 ) { directive->dtype = DT_FONT; directive->sprop = SPT_FONT; directive->ftype = SF_TEXT; goto END; } else if ( - strcmp(font_directives[16], name) == 0 || - strcmp(font_directives[17], name) == 0 + strcmp(font_directives[TEXTSIZE], name) == 0 || + strcmp(font_directives[TS], name) == 0 ) { directive->dtype = DT_FONT; directive->sprop = SPT_SIZE; directive->ftype = SF_TEXT; goto END; - } else if (strcmp(font_directives[18], name) == 0) { + } else if (strcmp(font_directives[TEXTCOLOR], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_COLOR; directive->ftype = SF_TEXT; goto END; - } else if (strcmp(font_directives[19], name) == 0) { + } else if (strcmp(font_directives[TITLEFONT], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_FONT; directive->ftype = SF_TITLE; goto END; - } else if (strcmp(font_directives[20], name) == 0) { + } else if (strcmp(font_directives[TITLESIZE], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_SIZE; directive->ftype = SF_TITLE; goto END; - } else if (strcmp(font_directives[21], name) == 0) { + } else if (strcmp(font_directives[TITLECOLOR], name) == 0) { directive->dtype = DT_FONT; directive->sprop = SPT_COLOR; directive->ftype = SF_TITLE; goto END; } - if (strcmp(chord_directives[0], name) == 0) { + if (strcmp(chord_directives[TRANSPOSE], name) == 0) { directive->dtype = DT_CHORD; goto END; } if ( - strcmp(output_directives[0], name) == 0 || - strcmp(output_directives[1], name) == 0 + strcmp(output_directives[NEW_PAGE], name) == 0 || + strcmp(output_directives[NP], name) == 0 ) { directive->dtype = DT_OUTPUT; directive->btype = BT_PAGE; goto END; } else if ( - strcmp(output_directives[2], name) == 0 || - strcmp(output_directives[3], name) == 0 + strcmp(output_directives[COLUMN_BREAK], name) == 0 || + strcmp(output_directives[COLB], name) == 0 ) { directive->dtype = DT_OUTPUT; directive->btype = BT_COLUMN; @@ -2165,7 +2352,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) char custom_directive[64]; enum State state = STATE_LYRICS; enum State prev_state = STATE_LYRICS; - unsigned int line_number = 1; + size_t line_number = 1; int dn = 0; int dv = 0; int ch = 0; @@ -2205,6 +2392,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) struct Style *tag_style; struct StyleProperty sprop; struct ChoChord *tmp_chord; + struct ChoImage *image; while (feof(fp) == 0) { read = fread(&buf, 1, 1, fp); if (read == 1) { @@ -2357,6 +2545,9 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) case DT_FORMATTING: fprintf(stderr, "WARN: Formatting directive '%s' has no value.\n", directive_name); break; + case DT_IMAGE: + fprintf(stderr, "ERR: Directive 'image' has no value.\n"); + return NULL; case DT_PREAMBLE: // INFO: The only preamble directive is 'new_song' cho_line_item_free(songs[so]->sections[se]->lines[li]->lyrics[ly]); @@ -2544,10 +2735,17 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) songs[so]->sections[se]->lines[li]->lyrics[ly]->text = trimmed_directive_value; te += strlen(trimmed_directive_value); break; + case DT_IMAGE: + image = cho_image_directive_parse(directive_value, line_number); + if (!image) { + fprintf(stderr, "cho_image_directive_parse failed.\n"); + return NULL; + } + cho_image_free(image); + break; case DT_PREAMBLE: fprintf(stderr, "ERR: Preamble directive '%s' can't have a value.\n", directive_name); return NULL; - break; case DT_FONT: sprop.ftype = directive->ftype; char *dir_value = str_remove_leading_whitespace(directive_value); @@ -2631,7 +2829,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) tmp_chord = cho_chord_parse(chord); cho_chord_complete(songs[so]->sections[se]->lines[li]->text_above[c]->u.chord, tmp_chord); if (!songs[so]->sections[se]->lines[li]->text_above[c]->u.chord->is_canonical) { - fprintf(stderr, "INFO: Didn't recognize the chord '%s' in line %d.\n", songs[so]->sections[se]->lines[li]->text_above[c]->u.chord->name, line_number); + fprintf(stderr, "INFO: Didn't recognize the chord '%s' in line %ld.\n", songs[so]->sections[se]->lines[li]->text_above[c]->u.chord->name, line_number); } cho_chord_free(tmp_chord); is_chord_already_initialized = false; @@ -2644,7 +2842,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) songs[so]->sections[se]->lines[li]->text_above[c]->position = chord_pos; songs[so]->sections[se]->lines[li]->text_above[c]->u.chord = cho_chord_parse(chord); if (!songs[so]->sections[se]->lines[li]->text_above[c]->u.chord->is_canonical) { - fprintf(stderr, "INFO: Didn't recognize the chord '%s' in line %d.\n", songs[so]->sections[se]->lines[li]->text_above[c]->u.chord->name, line_number); + fprintf(stderr, "INFO: Didn't recognize the chord '%s' in line %ld.\n", songs[so]->sections[se]->lines[li]->text_above[c]->u.chord->name, line_number); } // cho_debug_chord_print(songs[so]->sections[se]->lines[li]->text_above[c]->u.chord); } @@ -2737,7 +2935,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) songs[so]->sections[se]->lines[li]->text_above[c]->u.annot->style = cho_style_duplicate(tag_style); break; default: - fprintf(stderr, "ERR: Invalid prev_state '%s'.\n", cho_state_to_string(prev_state)); + fprintf(stderr, "ERR: Invalid prev_state '%s'.\n", state_enums[prev_state]); return NULL; } memset(tag_begin, 0, strlen(tag_begin)); @@ -2807,7 +3005,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) } else { tags[ta]->attrs[at]->name = realloc(tags[ta]->attrs[at]->name, (atn+1) * sizeof(char)); tags[ta]->attrs[at]->name[atn] = 0; - fprintf(stderr, "ERR: Attribute with name '%s' of tag '%s' in line '%d' has no value.\n", tags[ta]->attrs[at]->name, tag_begin, line_number); + fprintf(stderr, "ERR: Attribute with name '%s' of tag '%s' in line '%ld' has no value.\n", tags[ta]->attrs[at]->name, tag_begin, line_number); return NULL; } } @@ -2819,7 +3017,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) } tags[ta]->attrs[at]->name = realloc(tags[ta]->attrs[at]->name, (atn+1) * sizeof(char)); tags[ta]->attrs[at]->name[atn] = 0; - fprintf(stderr, "ERR: Attribute with name '%s' of tag '%s' in line '%d' has no value.\n", tags[ta]->attrs[at]->name, tag_begin, line_number); + fprintf(stderr, "ERR: Attribute with name '%s' of tag '%s' in line '%ld' has no value.\n", tags[ta]->attrs[at]->name, tag_begin, line_number); return NULL; } if (buf == '>') { @@ -2846,7 +3044,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) songs[so]->sections[se]->lines[li]->text_above[c]->u.annot->style = cho_style_duplicate(tag_style); break; default: - fprintf(stderr, "ERR: Invalid prev_state '%s'.\n", cho_state_to_string(prev_state)); + fprintf(stderr, "ERR: Invalid prev_state '%s'.\n", state_enums[prev_state]); return NULL; } at = 0; @@ -2857,7 +3055,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) tags[ta]->attrs[at]->name = realloc(tags[ta]->attrs[at]->name, (atn+1) * sizeof(char)); tags[ta]->attrs[at]->name[atn] = 0; atn = 0; - fprintf(stderr, "ERR: Attribute with name '%s' of tag '%s' in line '%d' has no value.\n", tags[ta]->attrs[at]->name, tag_begin, line_number); + fprintf(stderr, "ERR: Attribute with name '%s' of tag '%s' in line '%ld' has no value.\n", tags[ta]->attrs[at]->name, tag_begin, line_number); return NULL; } } @@ -2868,11 +3066,11 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) case STATE_MARKUP_ATTR_VALUE: if (avs == AVS_NO) { if (is_whitespace(buf)) { - fprintf(stderr, "ERR: Whitespace character after equals sign in line '%d' is invalid.\n", line_number); + fprintf(stderr, "ERR: Whitespace character after equals sign in line '%ld' is invalid.\n", line_number); return NULL; } if (buf == '>') { - fprintf(stderr, "ERR: Attribute with name '%s' of tag '%s' in line '%d' has no value.\n", tags[ta]->attrs[at]->name, tag_begin, line_number); + fprintf(stderr, "ERR: Attribute with name '%s' of tag '%s' in line '%ld' has no value.\n", tags[ta]->attrs[at]->name, tag_begin, line_number); return NULL; } if (buf == '\'') { @@ -2888,7 +3086,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) break; } if (buf == '\n') { - fprintf(stderr, "ERR: Newline character inside an attribute value in line '%d' is invalid.\n", line_number); + fprintf(stderr, "ERR: Newline character inside an attribute value in line '%ld' is invalid.\n", line_number); return NULL; } if (avs == AVS_UNQUOTED && buf == '>') { @@ -2918,7 +3116,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) songs[so]->sections[se]->lines[li]->text_above[c]->u.annot->style = cho_style_duplicate(tag_style); break; default: - fprintf(stderr, "ERR: Invalid prev_state '%s'.\n", cho_state_to_string(prev_state)); + fprintf(stderr, "ERR: Invalid prev_state '%s'.\n", state_enums[prev_state]); return NULL; } at = 0; @@ -2958,8 +3156,9 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) fprintf(stderr, "fread failed.\n"); return NULL; } - if (buf == '\n') + if (buf == '\n') { line_number++; + } } int e = 0; while (e <= ta) { @@ -3017,167 +3216,3 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) } return songs; } - -#ifdef DEBUG - -static const char *cho_debug_the_dtype(enum DirectiveType dtype) -{ - switch (dtype) { - case DT_EMPTY: - return "DT_EMPTY"; - case DT_ENVIRONMENT: - return "DT_ENVIRONMENT"; - case DT_METADATA: - return "DT_METADATA"; - case DT_FORMATTING: - return "DT_FORMATTING"; - case DT_PREAMBLE: - return "DT_PREAMBLE"; - case DT_FONT: - return "DT_FONT"; - case DT_CHORD: - return "DT_CHORD"; - case DT_OUTPUT: - return "DT_OUTPUT"; - case DT_CUSTOM: - return "DT_CUSTOM"; - } - return ""; -} - -static const char *cho_debug_the_stype(enum SectionType stype) -{ - switch (stype) { - case ST_EMPTY: - return "ST_EMPTY"; - case ST_CHORUS: - return "ST_CHORUS"; - case ST_VERSE: - return "ST_VERSE"; - case ST_BRIDGE: - return "ST_BRIDGE"; - case ST_TAB: - return "ST_TAB"; - case ST_GRID: - return "ST_GRID"; - case ST_NEWSONG: - return "ST_NEWSONG"; - case ST_CUSTOM: - return "ST_CUSTOM"; - } - return ""; -} - -static const char *cho_debug_the_pos(enum Position pos) -{ - switch (pos) { - case POS_EMPTY: - return "POS_EMPTY"; - case POS_START: - return "POS_START"; - case POS_END: - return "POS_END"; - } - return ""; -} - -static const char *cho_debug_the_song_fragment_type(enum SongFragmentType ftype) -{ - switch (ftype) { - case SF_EMPTY: - return "SF_EMPTY"; - case SF_CHORD: - return "SF_CHORD"; - case SF_ANNOT: - return "SF_ANNOT"; - case SF_CHORUS: - return "SF_CHORUS"; - case SF_FOOTER: - return "SF_FOOTER"; - case SF_GRID: - return "SF_GRID"; - case SF_TAB: - return "SF_TAB"; - case SF_TOC: - return "SF_TOC"; - case SF_TEXT: - return "SF_TEXT"; - case SF_TITLE: - return "SF_TITLE"; - case SF_SUBTITLE: - return "SF_SUBTITLE"; - case SF_LABEL: - return "SF_LABEL"; - } - return ""; -} - -static const char *cho_debug_style_property_type_to_string(enum StylePropertyType type) -{ - switch (type) { - case SPT_EMPTY: - return "SPT_EMPTY"; - case SPT_FONT: - return "SPT_FONT"; - case SPT_SIZE: - return "SPT_SIZE"; - case SPT_COLOR: - return "SPT_COLOR"; - } - return ""; -} - -static void cho_debug_the_default_style_properties(void) -{ - unsigned int i; - for (i = 0; i<LENGTH(default_style_properties); i++) { - printf( - "%s %s ", - cho_debug_the_song_fragment_type(default_style_properties[i].ftype), - cho_debug_style_property_type_to_string(default_style_properties[i].type) - ); - switch (default_style_properties[i].type) { - case SPT_FONT: - if (default_style_properties[i].u.font_name) - printf("%s\n", default_style_properties[i].u.font_name); - else - printf("NULL\n"); - break; - case SPT_SIZE: - printf("%.1f\n", default_style_properties[i].u.font_size); - break; - case SPT_COLOR: - if (default_style_properties[i].u.foreground_color) - printf("%s\n", cho_rgbcolor_to_string(default_style_properties[i].u.foreground_color)); - else - printf("NULL\n"); - break; - default: - printf("Invalid StylePropertyType value '%d'.\n", default_style_properties[i].type); - } - } -} - -void cho_debug_style_print(struct Style *style) -{ - printf("---- BEGIN STYLE ----\n"); - cho_font_print(style->font); - printf("foreground_color: %s\n", cho_rgbcolor_to_string(style->foreground_color)); - printf("background_color: %s\n", cho_rgbcolor_to_string(style->background_color)); - printf("underline_style: %s\n", cho_linestyle_to_config_string(style->underline_style)); - printf("underline_color: %s\n", cho_rgbcolor_to_string(style->underline_color)); - printf("overline_style: %s\n", cho_linestyle_to_config_string(style->overline_style)); - printf("overline_color: %s\n", cho_rgbcolor_to_string(style->overline_color)); - printf("strikethrough: %d\n", style->strikethrough); - printf("strikethrough_color: %s\n", cho_rgbcolor_to_string(style->strikethrough_color)); - printf("boxed: %d\n", style->boxed); - printf("boxed_color: %s\n", cho_rgbcolor_to_string(style->boxed_color)); - printf("rise: %f\n", style->rise); - if (style->href) - printf("href: %s\n", style->href); - else - printf("href: NULL\n"); - printf("---- END STYLE ------\n\n"); -} - -#endif /* DEBUG */ diff --git a/chordpro.h b/chordpro.h @@ -12,25 +12,74 @@ #define URL_MAX_LEN 2000 #define FONT_NAME_MAX 100 +enum EnvironmentDirective { + START_OF_CHORUS, SOC, END_OF_CHORUS, EOC, CHORUS, + START_OF_VERSE, SOV, END_OF_VERSE, EOV, + START_OF_BRIDGE, SOB, END_OF_BRIDGE, EOB, + START_OF_TAB, SOT, END_OF_TAB, EOT, + START_OF_GRID, SOG, END_OF_GRID, EOG + +}; + +/* enum MetadataDirective { + TITLE, SORTTITLE, SUBTITLE, + ARTIST, COMPOSER, LYRICIST, + COPYRIGHT, ALBUM, YEAR, KEY, + TIME, TEMPO, DURATION, CAPO, + META, ARRANGER +}; */ + +enum FormattingDirective { + COMMENT, C, HIGHLIGHT, COMMENT_ITALIC, CI, + COMMENT_BOX, CB +}; + +enum ImageDirectve { + IMAGE +}; + +/* enum PreabmleDirective { + NEW_SONG, NS +}; */ + +enum FontDirective { + CHORDFONT, CF, CHORDSIZE, CS, CHORDCOLOR, + CHORUSFONT, CHORUSSIZE, CHORUSCOLOR, + GRIDFONT, GRIDSIZE, GRIDCOLOR, + TABFONT, TABSIZE, TABCOLOR, + TEXTFONT, TF, TEXTSIZE, TS, TEXTCOLOR, + TITLEFONT, TITLESIZE, TITLECOLOR, + /* FOOTERFONT, FOOTERSIZE, FOOTERCOLOR, + TOCFONT, TOCSIZE, TOCCOLOR */ +}; + +enum ChordDirective { + TRANSPOSE, /* DEFINE, CHORD */ +}; + +enum OutputDirective { + NEW_PAGE, NP, COLUMN_BREAK, COLB +}; + enum FontFamily { - FF_EMPTY = -1, FF_NORMAL, FF_SANS, FF_SERIF, - FF_MONOSPACE + FF_MONOSPACE, + FF_EMPTY, }; enum FontStyle { - FS_EMPTY = -1, FS_ROMAN, FS_OBLIQUE, - FS_ITALIC + FS_ITALIC, + FS_EMPTY }; enum FontWeight { - FW_EMPTY = -1, FW_REGULAR, - FW_BOLD + FW_BOLD, + FW_EMPTY }; struct Font { @@ -42,10 +91,10 @@ struct Font { }; enum LineStyle { - LS_EMPTY = -1, LS_SINGLE, LS_DOUBLE, - LS_NONE + LS_NONE, + LS_EMPTY }; struct RGBColor { @@ -82,6 +131,39 @@ struct Tag { bool is_closed; }; +enum Alignment { + A_LEFT, + A_CENTER, + A_RIGHT +}; + +enum Anchor { + AN_PAPER, + AN_PAGE, + AN_COLUMN, + AN_LINE, + AN_FLOAT +}; + +struct ChoImage { + char *id; + char *src; + double width; + double height; + double scale; + enum Alignment align; + double border; + double spread_space; + char *href; + double x; + double y; + enum Anchor anchor; + double dx; + double dy; + double w; + double h; +}; + enum AttrValueSyntax { AVS_NO, AVS_QUOTATION_MARK, @@ -103,9 +185,8 @@ enum State { STATE_COMMENT }; -/* Similar to SectionType but different enough for a separate type */ enum SongFragmentType { - SF_EMPTY = -1, + SF_EMPTY, SF_CHORD, SF_ANNOT, SF_CHORUS, @@ -120,7 +201,7 @@ enum SongFragmentType { }; enum StylePropertyType { - SPT_EMPTY = -1, + SPT_EMPTY, SPT_FONT, SPT_SIZE, SPT_COLOR @@ -139,10 +220,11 @@ struct StyleProperty { }; enum DirectiveType { - DT_EMPTY = -1, + DT_EMPTY, DT_ENVIRONMENT, DT_METADATA, DT_FORMATTING, + DT_IMAGE, DT_PREAMBLE, DT_FONT, DT_CHORD, @@ -151,7 +233,7 @@ enum DirectiveType { }; enum SectionType { - ST_EMPTY = -1, + ST_EMPTY, ST_NEWSONG, ST_CHORUS, ST_VERSE, @@ -162,13 +244,13 @@ enum SectionType { }; enum Position { - POS_EMPTY = -1, + POS_EMPTY, POS_START, POS_END }; enum ChordQualifier { - CQ_EMPTY = -1, + CQ_EMPTY, CQ_MIN, CQ_MAJ, CQ_AUG, @@ -176,10 +258,10 @@ enum ChordQualifier { }; enum BreakType { - BT_EMPTY = -1, BT_LINE, BT_PAGE, - BT_COLUMN + BT_COLUMN, + BT_EMPTY }; struct ChoDirective { @@ -282,8 +364,6 @@ enum FontWeight cho_font_weight_parse(const char *str); const char *cho_font_weight_to_config_string(enum FontWeight weight); void cho_font_print_as_toml(struct Font *font, const char *section); -#ifdef DEBUG void cho_debug_style_print(struct Style *style); -#endif /* DEBUG */ #endif /* _CHORDPRO_H_ */ diff --git a/config.c b/config.c @@ -5,6 +5,21 @@ #include "chordpro.h" #include "config.h" +static char *naming_systems[] = { + "common", + "german", + "scandinavian", + "latin", + "roman", + "nashville", + "custom" // TODO: Is this needed +}; + +static char *parse_modes[] = { + "strict", + "relaxed" +}; + static const char *g_valid_styles[] = { "title", "subtitle", @@ -116,17 +131,17 @@ struct PrintableItem *config_printable_item_get(struct PrintableItem **items, co static enum NamingSystem config_naming_system_parse(const char *str) { - if (strcmp(str, "common") == 0 || strcmp(str, "dutch") == 0) { + if (strcmp(str, naming_systems[NS_COMMON]) == 0 || strcmp(str, "dutch") == 0) { return NS_COMMON; - } else if (strcmp(str, "german") == 0) { + } else if (strcmp(str, naming_systems[NS_GERMAN]) == 0) { return NS_GERMAN; - } else if (strcmp(str, "scandinavian") == 0) { + } else if (strcmp(str, naming_systems[NS_SCANDINAVIAN]) == 0) { return NS_SCANDINAVIAN; - } else if (strcmp(str, "latin") == 0) { + } else if (strcmp(str, naming_systems[NS_LATIN]) == 0) { return NS_LATIN; - } else if (strcmp(str, "roman") == 0) { + } else if (strcmp(str, naming_systems[NS_ROMAN]) == 0) { return NS_ROMAN; - } else if (strcmp(str, "nashville") == 0) { + } else if (strcmp(str, naming_systems[NS_NASHVILLE]) == 0) { return NS_NASHVILLE; } else { return NS_CUSTOM; @@ -135,20 +150,7 @@ static enum NamingSystem config_naming_system_parse(const char *str) static const char *config_naming_system_to_config_string(enum NamingSystem system) { - switch (system) { - case NS_GERMAN: - return "german"; - case NS_SCANDINAVIAN: - return "scandinavian"; - case NS_LATIN: - return "latin"; - case NS_ROMAN: - return "roman"; - case NS_NASHVILLE: - return "nashville"; - default: - return "common"; - } + return naming_systems[system]; } static struct Note *config_note_new(void) @@ -288,10 +290,7 @@ static void config_notes_print_as_toml(enum NamingSystem system, struct Note **n static const char *config_parse_mode_to_config_string(enum ParseMode mode) { - if (mode == PM_RELAXED) { - return "relaxed"; - } - return "strict"; + return parse_modes[mode]; } static struct Config *config_load_default(void) diff --git a/todo b/todo @@ -7,7 +7,6 @@ metadata directives conditional metadata directives don't forget key, key_actual, key_from chords - transpose define chords chord diagrams strict and relaxed parsing makes no difference!? @@ -23,3 +22,4 @@ render in two or more columns find better name for PrintableItem, TextAbove consider freeing memory in case of errors +add line number to error, warning and info fprintf's.