lorid

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

commit 046dab778e3d99f70f014efd17fee564cf74b7e9
parent 2ea90d7ba13dec5ab412b5669b4849f993bd17c1
Author: nibo <nibo@relim.de>
Date:   Sat, 31 May 2025 09:12:01 +0200

Add rudimetary grid section implementation

Diffstat:
MMakefile | 1-
Msrc/chord_diagram.h | 1+
Msrc/chordpro.c | 251++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/chordpro.h | 16++++++++++++++--
Msrc/core.c | 5+++--
Msrc/out_pdf.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 274 insertions(+), 53 deletions(-)

diff --git a/Makefile b/Makefile @@ -21,7 +21,6 @@ static: debug: $(CC) ${CFLAGS} ${VARS} -DDEBUG -g ${SRC} -o lorid ${LDFLAGS} clean: - rm *.o rm lorid install: lorid mkdir -p ${PREFIX}/bin diff --git a/src/chord_diagram.h b/src/chord_diagram.h @@ -1,4 +1,5 @@ #include <pdfio.h> +#include "out_pdf.h" enum TextRendering { FILL, diff --git a/src/chordpro.c b/src/chordpro.c @@ -19,6 +19,8 @@ static const char *font_styles[] = { "normal", "oblique", "italic" }; static const char *font_weights[] = { "normal", "bold" }; static const char *line_styles[] = { "single", "double", "none" }; static const char *chord_qualifiers[] = { "m", "", "+", "°" }; +static const char *grid_tokens[] = { "GT_CHORD", "GT_BAR_LINE_SYMBOL", "GT_PLACEHOLDER" }; +static const char *section_types[] = { "", "", "chorus", "verse", "bridge", "tab", "grid", "custom" }; extern const struct InstrumentInfo instruments[]; static const char *state_enums[] = { @@ -3938,7 +3940,7 @@ static struct ChoSection * cho_section_new(void) { struct ChoSection *section = emalloc(sizeof(struct ChoSection)); - section->type = -1; + section->type = ST_UNINITIALIZED; section->label = NULL; section->lines = NULL; return section; @@ -4695,6 +4697,7 @@ cho_attrs_get(struct Attr **attrs, const char *name) static bool cho_grid_shape_parse_and_set(struct ChoContext *ctx, const char *str) { + printf("str '%s'\n", str); enum { BEFORE_FIRST_PLUS, AFTER_FIRST_PLUS, @@ -4717,7 +4720,7 @@ cho_grid_shape_parse_and_set(struct ChoContext *ctx, const char *str) LOG_DEBUG("atoi failed."); return false; } - ctx->grid_shape.left = i; + ctx->grid.left = i; memset(tmp, 0, t); t = 0; state = AFTER_FIRST_PLUS; @@ -4750,7 +4753,7 @@ cho_grid_shape_parse_and_set(struct ChoContext *ctx, const char *str) LOG_DEBUG("atoi failed."); return false; } - ctx->grid_shape.cells = i; + ctx->grid.cells = i; memset(tmp, 0, t); t = 0; state = AFTER_SECOND_PLUS; @@ -4785,7 +4788,7 @@ cho_grid_shape_parse_and_set(struct ChoContext *ctx, const char *str) return false; } beats = i; - ctx->grid_shape.cells = measures * beats; + ctx->grid.cells = measures * beats; memset(tmp, 0, t); t = 0; state = AFTER_SECOND_PLUS; @@ -4806,30 +4809,61 @@ cho_grid_shape_parse_and_set(struct ChoContext *ctx, const char *str) case BEFORE_FIRST_PLUS: /* fallthrough */ case AFTER_FIRST_PLUS: - ctx->grid_shape.cells = i; + ctx->grid.cells = i; break; case AFTER_SECOND_PLUS: - ctx->grid_shape.right = i; + ctx->grid.right = i; break; case AFTER_X: beats = i; - ctx->grid_shape.cells = measures * beats; + ctx->grid.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 void -cho_grid_shape_set_default(struct ChoContext *ctx) +static enum GridToken +cho_grid_token_parse(struct ChoContext *ctx, const char *token, bool *err) { - ctx->grid_shape.cells = 8; - ctx->grid_shape.left = 1; - ctx->grid_shape.right = 1; + struct ChoChord *chord; + + *err = false; + if (!strcmp(token, ".")) { + return GT_PLACEHOLDER; + } else + if (!strcmp(token, "/")) { + return GT_PLACEHOLDER; + } else + if (!strcmp(token, "|")) { + return GT_BAR_LINE_SYMBOL; + } else + if (!strcmp(token, "||")) { + return GT_BAR_LINE_SYMBOL; + } else + if (!strcmp(token, "|.")) { + return GT_BAR_LINE_SYMBOL; + } else + if (!strcmp(token, "|:")) { + return GT_BAR_LINE_SYMBOL; + } else + if (!strcmp(token, ":|")) { + return GT_BAR_LINE_SYMBOL; + } else + if (!strcmp(token, ":|:")) { + return GT_BAR_LINE_SYMBOL; + } else + if (token[0] == '|' && isdigit(token[1])) { + return GT_BAR_LINE_SYMBOL; + } else + if (!strcmp(token, "|2>")) { + return GT_BAR_LINE_SYMBOL; + } else + if ((chord = cho_chord_parse(ctx, token)) && chord->is_canonical) { + cho_chord_free(chord); + return GT_CHORD; + } + *err = true; + return GT_CHORD; } static bool @@ -4858,6 +4892,7 @@ cho_context_init( ctx->dia = 0; ctx->dn = 0; ctx->dv = 0; + ctx->gt = 0; ctx->ia = 0; ctx->li = 0; ctx->lia = 0; @@ -4874,7 +4909,13 @@ cho_context_init( ctx->line_no = 1; ctx->tags = NULL; ctx->image_assets = NULL; - cho_grid_shape_set_default(ctx); + ctx->grid.cells = 8; + ctx->grid.left = 1; + ctx->grid.right = 1; + ctx->grid.bar_line_symbol_count = 0; + ctx->grid.bar_line_symbol_in_line_count = 0; + ctx->grid.tokens_per_cell = 0; + ctx->grid.expected_tokens_per_cell = 0; ctx->transpose_history = emalloc((ctx->th+1) * sizeof(int *)); ctx->transpose_history[ctx->th] = 0; @@ -4899,6 +4940,7 @@ struct ChoSong ** cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *config) { enum AttrValueSyntax avs = AVS_UNINITIALIZED; + enum GridToken token; struct Attr **attrs = NULL; struct ChoStyle *tag_style; struct StyleProperty sprop; @@ -4910,15 +4952,17 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c struct ChoMetadata *metadata; struct ChoLine ***lines; struct ChoContext ctx; + bool err; char *metadata_value, *stripped_directive_value, *shape; char *label = NULL; char directive_name[128]; char directive_value[4096]; - char chord[15]; + 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; @@ -4934,7 +4978,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c ctx.songs[ctx.so]->present_text_types[TT_TOC] = config->output->toc->show; for (; *str; str++) { c = *str; - // printf("state: %s, buf: %c\n", state_enums[state], buf); + // printf("state: %s, c: %c\n", state_enums[ctx.state], c); if (c == '\r') { continue; } @@ -5075,6 +5119,16 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c (*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 ST_TAB: + ctx.state = STATE_TAB; + break; + case ST_GRID: + ctx.state = STATE_GRID; + break; + default: + ctx.state = STATE_LYRICS; + } break; } case POS_END: { @@ -5085,7 +5139,9 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c } } if (directive->stype == ST_GRID) { - cho_grid_shape_set_default(&ctx); + 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); @@ -5101,6 +5157,15 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c (*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); + } + return NULL; } break; } @@ -5299,16 +5364,6 @@ 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; } - switch (directive->stype) { - case ST_TAB: - ctx.state = STATE_TAB; - break; - case ST_GRID: - ctx.state = STATE_GRID; - break; - default: - ctx.state = STATE_LYRICS; - } cho_directive_free(directive); directive = NULL; break; @@ -5426,23 +5481,8 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c break; } case POS_END: { - if (directive->stype == ctx.songs[ctx.so]->sections[ctx.se]->type) { - 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); - } - break; + cho_log(&ctx, LOG_ERR, "A directive that closes a section can't have arguments."); + return NULL; } case POS_NO: { /* INFO: {chorus: ...} */ @@ -5859,6 +5899,10 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c 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); + return NULL; + } chord[ctx.ch] = c; ctx.ch++; break; @@ -6032,6 +6076,117 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *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); + return NULL; + } + if (token == GT_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 + ); + return NULL; + } + } + ctx.grid.tokens_per_cell = 0; + } else { + ctx.grid.tokens_per_cell++; + } + if (token == GT_PLACEHOLDER) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(" "); + } else { + ctx.prev_ttype = ctx.current_ttype; + ctx.current_ttype = TT_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); + return NULL; + } + if (token == GT_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 + ); + return NULL; + } + } + ctx.grid.tokens_per_cell = 0; + } else { + ctx.grid.tokens_per_cell++; + } + if (token == GT_PLACEHOLDER) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(" "); + } else { + ctx.prev_ttype = ctx.current_ttype; + ctx.current_ttype = TT_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: { diff --git a/src/chordpro.h b/src/chordpro.h @@ -12,6 +12,7 @@ // INFO: Based on https://stackoverflow.com/a/417184 #define URL_MAX_LEN 2000 #define FONT_NAME_MAX 100 +#define CHORD_LEN 15 enum AttrValueSyntax { AVS_UNINITIALIZED, @@ -59,6 +60,12 @@ enum DirectiveType { DT_CUSTOM }; +enum GridToken { + GT_CHORD, + GT_BAR_LINE_SYMBOL, + GT_PLACEHOLDER +}; + enum MetadataDirective { MD_TITLE, MD_SUBTITLE, @@ -143,10 +150,14 @@ struct Tag { bool is_closed; }; -struct GridShape { +struct GridContext { int cells; int left; int right; + int bar_line_symbol_count; + int bar_line_symbol_in_line_count; + int tokens_per_cell; + int expected_tokens_per_cell; }; typedef int index_t; @@ -171,6 +182,7 @@ struct ChoContext { index_t dia; // struct ChordDiagram index_t dn; // char directive_name index_t dv; // char directive_value + index_t gt; // char grid_token index_t ia; // struct ChoImage index_t li; // struct ChoLine index_t lia; // struct ChoLineItemAbove @@ -192,7 +204,7 @@ struct ChoContext { struct Tag **tags; struct Config *config; struct ChoImage **image_assets; - struct GridShape grid_shape; + struct GridContext grid; }; void cho_log_enable_info_logs(void); diff --git a/src/core.c b/src/core.c @@ -255,6 +255,7 @@ str_remove_leading_whitespace(const char *str) return strdup(&str[i]); } +/* INFO: Allows a and b to be NULL. */ int str_compare(const char *a, const char *b) { @@ -266,8 +267,8 @@ str_compare(const char *a, const char *b) } else if (a && !b) { return 1; - } else - if (!a && b) { + } else { + // if (!a && b) { return -1; } } diff --git a/src/out_pdf.c b/src/out_pdf.c @@ -2277,6 +2277,29 @@ pdf_texts_add_text( return true; } +static double +find_biggest_line_item_width(struct PDFContext *ctx, struct ChoLine **lines) +{ + struct ChoLine **li; + struct ChoLineItem **lii; + double width; + double biggest = 0.0; + + for (li = lines; *li; li++) { + for (lii = (*li)->items; *lii; lii++) { + width = text_width(ctx, (*lii)->u.text->text, (*lii)->u.text->style); + if (width == -1) { + LOG_DEBUG("text_width failed."); + return -1; + } + if (width > biggest) { + biggest = width; + } + } + } + return biggest; +} + static bool pdf_toc_create( struct PDFContext *ctx, @@ -2427,6 +2450,36 @@ pdf_body_create( imgs = &ctx->b_ctx.content->pages[ctx->b_ctx.page]->images; diagrams = &ctx->b_ctx.content->pages[ctx->b_ctx.page]->diagrams; } + if ((*se)->type == ST_GRID) { + double biggest_line_item_width, x, biggest_font_size; + struct ChoLine **li; + struct ChoLineItem **lii; + + biggest_line_item_width = find_biggest_line_item_width(ctx, (*se)->lines); + if (biggest_line_item_width == -1) { + LOG_DEBUG("find_biggest_line_item_width failed."); + return false; + } + for (li = (*se)->lines; *li; li++) { + x = MARGIN_HORIZONTAL; + biggest_font_size = 0.0; + for (lii = (*li)->items; *lii; lii++) { + if ((*lii)->u.text->style->font->size > biggest_font_size) { + biggest_font_size = (*lii)->u.text->style->font->size; + } + *texts = erealloc(*texts, (ctx->b_ctx.text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->b_ctx.text] = pdf_text_new(); + (*texts)[ctx->b_ctx.text]->text = strdup((*lii)->u.text->text); + (*texts)[ctx->b_ctx.text]->style = cho_style_copy((*lii)->u.text->style); + (*texts)[ctx->b_ctx.text]->x = x; + (*texts)[ctx->b_ctx.text]->y = ctx->b_ctx.y; + ctx->b_ctx.text++; + x += biggest_line_item_width; + } + ctx->b_ctx.y -= 8.0 + biggest_font_size; + } + continue; + } for (li = (*se)->lines; *li; li++) { int item_index; int text_above_index;