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:
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