lorid

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

commit 10908ceb7380ef4261ac978f76f3436ac67c678d
parent 95d0641f560ac7c9d85870d25b1618ba7ff83dbc
Author: nibo <nibo@relim.de>
Date:   Sun,  4 Aug 2024 11:19:02 +0200

Implement chord style; Implement annotations

Diffstat:
MMakefile | 4+++-
Mchordpro.c | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mchordpro.h | 22++++++++++++++++++----
Mconfig.c | 7+++++--
Mout_pdf.c | 131++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mout_pdf.h | 6++++++
6 files changed, 193 insertions(+), 69 deletions(-)

diff --git a/Makefile b/Makefile @@ -11,4 +11,6 @@ debug: $(CC) ${CFLAGS} -g ${SRC} -o lorid ${LDFLAGS} fontconfig: $(CC) -g chordpro.c fontconfig.c -o fontconfig -lfontconfig -.PHONY: all debug fontconfig +parser: + $(CC) ${CFLAGS} -g util.c config.c chordpro.c lorid.c -o parser -ltoml +.PHONY: all debug fontconfig parser diff --git a/chordpro.c b/chordpro.c @@ -66,6 +66,7 @@ struct StyleProperty default_style_properties[18] = { }; static enum SongFragmentType g_current_ftype = SF_TEXT; +static enum SongFragmentType g_prev_ftype = SF_TEXT; static struct Config *g_config = NULL; static const char *the_state(enum State state) @@ -228,6 +229,8 @@ const char *the_song_fragment_type(enum SongFragmentType ftype) return "SF_EMPTY"; case SF_CHORD: return "SF_CHORD"; + case SF_ANNOT: + return "SF_ANNOT"; case SF_CHORUS: return "SF_CHORUS"; case SF_FOOTER: @@ -756,9 +759,12 @@ struct Style *cho_style_new_from_config(void) { struct PrintableItem *printable_item; switch (g_current_ftype) { - /* case SF_CHORD: + case SF_CHORD: printable_item = config_printable_item_get(g_config->printable_items, "chord"); - return cho_style_duplicate(printable_item->style); */ + return cho_style_duplicate(printable_item->style); + case SF_ANNOT: + printable_item = config_printable_item_get(g_config->printable_items, "annotation"); + return cho_style_duplicate(printable_item->style); case SF_GRID: printable_item = config_printable_item_get(g_config->printable_items, "grid"); return cho_style_duplicate(printable_item->style); @@ -1362,15 +1368,23 @@ static struct ChoMetadata *cho_metadata_split(const char *directive_value) static struct ChoChord *cho_chord_new(void) { struct ChoChord *chord = malloc(sizeof(struct ChoChord)); - chord->position = -1; + chord->style = cho_style_new_default(); chord->chord = NULL; return chord; } -int cho_chord_count(struct ChoChord **chords) +static struct ChoAnnotation *cho_annotation_new(void) +{ + struct ChoAnnotation *annot = malloc(sizeof(struct ChoAnnotation)); + annot->style = cho_style_new_default(); + annot->text = NULL; + return annot; +} + +int cho_text_above_count(struct ChoLineItemAbove **text_above) { int i = 0; - while (chords[i] != NULL) + while (text_above[i] != NULL) i++; return i; } @@ -1378,7 +1392,7 @@ int cho_chord_count(struct ChoChord **chords) static struct ChoLine *cho_line_new(void) { struct ChoLine *line = malloc(sizeof(struct ChoLine)); - line->chords = NULL; + line->text_above = NULL; line->lyrics = NULL; return line; } @@ -1467,13 +1481,21 @@ static void cho_song_free(struct ChoSong *song) free(song->sections[i]->lines[k]->lyrics[ly]); ly++; } - while (song->sections[i]->lines[k]->chords[c] != NULL) { - free(song->sections[i]->lines[k]->chords[c]->chord); - free(song->sections[i]->lines[k]->chords[c]); + while (song->sections[i]->lines[k]->text_above[c] != NULL) { + if (song->sections[i]->lines[k]->text_above[c]->is_chord) { + cho_style_free(song->sections[i]->lines[k]->text_above[c]->u.chord->style); + free(song->sections[i]->lines[k]->text_above[c]->u.chord->chord); + free(song->sections[i]->lines[k]->text_above[c]->u.chord); + } else { + cho_style_free(song->sections[i]->lines[k]->text_above[c]->u.annot->style); + free(song->sections[i]->lines[k]->text_above[c]->u.annot->text); + free(song->sections[i]->lines[k]->text_above[c]->u.annot); + } + free(song->sections[i]->lines[k]->text_above[c]); c++; } free(song->sections[i]->lines[k]->lyrics); - free(song->sections[i]->lines[k]->chords); + free(song->sections[i]->lines[k]->text_above); free(song->sections[i]->lines[k]); ly = 0; c = 0; @@ -1781,6 +1803,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) int at = 0; int atn = 0; int atv = 0; + int ann = 0; int chord_pos; size_t read; enum AttrValueSyntax avs = AVS_NO; @@ -1837,7 +1860,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) if (strlen(songs[so]->sections[se]->lines[li]->lyrics[ly]->text) == 0) { cho_line_item_free(songs[so]->sections[se]->lines[li]->lyrics[ly]); if (ly == 0) { - if (!songs[so]->sections[se]->lines[li]->chords) { + if (!songs[so]->sections[se]->lines[li]->text_above) { free(songs[so]->sections[se]->lines[li]->lyrics); free(songs[so]->sections[se]->lines[li]); songs[so]->sections[se]->lines = realloc(songs[so]->sections[se]->lines, (li+1) * sizeof(struct ChoLine *)); @@ -1854,8 +1877,8 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) songs[so]->sections[se]->lines[li]->lyrics[ly] = NULL; ly = 0; te = 0; - songs[so]->sections[se]->lines[li]->chords = realloc(songs[so]->sections[se]->lines[li]->chords, (c+1) * sizeof(struct ChoChord *)); - songs[so]->sections[se]->lines[li]->chords[c] = NULL; + songs[so]->sections[se]->lines[li]->text_above = realloc(songs[so]->sections[se]->lines[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + songs[so]->sections[se]->lines[li]->text_above[c] = NULL; c = 0; li++; songs[so]->sections[se]->lines = realloc(songs[so]->sections[se]->lines, (li+1) * sizeof(struct ChoLine *)); @@ -2142,13 +2165,18 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) if (buf == ']') { chord[ch] = 0; ch = 0; - songs[so]->sections[se]->lines[li]->chords = realloc(songs[so]->sections[se]->lines[li]->chords, (c+1) * sizeof(struct ChoChord *)); - songs[so]->sections[se]->lines[li]->chords[c] = cho_chord_new(); + g_prev_ftype = g_current_ftype; + g_current_ftype = SF_CHORD; + songs[so]->sections[se]->lines[li]->text_above = realloc(songs[so]->sections[se]->lines[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + songs[so]->sections[se]->lines[li]->text_above[c] = malloc(sizeof(struct ChoLineItemAbove)); + songs[so]->sections[se]->lines[li]->text_above[c]->is_chord = true; chord_pos = cho_line_compute_chord_position(songs[so]->sections[se]->lines[li], ly, te); - songs[so]->sections[se]->lines[li]->chords[c]->position = chord_pos; - songs[so]->sections[se]->lines[li]->chords[c]->chord = strdup(chord); + songs[so]->sections[se]->lines[li]->text_above[c]->position = chord_pos; + songs[so]->sections[se]->lines[li]->text_above[c]->u.chord = cho_chord_new(); + songs[so]->sections[se]->lines[li]->text_above[c]->u.chord->chord = strdup(chord); memset(chord, 0, strlen(chord)); c++; + g_current_ftype = g_prev_ftype; state = STATE_LYRICS; break; } @@ -2161,7 +2189,25 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) break; case STATE_ANNOTATION: if (buf == ']') { + annotation[ann] = 0; + ann = 0; + g_prev_ftype = g_current_ftype; + g_current_ftype = SF_ANNOT; + songs[so]->sections[se]->lines[li]->text_above = realloc(songs[so]->sections[se]->lines[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + songs[so]->sections[se]->lines[li]->text_above[c] = malloc(sizeof(struct ChoLineItemAbove)); + songs[so]->sections[se]->lines[li]->text_above[c]->is_chord = false; + chord_pos = cho_line_compute_chord_position(songs[so]->sections[se]->lines[li], ly, te); + songs[so]->sections[se]->lines[li]->text_above[c]->position = chord_pos; + songs[so]->sections[se]->lines[li]->text_above[c]->u.annot = cho_annotation_new(); + songs[so]->sections[se]->lines[li]->text_above[c]->u.annot->text = strdup(annotation); + memset(annotation, 0, strlen(annotation)); + c++; + g_current_ftype = g_prev_ftype; + state = STATE_LYRICS; + break; } + annotation[ann] = buf; + ann++; break; case STATE_MARKUP_TAG_BEGIN: MARKUP_TAG_BEGIN: @@ -2371,8 +2417,18 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) free(tags); cho_line_item_free(songs[so]->sections[se]->lines[li]->lyrics[ly]); free(songs[so]->sections[se]->lines[li]->lyrics); - free(songs[so]->sections[se]->lines[li]->chords); + free(songs[so]->sections[se]->lines[li]->text_above); free(songs[so]->sections[se]->lines[li]); + + unsigned int i; + for (i = 0; i<LENGTH(default_style_properties); i++) { + if (default_style_properties[i].type == SPT_FONT) { + free(default_style_properties[i].u.font_name); + } else if (default_style_properties[i].type == SPT_COLOR) { + free(default_style_properties[i].u.foreground_color); + } + } + songs[so]->sections[se]->lines[li] = NULL; songs[so]->metadata = realloc(songs[so]->metadata, (m+1) * sizeof(struct ChoMetadata *)); songs[so]->metadata[m] = NULL; diff --git a/chordpro.h b/chordpro.h @@ -109,6 +109,7 @@ enum State { enum SongFragmentType { SF_EMPTY = -1, SF_CHORD, + SF_ANNOT, SF_CHORUS, SF_FOOTER, SF_GRID, @@ -178,18 +179,31 @@ struct ChoMetadata { }; struct ChoChord { - // struct Style *style; - int position; + struct Style *style; char *chord; }; +struct ChoAnnotation { + struct Style *style; + char *text; +}; + struct ChoLineItem { struct Style *style; char *text; }; +struct ChoLineItemAbove { + int position; + bool is_chord; + union { + struct ChoChord *chord; + struct ChoAnnotation *annot; + } u; +}; + struct ChoLine { - struct ChoChord **chords; + struct ChoLineItemAbove **text_above; struct ChoLineItem **lyrics; }; @@ -208,7 +222,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config); int cho_song_count(struct ChoSong **songs); void cho_songs_free(struct ChoSong **song); int cho_line_item_count(struct ChoLineItem **items); -int cho_chord_count(struct ChoChord **chords); +int cho_text_above_count(struct ChoLineItemAbove **text_above); const char *cho_metadata_get(struct ChoMetadata **metadata, const char *name); diff --git a/config.c b/config.c @@ -57,7 +57,7 @@ struct PrintableItem *config_printable_item_get(struct PrintableItem **items, co static struct Config *config_load_default(void) { struct Config *config = malloc(sizeof(struct Config)); - config->printable_items = malloc(11 * sizeof(struct PrintableItem *)); + config->printable_items = malloc(12 * sizeof(struct PrintableItem *)); config->printable_items[0] = config_printable_item_new("title"); config->printable_items[0]->style->font->name = strdup("Inter"); config->printable_items[0]->style->font->weight = FW_BOLD; @@ -85,7 +85,10 @@ static struct Config *config_load_default(void) config->printable_items[9] = config_printable_item_new("label"); config->printable_items[9]->style->font->name = strdup("Inter"); config->printable_items[9]->style->font->style = FS_ITALIC; - config->printable_items[10] = NULL; + config->printable_items[10] = config_printable_item_new("annotation"); + config->printable_items[10]->style->font->name = strdup("Inter"); + config->printable_items[10]->style->font->style = FS_ITALIC; + config->printable_items[11] = NULL; return config; } diff --git a/out_pdf.c b/out_pdf.c @@ -129,10 +129,23 @@ static struct Font **out_pdf_font_get_all(struct ChoSong **songs, struct Config int se = 0; int li = 0; int ly = 0; + int ch = 0; struct Style *style; while (songs[so] != NULL) { while (songs[so]->sections[se] != NULL) { while (songs[so]->sections[se]->lines[li]) { + while (songs[so]->sections[se]->lines[li]->text_above[ch] != NULL) { + if (songs[so]->sections[se]->lines[li]->text_above[ch]->is_chord) { + style = songs[so]->sections[se]->lines[li]->text_above[ch]->u.chord->style; + } else { + style = songs[so]->sections[se]->lines[li]->text_above[ch]->u.annot->style; + } + if (style->font->name) { + out_pdf_add_fonts(style->font, &fonts); + } + ch++; + } + ch = 0; while (songs[so]->sections[se]->lines[li]->lyrics[ly] != NULL) { style = songs[so]->sections[se]->lines[li]->lyrics[ly]->style; if (style->font->name) { @@ -486,7 +499,7 @@ static bool out_pdf_text_show(pdfio_stream_t *stream, struct TextLineItem *item, return true; } -static double line_width_until_chord(struct ChoLine *line, struct ChoChord *chord, struct SpaceNeeded *space) +static double line_width_until_chord(struct ChoLine *line, struct ChoLineItemAbove *text_above, struct SpaceNeeded *space) { int i = -1; int ly = -1; @@ -497,7 +510,7 @@ static double line_width_until_chord(struct ChoLine *line, struct ChoChord *chor pdfio_obj_t *font_obj; for (ly = 0; line->lyrics[ly]; ly++) { for (i = 0; line->lyrics[ly]->text[i]; i++) { - if (pos == chord->position) { + if (pos == text_above->position) { last_ly = ly; last_i = i; goto FOUND; @@ -516,6 +529,10 @@ static double line_width_until_chord(struct ChoLine *line, struct ChoChord *chor for (ly = 0; line->lyrics[ly] && ly <= last_ly; ly++) { name = out_pdf_fnt_name_create(line->lyrics[ly]->style->font); font_obj = out_pdf_fnt_obj_get_by_name(name); + if (!font_obj) { + fprintf(stderr, "out_pdf_fnt_obj_get_by_name failed.\n"); + return EMPTY; + } if (ly == last_ly) { char tmp[strlen(line->lyrics[ly]->text)+1]; strcpy((char *)&tmp, line->lyrics[ly]->text); @@ -529,18 +546,44 @@ static double line_width_until_chord(struct ChoLine *line, struct ChoChord *chor return width; } -static bool out_pdf_chord_is_enough_space(struct ChoLine *line, struct ChoChord *prev_chord, struct ChoChord *chord, struct Font *chord_font, struct SpaceNeeded *space) +static enum Bool out_pdf_text_above_is_enough_space(struct ChoLine *line, struct ChoLineItemAbove *prev_text_above, struct ChoLineItemAbove *text_above, struct SpaceNeeded *space) { - double width_between_chords = line_width_until_chord(line, chord, NULL) - line_width_until_chord(line, prev_chord, space); - char *name = out_pdf_fnt_name_create(chord_font); - pdfio_obj_t *font_obj = out_pdf_fnt_obj_get_by_name(name); - free(name); - double prev_chord_width = pdfioContentTextMeasure(font_obj, prev_chord->chord, chord_font->size); - if (prev_chord_width > width_between_chords) { - space->amount = prev_chord_width - width_between_chords + MIN_CHORD_GAP_WIDTH; - return false; + double prev_text_above_width; + double cur = line_width_until_chord(line, text_above, NULL); + if (cur == EMPTY) { + return B_ERROR; } - return true; + double prev = line_width_until_chord(line, prev_text_above, space); + if (prev == EMPTY) { + return B_ERROR; + } + double width_between = cur - prev; + char *name; + pdfio_obj_t *font_obj; + if (prev_text_above->is_chord) { + name = out_pdf_fnt_name_create(prev_text_above->u.chord->style->font); + font_obj = out_pdf_fnt_obj_get_by_name(name); + if (!font_obj) { + fprintf(stderr, "out_pdf_fnt_obj_get_by_name failed.\n"); + return B_ERROR; + } + free(name); + prev_text_above_width = pdfioContentTextMeasure(font_obj, prev_text_above->u.chord->chord, prev_text_above->u.chord->style->font->size); + } else { + name = out_pdf_fnt_name_create(prev_text_above->u.annot->style->font); + font_obj = out_pdf_fnt_obj_get_by_name(name); + if (!font_obj) { + fprintf(stderr, "out_pdf_fnt_obj_get_by_name failed.\n"); + return B_ERROR; + } + free(name); + prev_text_above_width = pdfioContentTextMeasure(font_obj, prev_text_above->u.annot->text, prev_text_above->u.annot->style->font->size); + } + if (prev_text_above_width >= width_between) { + space->amount = prev_text_above_width - width_between + MIN_CHORD_GAP_WIDTH; + return B_FALSE; + } + return B_TRUE; } static struct SpaceNeeded *needs_space(struct SpaceNeeded **spaces, int ly, int i) { @@ -558,15 +601,8 @@ static void text_line_set_lineheight(struct TextLine *line, enum SongFragmentTyp double biggest_font_size = 0.0; int tli; for (tli = 0; line->items[tli]; tli++) { - // TODO: Implement chord style - if (line->items[tli]->style) { - if (line->items[tli]->style->font->size > biggest_font_size) { - biggest_font_size = line->items[tli]->style->font->size; - } - } else { - if (14.0 > biggest_font_size) { - biggest_font_size = 14.0; - } + if (line->items[tli]->style->font->size > biggest_font_size) { + biggest_font_size = line->items[tli]->style->font->size; } } switch (ftype) { @@ -585,7 +621,7 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) double width; bool add_space_to_next_chord = false; struct ChoLine **lines; - struct ChoChord **chords; + struct ChoLineItemAbove **text_above; struct SpaceNeeded space; struct SpaceNeeded **spaces = NULL; int sn = 0; @@ -671,44 +707,48 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) } lines = songs[so]->sections[se]->lines; for (li = 0; lines[li]; li++, tl++) { - chords = songs[so]->sections[se]->lines[li]->chords; - int chords_len = cho_chord_count(chords); - if (chords_len > 0) { + text_above = songs[so]->sections[se]->lines[li]->text_above; + int text_above_len = cho_text_above_count(text_above); + if (text_above_len > 0) { text[t]->lines = realloc(text[t]->lines, (tl+1) * sizeof(struct TextLine *)); text[t]->lines[tl] = malloc(sizeof(struct TextLine)); text[t]->lines[tl]->items = NULL; - printable_item = config_printable_item_get(config->printable_items, "chord"); - if (!printable_item) { - fprintf(stderr, "config_printable_item_get failed.\n"); - return NULL; - } double added_space = 0.0; - for (ch = 0; chords[ch]; ch++, tli++) { + enum Bool enough_space; + for (ch = 0; text_above[ch]; ch++, tli++) { text[t]->lines[tl]->items = realloc(text[t]->lines[tl]->items, (tli+1) * sizeof(struct TextLineItem *)); text[t]->lines[tl]->items[tli] = malloc(sizeof(struct TextLineItem)); + double width_until = line_width_until_chord(lines[li], text_above[ch], NULL); + if (width_until == EMPTY) { + fprintf(stderr, "line_width_until_chord failed.\n"); + return NULL; + } if (tli == 0) { - text[t]->lines[tl]->items[tli]->x = PADDING + line_width_until_chord(lines[li], chords[ch], NULL); + text[t]->lines[tl]->items[tli]->x = PADDING + width_until; } else { - text[t]->lines[tl]->items[tli]->x = PADDING + line_width_until_chord(lines[li], chords[ch], NULL) + added_space; + text[t]->lines[tl]->items[tli]->x = PADDING + width_until + added_space; } if (add_space_to_next_chord) { added_space += space.amount; text[t]->lines[tl]->items[tli]->x += space.amount; add_space_to_next_chord = false; } - // TODO: implement chord style - text[t]->lines[tl]->items[tli]->style = cho_style_duplicate(printable_item->style); - text[t]->lines[tl]->items[tli]->text = strdup(chords[ch]->chord); - if (chords_len == ch+1) { + if (text_above[ch]->is_chord) { + text[t]->lines[tl]->items[tli]->style = cho_style_duplicate(text_above[ch]->u.chord->style); + text[t]->lines[tl]->items[tli]->text = strdup(text_above[ch]->u.chord->chord); + } else { + text[t]->lines[tl]->items[tli]->style = cho_style_duplicate(text_above[ch]->u.annot->style); + text[t]->lines[tl]->items[tli]->text = strdup(text_above[ch]->u.annot->text); + } + if (text_above_len == ch+1) { break; } - if (!out_pdf_chord_is_enough_space( - songs[so]->sections[se]->lines[li], - chords[ch], - chords[ch+1], - printable_item->style->font, - &space - )) { + enough_space = out_pdf_text_above_is_enough_space(songs[so]->sections[se]->lines[li], text_above[ch], text_above[ch+1], &space); + if (enough_space == B_ERROR) { + fprintf(stderr, "out_pdf_text_above_is_enough_space failed.\n"); + return NULL; + } + if (!enough_space) { spaces = realloc(spaces, (sn+1) * sizeof(struct SpaceNeeded *)); spaces[sn] = malloc(sizeof(struct SpaceNeeded)); spaces[sn]->line_item_index = space.line_item_index; @@ -956,6 +996,9 @@ bool out_pdf_new(const char *cho_filepath, const char *output_folder_or_file, st f++; } cho_fonts_free(needed_fonts); + for (f = 0; g_fonts[f]; f++) { + printf("%s\n", g_fonts[f]->name); + } struct Text **text = text_create(songs, config); if (!text) { fprintf(stderr, "text_create failed.\n"); diff --git a/out_pdf.h b/out_pdf.h @@ -6,6 +6,12 @@ #define LINE_LEN MEDIABOX_WIDTH - PADDING * 2 #define MIN_CHORD_GAP_WIDTH 5.0 +enum Bool { + B_ERROR = -1, + B_FALSE, + B_TRUE +}; + enum Alignment { LEFT, CENTER,