lorid

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

commit 3aaf690adcd8fbbbcfa5ff37d9bb47df6995b550
parent dfe8ff098c8cd88c717cb7f3bf57986a3d20ffbd
Author: nibo <nibo@relim.de>
Date:   Fri, 11 Apr 2025 11:57:50 +0200

WIP: Start implementing grid sections

Diffstat:
Msrc/chordpro.c | 347+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/chordpro.h | 8++++++++
2 files changed, 339 insertions(+), 16 deletions(-)

diff --git a/src/chordpro.c b/src/chordpro.c @@ -29,6 +29,7 @@ static const char *state_enums[] = { "STATE_CHORD", "STATE_ANNOTATION", "STATE_TAB", + "STATE_GRID", "STATE_MARKUP_TAG", "STATE_MARKUP_TAG_START", "STATE_MARKUP_TAG_END", @@ -4582,6 +4583,247 @@ cho_directive_label_parse(struct ChoContext *ctx, const char *directive_name, co return label_name; } +static struct Attr ** +cho_attrs_parse(struct ChoContext *ctx, const char *str) +{ + const char *directive_name = "something"; + struct Attr **attrs = NULL; + int a = 0; + enum OptionState state = OS_NAME; + enum AttrValueSyntax avs = AVS_UNINITIALIZED; + char name[5+1]; + char value[URL_MAX_LEN+1]; + int n = 0; + int v = 0; + memset(name, 0, sizeof(name)); + memset(value, 0, sizeof(value)); + const char *c; + for (c = str; *c; c++) { + switch (state) { + case OS_NAME: + if (is_whitespace(*c)) { + if (n == 0) { + break; + } else { + name[n] = 0; + cho_log(ctx, LOG_ERR, "Option with name '%s' in environment directive '%s' has no value.", name, directive_name); + return NULL; + } + } + if (*c == '=') { + name[n] = 0; + state = OS_VALUE; + break; + } + if (n > 4) { + cho_log(ctx, LOG_ERR, "Option name in environment directive '%s' is too long.", directive_name); + return NULL; + } + name[n] = *c; + n++; + break; + case OS_VALUE: + if (avs == AVS_UNINITIALIZED) { + if (is_whitespace(*c)) { + cho_log(ctx, LOG_ERR, "Whitespace character after equals sign in environment directive '%s' is invalid.", directive_name); + return NULL; + } + if (*c == '\'') { + avs = AVS_APOSTROPHE; + } else if (*c == '"') { + avs = AVS_QUOTATION_MARK; + } else { + avs = AVS_UNQUOTED; + value[v] = *c; + v++; + } + break; + } + if (*c == '\n') { + cho_log(ctx, LOG_ERR, "Newline character inside an option value in environment directive '%s' is invalid.", directive_name); + return NULL; + } + if ( + (avs == AVS_APOSTROPHE && *c == '\'') || + (avs == AVS_QUOTATION_MARK && *c == '"') || + (avs == AVS_UNQUOTED && (*c == ' ' || *c == '\t')) + ) { + value[v] = 0; + attrs = erealloc(attrs, (a+1) * sizeof(struct Attr *)); + attrs[a] = emalloc(sizeof(struct Attr)); + attrs[a]->name = strdup(name); + attrs[a]->value = strdup(value); + a++; + memset(name, 0, n); + n = 0; + memset(value, 0, v); + v = 0; + avs = AVS_UNINITIALIZED; + state = OS_NAME; + break; + } + value[v] = *c; + v++; + break; + } + } + if (avs == AVS_UNQUOTED) { + value[v] = 0; + attrs = erealloc(attrs, (a+1) * sizeof(struct Attr *)); + attrs[a] = emalloc(sizeof(struct Attr)); + attrs[a]->name = strdup(name); + attrs[a]->value = strdup(value); + a++; + } + attrs = erealloc(attrs, (a+1) * sizeof(struct Attr *)); + attrs[a] = NULL; + return attrs; +} + +static char * +cho_attrs_get(struct Attr **attrs, const char *name) +{ + struct Attr **a; + for (a = attrs; *a; a++) { + if (!strcmp((*a)->name, name)) { + return (*a)->value; + } + } + return NULL; +} + +static bool +cho_grid_shape_parse_and_set(struct ChoContext *ctx, const char *str) +{ + enum { + BEFORE_FIRST_PLUS, + AFTER_FIRST_PLUS, + AFTER_SECOND_PLUS, + AFTER_X + } state = BEFORE_FIRST_PLUS; + const char *c; + char tmp[6+1]; + int t = 0; + int measures = 0; + int beats = 0; + int i; + for (c = str; *c; c++) { + switch (state) { + case BEFORE_FIRST_PLUS: + if (*c == '+') { + tmp[t] = 0; + i = atoi(tmp); + if (i == 0) { + LOG_DEBUG("atoi failed."); + return false; + } + ctx->grid_shape.left = i; + memset(tmp, 0, t); + t = 0; + state = AFTER_FIRST_PLUS; + break; + } + if (*c == 'x') { + tmp[t] = 0; + i = atoi(tmp); + if (i == 0) { + LOG_DEBUG("atoi failed."); + return false; + } + measures = i; + memset(tmp, 0, t); + t = 0; + state = AFTER_X; + break; + } + if (t > 5) { + cho_log(ctx, LOG_ERR, ""); + } + tmp[t] = *c; + t++; + break; + case AFTER_FIRST_PLUS: + if (*c == '+') { + tmp[t] = 0; + i = atoi(tmp); + if (i == 0) { + LOG_DEBUG("atoi failed."); + return false; + } + ctx->grid_shape.cells = i; + memset(tmp, 0, t); + t = 0; + state = AFTER_SECOND_PLUS; + break; + } + if (*c == 'x') { + tmp[t] = 0; + i = atoi(tmp); + if (i == 0) { + LOG_DEBUG("atoi failed."); + return false; + } + measures = i; + memset(tmp, 0, t); + t = 0; + state = AFTER_X; + break; + } + tmp[t] = *c; + t++; + break; + case AFTER_SECOND_PLUS: + tmp[t] = *c; + t++; + break; + case AFTER_X: + if (*c == '+') { + tmp[t] = 0; + i = atoi(tmp); + if (i == 0) { + LOG_DEBUG("atoi failed."); + return false; + } + beats = i; + ctx->grid_shape.cells = measures * beats; + memset(tmp, 0, t); + t = 0; + state = AFTER_SECOND_PLUS; + break; + } + tmp[t] = *c; + t++; + break; + } + } + tmp[t] = 0; + i = atoi(tmp); + if (i == 0) { + LOG_DEBUG("atoi failed."); + return false; + } + switch (state) { + case BEFORE_FIRST_PLUS: + /* fallthrough */ + case AFTER_FIRST_PLUS: + ctx->grid_shape.cells = i; + break; + case AFTER_SECOND_PLUS: + ctx->grid_shape.right = i; + break; + case AFTER_X: + beats = i; + ctx->grid_shape.cells = measures * beats; + break; + } + printf("grid shape: left %d right %d cells %d\n", + ctx->grid_shape.left, + ctx->grid_shape.right, + ctx->grid_shape.cells + ); + return true; +} + static bool cho_context_init( struct ChoContext *ctx, @@ -4624,6 +4866,9 @@ cho_context_init( ctx->line_no = 1; ctx->tags = NULL; ctx->image_assets = NULL; + ctx->grid_shape.cells = 8; + ctx->grid_shape.left = 1; + ctx->grid_shape.right = 1; ctx->transpose_history = emalloc((ctx->th+1) * sizeof(int *)); ctx->transpose_history[ctx->th] = 0; @@ -4648,6 +4893,7 @@ struct ChoSong ** cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *config) { enum AttrValueSyntax avs = AVS_UNINITIALIZED; + struct Attr **attrs; struct ChoStyle *tag_style; struct StyleProperty sprop; struct ChoChord *tmp_chord; @@ -4658,7 +4904,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c struct ChoMetadata *metadata; struct ChoLine ***lines; struct ChoContext ctx; - char *label, *metadata_value, *stripped_directive_value; + char *label, *metadata_value, *stripped_directive_value, *shape; char directive_name[128]; char directive_value[4096]; char chord[15]; @@ -5043,9 +5289,14 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c cho_log(&ctx, LOG_ERR, "Invalid directive '%s'.", directive_name); return NULL; } - if (directive->stype == ST_TAB) { + switch (directive->stype) { + case ST_TAB: ctx.state = STATE_TAB; - } else { + break; + case ST_GRID: + ctx.state = STATE_GRID; + break; + default: ctx.state = STATE_LYRICS; } cho_directive_free(directive); @@ -5108,22 +5359,69 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c 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.songs[ctx.so]->sections[ctx.se]->label = emalloc(sizeof(struct ChoText)); + if (strstr(directive_value, "=")) { - label = cho_directive_label_parse(&ctx, directive_name, directive_value); - if (!label) { - LOG_DEBUG("cho_directive_label_parse failed."); - cho_log(&ctx, LOG_ERR, "Failed to parse the section label. You have to ways of specifying a label:\n\t\t\t1. {start_of_*: label=\"Label name\"}\n\t\t\t2. {start_of*: Label name}"); + attrs = cho_attrs_parse(&ctx, directive_value); + if (!attrs) { + LOG_DEBUG("cho_attrs_parse failed."); return NULL; } - ctx.songs[ctx.so]->sections[ctx.se]->label->text = label; + label = cho_attrs_get(attrs, "label"); + if (directive->stype == ST_GRID) { + shape = cho_attrs_get(attrs, "shape"); + if (shape) { + if (!cho_grid_shape_parse_and_set(&ctx, shape)) { + LOG_DEBUG("cho_grid_parse_and_set_shape failed."); + return NULL; + } + } + } } else { - ctx.songs[ctx.so]->sections[ctx.se]->label->text = strdup(stripped_directive_value); + if (directive->stype == ST_GRID) { + // TODO: Parse the optional label name after the shape + if (!cho_grid_shape_parse_and_set(&ctx, stripped_directive_value)) { + LOG_DEBUG("cho_grid_parse_and_set_shape failed."); + return NULL; + } + } else { + label = stripped_directive_value; + } } - ctx.prev_ttype = ctx.current_ttype; - ctx.current_ttype = TT_LABEL; - ctx.songs[ctx.so]->sections[ctx.se]->label->style = cho_style_new_default(&ctx); - ctx.current_ttype = ctx.prev_ttype; + 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 = TT_LABEL; + ctx.songs[ctx.so]->sections[ctx.se]->label->style = cho_style_new_default(&ctx); + ctx.current_ttype = ctx.prev_ttype; + } + /* if (directive->stype == ST_GRID) { + if (strstr(directive_value, "=")) { + struct Attr **the_attrs = cho_attrs_parse(&ctx, directive_value); + struct Attr **tala; + for (tala = the_attrs; *tala; tala++) { + printf("attr %s = %s\n", (*tala)->name, (*tala)->value); + } + if (!the_attrs) { + LOG_DEBUG("cho_attrs_parse failed."); + return NULL; + } + cho_tag_attrs_free(the_attrs); + } else { + } + } else { + if (strstr(directive_value, "=")) { + label = cho_directive_label_parse(&ctx, directive_name, directive_value); + if (!label) { + LOG_DEBUG("cho_directive_label_parse failed."); + cho_log(&ctx, LOG_ERR, "Failed to parse the section label. You have to ways of specifying a label:\n\t\t\t1. {start_of_*: label=\"Label name\"}\n\t\t\t2. {start_of*: Label name}"); + return NULL; + } + ctx.songs[ctx.so]->sections[ctx.se]->label->text = label; + } else { + ctx.songs[ctx.so]->sections[ctx.se]->label->text = strdup(stripped_directive_value); + } + } */ 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; @@ -5449,9 +5747,14 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c cho_log(&ctx, LOG_ERR, "Invalid directive type '%d'.", directive->dtype); return NULL; } - if (directive->stype == ST_TAB) { + switch (directive->stype) { + case ST_TAB: ctx.state = STATE_TAB; - } else { + break; + case ST_GRID: + ctx.state = STATE_GRID; + break; + default: ctx.state = STATE_LYRICS; } memset(directive_value, 0, strlen(directive_value)); @@ -5729,6 +6032,18 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *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; + } + break; + } case STATE_MARKUP_TAG: { if (c == '/') { ctx.state = STATE_MARKUP_TAG_END; diff --git a/src/chordpro.h b/src/chordpro.h @@ -84,6 +84,7 @@ enum State { STATE_CHORD, STATE_ANNOTATION, STATE_TAB, + STATE_GRID, STATE_MARKUP_TAG, STATE_MARKUP_TAG_START, STATE_MARKUP_TAG_END, @@ -142,6 +143,12 @@ struct Tag { bool is_closed; }; +struct GridShape { + int cells; + int left; + int right; +}; + typedef int index_t; struct ChoContext { @@ -185,6 +192,7 @@ struct ChoContext { struct Tag **tags; struct Config *config; struct ChoImage **image_assets; + struct GridShape grid_shape; }; void cho_log_enable_info_logs(void);