lorid

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

commit 6904c2a57c8635c9e773fd3cd11ad8d6a84a7da9
parent 788540d4a424924c494826a8897c6fcccf0e15fa
Author: nibo <nibo@relim.de>
Date:   Tue, 18 Jun 2024 20:42:33 +0200

Improve chordpro parser and first pdf output tests

Diffstat:
MMakefile | 4++--
Mchordpro.c | 28++++++++++++++++++++++------
Mchordpro.h | 3++-
Afonts/Inter-Bold.ttf | 0
Afonts/Inter-BoldItalic.ttf | 0
Afonts/Inter-Italic.ttf | 0
Afonts/Inter-Light.ttf | 0
Afonts/Inter-LightItalic.ttf | 0
Afonts/Inter-Regular.ttf | 0
Mlorid.c | 5+++++
Aout_pdf.c | 274+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aout_pdf.h | 14++++++++++++++
Mtodo | 9+++++++--
13 files changed, 326 insertions(+), 11 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,4 +1,4 @@ all: - $(CC) -Wall -Wextra -O2 chordpro.c lorid.c -o lorid + $(CC) -Wall -Wextra -O2 chordpro.c out_pdf.c lorid.c -o lorid -lpdfio debug: - $(CC) -g -Wall -Wextra chordpro.c lorid.c -o lorid + $(CC) -g -Wall -Wextra chordpro.c out_pdf.c lorid.c -o lorid -lpdfio diff --git a/chordpro.c b/chordpro.c @@ -19,7 +19,7 @@ static const char *metadata_directives[] = { "artist", "composer", "lyricist", "copyright", "album", "year", "key", "time", "tempo", "duration", "capo", - "meta", NULL + "meta", "arranger", NULL }; static const char *formatting_directives[] = { @@ -828,6 +828,14 @@ void cho_line_item_free(struct ChoLineItem *item) free(item); } +int cho_line_item_count(struct ChoLineItem **items) +{ + int i = 0; + while (items[i] != NULL) + i++; + return i; +} + struct ChoSection *cho_section_new(void) { struct ChoSection *section = malloc(sizeof(struct ChoSection)); @@ -1042,8 +1050,8 @@ END: struct ChoSong **cho_parse(FILE *fp) { - char buf; - char prev_buf; + char buf = 0; + char prev_buf = '\n'; char directive_name[16]; char directive_value[512]; char chord[15]; @@ -1092,7 +1100,7 @@ struct ChoSong **cho_parse(FILE *fp) state = STATE_COMMENT; break; } - if (buf == '{') { + if (prev_buf == '\n' && buf == '{') { state = STATE_DIRECTIVE_NAME; break; } @@ -1104,7 +1112,11 @@ struct ChoSong **cho_parse(FILE *fp) songs[so]->sections[se]->lines[li]->lyrics[ly]->text = realloc(songs[so]->sections[se]->lines[li]->lyrics[ly]->text, (te+1) * sizeof(char)); songs[so]->sections[se]->lines[li]->lyrics[ly]->text[te] = 0; te = 0; - ly++; + if (strlen(songs[so]->sections[se]->lines[li]->lyrics[ly]->text) == 0) { + cho_line_item_free(songs[so]->sections[se]->lines[li]->lyrics[ly]); + } else { + ly++; + } songs[so]->sections[se]->lines[li]->lyrics = realloc(songs[so]->sections[se]->lines[li]->lyrics, (ly+1) * sizeof(struct ChoLineItem *)); songs[so]->sections[se]->lines[li]->lyrics[ly] = cho_line_item_new(); return_to_state = STATE_LYRICS; @@ -1310,7 +1322,11 @@ struct ChoSong **cho_parse(FILE *fp) case DT_FORMATTING: songs[so]->sections[se]->lines[li]->lyrics[ly]->text = realloc(songs[so]->sections[se]->lines[li]->lyrics[ly]->text, (te+1) * sizeof(char)); songs[so]->sections[se]->lines[li]->lyrics[ly]->text[te] = 0; - ly++; + if (strlen(songs[so]->sections[se]->lines[li]->lyrics[ly]->text) == 0) { + cho_line_item_free(songs[so]->sections[se]->lines[li]->lyrics[ly]); + } else { + ly++; + } songs[so]->sections[se]->lines[li]->lyrics = realloc(songs[so]->sections[se]->lines[li]->lyrics, (ly+1) * sizeof(struct ChoLineItem *)); songs[so]->sections[se]->lines[li]->lyrics[ly] = cho_line_item_new(); cho_style_free(songs[so]->sections[se]->lines[li]->lyrics[ly]->style); diff --git a/chordpro.h b/chordpro.h @@ -150,9 +150,10 @@ struct ChoSong { struct ChoSong **cho_parse(FILE *fp); void cho_songs_free(struct ChoSong **song); -void cho_style_print(struct Style *style); +int cho_line_item_count(struct ChoLineItem **items); /* Debugging */ +void cho_style_print(struct Style *style); const char *the_dtype(enum DirectiveType dtype); const char *the_stype(enum SectionType stype); const char *the_pos(enum Position pos); diff --git a/fonts/Inter-Bold.ttf b/fonts/Inter-Bold.ttf Binary files differ. diff --git a/fonts/Inter-BoldItalic.ttf b/fonts/Inter-BoldItalic.ttf Binary files differ. diff --git a/fonts/Inter-Italic.ttf b/fonts/Inter-Italic.ttf Binary files differ. diff --git a/fonts/Inter-Light.ttf b/fonts/Inter-Light.ttf Binary files differ. diff --git a/fonts/Inter-LightItalic.ttf b/fonts/Inter-LightItalic.ttf Binary files differ. diff --git a/fonts/Inter-Regular.ttf b/fonts/Inter-Regular.ttf Binary files differ. diff --git a/lorid.c b/lorid.c @@ -5,6 +5,7 @@ #include <string.h> #include <getopt.h> #include "chordpro.h" +#include "out_pdf.h" void print_hex(const char *str) { @@ -32,6 +33,10 @@ int main(int argc, char *argv[]) fprintf(stderr, "cho_parse failed.\n"); return 1; } + if (!out_pdf_new(argv[1], songs)) { + fprintf(stderr, "out_pdf_new failed.\n"); + return 1; + } /*int m = 0;*/ /*printf("---- BEGIN METADATA ----\n");*/ /*while (song->metadata[m] != NULL) {*/ diff --git a/out_pdf.c b/out_pdf.c @@ -0,0 +1,274 @@ +#include <string.h> +#include <stdbool.h> +#include <stdint.h> +#include <pdfio.h> +#include <pdfio-content.h> +#include "chordpro.h" +#include "out_pdf.h" + +static char current_font[17]; +static double current_font_size; +static double current_y = MEDIABOX_HEIGHT - PADDING / 2; +static pdfio_obj_t *current_font_obj = NULL; +static pdfio_obj_t *inter_regular; +static pdfio_obj_t *inter_bold; +static pdfio_obj_t *inter_italic; +static pdfio_obj_t *inter_bold_italic; +static pdfio_obj_t *inter_light; +static pdfio_obj_t *inter_light_italic; + +static pdfio_obj_t *get_font_obj_by_name(const char *name) +{ + if (strcmp(name, "Inter-Regular") == 0) { + return inter_regular; + } else if (strcmp(name, "Inter-Bold") == 0) { + return inter_bold; + } else if (strcmp(name, "Inter-Italic") == 0) { + return inter_italic; + } else if (strcmp(name, "Inter-BoldItalic") == 0) { + return inter_bold_italic; + } else if (strcmp(name, "Inter-Light") == 0) { + return inter_light; + } else if (strcmp(name, "Inter-LightItalic") == 0) { + return inter_light_italic; + } + return NULL; +} + +static bool SetTextFont(pdfio_stream_t *stream, const char *name, double size) +{ + if (!pdfioContentSetTextFont(stream, name, size)) { + fprintf(stderr, "pdfioContentSetTextFont failed.\n"); + return false; + } + if (strlen(name) > 17) + return false; + pdfio_obj_t *font_obj = get_font_obj_by_name(name); + if (font_obj == NULL) + return false; + strcpy(current_font, name); + current_font_obj = font_obj; + current_font_size = size; + return true; +} + +static bool ShowText(pdfio_stream_t *stream, const char *str, enum Alignment align, bool linebreak) +{ + static double x_continue = PADDING; + double x; + double width = pdfioContentTextMeasure(current_font_obj, str, current_font_size); + if (LINE_LEN < width) + return false; + switch (align) { + case CENTER: + x_continue = PADDING; + x = PADDING + (LINE_LEN - width) / 2; + break; + case LEFT: + x_continue = PADDING; + x = PADDING; + break; + case CONTINUE: + x = x_continue; + x_continue += width; + break; + } + // printf("x: %f, y: %f\n", x, current_y); + if (!pdfioContentTextBegin(stream)) { + fprintf(stderr, "pdfioContentTextBegin failed.\n"); + return false; + } + if (!pdfioContentTextMoveTo(stream, x, current_y)) { + fprintf(stderr, "pdfioContentTextMoveTo failed.\n"); + return false; + } + if (!pdfioContentTextShow(stream, true, str)) { + fprintf(stderr, "pdfioContentTextShow failed.\n"); + return false; + } + if (!pdfioContentTextEnd(stream)) { + fprintf(stderr, "pdfioContentTextEnd failed.\n"); + return false; + } + if (linebreak) { + current_y -= current_font_size + 5.0; + x_continue = PADDING; + } + return true; +} + +static char *out_pdf_filename(const char *old) +{ + char *new = NULL; + int mark = -1; + int i = 0; + int old_len; + while (old[i] != 0) { + if (old[i] == '.') + mark = i; + i++; + } + i = 0; + if (mark == -1) { + old_len = (int)strlen(old); + new = malloc((old_len+5) * sizeof(char)); + while (i < old_len) { + new[i] = old[i]; + i++; + } + new[i] = '.'; + new[++i] = 'p'; + new[++i] = 'd'; + new[++i] = 'f'; + new[++i] = 0; + } else { + new = malloc((mark+5) * sizeof(char)); + while (i <= mark) { + new[i] = old[i]; + i++; + } + new[i] = 'p'; + new[++i] = 'd'; + new[++i] = 'f'; + new[++i] = 0; + } + return new; +} + +bool out_pdf_new(const char *cho_filename, struct ChoSong **songs) +{ + char *pdf_filename = out_pdf_filename(cho_filename); + pdfio_rect_t media_box_a4 = { 0.0, 0.0, MEDIABOX_WIDTH, MEDIABOX_HEIGHT }; + pdfio_rect_t crop_box = { 36.0, 36.0, MEDIABOX_WIDTH, MEDIABOX_HEIGHT }; + pdfio_file_t *pdf = pdfioFileCreate(pdf_filename, "2.0", &media_box_a4, &crop_box, NULL, NULL); + free(pdf_filename); + inter_regular = pdfioFileCreateFontObjFromFile(pdf, "./fonts/Inter-Regular.ttf", true); + inter_bold = pdfioFileCreateFontObjFromFile(pdf, "./fonts/Inter-Bold.ttf", true); + inter_italic = pdfioFileCreateFontObjFromFile(pdf, "./fonts/Inter-Italic.ttf", true); + inter_bold_italic = pdfioFileCreateFontObjFromFile(pdf, "./fonts/Inter-BoldItalic.ttf", true); + inter_light = pdfioFileCreateFontObjFromFile(pdf, "./fonts/Inter-Light.ttf", true); + inter_light_italic = pdfioFileCreateFontObjFromFile(pdf, "./fonts/Inter-LightItalic.ttf", true); + pdfio_dict_t *page1_dict = pdfioDictCreate(pdf); + if (!pdfioPageDictAddFont(page1_dict, "Inter-Regular", inter_regular)) { + fprintf(stderr, "pdfioPageDictAddFont failed.\n"); + return false; + } + if (!pdfioPageDictAddFont(page1_dict, "Inter-Bold", inter_bold)) { + fprintf(stderr, "pdfioPageDictAddFont failed.\n"); + return false; + } + if (!pdfioPageDictAddFont(page1_dict, "Inter-Italic", inter_italic)) { + fprintf(stderr, "pdfioPageDictAddFont failed.\n"); + return false; + } + if (!pdfioPageDictAddFont(page1_dict, "Inter-BoldItalic", inter_bold_italic)) { + fprintf(stderr, "pdfioPageDictAddFont failed.\n"); + return false; + } + if (!pdfioPageDictAddFont(page1_dict, "Inter-Light", inter_light)) { + fprintf(stderr, "pdfioPageDictAddFont failed.\n"); + return false; + } + if (!pdfioPageDictAddFont(page1_dict, "Inter-LightItalic", inter_light_italic)) { + fprintf(stderr, "pdfioPageDictAddFont failed.\n"); + return false; + } + pdfio_stream_t *page1_stream = pdfioFileCreatePage(pdf, page1_dict); + if (!SetTextFont(page1_stream, "Inter-Regular", 14.0)) { + fprintf(stderr, "SetTextFont failed.\n"); + return false; + } + if (!pdfioContentSetFillColorRGB(page1_stream, 0.0, 0.0, 0.0)) { + fprintf(stderr, "pdfioContentSetFillColorRGB failed.\n"); + return false; + } + if (!pdfioContentSetTextCharacterSpacing(page1_stream, -0.2)) { + fprintf(stderr, "pdfioContentSetTextCharacterSpacing failed.\n"); + return false; + } + /* if (!pdfioContentSetTextLeading(page1_stream, 1.5)) { + fprintf(stderr, "pdfioContentSetTextLeading failed.\n"); + return false; + } */ + if (!pdfioContentSetLineWidth(page1_stream, LINE_LEN)) { + fprintf(stderr, "pdfioContentSetLineWidth failed.\n"); + return false; + } + int so = 0; + int se = 0; + int m = 0; + int li = 0; + int ly = 0; + while (songs[so] != NULL) { + while (songs[so]->metadata[m] != NULL) { + if (strcmp(songs[so]->metadata[m]->name, "title") == 0) { + const char *title = songs[so]->metadata[m]->value; + pdfioFileSetTitle(pdf, title); + if (!SetTextFont(page1_stream, "Inter-Bold", 18.0)) { + fprintf(stderr, "SetTextFont failed.\n"); + return false; + } + if (!ShowText(page1_stream, title, CENTER, true)) { + fprintf(stderr, "ShowText failed.\n"); + return false; + } + if (!ShowText(page1_stream, "", CENTER, true)) { + fprintf(stderr, "ShowText failed.\n"); + return false; + } + } + m++; + } + m = 0; + while (songs[so]->sections[se] != NULL) { + if (songs[so]->sections[se]->name) { + const char *section_name = songs[so]->sections[se]->name; + if (!SetTextFont(page1_stream, "Inter-Italic", 14.0)) { + fprintf(stderr, "SetTextFont failed.\n"); + return false; + } + if (!ShowText(page1_stream, section_name, LEFT, true)) { + fprintf(stderr, "ShowText failed.\n"); + return false; + } + } + if (!SetTextFont(page1_stream, "Inter-Regular", 16.0)) { + fprintf(stderr, "SetTextFont failed.\n"); + return false; + } + while (songs[so]->sections[se]->lines[li] != NULL) { + char *text; + int items_count = cho_line_item_count(songs[so]->sections[se]->lines[li]->lyrics); + while (ly < items_count - 1) { + text = songs[so]->sections[se]->lines[li]->lyrics[ly]->text; + if (!ShowText(page1_stream, text, CONTINUE, false)) { + fprintf(stderr, "ShowText failed.\n"); + return false; + } + ly++; + } + text = songs[so]->sections[se]->lines[li]->lyrics[ly]->text; + if (!ShowText(page1_stream, text, CONTINUE, true)) { + fprintf(stderr, "ShowText failed.\n"); + return false; + } + ly = 0; + li++; + } + current_y -= 10.0; + li = 0; + se++; + } + se = 0; + so++; + } + if (!pdfioStreamClose(page1_stream)) { + fprintf(stderr, "pdfioStreamClose failed.\n"); + return false; + } + if (!pdfioFileClose(pdf)) { + fprintf(stderr, "pdfioFileClose failed.\n"); + return false; + } + return true; +} diff --git a/out_pdf.h b/out_pdf.h @@ -0,0 +1,14 @@ +#define MEDIABOX_HEIGHT 842.0 +#define MEDIABOX_WIDTH 595.0 +#define PAGE_HEIGHT MEDIABOX_HEIGHT - 36.0 +#define PAGE_WIDTH MEDIABOX_WIDTH - 36.0 +#define PADDING 50.0 +#define LINE_LEN MEDIABOX_WIDTH - PADDING * 2 + +enum Alignment { + LEFT, + CENTER, + CONTINUE +}; + +bool out_pdf_new(const char *cho_filename, struct ChoSong **songs); diff --git a/todo b/todo @@ -1,6 +1,11 @@ 'chorus' directive decide how to implement -'image' directive - decide if implement +metadata directives + %{blabla} +'image' directive; decide if implement + https://chordpro.org/chordpro/directives-image/ chordpro markup implement inside chords +the whole chords advanced stuff + transpose + define chords