commit 046dab778e3d99f70f014efd17fee564cf74b7e9
parent 2ea90d7ba13dec5ab412b5669b4849f993bd17c1
Author: nibo <nibo@relim.de>
Date: Sat, 31 May 2025 09:12:01 +0200
Add rudimetary grid section implementation
Diffstat:
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;