commit b10b5d93fcbe7ce68f596db38e98b0a7527a6068
parent c8d2989df01e732224ee06a152bf90ed134b2963
Author: nibo <nibo@relim.de>
Date: Tue, 22 Jul 2025 20:27:31 +0200
Improve freeing of memory in case of error
Also correct a misunderstanding I had when working with fonts.
sans, serif and monospace are aliases of a group of font families.
Diffstat:
| M | src/chordpro.c | | | 151 | ++++++++++++++++++++++++++++++++----------------------------------------------- |
| M | src/chordpro.h | | | 2 | -- |
| M | src/config.c | | | 58 | +++++++++++++++++++--------------------------------------- |
| M | src/core.c | | | 6 | +++--- |
| M | src/core.h | | | 12 | ++---------- |
| M | src/lorid.c | | | 60 | ++++++++++++++++++++++++++++++++++++++++++------------------ |
| M | src/out_pdf.c | | | 143 | +++++++++++++++++++++++++++++++++++++++++++++---------------------------------- |
| A | src/some.c | | | 1715 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
8 files changed, 1924 insertions(+), 223 deletions(-)
diff --git a/src/chordpro.c b/src/chordpro.c
@@ -14,7 +14,6 @@
#include "chord_diagram.h"
#include "config.h"
-static const char *font_families[] = { "normal", "sans", "serif", "monospace" };
static const char *font_styles[] = { "normal", "oblique", "italic" };
static const char *font_weights[] = { "normal", "bold" };
static const char *line_styles[] = { "single", "double", "none" };
@@ -548,8 +547,7 @@ static struct Font *
cho_font_new(void)
{
struct Font *font = emalloc(sizeof(struct Font));
- font->name = NULL;
- font->family = FONT_FAMILY_NORMAL;
+ font->family = NULL;
font->style = FONT_STYLE_ROMAN;
font->weight = FONT_WEIGHT_REGULAR;
font->size = DEFAULT_FONT_SIZE;
@@ -560,8 +558,7 @@ struct Font *
cho_font_copy(struct Font *font)
{
struct Font *copy = emalloc(sizeof(struct Font));
- copy->name = font->name ? strdup(font->name) : NULL;
- copy->family = font->family;
+ copy->family = font->family ? strdup(font->family) : NULL;
copy->style = font->style;
copy->weight = font->weight;
copy->size = font->size;
@@ -574,7 +571,7 @@ cho_font_free(struct Font *font)
if (!font) {
return;
}
- free(font->name);
+ free(font->family);
free(font);
}
@@ -591,29 +588,6 @@ cho_fonts_free(struct Font **fonts)
free(fonts);
}
-enum FontFamily
-cho_font_family_parse(const char *str, bool *error)
-{
- *error = false;
- if (!strcmp(str, font_families[FONT_FAMILY_SANS])) {
- return FONT_FAMILY_SANS;
- } else if (!strcmp(str, font_families[FONT_FAMILY_SERIF])) {
- return FONT_FAMILY_SERIF;
- } else if (!strcmp(str, font_families[FONT_FAMILY_MONOSPACE])) {
- return FONT_FAMILY_MONOSPACE;
- } else if (!strcmp(str, font_families[FONT_FAMILY_NORMAL])) {
- return FONT_FAMILY_NORMAL;
- }
- *error = true;
- return FONT_FAMILY_SANS; // unused
-}
-
-const char *
-cho_font_family_to_config_string(enum FontFamily font_family)
-{
- return font_families[font_family];
-}
-
enum FontStyle
cho_font_style_parse(const char *str, bool *error)
{
@@ -658,8 +632,7 @@ static void
cho_font_print_as_toml(struct Font *font, const char *section)
{
printf("[output.styles.%s.font]\n", section);
- printf("name = \"%s\"\n", font->name);
- printf("family = \"%s\"\n", cho_font_family_to_config_string(font->family));
+ printf("family = \"%s\"\n", font->family);
printf("style = \"%s\"\n", cho_font_style_to_config_string(font->style));
printf("weight = \"%s\"\n", cho_font_weight_to_config_string(font->weight));
printf("size = %.1f\n\n", font->size);
@@ -669,12 +642,11 @@ void
cho_font_print(struct Font *font)
{
printf("---- BEGIN FONT ----\n");
- if (font->name) {
- printf("font name: %s\n", font->name);
+ if (font->family) {
+ printf("font family: %s\n", font->family);
} else {
- printf("font name: NULL\n");
+ printf("font family: NULL\n");
}
- printf("font family: %s\n", cho_font_family_to_config_string(font->family));
printf("font style: %s\n", cho_font_style_to_config_string(font->style));
printf("font weight: %s\n", cho_font_weight_to_config_string(font->weight));
printf("font size: %f\n", font->size);
@@ -777,8 +749,8 @@ cho_style_property_apply_default(
switch (ptype) {
case STYLE_PROPERTY_TYPE_FONT:
if (default_style_properties[i].u.font_name) {
- free(style->font->name);
- style->font->name = strdup(default_style_properties[i].u.font_name);
+ free(style->font->family);
+ style->font->family = strdup(default_style_properties[i].u.font_name);
return true;
} else {
return false;
@@ -874,12 +846,9 @@ cho_style_complement(
struct ChoStylePresence *presence
)
{
- if (presence->font.name) {
- free(old->font->name);
- old->font->name = strdup(new->font->name);
- }
if (presence->font.family) {
- old->font->family = new->font->family;
+ free(old->font->family);
+ old->font->family = strdup(new->font->family);
}
if (presence->font.style) {
old->font->style = new->font->style;
@@ -1046,13 +1015,13 @@ cho_style_font_desc_parse(const char *font_desc, struct ChoStylePresence *presen
} else if (strcasecmp(words[w], "serif") == 0) {
font->family = FONT_FAMILY_SERIF;
if (name_until == EMPTY_INT)
- name_until = w; */
+ name_until = w;
} else if (!strcasecmp(words[w], "monospace")) {
font->family = FONT_FAMILY_MONOSPACE;
presence->font.family = true;
if (name_until == EMPTY_INT) {
name_until = w;
- }
+ } */
} else {
size = strtod(words[w], NULL);
if (size == 0.0) {
@@ -1071,15 +1040,15 @@ cho_style_font_desc_parse(const char *font_desc, struct ChoStylePresence *presen
if (name_until > 0) {
for (w = 0, n = 0; w<name_until; w++) {
len = strlen(words[w]);
- font->name = erealloc(font->name, (n+len+1) * sizeof(char));
- memcpy(&font->name[n], words[w], len);
+ font->family = erealloc(font->family, (n+len+1) * sizeof(char));
+ memcpy(&font->family[n], words[w], len);
n += len;
- font->name[n] = ' ';
+ font->family[n] = ' ';
n++;
}
n--;
- font->name[n] = '\0';
- presence->font.name = true;
+ font->family[n] = '\0';
+ presence->font.family = true;
}
free(words);
return font;
@@ -1111,8 +1080,8 @@ cho_style_parse(
if (!strcmp((*a)->name, "font_desc")) {
font = cho_style_font_desc_parse((*a)->value, presence);
if (font) {
- if (!font->name) {
- font->name = strdup(style->font->name);
+ if (!font->family) {
+ font->family = strdup(style->font->family);
}
cho_font_free(style->font);
style->font = font;
@@ -1121,22 +1090,8 @@ cho_style_parse(
!strcmp((*a)->name, "font_family") ||
!strcmp((*a)->name, "face")
) {
- if (!strcmp((*a)->value, "normal")) {
- style->font->family = FONT_FAMILY_NORMAL;
- presence->font.family = true;
- } else if (!strcmp((*a)->value, "sans")) {
- style->font->family = FONT_FAMILY_SANS;
- presence->font.family = true;
- } else if (!strcmp((*a)->value, "serif")) {
- style->font->family = FONT_FAMILY_SERIF;
- presence->font.family = true;
- } else if (!strcmp((*a)->value, "monospace")) {
- style->font->family = FONT_FAMILY_MONOSPACE;
- presence->font.family = true;
- } else {
- cho_log(ctx, LOG_ERR, "Attribute 'font_family/face' of markup tag '%s' has an invalid value '%s'.", tag_name, (*a)->value);
- goto ERR;
- }
+ free(style->font->family);
+ style->font->family = strdup((*a)->value);
} else if (!strcmp((*a)->name, "size")) {
value_len = strlen((*a)->value);
last_char = value_len - 1;
@@ -1413,7 +1368,7 @@ cho_style_parse(
style->font->size *= 0.8;
presence->font.size = true;
} else if (!strcmp(tag_name, "tt")) {
- style->font->family = FONT_FAMILY_MONOSPACE;
+ style->font->family = strdup("monospace");
presence->font.family = true;
} else if (!strcmp(tag_name, "u")) {
style->underline_style = LINE_STYLE_SINGLE;
@@ -3948,6 +3903,7 @@ cho_section_new(void)
section->type = SECTION_TYPE_UNINITIALIZED;
section->label = NULL;
section->lines = NULL;
+ section->is_closed = false;
return section;
}
@@ -4779,10 +4735,9 @@ cho_songs_close(struct ChoContext *ctx, struct ChoLine ***lines)
(*lines) &&
(*lines)[ctx->li] &&
(*lines)[ctx->li]->items &&
- (*lines)[ctx->li]->items[ctx->lii] &&
- (*lines)[ctx->li]->items[ctx->lii]->is_text
+ (*lines)[ctx->li]->items[ctx->lii]
) {
- if ((*lines)[ctx->li]->items[ctx->lii]->u.text->text) {
+ if ((*lines)[ctx->li]->items[ctx->lii]->is_text && (*lines)[ctx->li]->items[ctx->lii]->u.text->text) {
(*lines)[ctx->li]->items[ctx->lii]->u.text->text = erealloc((*lines)[ctx->li]->items[ctx->lii]->u.text->text, (ctx->te+1) * sizeof(char));
(*lines)[ctx->li]->items[ctx->lii]->u.text->text[ctx->te] = 0;
ctx->lii++;
@@ -4877,6 +4832,8 @@ cho_context_init(
ctx->th++;
ctx->songs[ctx->so]->sections = emalloc((ctx->se+1) * sizeof(struct ChoSection *));
ctx->songs[ctx->so]->sections[ctx->se] = cho_section_new();
+ // INFO: The first section has no start and end directive
+ ctx->songs[ctx->so]->sections[ctx->se]->is_closed = true;
ctx->songs[ctx->so]->sections[ctx->se]->lines = emalloc(sizeof(struct ChoLine *));
ctx->songs[ctx->so]->sections[ctx->se]->lines[ctx->li] = cho_line_new();
return true;
@@ -4898,6 +4855,20 @@ cho_context_cleanup(struct ChoContext *ctx)
free(ctx->image_assets);
}
+static bool
+is_previous_section_closed(struct ChoContext *ctx)
+{
+ int i;
+
+ for (i = ctx->se; i>=0; i--) {
+ if (ctx->songs[ctx->so]->sections[i]->type == SECTION_TYPE_UNINITIALIZED) {
+ continue;
+ }
+ return ctx->songs[ctx->so]->sections[i]->is_closed ? true : false;
+ }
+ return true;
+}
+
struct ChoSong **
cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *config)
{
@@ -4995,7 +4966,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
if (ctx.ta > -1 && !ctx.tags[ctx.ta]->is_closed && strcmp(ctx.tags[ctx.ta]->name, "img")) {
// TODO: This seems to be unreachable
cho_log(&ctx, LOG_ERR, "Tag has to be closed on same line.");
- return NULL;
+ goto ERR;
}
if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
(*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
@@ -5063,6 +5034,10 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
ctx.current_ttype = directive->ttype;
switch (directive->position) {
case POSITION_START: {
+ if (!is_previous_section_closed(&ctx)) {
+ cho_log(&ctx, LOG_ERR, "Can't start a new section when the previous one is not yet closed.");
+ goto ERR;
+ }
if (directive->stype == SECTION_TYPE_CUSTOM) {
memset(custom_directive, 0, sizeof(custom_directive));
strcpy(custom_directive, &directive_name[9]);
@@ -5112,6 +5087,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
ctx.lii = 0;
free((*lines)[ctx.li]);
(*lines)[ctx.li] = NULL;
+ ctx.songs[ctx.so]->sections[ctx.se]->is_closed = true;
ctx.se++;
ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new();
@@ -5123,12 +5099,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
(*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
ctx.state = STATE_LYRICS;
} else {
- const char *section_type = section_types[directive->stype];
- if (section_type[0] == 0) {
- cho_log(&ctx, LOG_ERR, "Can't close a section that wasn't opened earlier.");
- } else {
- cho_log(&ctx, LOG_ERR, "Can't close a %s section that wasn't opened earlier.", section_type);
- }
+ cho_log(&ctx, LOG_ERR, "Can't close a %s section that wasn't opened earlier.", section_types[directive->stype]);
goto ERR;
}
break;
@@ -5366,6 +5337,10 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
ctx.current_ttype = directive->ttype;
switch (directive->position) {
case POSITION_START: {
+ if (!is_previous_section_closed(&ctx)) {
+ cho_log(&ctx, LOG_ERR, "Can't start a new section when the previous one is not yet closed.");
+ goto ERR;
+ }
if (directive->stype == SECTION_TYPE_CUSTOM) {
memset(custom_directive, 0, sizeof(custom_directive));
strcpy(custom_directive, &directive_name[9]);
@@ -5650,32 +5625,28 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
goto ERR;
case DIRECTIVE_TYPE_FONT: {
sprop.ttype = directive->ttype;
- char *dir_value = strdup(stripped_directive_value);
switch (directive->sprop) {
case STYLE_PROPERTY_TYPE_FONT:
- sprop.u.font_name = emalloc((strlen(dir_value)+1) * sizeof(char));
- strcpy(sprop.u.font_name, dir_value);
+ sprop.u.font_name = emalloc((strlen(stripped_directive_value)+1) * sizeof(char));
+ strcpy(sprop.u.font_name, stripped_directive_value);
sprop.type = STYLE_PROPERTY_TYPE_FONT;
break;
case STYLE_PROPERTY_TYPE_SIZE:
- sprop.u.font_size = strtod(dir_value, NULL);
- if (sprop.u.font_size == 0.0) {
- cho_log(&ctx, LOG_ERR, "Font directive '%s' has an invalid value.", directive_name);
+ sprop.u.font_size = strtod(stripped_directive_value, NULL);
+ if (sprop.u.font_size <= 0.0) {
+ cho_log(&ctx, LOG_ERR, "Font directive '%s' has to contain a positive number.", directive_name);
goto ERR;
}
sprop.type = STYLE_PROPERTY_TYPE_SIZE;
break;
case STYLE_PROPERTY_TYPE_COLOR:
- sprop.u.foreground_color = cho_color_parse(dir_value);
+ sprop.u.foreground_color = cho_color_parse(stripped_directive_value);
if (sprop.u.foreground_color == NULL) {
cho_log(&ctx, LOG_ERR, "Font directive '%s' has an invalid value.", directive_name);
goto ERR;
}
sprop.type = STYLE_PROPERTY_TYPE_COLOR;
break;
- default:
- cho_log(&ctx, LOG_ERR, "Invalid style property type '%d'.", directive->sprop);
- goto ERR;
}
if (!cho_style_change_default(sprop)) {
LOG_DEBUG("cho_style_change_default failed.");
@@ -5686,7 +5657,6 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
} else if (sprop.type == STYLE_PROPERTY_TYPE_COLOR) {
free(sprop.u.foreground_color);
}
- free(dir_value);
break;
}
case DIRECTIVE_TYPE_CHORD: {
@@ -5738,6 +5708,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
}
memset(directive_value, 0, strlen(directive_value));
free(stripped_directive_value);
+ stripped_directive_value = NULL;
cho_directive_free(directive);
directive = NULL;
break;
@@ -5881,6 +5852,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
break;
}
cho_log(&ctx, LOG_ERR, "Newline character inside an annotation is invalid.");
+ ctx.lia++;
goto ERR;
}
(*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text = erealloc((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text, (ctx.ann+1) * sizeof(char));
@@ -6612,7 +6584,6 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
free(stripped_directive_value);
cho_directive_free(directive);
cho_tag_attrs_free(directive_attrs);
- cho_metadata_free(metadata);
return NULL;
}
diff --git a/src/chordpro.h b/src/chordpro.h
@@ -247,8 +247,6 @@ void cho_font_print(struct Font *font);
struct Font *cho_font_copy(struct Font *font);
void cho_fonts_free(struct Font **fonts);
char *cho_font_name_normalize(const char *name);
-enum FontFamily cho_font_family_parse(const char *str, bool *error);
-const char *cho_font_family_to_config_string(enum FontFamily font_family);
enum FontStyle cho_font_style_parse(const char *str, bool *error);
const char *cho_font_style_to_config_string(enum FontStyle style);
enum FontWeight cho_font_weight_parse(const char *str, bool *error);
diff --git a/src/config.c b/src/config.c
@@ -481,32 +481,16 @@ config_load_font(
char (*err_buf)[25]
)
{
- enum FontFamily family;
enum FontStyle style;
enum FontWeight weight;
toml_value_t value;
bool error;
- value = toml_table_string(table, "name");
- if (value.ok) {
- presence->font.name = true;
- free(font->name);
- font->name = value.u.s;
- }
value = toml_table_string(table, "family");
if (value.ok) {
presence->font.family = true;
- family = cho_font_family_parse(value.u.s, &error);
- if (error) {
- if (err_buf) {
- strcpy((char *)err_buf, "family value is invalid.");
- }
- free(value.u.s);
- return false;
- } else {
- font->family = family;
- }
- free(value.u.s);
+ free(font->family);
+ font->family = value.u.s;
}
value = toml_table_string(table, "style");
if (value.ok) {
@@ -709,7 +693,6 @@ debug_presence_print(const char *name, struct ChoStylePresence *presence)
{
printf("---- BEGIN PRESENCE ----\n");
printf("style '%s'\n", name);
- printf("font.name %d\n", presence->font.name);
printf("font.family %d\n", presence->font.family);
printf("font.style %d\n", presence->font.style);
printf("font.weight %d\n", presence->font.weight);
@@ -740,12 +723,9 @@ set_text_style(
)
{
- if (!presence->font.name && text_presence->font.name) {
- free(style->font->name);
- style->font->name = strdup(text_style->font->name);
- }
if (!presence->font.family && text_presence->font.family) {
- style->font->family = text_style->font->family;
+ free(style->font->family);
+ style->font->family = strdup(text_style->font->family);
}
if (!presence->font.style && text_presence->font.style) {
style->font->style = text_style->font->style;
@@ -854,50 +834,50 @@ config_load_default(void)
config->output->toc->title = strdup("Table Of Contents");
config->output->styles[TEXT_TYPE_CHORD] = cho_style_new();
- config->output->styles[TEXT_TYPE_CHORD]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_CHORD]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_CHORD]->font->weight = FONT_WEIGHT_BOLD;
config->output->styles[TEXT_TYPE_ANNOT] = cho_style_new();
- config->output->styles[TEXT_TYPE_ANNOT]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_ANNOT]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_ANNOT]->font->style = FONT_STYLE_ITALIC;
config->output->styles[TEXT_TYPE_CHORUS] = cho_style_new();
- config->output->styles[TEXT_TYPE_CHORUS]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_CHORUS]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_FOOTER] = cho_style_new();
- config->output->styles[TEXT_TYPE_FOOTER]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_FOOTER]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_GRID] = cho_style_new();
- config->output->styles[TEXT_TYPE_GRID]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_GRID]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_GRID]->font->weight = FONT_WEIGHT_BOLD;
config->output->styles[TEXT_TYPE_TAB] = cho_style_new();
- config->output->styles[TEXT_TYPE_TAB]->font->name = strdup("Courier");
+ config->output->styles[TEXT_TYPE_TAB]->font->family = strdup("Courier");
// config->output->styles[TEXT_TYPE_TAB]->font->family = FONT_FAMILY_MONOSPACE;
config->output->styles[TEXT_TYPE_TOC] = cho_style_new();
- config->output->styles[TEXT_TYPE_TOC]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_TOC]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_TOC]->font->size = 12.0;
config->output->styles[TEXT_TYPE_TOC_TITLE] = cho_style_new();
- config->output->styles[TEXT_TYPE_TOC_TITLE]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_TOC_TITLE]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_TOC_TITLE]->font->weight = FONT_WEIGHT_BOLD;
config->output->styles[TEXT_TYPE_TOC_TITLE]->font->size = 18.0;
config->output->styles[TEXT_TYPE_TEXT] = cho_style_new();
- config->output->styles[TEXT_TYPE_TEXT]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_TEXT]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_TITLE] = cho_style_new();
- config->output->styles[TEXT_TYPE_TITLE]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_TITLE]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_TITLE]->font->weight = FONT_WEIGHT_BOLD;
config->output->styles[TEXT_TYPE_TITLE]->font->size = 18.0;
config->output->styles[TEXT_TYPE_SUBTITLE] = cho_style_new();
- config->output->styles[TEXT_TYPE_SUBTITLE]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_SUBTITLE]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_SUBTITLE]->font->size = 12.0;
config->output->styles[TEXT_TYPE_LABEL] = cho_style_new();
- config->output->styles[TEXT_TYPE_LABEL]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_LABEL]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_LABEL]->font->style = FONT_STYLE_ITALIC;
config->output->styles[TEXT_TYPE_COMMENT] = cho_style_new();
- config->output->styles[TEXT_TYPE_COMMENT]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_COMMENT]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_COMMENT]->background_color->red = 228;
config->output->styles[TEXT_TYPE_COMMENT]->background_color->green = 228;
config->output->styles[TEXT_TYPE_COMMENT]->background_color->blue = 228;
config->output->styles[TEXT_TYPE_COMMENT_ITALIC] = cho_style_new();
- config->output->styles[TEXT_TYPE_COMMENT_ITALIC]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_COMMENT_ITALIC]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_COMMENT_ITALIC]->font->style = FONT_STYLE_ITALIC;
config->output->styles[TEXT_TYPE_COMMENT_BOX] = cho_style_new();
- config->output->styles[TEXT_TYPE_COMMENT_BOX]->font->name = strdup(DEFAULT_FONT);
+ config->output->styles[TEXT_TYPE_COMMENT_BOX]->font->family = strdup(DEFAULT_FONT);
config->output->styles[TEXT_TYPE_COMMENT_BOX]->boxed = true;
return config;
}
diff --git a/src/core.c b/src/core.c
@@ -622,7 +622,7 @@ size_to_string(struct Size *size)
const char *
is_base_font(struct Font *font)
{
- if (!strcmp(font->name, "Courier")) {
+ if (!strcmp(font->family, "Courier")) {
if (font->style == FONT_STYLE_ITALIC && font->weight == FONT_WEIGHT_BOLD) {
return "Courier-BoldItalic";
} else
@@ -634,7 +634,7 @@ is_base_font(struct Font *font)
}
return "Courier";
} else
- if (!strcmp(font->name, "Helvetica")) {
+ if (!strcmp(font->family, "Helvetica")) {
if (font->style == FONT_STYLE_OBLIQUE && font->weight == FONT_WEIGHT_BOLD) {
return "Helvetica-BoldOblique";
} else
@@ -646,7 +646,7 @@ is_base_font(struct Font *font)
}
return "Helvetica";
} else
- if (!strcmp(font->name, "Times")) {
+ if (!strcmp(font->family, "Times")) {
if (font->style == FONT_STYLE_ITALIC && font->weight == FONT_WEIGHT_BOLD) {
return "Times-BoldItalic";
} else
diff --git a/src/core.h b/src/core.h
@@ -61,13 +61,6 @@ enum FileType {
FILE_TYPE_OTHER
};
-enum FontFamily {
- FONT_FAMILY_NORMAL,
- FONT_FAMILY_SANS,
- FONT_FAMILY_SERIF,
- FONT_FAMILY_MONOSPACE
-};
-
enum FontStyle {
FONT_STYLE_ROMAN,
FONT_STYLE_OBLIQUE,
@@ -158,7 +151,6 @@ enum TextType {
};
struct FontPresence {
- bool name;
bool family;
bool style;
bool weight;
@@ -182,8 +174,7 @@ struct ChoStylePresence {
};
struct Font {
- char *name; // INFO: Exact font family name, e.g. from `fc-list : family`
- enum FontFamily family;
+ char *family; // INFO: Exact font family name, e.g. from `fc-list : family`
enum FontStyle style;
enum FontWeight weight;
double size;
@@ -260,6 +251,7 @@ struct ChoSection {
enum SectionType type;
struct ChoText *label;
struct ChoLine **lines;
+ bool is_closed;
};
struct ChoSong {
diff --git a/src/lorid.c b/src/lorid.c
@@ -3,6 +3,7 @@
#include <stdbool.h>
#include <string.h>
#include <getopt.h>
+#include <fontconfig/fontconfig.h>
#include "core.h"
#include "chordpro.h"
#include "config.h"
@@ -24,13 +25,14 @@ main(int argc, char *argv[])
int s = 0;
const char *chordpro_filepath = NULL;
char *config_filepath = NULL;
+ char *input = NULL;
char *output = NULL;
- char *input, *pdf_filename;
+ char *pdf_filename = NULL;
struct ChoSong **all_songs = NULL;
struct ChoSong **songs = NULL;
struct ChoSong **so;
- struct Config *config;
- FILE *fp;
+ struct Config *config = NULL;
+ FILE *fp = NULL;
while ((o = getopt_long(argc, argv, "pc:o:Vvh", long_options, &option_index)) != -1) {
switch(o) {
@@ -60,14 +62,14 @@ main(int argc, char *argv[])
config = config_load_from_file(config_filepath);
if (!config) {
util_log(NULL, 0, LOG_ERR, "Failed to load the config file '%s'.", config_filepath);
- return 1;
+ goto ERR;
}
} else {
char *home = getenv("HOME");
if (!home) {
LOG_DEBUG("getenv failed.");
util_log(NULL, 0, LOG_ERR, "Failed to read the environment variable 'HOME'.");
- return 1;
+ goto ERR;
}
size_t size = 26 + strlen(home) + 1;
char default_config_path[size];
@@ -78,31 +80,27 @@ main(int argc, char *argv[])
config = config_load_default();
}
}
- free(config_filepath);
if (argc == optind) {
fp = stdin;
input = file_read(fp);
all_songs = cho_songs_parse(input, NULL, config);
if (!all_songs) {
LOG_DEBUG("cho_songs_parse failed.");
- return 1;
+ goto ERR;
}
- free(input);
} else if (argc == optind+1) {
fp = fopen(argv[argc-1], "r");
if (!fp) {
LOG_DEBUG("fopen failed.");
- return 1;
+ goto ERR;
}
input = file_read(fp);
chordpro_filepath = argv[argc-1];
all_songs = cho_songs_parse(input, chordpro_filepath, config);
if (!all_songs) {
LOG_DEBUG("cho_songs_parse failed.");
- return 1;
+ goto ERR;
}
- free(input);
- fclose(fp);
} else {
int file_count = argc - optind;
int i;
@@ -110,35 +108,61 @@ main(int argc, char *argv[])
fp = fopen(argv[i], "r");
if (!fp) {
LOG_DEBUG("fopen failed.");
- return 1;
+ goto ERR;
}
input = file_read(fp);
songs = cho_songs_parse(input, argv[i], config);
if (!songs) {
LOG_DEBUG("cho_songs_parse failed.");
- return 1;
+ goto ERR;
}
- free(input);
- fclose(fp);
for (so = songs; *so; s++, so++) {
all_songs = erealloc(all_songs, (s+1) * sizeof(struct ChoSong *));
all_songs[s] = *so;
}
+ free(input);
+ fclose(fp);
free(songs);
+ input = NULL;
+ fp = NULL;
+ songs = NULL;
}
all_songs = erealloc(all_songs, (s+1) * sizeof(struct ChoSong *));
all_songs[s] = NULL;
+ printf("ending at index %d\n", s);
}
qsort(all_songs, cho_song_count(all_songs), sizeof(struct ChoSong *), cho_song_compare);
pdf_filename = out_pdf_create(chordpro_filepath, output, all_songs, config);
if (!pdf_filename) {
LOG_DEBUG("out_pdf_create failed.");
- return 1;
+ goto ERR;
}
util_log(NULL, 0, LOG_INFO, "Writing pdf to file: '%s'.", pdf_filename);
- free(pdf_filename);
+ free(config_filepath);
+ free(input);
free(output);
+ free(pdf_filename);
cho_songs_free(all_songs);
+ cho_songs_free(songs);
config_free(config);
+ if (fp) {
+ fclose(fp);
+ }
return 0;
+ ERR:
+ if (s > 0 && all_songs[s-1]) {
+ all_songs = erealloc(all_songs, (s+1) * sizeof(struct ChoSong *));
+ all_songs[s] = NULL;
+ }
+ free(config_filepath);
+ free(input);
+ free(output);
+ free(pdf_filename);
+ cho_songs_free(all_songs);
+ cho_songs_free(songs);
+ config_free(config);
+ if (fp) {
+ fclose(fp);
+ }
+ return 1;
}
diff --git a/src/out_pdf.c b/src/out_pdf.c
@@ -91,19 +91,13 @@ fnt_name_create(struct Font *font)
{
const char *c;
char *f;
- char *name = str_normalize(font->name);
- const char *family = cho_font_family_to_config_string(font->family);
+ char *family = str_normalize(font->family);
const char *style = cho_font_style_to_config_string(font->style);
const char *weight = cho_font_weight_to_config_string(font->weight);
- size_t len = strlen(name) + strlen(family) + strlen(style) + strlen(weight);
+ size_t len = strlen(family) + strlen(style) + strlen(weight);
char *fnt_name = emalloc((len + 4) * sizeof(char));
f = fnt_name;
- for (c = name; *c; c++, f++) {
- *f = *c;
- }
- *f = '-';
- f++;
for (c = family; *c; c++, f++) {
*f = *c;
}
@@ -118,7 +112,7 @@ fnt_name_create(struct Font *font)
*f = *c;
}
*f = '\0';
- free(name);
+ free(family);
return fnt_name;
}
@@ -134,8 +128,7 @@ fonts_add_if_not_in(struct Font ***array, struct Font *font)
int a;
for (a = 0; (*array)[a]; a++) {
if (
- !strcmp((*array)[a]->name, font->name) &&
- (*array)[a]->family == font->family &&
+ !strcmp((*array)[a]->family, font->family) &&
(*array)[a]->style == font->style &&
(*array)[a]->weight == font->weight
) {
@@ -182,7 +175,7 @@ fonts_get_all(struct ChoSong **songs, struct Config *config)
}
}
for (se = (*so)->sections; *se; se++) {
- if ((*se)->label && (*se)->label->style->font->name) {
+ if ((*se)->label && (*se)->label->style->font->family) {
font = cho_font_copy((*se)->label->style->font);
added = fonts_add_if_not_in(&fonts, font);
if (!added) {
@@ -196,7 +189,7 @@ fonts_get_all(struct ChoSong **songs, struct Config *config)
} else {
style = (*above)->u.annot->style;
}
- if (style->font->name) {
+ if (style->font->family) {
font = cho_font_copy(style->font);
added = fonts_add_if_not_in(&fonts, font);
if (!added) {
@@ -207,7 +200,7 @@ fonts_get_all(struct ChoSong **songs, struct Config *config)
for (it = (*li)->items; *it; it++) {
if ((*it)->is_text) {
style = (*it)->u.text->style;
- if (style->font->name) {
+ if (style->font->family) {
font = cho_font_copy(style->font);
added = fonts_add_if_not_in(&fonts, font);
if (!added) {
@@ -239,36 +232,27 @@ fontpath_count_fonts(FcChar8 *path)
return count;
}
-static char *
-fontpath_find(struct Font *font, enum FontType font_type)
+static FcPattern *
+fontconfig_pattern_create(struct Font *font, enum FontType font_type)
{
- FcObjectSet *obj;
FcPattern *pattern;
- char *filepath = NULL;
- obj = FcObjectSetBuild(FC_FAMILY, FC_SLANT, FC_WIDTH, FC_WEIGHT, FC_FONTFORMAT, FC_FONT_WRAPPER, FC_FILE, NULL);
+ FcValue family, wrapper, variable, width, type, style, weight;
+
pattern = FcPatternCreate();
- FcValue family = { .type = FcTypeString, .u.s = (FcChar8 *)font->name };
- FcPatternAdd(pattern, FC_FAMILY, family, FcFalse);
- FcValue font_wrapper = { .type = FcTypeString, .u.s = (FcChar8 *)"SFNT" };
- FcPatternAdd(pattern, FC_FONT_WRAPPER, font_wrapper, FcFalse);
- FcValue variable = { .type = FcTypeBool, .u.b = FcFalse };
- FcPatternAdd(pattern, FC_VARIABLE, variable, FcFalse);
- FcValue width = { .type = FcTypeInteger, .u.i = FC_WIDTH_NORMAL };
- FcPatternAdd(pattern, FC_WIDTH, width, FcFalse);
- /* TODO: Also handle FONT_FAMILY_NORMAL, FONT_FAMILY_SANS and FONT_FAMILY_SERIF, but how? */
- if (font->family == FONT_FAMILY_MONOSPACE) {
- FcValue spacing = { .type = FcTypeInteger, .u.i = FC_MONO };
- FcPatternAdd(pattern, FC_SPACING, spacing, FcFalse);
+ if (!pattern) {
+ LOG_DEBUG("FcPatternCreate failed.\n");
+ return NULL;
}
- FcValue type;
+ family.type = FcTypeString;
+ family.u.s = (FcChar8 *)font->family;
+ wrapper.type = FcTypeString;
+ wrapper.u.s = (FcChar8 *)"SFNT";
+ variable.type = FcTypeBool;
+ variable.u.b = FcFalse;
+ width.type = FcTypeInteger;
+ width.u.i = FC_WIDTH_NORMAL;
type.type = FcTypeString;
- if (font_type == FONT_TYPE_OTF) {
- type.u.s = (FcChar8 *)"CFF";
- } else {
- type.u.s = (FcChar8 *)"TrueType";
- }
- FcPatternAdd(pattern, FC_FONTFORMAT, type, FcFalse);
- FcValue style;
+ type.u.s = (FcChar8 *)(font_type == FONT_TYPE_OTF ? "CFF" : "TrueType");
style.type = FcTypeInteger;
switch (font->style) {
case FONT_STYLE_ROMAN:
@@ -281,35 +265,70 @@ fontpath_find(struct Font *font, enum FontType font_type)
style.u.i = FC_SLANT_ITALIC;
break;
}
- FcPatternAdd(pattern, FC_SLANT, style, FcFalse);
- FcValue weight;
weight.type = FcTypeInteger;
- switch (font->weight) {
- case FONT_WEIGHT_REGULAR:
- weight.u.i = FC_WEIGHT_REGULAR;
- break;
- case FONT_WEIGHT_BOLD:
- weight.u.i = FC_WEIGHT_BOLD;
- break;
- }
+ weight.u.i = font->weight == FONT_WEIGHT_REGULAR ? FC_WEIGHT_REGULAR : FC_WEIGHT_BOLD;
+ FcPatternAdd(pattern, FC_FAMILY, family, FcFalse);
+ FcPatternAdd(pattern, FC_FONT_WRAPPER, wrapper, FcFalse);
+ FcPatternAdd(pattern, FC_VARIABLE, variable, FcFalse);
+ FcPatternAdd(pattern, FC_WIDTH, width, FcFalse);
+ FcPatternAdd(pattern, FC_FONTFORMAT, type, FcFalse);
+ FcPatternAdd(pattern, FC_SLANT, style, FcFalse);
FcPatternAdd(pattern, FC_WEIGHT, weight, FcFalse);
- FcFontSet *set = FcFontList(NULL, pattern, obj);
+ return pattern;
+}
+
+static char *
+fontpath_find(struct Font *font, enum FontType font_type)
+{
FcChar8 *file;
+ FcFontSet *set;
+ FcObjectSet *obj;
+ FcPattern *pattern, *match;
+ FcResult result;
int i;
- for (i = 0; i<set->nfont; i++) {
- if (FcPatternGetString(set->fonts[i], FC_FILE, 0, &file) == FcResultMatch) {
+ char *filepath = NULL;
+
+ pattern = fontconfig_pattern_create(font, font_type);
+ if (!pattern) {
+ LOG_DEBUG("fontconfig_pattern_create failed.\n");
+ return NULL;
+ }
+ if (
+ !strcmp(font->family, "sans") ||
+ !strcmp(font->family, "serif") ||
+ !strcmp(font->family, "monospace")
+ ) {
+ // pattern = FcNameParse((const FcChar8 *)font->family);
+ FcConfigSubstitute(0, pattern, FcMatchPattern);
+ FcDefaultSubstitute(pattern);
+ match = FcFontMatch(NULL, pattern, &result);
+ if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch) {
if (
!file_extension_equals((const char *)file, "ttc") &&
fontpath_count_fonts(file) == 1
) {
filepath = strdup((const char *)file);
- break;
}
}
+ FcPatternDestroy(match);
+ } else {
+ obj = FcObjectSetBuild(FC_FAMILY, FC_SLANT, FC_WIDTH, FC_WEIGHT, FC_FONTFORMAT, FC_FONT_WRAPPER, FC_FILE, NULL);
+ set = FcFontList(NULL, pattern, obj);
+ for (i = 0; i<set->nfont; i++) {
+ if (FcPatternGetString(set->fonts[i], FC_FILE, 0, &file) == FcResultMatch) {
+ if (
+ !file_extension_equals((const char *)file, "ttc") &&
+ fontpath_count_fonts(file) == 1
+ ) {
+ filepath = strdup((const char *)file);
+ break;
+ }
+ }
+ }
+ FcObjectSetDestroy(obj);
+ FcFontSetDestroy(set);
}
- FcObjectSetDestroy(obj);
FcPatternDestroy(pattern);
- FcFontSetDestroy(set);
return filepath;
}
@@ -320,8 +339,7 @@ pdf_load_chord_diagram_fonts(struct PDFContext *ctx)
const char *font_name;
struct Obj *fnt;
struct Font font = {
- .name = DEFAULT_FONT,
- .family = FONT_FAMILY_NORMAL,
+ .family = DEFAULT_FONT,
.style = FONT_STYLE_ROMAN,
.weight = FONT_WEIGHT_REGULAR
};
@@ -375,8 +393,8 @@ pdf_load_fonts(struct PDFContext *ctx, struct Font **needed_fonts)
struct Obj *fnt;
for (f = needed_fonts; *f; f++) {
- if (font_name_is_path((*f)->name)) {
- fontpath = filepath_resolve_tilde((*f)->name);
+ if (font_name_is_path((*f)->family)) {
+ fontpath = filepath_resolve_tilde((*f)->family);
fnt = obj_new();
fnt->name = fnt_name_create(*f);
index = strs_get_index_if_in(fontpaths, fontpath);
@@ -387,7 +405,7 @@ pdf_load_fonts(struct PDFContext *ctx, struct Font **needed_fonts)
goto ERR;
}
strs_add(&fontpaths, fontpath);
- util_log(NULL, 0, LOG_INFO, "Loaded font from '%s'.", (*f)->name);
+ util_log(NULL, 0, LOG_INFO, "Loaded font from '%s'.", (*f)->family);
} else {
fnt->value = ctx->fonts[index]->value;
}
@@ -1760,7 +1778,7 @@ pdf_page_add_page_no(
double width, x;
style = cho_style_new();
- style->font->name = strdup(DEFAULT_FONT);
+ style->font->family = strdup(DEFAULT_FONT);
texts = &c_ctx->content->pages[c_ctx->page]->texts;
page_no = numeral_system_number_to_str(numeral_system, c_ctx->page+1);
if (!page_no) {
@@ -2902,6 +2920,7 @@ out_pdf_create(
char *pdf_filepath = NULL;
char *pdf_dot_filepath = NULL;
+ FcInit();
ctx.diagram_font_is_base_font = false;
ctx.fonts = NULL;
ctx.config = config;
@@ -2979,6 +2998,7 @@ out_pdf_create(
);
}
free(pdf_dot_filepath);
+ FcFini();
return pdf_filepath;
ERR:
objs_free(img_objs);
@@ -2993,5 +3013,6 @@ out_pdf_create(
LOG_DEBUG("unlink failed.");
}
free(pdf_dot_filepath);
+ FcFini();
return NULL;
}
diff --git a/src/some.c b/src/some.c
@@ -0,0 +1,1715 @@
+{
+ enum AttrValueSyntax avs = ATTRIBUTE_VALUE_SYNTAX_UNINITIALIZED;
+ enum GridToken token;
+ struct Attr **directive_attrs = NULL;
+ struct ChoStyle *tag_style;
+ struct StyleProperty sprop;
+ struct ChoChord *tmp_chord;
+ struct ChoSection *chorus;
+ struct ChoImage *image;
+ struct ChordDiagram *diagram;
+ struct ChoDirective *directive = NULL;
+ struct ChoMetadata *metadata = NULL;
+ struct ChoLine ***lines;
+ struct ChoContext ctx;
+ bool err;
+ char *metadata_value, *shape;
+ char *stripped_directive_value = NULL;
+ char *label = NULL;
+ char directive_name[128];
+ char directive_value[4096];
+ char chord[CHORD_LEN];
+ char tag_start[6];
+ char tag_end[6];
+ char custom_directive[64];
+ char metadata_substitution[4096];
+ char grid_token[CHORD_LEN];
+ char c = 0;
+ char prev_c = '\n';
+ int transpose;
+
+ if (!cho_context_init(&ctx, config, chordpro_filepath)) {
+ LOG_DEBUG("cho_context_init failed.");
+ return NULL;
+ }
+
+ lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines;
+ (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_TOC] = config->output->toc->show;
+ for (; *str; str++) {
+ c = *str;
+ // printf("state: %s, c: %c\n", state_enums[ctx.state], c);
+ if (c == '\r') {
+ continue;
+ }
+ switch (ctx.state) {
+ case STATE_LYRICS: {
+ if (prev_c == '\n' && c == '#') {
+ ctx.state_before_comment = STATE_LYRICS;
+ ctx.state = STATE_COMMENT;
+ break;
+ }
+ if (prev_c == '\n' && c == '{') {
+ ctx.state = STATE_DIRECTIVE_NAME;
+ break;
+ }
+ if (c == '[') {
+ ctx.state = STATE_CHORD;
+ break;
+ }
+ if (c == '<') {
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) {
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ } else {
+ ctx.lii++;
+ }
+ } else {
+ ctx.lii++;
+ }
+ ctx.te = 0;
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ ctx.state_before_tag = STATE_LYRICS;
+ ctx.state = STATE_MARKUP_TAG;
+ break;
+ }
+ if (c == '%') {
+ ctx.state_before_metadata_substitution = STATE_LYRICS;
+ ctx.state = STATE_MAYBE_METADATA_SUBSTITUTION;
+ break;
+ }
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c == '\\') {
+ ctx.state_before_backslash = STATE_LYRICS;
+ ctx.state = STATE_BACKSLASH;
+ ctx.te--; // INFO: This will later overwrite the backslash
+ break;
+ }
+ if (ctx.ta > -1 && !ctx.tags[ctx.ta]->is_closed && strcmp(ctx.tags[ctx.ta]->name, "img")) {
+ // TODO: This seems to be unreachable
+ cho_log(&ctx, LOG_ERR, "Tag has to be closed on same line.");
+ return NULL;
+ }
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) {
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ if (ctx.lii == 0) {
+ if (
+ !(*lines)[ctx.li]->text_above &&
+ (*lines)[ctx.li]->btype == BREAK_TYPE_LINE
+ ) {
+ free((*lines)[ctx.li]->items);
+ free((*lines)[ctx.li]);
+ *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ break;
+ }
+ }
+ } else {
+ ctx.lii++;
+ }
+ } else {
+ ctx.lii++;
+ }
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = NULL;
+ ctx.lii = 0;
+ ctx.te = 0;
+ (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *));
+ (*lines)[ctx.li]->text_above[ctx.lia] = NULL;
+ ctx.lia = 0;
+ ctx.li++;
+ *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ break;
+ }
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = c;
+ ctx.te++;
+ break;
+ }
+ case STATE_BACKSLASH: {
+ if (!is_whitespace(c)) {
+ str--;
+ ctx.state = ctx.state_before_backslash;
+ break;
+ }
+ break;
+ }
+ case STATE_DIRECTIVE_NAME: {
+ if (c == '}') {
+ directive_name[ctx.dn] = 0;
+ ctx.dn = 0;
+ directive = cho_directive_parse(&ctx, directive_name);
+ /* printf(
+ "directive: '%s'\ndtype: %s, stype: %s, position: %s\n",
+ directive_name, cho_debug_dtype(directive->dtype), cho_debug_the_stype(directive->stype), cho_debug_the_pos(directive->position)
+ ); */
+ switch (directive->dtype) {
+ case DIRECTIVE_TYPE_ENVIRONMENT: {
+ ctx.current_ttype = directive->ttype;
+ switch (directive->position) {
+ case POSITION_START: {
+ if (directive->stype == SECTION_TYPE_CUSTOM) {
+ memset(custom_directive, 0, sizeof(custom_directive));
+ strcpy(custom_directive, &directive_name[9]);
+ }
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ free((*lines)[ctx.li]->items);
+ ctx.lii = 0;
+ free((*lines)[ctx.li]);
+ (*lines)[ctx.li] = NULL;
+ ctx.se++;
+ ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
+ ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new();
+ ctx.songs[ctx.so]->sections[ctx.se]->type = directive->stype;
+ ctx.li = 0;
+ lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines;
+ *lines = emalloc(sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ ctx.songs[ctx.so]->present_text_types[directive->ttype] = true;
+ switch (directive->stype) {
+ case SECTION_TYPE_TAB:
+ ctx.state = STATE_TAB;
+ break;
+ case SECTION_TYPE_GRID:
+ ctx.state = STATE_GRID;
+ break;
+ default:
+ ctx.state = STATE_LYRICS;
+ }
+ break;
+ }
+ case POSITION_END: {
+ if (directive->stype == ctx.songs[ctx.so]->sections[ctx.se]->type) {
+ if (directive->stype == SECTION_TYPE_CUSTOM) {
+ if (strcmp(custom_directive, &directive_name[7]) != 0) {
+ break;
+ }
+ }
+ if (directive->stype == SECTION_TYPE_GRID) {
+ ctx.grid.bar_line_symbol_count = 0;
+ ctx.grid.tokens_per_cell = 0;
+ ctx.grid.expected_tokens_per_cell = 0;
+ }
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ free((*lines)[ctx.li]->items);
+ ctx.lii = 0;
+ free((*lines)[ctx.li]);
+ (*lines)[ctx.li] = NULL;
+ ctx.se++;
+ ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
+ ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new();
+ ctx.li = 0;
+ lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines;
+ *lines = emalloc(sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ ctx.state = STATE_LYRICS;
+ } else {
+ const char *section_type = section_types[directive->stype];
+ if (section_type[0] == 0) {
+ cho_log(&ctx, LOG_ERR, "Can't close a section that wasn't opened earlier.");
+ } else {
+ cho_log(&ctx, LOG_ERR, "Can't close a %s section that wasn't opened earlier.", section_type);
+ }
+ goto ERR;
+ }
+ break;
+ }
+ case POSITION_NO: {
+ /* INFO: {chorus} */
+ chorus = cho_find_previous_chorus(ctx.songs[ctx.so]->sections, ctx.se);
+ if (chorus) {
+ if (config->output->chorus->quote) {
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ }
+ ctx.lii++;
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = NULL;
+ ctx.lii = 0;
+ ctx.te = 0;
+ (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *));
+ (*lines)[ctx.li]->text_above[ctx.lia] = NULL;
+ ctx.lia = 0;
+ cho_line_free((*lines)[ctx.li]);
+ (*lines)[ctx.li] = NULL;
+ ctx.li = 0;
+ ctx.se++;
+ ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
+ ctx.songs[ctx.so]->sections[ctx.se] = cho_section_copy(chorus);
+ ctx.se++;
+ ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
+ ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new();
+ lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines;
+ *lines = emalloc(sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ } else {
+ if (chorus->label) {
+ label = strdup(chorus->label->text);
+ } else {
+ label = strdup(config->output->chorus->label);
+ }
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) {
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ } else {
+ ctx.lii++;
+ }
+ } else {
+ ctx.lii++;
+ }
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style);
+ ctx.current_ttype = TEXT_TYPE_LABEL;
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = label;
+ ctx.te += strlen(label);
+ }
+ } else {
+ if (config->output->chorus->quote) {
+ cho_log(&ctx, LOG_WARN, "Can't quote chorus because it's not defined previously.");
+ }
+ label = strdup(config->output->chorus->label);
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) {
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ } else {
+ ctx.lii++;
+ }
+ } else {
+ ctx.lii++;
+ }
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style);
+ ctx.current_ttype = TEXT_TYPE_LABEL;
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = label;
+ ctx.te += strlen(label);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ case DIRECTIVE_TYPE_METADATA:
+ cho_log(&ctx, LOG_WARN, "Ignoring metadata directive '%s' because it has no value.", directive_name);
+ break;
+ case DIRECTIVE_TYPE_FORMATTING:
+ cho_log(&ctx, LOG_WARN, "Formatting directive '%s' has no value.", directive_name);
+ break;
+ case DIRECTIVE_TYPE_IMAGE:
+ cho_log(&ctx, LOG_ERR, "Directive 'image' has no value.");
+ goto ERR;
+ case DIRECTIVE_TYPE_PREAMBLE: {
+ // INFO: The only preamble directive is 'new_song'
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ free((*lines)[ctx.li]->items);
+ free((*lines)[ctx.li]);
+ (*lines)[ctx.li] = NULL;
+ ctx.songs[ctx.so]->metadata = erealloc(ctx.songs[ctx.so]->metadata, (ctx.m+1) * sizeof(struct ChoMetadata *));
+ ctx.songs[ctx.so]->metadata[ctx.m] = NULL;
+ ctx.se++;
+ ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
+ ctx.songs[ctx.so]->sections[ctx.se] = NULL;
+ ctx.songs[ctx.so]->diagrams = erealloc(ctx.songs[ctx.so]->diagrams, (ctx.dia+1) * sizeof(struct ChordDiagram *));
+ ctx.songs[ctx.so]->diagrams[ctx.dia] = NULL;
+ if (!cho_style_reset_default()) {
+ LOG_DEBUG("cho_style_reset_default failed.");
+ goto ERR;
+ }
+ for (int e = 0; e<ctx.ia; e++) {
+ cho_image_free(ctx.image_assets[e]);
+ }
+ free(ctx.image_assets);
+ ctx.image_assets = NULL;
+ ctx.ia = 0;
+ ctx.so++;
+ ctx.songs = erealloc(ctx.songs, (ctx.so+1) * sizeof(struct ChoSong *));
+ ctx.songs[ctx.so] = cho_song_new(&ctx);
+ if (!ctx.songs[ctx.so]) {
+ LOG_DEBUG("cho_song_new failed.");
+ goto ERR;
+ }
+ free(ctx.transpose_history);
+ ctx.th = 0;
+ ctx.transpose_history = emalloc((ctx.th+1) * sizeof(int *));
+ ctx.transpose_history[ctx.th] = 0;
+ ctx.transpose = &ctx.transpose_history[ctx.th];
+ ctx.th++;
+ ctx.se = 0;
+ ctx.li = 0;
+ ctx.lii = 0;
+ // ctx.m = 0;
+ ctx.dia = 0;
+ ctx.songs[ctx.so]->sections = emalloc((ctx.se+1) * sizeof(struct ChoSection *));
+ ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new();
+ lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines;
+ *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ break;
+ }
+ case DIRECTIVE_TYPE_FONT: {
+ sprop.ttype = directive->ttype;
+ sprop.type = directive->sprop;
+ switch (directive->sprop) {
+ case STYLE_PROPERTY_TYPE_FONT:
+ sprop.u.font_name = NULL;
+ break;
+ case STYLE_PROPERTY_TYPE_SIZE:
+ sprop.u.font_size = EMPTY_DOUBLE;
+ break;
+ case STYLE_PROPERTY_TYPE_COLOR:
+ sprop.u.foreground_color = NULL;
+ break;
+ }
+ if (!cho_style_change_default(sprop)) {
+ LOG_DEBUG("cho_style_change_default failed.");
+ goto ERR;
+ }
+ break;
+ }
+ case DIRECTIVE_TYPE_CHORD: {
+ switch (directive->ctype) {
+ case CHORD_DIRECTIVE_TRANSPOSE:
+ ctx.transpose--;
+ ctx.th--;
+ break;
+ case CHORD_DIRECTIVE_DEFINE:
+ cho_log(&ctx, LOG_WARN, "Ignoring chord directive '%s' because it has no value.", directive_name);
+ break;
+ }
+ break;
+ }
+ case DIRECTIVE_TYPE_OUTPUT:
+ (*lines)[ctx.li]->btype = directive->btype;
+ break;
+ case DIRECTIVE_TYPE_EXTENSION:
+ // INFO: Such a directive should not be logged.
+ break;
+ case DIRECTIVE_TYPE_CUSTOM:
+ cho_log(&ctx, LOG_INFO, "Ignoring custom directive '%s'.", directive_name);
+ break;
+ }
+ cho_directive_free(directive);
+ directive = NULL;
+ break;
+ }
+ if (c == '{') {
+ cho_log(&ctx, LOG_ERR, "Can't start a new directive if the previous one is not yet closed.");
+ goto ERR;
+ }
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c == '\\') {
+ ctx.state_before_backslash = STATE_DIRECTIVE_NAME;
+ ctx.state = STATE_BACKSLASH;
+ ctx.dn--;
+ break;
+ }
+ cho_log(&ctx, LOG_ERR, "Can't have a newline in a directive name.");
+ goto ERR;
+ }
+ if (c == ':' || c == ' ') {
+ directive_name[ctx.dn] = 0;
+ ctx.dn = 0;
+ ctx.state = STATE_DIRECTIVE_VALUE;
+ break;
+ }
+ directive_name[ctx.dn] = c;
+ ctx.dn++;
+ break;
+ }
+ case STATE_DIRECTIVE_VALUE: {
+ if (c == '}') {
+ directive_value[ctx.dv] = 0;
+ ctx.dv = 0;
+ stripped_directive_value = str_remove_leading_whitespace(directive_value);
+ directive = cho_directive_parse(&ctx, directive_name);
+ /* printf(
+ "directive: '%s'\ndtype: %s, stype: %s, position: %s\n",
+ directive_name, dtype(directive->dtype), the_stype(directive->stype), pos(directive->position)
+ ); */
+ switch (directive->dtype) {
+ case DIRECTIVE_TYPE_ENVIRONMENT: {
+ if (strlen(stripped_directive_value) > 0) {
+ ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_LABEL] = true;
+ }
+ ctx.current_ttype = directive->ttype;
+ switch (directive->position) {
+ case POSITION_START: {
+ if (directive->stype == SECTION_TYPE_CUSTOM) {
+ memset(custom_directive, 0, sizeof(custom_directive));
+ strcpy(custom_directive, &directive_name[9]);
+ }
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ free((*lines)[ctx.li]->items);
+ ctx.lii = 0;
+ free((*lines)[ctx.li]);
+ (*lines)[ctx.li] = NULL;
+ ctx.se++;
+ ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
+ ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new();
+ ctx.songs[ctx.so]->sections[ctx.se]->type = directive->stype;
+
+ if (strchr(stripped_directive_value, '=')) {
+ directive_attrs = cho_attrs_parse(&ctx, stripped_directive_value, directive_name);
+ if (!directive_attrs) {
+ LOG_DEBUG("cho_attrs_parse failed.");
+ goto ERR;
+ }
+ label = cho_attrs_get(directive_attrs, "label");
+ if (directive->stype == SECTION_TYPE_GRID) {
+ shape = cho_attrs_get(directive_attrs, "shape");
+ if (shape) {
+ if (!cho_grid_shape_parse_and_set(&ctx, shape)) {
+ LOG_DEBUG("cho_grid_parse_and_set_shape failed.");
+ goto ERR;
+ }
+ }
+ }
+ } else {
+ if (directive->stype == SECTION_TYPE_GRID) {
+ int index = str_index_of(stripped_directive_value, ' ');
+ if (index != -1) {
+ stripped_directive_value[index] = 0;
+ label = &stripped_directive_value[index+1];
+ }
+ if (!cho_grid_shape_parse_and_set(&ctx, stripped_directive_value)) {
+ LOG_DEBUG("cho_grid_parse_and_set_shape failed.");
+ goto ERR;
+ }
+ } else {
+ label = stripped_directive_value;
+ }
+ }
+ if (label) {
+ ctx.songs[ctx.so]->sections[ctx.se]->label = emalloc(sizeof(struct ChoText));
+ ctx.songs[ctx.so]->sections[ctx.se]->label->text = strdup(label);
+ ctx.prev_ttype = ctx.current_ttype;
+ ctx.current_ttype = TEXT_TYPE_LABEL;
+ ctx.songs[ctx.so]->sections[ctx.se]->label->style = cho_style_new_default(&ctx);
+ ctx.current_ttype = ctx.prev_ttype;
+ label = NULL;
+ }
+ cho_tag_attrs_free(directive_attrs);
+ directive_attrs = NULL;
+ if (ctx.directive_has_tag) {
+ cho_style_complement(ctx.songs[ctx.so]->sections[ctx.se]->label->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence);
+ ctx.directive_has_tag = false;
+ }
+ ctx.li = 0;
+ lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines;
+ *lines = emalloc(sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ ctx.songs[ctx.so]->present_text_types[directive->ttype] = true;
+ break;
+ }
+ case POSITION_END: {
+ cho_log(&ctx, LOG_ERR, "A directive that closes a section can't have arguments.");
+ goto ERR;
+ }
+ case POSITION_NO: {
+ /* INFO: {chorus: ...} */
+ label = strdup(stripped_directive_value);
+ chorus = cho_find_previous_chorus(ctx.songs[ctx.so]->sections, ctx.se);
+ if (chorus) {
+ if (config->output->chorus->quote) {
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ }
+ ctx.lii++;
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = NULL;
+ ctx.lii = 0;
+ ctx.te = 0;
+ (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *));
+ (*lines)[ctx.li]->text_above[ctx.lia] = NULL;
+ ctx.lia = 0;
+ cho_line_free((*lines)[ctx.li]);
+ // songs[so]->sections[se]->lines = erealloc(songs[so]->sections[se]->lines, (li+1) * sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = NULL;
+ ctx.li = 0;
+ ctx.se++;
+ ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
+ ctx.songs[ctx.so]->sections[ctx.se] = cho_section_copy(chorus);
+ if (ctx.songs[ctx.so]->sections[ctx.se]->label) {
+ free(ctx.songs[ctx.so]->sections[ctx.se]->label->text);
+ ctx.songs[ctx.so]->sections[ctx.se]->label->text = label;
+ } else {
+ ctx.current_ttype = TEXT_TYPE_LABEL;
+ ctx.songs[ctx.so]->sections[ctx.se]->label = cho_text_new(&ctx);
+ ctx.songs[ctx.so]->sections[ctx.se]->label->text = label;
+ }
+ if (ctx.directive_has_tag) {
+ cho_style_complement(ctx.songs[ctx.so]->sections[ctx.se]->label->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence);
+ ctx.directive_has_tag = false;
+ }
+ ctx.se++;
+ ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
+ ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new();
+ lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines;
+ *lines = emalloc(sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ } else {
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) {
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ } else {
+ ctx.lii++;
+ }
+ } else {
+ ctx.lii++;
+ }
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style);
+ ctx.current_ttype = TEXT_TYPE_LABEL;
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = label;
+ if (ctx.directive_has_tag) {
+ cho_style_complement((*lines)[ctx.li]->items[ctx.lii]->u.text->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence);
+ ctx.directive_has_tag = false;
+ }
+ ctx.te += strlen(label);
+ }
+ } else {
+ if (config->output->chorus->quote) {
+ cho_log(&ctx, LOG_WARN, "Can't quote chorus because it's not defined previously.");
+ }
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) {
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ } else {
+ ctx.lii++;
+ }
+ } else {
+ ctx.lii++;
+ }
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style);
+ ctx.current_ttype = TEXT_TYPE_LABEL;
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = label;
+ if (ctx.directive_has_tag) {
+ cho_style_complement((*lines)[ctx.li]->items[ctx.lii]->u.text->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence);
+ }
+ ctx.te += strlen(label);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ case DIRECTIVE_TYPE_METADATA: {
+ metadata_value = strdup(stripped_directive_value);
+ if (strlen(metadata_value) == 0) {
+ cho_log(&ctx, LOG_WARN, "Ignoring metadata directive '%s' because it has no value.", directive_name);
+ free(metadata_value);
+ break;
+ }
+ switch (directive->meta) {
+ case METADATA_DIRECTIVE_TITLE:
+ ctx.songs[ctx.so]->metadata = erealloc(ctx.songs[ctx.so]->metadata, (ctx.m+1) * sizeof(struct ChoMetadata *));
+ ctx.songs[ctx.so]->metadata[ctx.m] = cho_metadata_new(&ctx);
+ ctx.songs[ctx.so]->metadata[ctx.m]->name = strdup("title");
+ ctx.songs[ctx.so]->metadata[ctx.m]->value = metadata_value;
+ cho_style_free(ctx.songs[ctx.so]->metadata[ctx.m]->style);
+ ctx.songs[ctx.so]->metadata[ctx.m]->style = cho_style_copy(directive->style);
+ ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_TITLE] = true;
+ break;
+ case METADATA_DIRECTIVE_SUBTITLE:
+ ctx.songs[ctx.so]->metadata = erealloc(ctx.songs[ctx.so]->metadata, (ctx.m+1) * sizeof(struct ChoMetadata *));
+ ctx.songs[ctx.so]->metadata[ctx.m] = cho_metadata_new(&ctx);
+ ctx.songs[ctx.so]->metadata[ctx.m]->name = strdup("subtitle");
+ ctx.songs[ctx.so]->metadata[ctx.m]->value = metadata_value;
+ cho_style_free(ctx.songs[ctx.so]->metadata[ctx.m]->style);
+ ctx.songs[ctx.so]->metadata[ctx.m]->style = cho_style_copy(directive->style);
+ ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_SUBTITLE] = true;
+ break;
+ case METADATA_DIRECTIVE_OTHER: {
+ if (!strcmp(directive_name, "meta")) {
+ metadata = cho_metadata_split(&ctx, directive_value);
+ if (!metadata) {
+ LOG_DEBUG("cho_metadata_split failed.");
+ goto ERR;
+ }
+ ctx.songs[ctx.so]->metadata = erealloc(ctx.songs[ctx.so]->metadata, (ctx.m+1) * sizeof(struct ChoMetadata *));
+ ctx.songs[ctx.so]->metadata[ctx.m] = metadata;
+ free(metadata_value);
+ } else {
+ ctx.songs[ctx.so]->metadata = erealloc(ctx.songs[ctx.so]->metadata, (ctx.m+1) * sizeof(struct ChoMetadata *));
+ ctx.songs[ctx.so]->metadata[ctx.m] = cho_metadata_new(&ctx);
+ ctx.songs[ctx.so]->metadata[ctx.m]->name = strdup(directive_name);
+ ctx.songs[ctx.so]->metadata[ctx.m]->value = metadata_value;
+ }
+ }
+ }
+ if (ctx.directive_has_tag) {
+ cho_style_complement(ctx.songs[ctx.so]->metadata[ctx.m]->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence);
+ ctx.directive_has_tag = false;
+ }
+ ctx.m++;
+ break;
+ }
+ case DIRECTIVE_TYPE_FORMATTING: {
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) {
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ } else {
+ ctx.lii++;
+ }
+ } else {
+ ctx.lii++;
+ }
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_copy(directive->style);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(stripped_directive_value);
+ if (ctx.directive_has_tag) {
+ cho_style_complement((*lines)[ctx.li]->items[ctx.lii]->u.text->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence);
+ ctx.directive_has_tag = false;
+ }
+ ctx.te += strlen(stripped_directive_value);
+ ctx.songs[ctx.so]->present_text_types[directive->ttype] = true;
+ break;
+ }
+ case DIRECTIVE_TYPE_IMAGE: {
+ if (strchr(directive_value, '=')) {
+ image = cho_image_directive_parse(&ctx, directive_value);
+ if (!image) {
+ LOG_DEBUG("cho_image_directive_parse failed.");
+ goto ERR;
+ }
+ } else {
+ image = cho_image_new();
+ image->src = strdup(stripped_directive_value);
+ }
+ if (image->is_asset) {
+ ctx.image_assets = erealloc(ctx.image_assets, (ctx.ia+1) * sizeof(struct ChoImage *));
+ ctx.image_assets[ctx.ia] = image;
+ ctx.ia++;
+ } else {
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ ctx.te = 0;
+ }
+ ctx.lii++;
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ cho_text_free((*lines)[ctx.li]->items[ctx.lii]->u.text);
+ (*lines)[ctx.li]->items[ctx.lii]->is_text = false;
+ (*lines)[ctx.li]->items[ctx.lii]->u.image = image;
+ }
+ break;
+ }
+ case DIRECTIVE_TYPE_PREAMBLE:
+ cho_log(&ctx, LOG_ERR, "Preamble directive '%s' can't have a value.", directive_name);
+ goto ERR;
+ case DIRECTIVE_TYPE_FONT: {
+ sprop.ttype = directive->ttype;
+ char *dir_value = strdup(stripped_directive_value);
+ switch (directive->sprop) {
+ case STYLE_PROPERTY_TYPE_FONT:
+ sprop.u.font_name = emalloc((strlen(dir_value)+1) * sizeof(char));
+ strcpy(sprop.u.font_name, dir_value);
+ sprop.type = STYLE_PROPERTY_TYPE_FONT;
+ break;
+ case STYLE_PROPERTY_TYPE_SIZE:
+ sprop.u.font_size = strtod(dir_value, NULL);
+ if (sprop.u.font_size == 0.0) {
+ cho_log(&ctx, LOG_ERR, "Font directive '%s' has an invalid value.", directive_name);
+ goto ERR;
+ }
+ sprop.type = STYLE_PROPERTY_TYPE_SIZE;
+ break;
+ case STYLE_PROPERTY_TYPE_COLOR:
+ sprop.u.foreground_color = cho_color_parse(dir_value);
+ if (sprop.u.foreground_color == NULL) {
+ cho_log(&ctx, LOG_ERR, "Font directive '%s' has an invalid value.", directive_name);
+ goto ERR;
+ }
+ sprop.type = STYLE_PROPERTY_TYPE_COLOR;
+ break;
+ default:
+ cho_log(&ctx, LOG_ERR, "Invalid style property type '%d'.", directive->sprop);
+ goto ERR;
+ }
+ if (!cho_style_change_default(sprop)) {
+ LOG_DEBUG("cho_style_change_default failed.");
+ goto ERR;
+ }
+ if (sprop.type == STYLE_PROPERTY_TYPE_FONT) {
+ free(sprop.u.font_name);
+ } else if (sprop.type == STYLE_PROPERTY_TYPE_COLOR) {
+ free(sprop.u.foreground_color);
+ }
+ free(dir_value);
+ break;
+ }
+ case DIRECTIVE_TYPE_CHORD: {
+ switch (directive->ctype) {
+ case CHORD_DIRECTIVE_TRANSPOSE:
+ if (!transposition_parse(directive_value, &transpose)) {
+ LOG_DEBUG("transposition_parse failed.");
+ cho_log(&ctx, LOG_ERR, "Directive 'transpose' has an invalid value.");
+ goto ERR;
+ }
+ ctx.transpose_history = erealloc(ctx.transpose_history, (ctx.th+1) * sizeof(int *));
+ ctx.transpose_history[ctx.th] = ctx.transpose_history[ctx.th-1] + transpose;
+ ctx.transpose = &ctx.transpose_history[ctx.th];
+ ctx.th++;
+ break;
+ case CHORD_DIRECTIVE_DEFINE:
+ diagram = cho_chord_diagram_parse(&ctx, directive_value, ctx.songs[ctx.so]->diagrams, ctx.dia);
+ if (!diagram) {
+ LOG_DEBUG("cho_chord_diagram_parse failed.");
+ goto ERR;
+ }
+ // debug_chord_diagram_print(diagram);
+ ctx.songs[ctx.so]->diagrams = erealloc(ctx.songs[ctx.so]->diagrams, (ctx.dia+1) * sizeof(struct ChordDiagram *));
+ ctx.songs[ctx.so]->diagrams[ctx.dia] = diagram;
+ ctx.dia++;
+ break;
+ }
+ break;
+ }
+ case DIRECTIVE_TYPE_OUTPUT:
+ cho_log(&ctx, LOG_ERR, "Directive '%s' can't have a value.", directive_name);
+ goto ERR;
+ case DIRECTIVE_TYPE_EXTENSION:
+ // INFO: Such a directive should not be logged.
+ break;
+ case DIRECTIVE_TYPE_CUSTOM:
+ cho_log(&ctx, LOG_INFO, "Ignoring custom directive '%s'.", directive_name);
+ break;
+ }
+ switch (directive->stype) {
+ case SECTION_TYPE_TAB:
+ ctx.state = STATE_TAB;
+ break;
+ case SECTION_TYPE_GRID:
+ ctx.state = STATE_GRID;
+ break;
+ default:
+ ctx.state = STATE_LYRICS;
+ }
+ memset(directive_value, 0, strlen(directive_value));
+ free(stripped_directive_value);
+ stripped_directive_value = NULL;
+ cho_directive_free(directive);
+ directive = NULL;
+ break;
+ }
+ if (c == '%') {
+ ctx.state_before_metadata_substitution = STATE_DIRECTIVE_VALUE;
+ ctx.state = STATE_MAYBE_METADATA_SUBSTITUTION;
+ break;
+ }
+ if (c == '<') {
+ ctx.state_before_tag = STATE_DIRECTIVE_VALUE;
+ ctx.state = STATE_MARKUP_TAG;
+ break;
+ }
+ if (c == '{') {
+ cho_log(&ctx, LOG_ERR, "Can't start a new directive if the previous one is not yet closed.");
+ goto ERR;
+ }
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c == '\\') {
+ ctx.state_before_backslash = STATE_DIRECTIVE_VALUE;
+ ctx.state = STATE_BACKSLASH;
+ ctx.dv--;
+ break;
+ }
+ cho_log(&ctx, LOG_ERR, "Newline character inside a directive value is invalid.");
+ goto ERR;
+ }
+ if (ctx.dv > 4094) {
+ cho_log(&ctx, LOG_ERR, "Directive value can't be greater than 4095 bytes.");
+ goto ERR;
+ }
+ directive_value[ctx.dv] = c;
+ ctx.dv++;
+ break;
+ }
+ case STATE_CHORD: {
+ if (c == ']') {
+ chord[ctx.ch] = 0;
+ ctx.ch = 0;
+ ctx.prev_ttype = ctx.current_ttype;
+ ctx.current_ttype = TEXT_TYPE_CHORD;
+ if (ctx.is_chord_already_initialized) {
+ ctx.text_above_pos = cho_line_compute_text_above_position(ctx.songs[ctx.so]->sections[ctx.se]->lines[ctx.li], ctx.lii, ctx.te);
+ (*lines)[ctx.li]->text_above[ctx.lia]->position = ctx.text_above_pos;
+ tmp_chord = cho_chord_parse(&ctx, chord);
+ cho_chord_complete((*lines)[ctx.li]->text_above[ctx.lia]->u.chord, tmp_chord);
+ if (!(*lines)[ctx.li]->text_above[ctx.lia]->u.chord->is_canonical) {
+ cho_log(&ctx, LOG_INFO, "Didn't recognize the chord '%s'.", (*lines)[ctx.li]->text_above[ctx.lia]->u.chord->name);
+ }
+ cho_chord_free(tmp_chord);
+ ctx.is_chord_already_initialized = false;
+ } else {
+ (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *));
+ (*lines)[ctx.li]->text_above[ctx.lia] = emalloc(sizeof(struct ChoLineItemAbove));
+ (*lines)[ctx.li]->text_above[ctx.lia]->is_chord = true;
+ ctx.text_above_pos = cho_line_compute_text_above_position((*lines)[ctx.li], ctx.lii, ctx.te);
+ (*lines)[ctx.li]->text_above[ctx.lia]->position = ctx.text_above_pos;
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.chord = cho_chord_parse(&ctx, chord);
+ if (!(*lines)[ctx.li]->text_above[ctx.lia]->u.chord->is_canonical) {
+ cho_log(&ctx, LOG_INFO, "Didn't recognize the chord '%s'.", (*lines)[ctx.li]->text_above[ctx.lia]->u.chord->name);
+ }
+ }
+ ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_CHORD] = true;
+ memset(chord, 0, strlen(chord));
+ ctx.lia++;
+ ctx.current_ttype = ctx.prev_ttype;
+ ctx.state = STATE_LYRICS;
+ break;
+ }
+ if (prev_c == '[' && c == '*') {
+ ctx.prev_ttype = ctx.current_ttype;
+ ctx.current_ttype = TEXT_TYPE_ANNOT;
+ (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *));
+ (*lines)[ctx.li]->text_above[ctx.lia] = emalloc(sizeof(struct ChoLineItemAbove));
+ (*lines)[ctx.li]->text_above[ctx.lia]->is_chord = false;
+ ctx.text_above_pos = cho_line_compute_text_above_position((*lines)[ctx.li], ctx.lii, ctx.te);
+ (*lines)[ctx.li]->text_above[ctx.lia]->position = ctx.text_above_pos;
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot = cho_text_new(&ctx);
+ ctx.state = STATE_ANNOTATION;
+ break;
+ }
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c == '\\') {
+ ctx.state_before_backslash = STATE_CHORD;
+ ctx.state = STATE_BACKSLASH;
+ ctx.ch--;
+ break;
+ }
+ cho_log(&ctx, LOG_ERR, "Newline character inside a chord is invalid.");
+ goto ERR;
+ }
+ if (c == '[') {
+ cho_log(&ctx, LOG_ERR, "Can't start a new chord/annotation if the previous one is not yet closed.");
+ goto ERR;
+ }
+ if (c == '<') {
+ if (prev_c == '[') {
+ (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *));
+ (*lines)[ctx.li]->text_above[ctx.lia] = emalloc(sizeof(struct ChoLineItemAbove));
+ (*lines)[ctx.li]->text_above[ctx.lia]->is_chord = true;
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.chord = cho_chord_new(&ctx);
+ ctx.is_chord_already_initialized = true;
+ }
+ ctx.state_before_tag = STATE_CHORD;
+ ctx.state = STATE_MARKUP_TAG;
+ break;
+ }
+ if (ctx.ch > CHORD_LEN-2) {
+ cho_log(&ctx, LOG_ERR, "Chord can't be greater than %d bytes.", CHORD_LEN-1);
+ goto ERR;
+ }
+ chord[ctx.ch] = c;
+ ctx.ch++;
+ break;
+ }
+ case STATE_ANNOTATION: {
+ if (c == ']') {
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text = erealloc((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text, (ctx.ann+1) * sizeof(char));
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text[ctx.ann] = 0;
+ ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_ANNOT] = true;
+ ctx.ann = 0;
+ ctx.lia++;
+ ctx.current_ttype = ctx.prev_ttype;
+ ctx.state = STATE_LYRICS;
+ break;
+ }
+ if (c == '<') {
+ ctx.state_before_tag = STATE_ANNOTATION;
+ ctx.state = STATE_MARKUP_TAG;
+ break;
+ }
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c == '\\') {
+ ctx.state_before_backslash = STATE_ANNOTATION;
+ ctx.state = STATE_BACKSLASH;
+ ctx.ann--;
+ break;
+ }
+ cho_log(&ctx, LOG_ERR, "Newline character inside an annotation is invalid.");
+ goto ERR;
+ }
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text = erealloc((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text, (ctx.ann+1) * sizeof(char));
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text[ctx.ann] = c;
+ ctx.ann++;
+ break;
+ }
+ case STATE_TAB: {
+ // INFO: similar to STATE_LYRICS but without markup and directives
+ if (prev_c == '\n' && c == '#') {
+ ctx.state_before_comment = STATE_TAB;
+ ctx.state = STATE_COMMENT;
+ break;
+ }
+ if (ctx.is_maybe_end_of_tab_directive) {
+ if (c == '}') {
+ directive_name[ctx.dn] = 0;
+ ctx.dn = 0;
+ ctx.is_maybe_end_of_tab_directive = false;
+ if (!strcmp(directive_name, "end_of_tab") || !strcmp(directive_name, "eot")) {
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ free((*lines)[ctx.li]->items);
+ ctx.lii = 0;
+ free((*lines)[ctx.li]);
+ (*lines)[ctx.li] = NULL;
+ ctx.se++;
+ ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
+ ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new();
+ ctx.li = 0;
+ lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines;
+ *lines = emalloc(sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ ctx.current_ttype = TEXT_TYPE_TEXT;
+ ctx.state = STATE_LYRICS;
+ break;
+ } else {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = '{';
+ ctx.te++;
+ char *k;
+ for (k = (char *)&directive_name; *k; k++, ctx.te++) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = *k;
+ }
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = '}';
+ ctx.te++;
+ }
+ break;
+ }
+ if (c == ' ' || c == ':') {
+ directive_name[ctx.dn] = 0;
+ ctx.dn = 0;
+ ctx.is_maybe_end_of_tab_directive = false;
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = '{';
+ ctx.te++;
+ char *k;
+ for (k = (char *)&directive_name; *k; k++, ctx.te++) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = *k;
+ }
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = c;
+ ctx.te++;
+ break;
+ }
+ directive_name[ctx.dn] = c;
+ ctx.dn++;
+ break;
+ }
+ if (prev_c == '\n' && c == '{') {
+ ctx.is_maybe_end_of_tab_directive = true;
+ break;
+ }
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c == '\\') {
+ ctx.state_before_backslash = STATE_TAB;
+ ctx.state = STATE_BACKSLASH;
+ // INFO: This will later overwrite the backslash
+ ctx.te--;
+ break;
+ }
+ if (ctx.ta > -1 && !ctx.tags[ctx.ta]->is_closed && strcmp(ctx.tags[ctx.ta]->name, "img")) {
+ cho_log(&ctx, LOG_ERR, "Tag has to be closed on same line.");
+ goto ERR;
+ }
+ if ((*lines)[ctx.li]->items[ctx.lii]->is_text) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0;
+ if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) {
+ cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]);
+ if (ctx.lii == 0) {
+ if (
+ !(*lines)[ctx.li]->text_above &&
+ (*lines)[ctx.li]->btype == BREAK_TYPE_LINE
+ ) {
+ free((*lines)[ctx.li]->items);
+ free((*lines)[ctx.li]);
+ *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ break;
+ }
+ }
+ } else {
+ ctx.lii++;
+ }
+ } else {
+ ctx.lii++;
+ }
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = NULL;
+ ctx.lii = 0;
+ ctx.te = 0;
+ (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *));
+ (*lines)[ctx.li]->text_above[ctx.lia] = NULL;
+ ctx.lia = 0;
+ ctx.li++;
+ *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ break;
+ }
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = c;
+ ctx.te++;
+ break;
+ }
+ case STATE_GRID: {
+ if (prev_c == '\n' && c == '#') {
+ ctx.state_before_comment = STATE_GRID;
+ ctx.state = STATE_COMMENT;
+ break;
+ }
+ if (prev_c == '\n' && c == '{') {
+ ctx.state = STATE_DIRECTIVE_NAME;
+ break;
+ }
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c != '}') {
+ grid_token[ctx.gt] = 0;
+ ctx.gt = 0;
+ if (grid_token[0] == 0) {
+ break;
+ }
+ token = cho_grid_token_parse(&ctx, grid_token, &err);
+ if (err) {
+ cho_log(&ctx, LOG_ERR, "Invalid token '%s' in grid section.", grid_token);
+ goto ERR;
+ }
+ if (token == GRID_TOKEN_BAR_LINE_SYMBOL) {
+ ctx.grid.bar_line_symbol_count++;
+ ctx.grid.bar_line_symbol_in_line_count++;
+ if (ctx.grid.bar_line_symbol_count == 2) {
+ ctx.grid.expected_tokens_per_cell = ctx.grid.tokens_per_cell;
+ } else {
+ if (ctx.grid.bar_line_symbol_in_line_count > 1 && ctx.grid.expected_tokens_per_cell != ctx.grid.tokens_per_cell) {
+ cho_log(
+ &ctx,
+ LOG_ERR,
+ "Grid cell no. %d has %d tokens but should have %d tokens.",
+ ctx.grid.bar_line_symbol_in_line_count - 1,
+ ctx.grid.tokens_per_cell,
+ ctx.grid.expected_tokens_per_cell
+ );
+ goto ERR;
+ }
+ }
+ ctx.grid.tokens_per_cell = 0;
+ } else {
+ ctx.grid.tokens_per_cell++;
+ }
+ if (token == GRID_TOKEN_PLACEHOLDER) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(" ");
+ } else {
+ ctx.prev_ttype = ctx.current_ttype;
+ ctx.current_ttype = TEXT_TYPE_GRID;
+ cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(grid_token);
+ ctx.current_ttype = ctx.prev_ttype;
+ }
+ ctx.lii++;
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = NULL;
+ ctx.lii = 0;
+ (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->text_above[ctx.lia] = NULL;
+ ctx.lia = 0;
+ ctx.li++;
+ *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *));
+ (*lines)[ctx.li] = cho_line_new();
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ ctx.grid.bar_line_symbol_in_line_count = 0;
+ }
+ break;
+ }
+ if (c == ' ') {
+ grid_token[ctx.gt] = 0;
+ ctx.gt = 0;
+ if (grid_token[0] == 0) {
+ break;
+ }
+ token = cho_grid_token_parse(&ctx, grid_token, &err);
+ if (err) {
+ cho_log(&ctx, LOG_ERR, "Invalid token '%s' in grid section.", grid_token);
+ goto ERR;
+ }
+ if (token == GRID_TOKEN_BAR_LINE_SYMBOL) {
+ ctx.grid.bar_line_symbol_count++;
+ ctx.grid.bar_line_symbol_in_line_count++;
+ if (ctx.grid.bar_line_symbol_count == 2) {
+ ctx.grid.expected_tokens_per_cell = ctx.grid.tokens_per_cell;
+ } else {
+ if (ctx.grid.bar_line_symbol_in_line_count > 1 && ctx.grid.expected_tokens_per_cell != ctx.grid.tokens_per_cell) {
+ cho_log(
+ &ctx,
+ LOG_ERR,
+ "Grid cell no. %d has %d tokens but should have %d tokens.",
+ ctx.grid.bar_line_symbol_in_line_count - 1,
+ ctx.grid.tokens_per_cell,
+ ctx.grid.expected_tokens_per_cell
+ );
+ goto ERR;
+ }
+ }
+ ctx.grid.tokens_per_cell = 0;
+ } else {
+ ctx.grid.tokens_per_cell++;
+ }
+ if (token == GRID_TOKEN_PLACEHOLDER) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(" ");
+ } else {
+ ctx.prev_ttype = ctx.current_ttype;
+ ctx.current_ttype = TEXT_TYPE_GRID;
+ cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(grid_token);
+ ctx.current_ttype = ctx.prev_ttype;
+ }
+ ctx.lii++;
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ break;
+ }
+ grid_token[ctx.gt] = c;
+ ctx.gt++;
+ break;
+ }
+ case STATE_MARKUP_TAG: {
+ if (c == '/') {
+ ctx.state = STATE_MARKUP_TAG_END;
+ break;
+ }
+ ctx.ta++;
+ ctx.tags = erealloc(ctx.tags, (ctx.ta+1) * sizeof(struct Tag *));
+ ctx.tags[ctx.ta] = cho_tag_new();
+ ctx.state = STATE_MARKUP_TAG_START;
+ str--;
+ break;
+ }
+ case STATE_MARKUP_TAG_START: {
+ if (c == '>') {
+ tag_start[ctx.t] = 0;
+ ctx.t = 0;
+ if (!strcmp(tag_start, "img")) {
+ cho_log(&ctx, LOG_ERR, "'img' tag has to have at least the 'src' attribute.");
+ goto ERR;
+ }
+ ctx.tags[ctx.ta]->name = strdup(tag_start);
+ tag_style = cho_style_parse(&ctx, tag_start, NULL, cho_tag_style_inherit(ctx.tags, ctx.ta-1), &ctx.tags[ctx.ta]->style_presence);
+ if (!tag_style) {
+ LOG_DEBUG("cho_style_parse failed.");
+ goto ERR;
+ }
+ ctx.tags[ctx.ta]->style = tag_style;
+ switch (ctx.state_before_tag) {
+ case STATE_LYRICS:
+ cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_copy(tag_style);
+ break;
+ case STATE_CHORD:
+ cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style);
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style = cho_style_copy(tag_style);
+ break;
+ case STATE_ANNOTATION:
+ if (ctx.ann > 0) {
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text = erealloc((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text, (ctx.ann+1) * sizeof(char));
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text[ctx.ann] = 0;
+ ctx.ann = 0;
+ ctx.lia++;
+ (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *));
+ (*lines)[ctx.li]->text_above[ctx.lia] = emalloc(sizeof(struct ChoLineItemAbove));
+ (*lines)[ctx.li]->text_above[ctx.lia]->is_chord = false;
+ (*lines)[ctx.li]->text_above[ctx.lia]->position = ctx.text_above_pos;
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot = cho_text_new(&ctx);
+ }
+ cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style);
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style = cho_style_copy(tag_style);
+ break;
+ case STATE_DIRECTIVE_VALUE:
+ ctx.directive_has_tag = true;
+ break;
+ default:
+ cho_log(&ctx, LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[ctx.state_before_tag]);
+ goto ERR;
+ }
+ memset(tag_start, 0, strlen(tag_start));
+ ctx.state = ctx.state_before_tag;
+ break;
+ }
+ if (is_whitespace(c)) {
+ tag_start[ctx.t] = 0;
+ ctx.t = 0;
+ ctx.tags[ctx.ta]->name = strdup(tag_start);
+ ctx.tags[ctx.ta]->attrs = erealloc(ctx.tags[ctx.ta]->attrs, (ctx.at+1) * sizeof(struct Attr *));
+ ctx.tags[ctx.ta]->attrs[ctx.at] = cho_tag_attr_new();
+ ctx.state = STATE_MARKUP_ATTR_NAME;
+ break;
+ }
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c == '\\') {
+ ctx.state_before_backslash = STATE_MARKUP_TAG_START;
+ ctx.state = STATE_BACKSLASH;
+ ctx.t--;
+ break;
+ }
+ cho_log(&ctx, LOG_ERR, "Newline character inside a tag name is invalid.");
+ goto ERR;
+ }
+ if (ctx.t == 5) {
+ cho_log(&ctx, LOG_ERR, "Start tag name is too long.");
+ goto ERR;
+ }
+ tag_start[ctx.t] = c;
+ ctx.t++;
+ break;
+ }
+ case STATE_MARKUP_TAG_END: {
+ if (c == '>') {
+ tag_end[ctx.t] = 0;
+ ctx.t = 0;
+ if (!cho_tag_close_last_unclosed(&ctx, tag_end, ctx.tags, ctx.ta)) {
+ LOG_DEBUG("cho_tag_close_last_unclosed failed.");
+ goto ERR;
+ }
+ memset(tag_end, 0, strlen(tag_end));
+ ctx.state = ctx.state_before_tag;
+ break;
+ }
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c == '\\') {
+ ctx.state_before_backslash = STATE_MARKUP_TAG_END;
+ ctx.state = STATE_BACKSLASH;
+ ctx.t--;
+ break;
+ }
+ cho_log(&ctx, LOG_ERR, "Newline character inside a tag name is invalid.");
+ goto ERR;
+ }
+ if (ctx.t == 5) {
+ cho_log(&ctx, LOG_ERR, "End tag name is too long.");
+ goto ERR;
+ }
+ tag_end[ctx.t] = c;
+ ctx.t++;
+ break;
+ }
+ case STATE_MARKUP_ATTR_NAME: {
+ if (c == '=') {
+ ctx.tags[ctx.ta]->attrs[ctx.at]->name = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->name, (ctx.atn+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->name[ctx.atn] = 0;
+ ctx.atn = 0;
+ ctx.state = STATE_MARKUP_ATTR_VALUE;
+ break;
+ }
+ if (is_whitespace(c)) {
+ if (ctx.at == 0) {
+ if (!ctx.tags[ctx.ta]->attrs[ctx.at]->name) {
+ break;
+ } else {
+ ctx.tags[ctx.ta]->attrs[ctx.at]->name = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->name, (ctx.atn+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->name[ctx.atn] = 0;
+ cho_log(&ctx, LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", ctx.tags[ctx.ta]->attrs[ctx.at]->name, tag_start);
+ goto ERR;
+ }
+ }
+ if (ctx.tags[ctx.ta]->attrs[ctx.at-1]->name && ctx.tags[ctx.ta]->attrs[ctx.at-1]->value) {
+ break;
+ }
+ if (!ctx.tags[ctx.ta]->attrs[ctx.at-1]->name && !ctx.tags[ctx.ta]->attrs[ctx.at-1]->value) {
+ break;
+ }
+ ctx.tags[ctx.ta]->attrs[ctx.at]->name = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->name, (ctx.atn+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->name[ctx.atn] = 0;
+ cho_log(&ctx, LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", ctx.tags[ctx.ta]->attrs[ctx.at]->name, tag_start);
+ goto ERR;
+ }
+ if (c == '>') {
+ if (ctx.tags[ctx.ta]->attrs[ctx.at-1]->value) {
+ cho_tag_attr_free(ctx.tags[ctx.ta]->attrs[ctx.at]);
+ ctx.tags[ctx.ta]->attrs[ctx.at] = NULL;
+ ctx.atn = 0;
+ if (!strcmp(ctx.tags[ctx.ta]->name, "img")) {
+ cho_text_free((*lines)[ctx.li]->items[ctx.lii]->u.text);
+ (*lines)[ctx.li]->items[ctx.lii]->is_text = false;
+ image = cho_image_tag_parse(&ctx, ctx.tags[ctx.ta]->attrs);
+ if (!image) {
+ LOG_DEBUG("cho_image_tag_parse failed.");
+ goto ERR;
+ }
+ (*lines)[ctx.li]->items[ctx.lii]->u.image = image;
+ ctx.lii++;
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ } else {
+ tag_style = cho_style_parse(&ctx, tag_start, ctx.tags[ctx.ta]->attrs, cho_tag_style_inherit(ctx.tags, ctx.ta-1), &ctx.tags[ctx.ta]->style_presence);
+ if (!tag_style) {
+ LOG_DEBUG("cho_style_parse failed.");
+ goto ERR;
+ }
+ ctx.tags[ctx.ta]->style = tag_style;
+ switch (ctx.state_before_tag) {
+ case STATE_LYRICS:
+ cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_copy(tag_style);
+ break;
+ case STATE_CHORD:
+ cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style);
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style = cho_style_copy(tag_style);
+ break;
+ case STATE_ANNOTATION:
+ cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style);
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style = cho_style_copy(tag_style);
+ break;
+ case STATE_DIRECTIVE_VALUE:
+ ctx.directive_has_tag = true;
+ break;
+ default:
+ cho_log(&ctx, LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[ctx.state_before_tag]);
+ goto ERR;
+ }
+ }
+ ctx.at = 0;
+ memset(tag_start, 0, strlen(tag_start));
+ ctx.state = ctx.state_before_tag;
+ break;
+ } else {
+ ctx.tags[ctx.ta]->attrs[ctx.at]->name = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->name, (ctx.atn+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->name[ctx.atn] = 0;
+ ctx.atn = 0;
+ cho_log(&ctx, LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", ctx.tags[ctx.ta]->attrs[ctx.at]->name, tag_start);
+ goto ERR;
+ }
+ }
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c == '\\') {
+ ctx.state_before_backslash = STATE_MARKUP_ATTR_NAME;
+ ctx.state = STATE_BACKSLASH;
+ ctx.atn--;
+ break;
+ }
+ cho_log(&ctx, LOG_ERR, "Newline character inside an tag attribute name is invalid.");
+ goto ERR;
+ }
+ ctx.tags[ctx.ta]->attrs[ctx.at]->name = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->name, (ctx.atn+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->name[ctx.atn] = c;
+ ctx.atn++;
+ break;
+ }
+ case STATE_MARKUP_ATTR_VALUE: {
+ if (c == '\n') {
+ ctx.line_no++;
+ if (prev_c == '\\') {
+ ctx.state_before_backslash = STATE_MARKUP_ATTR_VALUE;
+ ctx.state = STATE_BACKSLASH;
+ ctx.atv--;
+ break;
+ }
+ cho_log(&ctx, LOG_ERR, "Newline character inside an attribute value is invalid.");
+ goto ERR;
+ }
+ if (avs == ATTRIBUTE_VALUE_SYNTAX_UNINITIALIZED) {
+ if (is_whitespace(c)) {
+ cho_log(&ctx, LOG_ERR, "Whitespace character after equals sign is invalid.");
+ goto ERR;
+ }
+ if (c == '>') {
+ cho_log(&ctx, LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", ctx.tags[ctx.ta]->attrs[ctx.at]->name, tag_start);
+ goto ERR;
+ }
+ if (c == '\'') {
+ avs = ATTRIBUTE_VALUE_SYNTAX_APOSTROPHE;
+ } else if (c == '"') {
+ avs = ATTRIBUTE_VALUE_SYNTAX_QUOTATION_MARK;
+ } else {
+ avs = ATTRIBUTE_VALUE_SYNTAX_UNQUOTED;
+ if (c == '%') {
+ ctx.state_before_metadata_substitution = STATE_MARKUP_ATTR_VALUE;
+ ctx.state = STATE_MAYBE_METADATA_SUBSTITUTION;
+ break;
+ }
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = c;
+ ctx.atv++;
+ }
+ break;
+ }
+ if (avs == ATTRIBUTE_VALUE_SYNTAX_UNQUOTED && c == '>') {
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = 0;
+ ctx.atv = 0;
+ ctx.at++;
+ ctx.tags[ctx.ta]->attrs = erealloc(ctx.tags[ctx.ta]->attrs, (ctx.at+1) * sizeof(struct Attr *));
+ ctx.tags[ctx.ta]->attrs[ctx.at] = NULL;
+ if (!strcmp(ctx.tags[ctx.ta]->name, "img")) {
+ cho_text_free((*lines)[ctx.li]->items[ctx.lii]->u.text);
+ (*lines)[ctx.li]->items[ctx.lii]->is_text = false;
+ image = cho_image_tag_parse(&ctx, ctx.tags[ctx.ta]->attrs);
+ if (!image) {
+ LOG_DEBUG("cho_image_tag_parse failed.");
+ goto ERR;
+ }
+ (*lines)[ctx.li]->items[ctx.lii]->u.image = image;
+ ctx.lii++;
+ (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *));
+ (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx);
+ } else {
+ tag_style = cho_style_parse(&ctx, tag_start, ctx.tags[ctx.ta]->attrs, cho_tag_style_inherit(ctx.tags, ctx.ta-1), &ctx.tags[ctx.ta]->style_presence);
+ if (!tag_style) {
+ LOG_DEBUG("cho_style_parse failed.");
+ goto ERR;
+ }
+ ctx.tags[ctx.ta]->style = tag_style;
+ switch (ctx.state_before_tag) {
+ case STATE_LYRICS:
+ cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style);
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_copy(tag_style);
+ break;
+ case STATE_CHORD:
+ cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style);
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style = cho_style_copy(tag_style);
+ break;
+ case STATE_ANNOTATION:
+ cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style);
+ (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style = cho_style_copy(tag_style);
+ break;
+ case STATE_DIRECTIVE_VALUE:
+ ctx.directive_has_tag = true;
+ break;
+ default:
+ cho_log(&ctx, LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[ctx.state_before_tag]);
+ goto ERR;
+ }
+ }
+ ctx.at = 0;
+ avs = ATTRIBUTE_VALUE_SYNTAX_UNINITIALIZED;
+ memset(tag_start, 0, strlen(tag_start));
+ ctx.state = ctx.state_before_tag;
+ break;
+ }
+ if (
+ (avs == ATTRIBUTE_VALUE_SYNTAX_APOSTROPHE && c == '\'') ||
+ (avs == ATTRIBUTE_VALUE_SYNTAX_QUOTATION_MARK && c == '"') ||
+ (avs == ATTRIBUTE_VALUE_SYNTAX_UNQUOTED && (c == ' ' || c == '\t'))
+ ) {
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = 0;
+ ctx.atv = 0;
+ ctx.at++;
+ ctx.tags[ctx.ta]->attrs = erealloc(ctx.tags[ctx.ta]->attrs, (ctx.at+1) * sizeof(struct Attr *));
+ ctx.tags[ctx.ta]->attrs[ctx.at] = cho_tag_attr_new();
+ avs = ATTRIBUTE_VALUE_SYNTAX_UNINITIALIZED;
+ ctx.state = STATE_MARKUP_ATTR_NAME;
+ break;
+ }
+ if (c == '%') {
+ ctx.state_before_metadata_substitution = STATE_MARKUP_ATTR_VALUE;
+ ctx.state = STATE_MAYBE_METADATA_SUBSTITUTION;
+ break;
+ }
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = c;
+ ctx.atv++;
+ break;
+ }
+ case STATE_COMMENT: {
+ if (c == '\n') {
+ ctx.line_no++;
+ ctx.state = ctx.state_before_comment;
+ break;
+ }
+ break;
+ }
+ case STATE_MAYBE_METADATA_SUBSTITUTION: {
+ if (c == '{') {
+ metadata_substitution[ctx.ms] = '%';
+ ctx.ms++;
+ metadata_substitution[ctx.ms] = '{';
+ ctx.ms++;
+ ctx.state = STATE_METADATA_SUBSTITUTION;
+ break;
+ }
+ switch (ctx.state_before_metadata_substitution) {
+ case STATE_LYRICS:
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = '%';
+ ctx.te++;
+ break;
+ case STATE_DIRECTIVE_VALUE:
+ directive_value[ctx.dv] = '%';
+ ctx.dv++;
+ break;
+ case STATE_MARKUP_ATTR_VALUE:
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = '%';
+ break;
+ default:
+ }
+ ctx.state = ctx.state_before_metadata_substitution;
+ str--;
+ break;
+ }
+ case STATE_METADATA_SUBSTITUTION: {
+ if (prev_c != '\\' && c == '}') {
+ if (ctx.nested_level == 0) {
+ metadata_substitution[ctx.ms] = '}';
+ ctx.ms++;
+ metadata_substitution[ctx.ms] = 0;
+ ctx.ms = 0;
+ char *substituted = cho_metadata_substitution_parse(
+ &ctx,
+ metadata_substitution,
+ NULL,
+ ctx.state_before_metadata_substitution
+ );
+ if (!substituted) {
+ LOG_DEBUG("cho_metadata_substitution_parse failed.");
+ goto ERR;
+ }
+ char *ch;
+ switch (ctx.state_before_metadata_substitution) {
+ case STATE_LYRICS:
+ for (ch = substituted; *ch; ch++) {
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char));
+ (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = *ch;
+ ctx.te++;
+ }
+ break;
+ case STATE_DIRECTIVE_VALUE:
+ for (ch = substituted; *ch; ch++) {
+ directive_value[ctx.dv] = *ch;
+ ctx.dv++;
+ }
+ break;
+ case STATE_MARKUP_ATTR_VALUE:
+ for (ch = substituted; *ch; ch++) {
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char));
+ ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = *ch;
+ ctx.atv++;
+ }
+ break;
+ default:
+ }
+ free(substituted);
+ ctx.state = ctx.state_before_metadata_substitution;
+ break;
+ }
+ ctx.nested_level--;
+ }
+ if (prev_c == '%' && c == '{') {
+ ctx.nested_level++;
+ }
+ if (ctx.ms > 4094) {
+ cho_log(&ctx, LOG_ERR, "Metadata substitution can't be greater than 4095 bytes.");
+ goto ERR;
+ }
+ metadata_substitution[ctx.ms] = c;
+ ctx.ms++;
+ break;
+ }
+ }
+ prev_c = c;
+ }
+ if (!cho_style_reset_default()) {
+ LOG_DEBUG("cho_style_reset_default failed.");
+ goto ERR;
+ }
+ cho_songs_close(&ctx, lines);
+ cho_context_cleanup(&ctx);
+ bool exist_title = false;
+ for (ctx.so = 0; ctx.songs[ctx.so]; ctx.so++) {
+ for (ctx.m = 0; ctx.songs[ctx.so]->metadata[ctx.m]; ctx.m++) {
+ if (
+ !strcmp(ctx.songs[ctx.so]->metadata[ctx.m]->name, "title") &&
+ ctx.songs[ctx.so]->metadata[ctx.m]->value &&
+ strcmp(ctx.songs[ctx.so]->metadata[ctx.m]->value, "") != 0
+ ) {
+ exist_title = true;
+ }
+ }
+ if (!exist_title) {
+ /* INFO: This cho_log() is not line specific. It's a workaround. */
+ ctx.line_no = 0;
+ cho_log(&ctx, LOG_ERR, "Song has no title.");
+ goto ERR;
+ }
+ exist_title = false;
+ }
+ return ctx.songs;
+ ERR:
+ cho_songs_close(&ctx, lines);
+ cho_context_cleanup(&ctx);
+ for (int e = 0; e<=ctx.so; e++) {
+ cho_song_free(ctx.songs[e]);
+ }
+ free(ctx.songs);
+ free(stripped_directive_value);
+ cho_directive_free(directive);
+ cho_tag_attrs_free(directive_attrs);
+ return NULL;
+}