lorid

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

commit a5b28eb3d9a33bedfb44568ad30ca23132c2e28c
parent 2997742861e389678c571664f4ba39f9363988c0
Author: nibo <nibo@relim.de>
Date:   Sun, 11 Aug 2024 19:48:56 +0200

Add directive {new_song}

Diffstat:
Mchordpro.c | 52+++++++++++++++++++++++++++++++++++++++++++---------
Mchordpro.h | 10++++++++++
Mout_pdf.c | 123++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mout_pdf.h | 3+++
Mtodo | 7++++++-
5 files changed, 147 insertions(+), 48 deletions(-)

diff --git a/chordpro.c b/chordpro.c @@ -44,6 +44,10 @@ static const char *font_directives[] = { "tocfont", "tocsize", "toccolour", */ }; +static const char *output_directives[] = { + "new_page", "np", "column_break", "colb", NULL +}; + struct StyleProperty default_style_properties[18] = { { SF_CHORD, SPT_FONT, { .font_name = NULL } }, { SF_CHORD, SPT_SIZE, { .font_size = EMPTY } }, @@ -841,7 +845,7 @@ struct Font *cho_style_font_desc_parse(const char *str) return font; } -struct Style *cho_style_get(const char *tag_name, struct Attr **attrs, struct Style *inherited_style) +struct Style *cho_style_parse(const char *tag_name, struct Attr **attrs, struct Style *inherited_style) { size_t value_len, last_char; struct RGBColor *rgb_color; @@ -1443,7 +1447,7 @@ static int cho_chord_bass_parse(const char *str, struct ChoChord *chord) for (i = 0; i<7; i++) { if (strcmp((char *)&str[1], g_config->chords->notes[i]->note) == 0) { chord->bass = strdup((char *)&str[1]); - return strlen((char *)&str[1]); + return strlen((char *)&str[1]) + 1; } } } @@ -1503,6 +1507,7 @@ static struct ChoLine *cho_line_new(void) struct ChoLine *line = malloc(sizeof(struct ChoLine)); line->text_above = NULL; line->lyrics = NULL; + line->btype = BT_LINE; return line; } @@ -1647,6 +1652,7 @@ static struct ChoDirective *cho_directive_new(void) directive->position = POS_EMPTY; directive->sprop = SPT_EMPTY; directive->ftype = SF_EMPTY; + directive->btype = BT_EMPTY; directive->style = cho_style_new_default(); return directive; } @@ -1888,6 +1894,21 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_TITLE; goto END; } + if ( + strcmp(output_directives[0], name) == 0 || + strcmp(output_directives[1], name) == 0 + ) { + directive->dtype = DT_OUTPUT; + directive->btype = BT_PAGE; + goto END; + } else if ( + strcmp(output_directives[2], name) == 0 || + strcmp(output_directives[3], name) == 0 + ) { + directive->dtype = DT_OUTPUT; + directive->btype = BT_COLUMN; + goto END; + } directive->dtype = DT_CUSTOM; END: return directive; @@ -1984,7 +2005,10 @@ 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]->text_above) { + if ( + !songs[so]->sections[se]->lines[li]->text_above && + songs[so]->sections[se]->lines[li]->btype == BT_LINE + ) { 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 *)); @@ -2129,6 +2153,11 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) return NULL; } break; + case DT_OUTPUT: + if (directive->btype != BT_EMPTY) { + songs[so]->sections[se]->lines[li]->btype = directive->btype; + } + break; case DT_CUSTOM: // TODO: Implement {start_of_*} and {end_of_*} custom directives fprintf(stderr, "INFO: Ignoring custom directive '%s'.\n", directive_name); @@ -2290,6 +2319,9 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) } free(dir_value); break; + case DT_OUTPUT: + fprintf(stderr, "ERR: Directive '%s' can't have a value.\n", directive_name); + return NULL; case DT_CUSTOM: fprintf(stderr, "INFO: Ignoring custom directive '%s'.\n", directive_name); break; @@ -2414,9 +2446,9 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) tag_begin[t] = 0; t = 0; tags[ta]->name = strdup(tag_begin); - tag_style = cho_style_get(tag_begin, NULL, cho_tag_style_inherit(tags, ta-1)); + tag_style = cho_style_parse(tag_begin, NULL, cho_tag_style_inherit(tags, ta-1)); if (!tag_style) { - fprintf(stderr, "cho_style_get failed.\n"); + fprintf(stderr, "cho_style_parse failed.\n"); return NULL; } tags[ta]->style = tag_style; @@ -2523,9 +2555,9 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) if (tags[ta]->attrs[at-1]->value) { cho_tag_attr_free(tags[ta]->attrs[at]); tags[ta]->attrs[at] = NULL; - tag_style = cho_style_get(tag_begin, tags[ta]->attrs, cho_tag_style_inherit(tags, ta-1)); + tag_style = cho_style_parse(tag_begin, tags[ta]->attrs, cho_tag_style_inherit(tags, ta-1)); if (tag_style == NULL) { - fprintf(stderr, "cho_style_get failed.\n"); + fprintf(stderr, "cho_style_parse failed.\n"); return NULL; } tags[ta]->style = tag_style; @@ -2595,9 +2627,9 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) at++; tags[ta]->attrs = realloc(tags[ta]->attrs, (at+1) * sizeof(struct Attr *)); tags[ta]->attrs[at] = NULL; - tag_style = cho_style_get(tag_begin, tags[ta]->attrs, cho_tag_style_inherit(tags, ta-1)); + tag_style = cho_style_parse(tag_begin, tags[ta]->attrs, cho_tag_style_inherit(tags, ta-1)); if (tag_style == NULL) { - fprintf(stderr, "cho_style_get failed.\n"); + fprintf(stderr, "cho_style_parse failed.\n"); return NULL; } tags[ta]->style = tag_style; @@ -2701,6 +2733,8 @@ static const char *cho_debug_the_dtype(enum DirectiveType dtype) return "DT_PREAMBLE"; case DT_FONT: return "DT_FONT"; + case DT_OUTPUT: + return "DT_OUTPUT"; case DT_CUSTOM: return "DT_CUSTOM"; } diff --git a/chordpro.h b/chordpro.h @@ -146,6 +146,7 @@ enum DirectiveType { DT_FORMATTING, DT_PREAMBLE, DT_FONT, + DT_OUTPUT, DT_CUSTOM }; @@ -173,12 +174,20 @@ enum ChordQualifier { CQ_DIM }; +enum BreakType { + BT_EMPTY = -1, + BT_LINE, + BT_PAGE, + BT_COLUMN +}; + struct ChoDirective { enum DirectiveType dtype; enum SectionType stype; enum Position position; enum StylePropertyType sprop; enum SongFragmentType ftype; + enum BreakType btype; struct Style *style; }; @@ -220,6 +229,7 @@ struct ChoLineItemAbove { struct ChoLine { struct ChoLineItemAbove **text_above; struct ChoLineItem **lyrics; + enum BreakType btype; }; struct ChoLabel { diff --git a/out_pdf.c b/out_pdf.c @@ -10,7 +10,6 @@ #include "util.h" static struct Fnt **g_fonts = NULL; - static char g_current_font_name[200]; static double g_current_font_size; static pdfio_obj_t *g_current_font_obj = NULL; @@ -637,6 +636,7 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) } text[t]->lines[tl]->items[0]->x = PADDING + (LINE_LEN - width) / 2; text[t]->lines[tl]->items[1] = NULL; + text[t]->lines[tl]->btype = BT_LINE; text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); tl++; } @@ -661,6 +661,7 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) } text[t]->lines[tl]->items[0]->x = PADDING + (LINE_LEN - width) / 2; text[t]->lines[tl]->items[1] = NULL; + text[t]->lines[tl]->btype = BT_LINE; text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); tl++; } @@ -668,6 +669,7 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) 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; + text[t]->lines[tl]->btype = BT_LINE; text[t]->lines[tl]->height = 30.0; tl++; for (se = 0; songs[so]->sections[se]; se++) { @@ -680,6 +682,7 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) text[t]->lines[tl]->items[0]->style = cho_style_duplicate(songs[so]->sections[se]->label->style); text[t]->lines[tl]->items[0]->x = PADDING; text[t]->lines[tl]->items[1] = NULL; + text[t]->lines[tl]->btype = BT_LINE; text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); tl++; } @@ -756,6 +759,7 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) tli++; text[t]->lines[tl]->items = realloc(text[t]->lines[tl]->items, (tli+1) * sizeof(struct TextLineItem *)); text[t]->lines[tl]->items[tli] = NULL; + text[t]->lines[tl]->btype = lines[li]->btype; text_line_set_lineheight(text[t]->lines[tl], SF_CHORD); tli = 0; tl++; @@ -814,6 +818,7 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) text[t]->lines[tl]->items = realloc(text[t]->lines[tl]->items, (tli+1) * sizeof(struct TextLineItem *)); text[t]->lines[tl]->items[tli] = NULL; text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); + text[t]->lines[tl]->btype = lines[li]->btype; tli = 0; if (spaces) { for (sn = 0; spaces[sn]; sn++) { @@ -824,6 +829,7 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) sn = 0; } } + text[t]->lines[tl-1]->height += SECTION_GAP_WIDTH; } text[t]->lines = realloc(text[t]->lines, (tl+1) * sizeof(struct TextLine *)); text[t]->lines[tl] = NULL; @@ -887,9 +893,14 @@ static bool annots_create(pdfio_file_t *pdf, pdfio_array_t *annots, struct Text struct TextLineItem *item; pdfio_dict_t *annot; pdfio_rect_t rect; - double y = MEDIABOX_HEIGHT - 25.0; + double y = MEDIABOX_HEIGHT - VERTICAL_MARGIN; double width; int t, tl, tli; + pdfio_array_t *page_annots = pdfioArrayCreate(pdf); + if (!page_annots) { + fprintf(stderr, "pdfioArrayCreate failed.\n"); + return false; + } for (t = 0; text[t]; t++) { for (tl = 0; text[t]->lines[tl]; tl++) { if (text[t]->lines[tl]->items) { @@ -906,16 +917,33 @@ static bool annots_create(pdfio_file_t *pdf, pdfio_array_t *annots, struct Text fprintf(stderr, "annot_create failed.\n"); return false; } - if (!pdfioArrayAppendDict(annots, annot)) { + if (!pdfioArrayAppendDict(page_annots, annot)) { fprintf(stderr, "pdfioArrayAppendDict failed.\n"); return false; } } } } - y -= text[t]->lines[tl]->height; + if (text[t]->lines[tl]->btype == BT_PAGE) { + if (!pdfioArrayAppendArray(annots, page_annots)) { + fprintf(stderr, "pdfioArrayAppendArray failed.\n"); + return false; + } + page_annots = pdfioArrayCreate(pdf); + if (!page_annots) { + fprintf(stderr, "pdfioArrayCreate failed.\n"); + return false; + } + y = MEDIABOX_HEIGHT - VERTICAL_MARGIN; + } else { + y -= text[t]->lines[tl]->height; + } } } + if (!pdfioArrayAppendArray(annots, page_annots)) { + fprintf(stderr, "pdfioArrayAppendArray failed.\n"); + return false; + } return true; } @@ -934,6 +962,41 @@ static bool out_pdf_set_title(pdfio_file_t *pdf, struct ChoSong **songs) return true; } +static pdfio_stream_t *out_pdf_page_create(pdfio_file_t *pdf, pdfio_array_t *annots, int page_no) +{ + pdfio_dict_t *page_dict; + pdfio_array_t *page_annots; + pdfio_stream_t *page_stream; + int f; + pdfio_array_t *color_array = pdfioArrayCreateColorFromStandard(pdf, 3, PDFIO_CS_ADOBE); + page_dict = pdfioDictCreate(pdf); + if (!pdfioPageDictAddColorSpace(page_dict, "rgbcolorspace", color_array)) { + fprintf(stderr, "pdfioPageDictAddColorSpace failed.\n"); + return false; + } + for (f = 0; g_fonts[f]; f++) { + if (!pdfioPageDictAddFont(page_dict, g_fonts[f]->name, g_fonts[f]->font)) { + fprintf(stderr, "pdfioPageDictAddFont failed.\n"); + return false; + } + } + page_annots = pdfioArrayGetArray(annots, page_no); + if (!pdfioDictSetArray(page_dict, "Annots", page_annots)) { + fprintf(stderr, "pdfioDictSetArray failed.\n"); + return false; + } + page_stream = pdfioFileCreatePage(pdf, page_dict); + if (!pdfioContentSetFillColorSpace(page_stream, "rgbcolorspace")) { + fprintf(stderr, "pdfioContentSetFillColorSpace failed.\n"); + return false; + } + if (!pdfioContentSetStrokeColorSpace(page_stream, "rgbcolorspace")) { + fprintf(stderr, "pdfioContentSetStrokeColorSpace failed.\n"); + return false; + } + return page_stream; +} + bool out_pdf_new(const char *cho_filepath, const char *output_folder_or_file, struct ChoSong **songs, struct Config *config) { memset(&g_current_font_name, 0, sizeof(g_current_font_name)); @@ -951,7 +1014,6 @@ bool out_pdf_new(const char *cho_filepath, const char *output_folder_or_file, st fprintf(stderr, "out_pdf_set_title failed.\n"); return false; } - pdfio_dict_t *page1_dict = pdfioDictCreate(pdf); struct Font **needed_fonts = out_pdf_font_get_all(songs, config); struct Fnt *fnt; int f = 0; @@ -963,10 +1025,6 @@ bool out_pdf_new(const char *cho_filepath, const char *output_folder_or_file, st fnt->name = out_pdf_fnt_name_create(needed_fonts[f]); fnt->font = pdfioFileCreateFontObjFromFile(pdf, fontpath, true); out_pdf_fnt_add(fnt, &g_fonts); - if (!pdfioPageDictAddFont(page1_dict, fnt->name, fnt->font)) { - fprintf(stderr, "pdfioPageDictAddFont failed.\n"); - return false; - } free(fontpath); } else { fontpath = fontconfig_fontpath_find(needed_fonts[f], FT_OTF); @@ -975,10 +1033,6 @@ bool out_pdf_new(const char *cho_filepath, const char *output_folder_or_file, st fnt->name = out_pdf_fnt_name_create(needed_fonts[f]); fnt->font = pdfioFileCreateFontObjFromFile(pdf, fontpath, true); out_pdf_fnt_add(fnt, &g_fonts); - if (!pdfioPageDictAddFont(page1_dict, fnt->name, fnt->font)) { - fprintf(stderr, "pdfioPageDictAddFont failed.\n"); - return false; - } free(fontpath); } else { fprintf(stderr, "ERR: Didn't find font file for following font:\n"); @@ -999,40 +1053,32 @@ bool out_pdf_new(const char *cho_filepath, const char *output_folder_or_file, st fprintf(stderr, "annotations_create failed.\n"); return false; } - if (pdfioArrayGetSize(annots) > 0) { - if (!pdfioDictSetArray(page1_dict, "Annots", annots)) { - fprintf(stderr, "pdfioDictSetArray failed.\n"); - return false; - } - } - pdfio_array_t *color_array = pdfioArrayCreateColorFromStandard(pdf, 3, PDFIO_CS_ADOBE); - if (!pdfioPageDictAddColorSpace(page1_dict, "rgbcolorspace", color_array)) { - fprintf(stderr, "pdfioPageDictAddColorSpace failed.\n"); - return false; - } - pdfio_stream_t *page1_stream = pdfioFileCreatePage(pdf, page1_dict); - if (!pdfioContentSetFillColorSpace(page1_stream, "rgbcolorspace")) { - fprintf(stderr, "pdfioContentSetFillColorSpace failed.\n"); - return false; - } - if (!pdfioContentSetStrokeColorSpace(page1_stream, "rgbcolorspace")) { - fprintf(stderr, "pdfioContentSetStrokeColorSpace failed.\n"); - return false; - } - double y = MEDIABOX_HEIGHT - 25.0; + pdfio_stream_t *page_stream; + int p = 0; + page_stream = out_pdf_page_create(pdf, annots, p); + double y = MEDIABOX_HEIGHT - VERTICAL_MARGIN; int t, tl, tli; for (t = 0; text[t]; t++) { for (tl = 0; text[t]->lines[tl]; tl++) { if (text[t]->lines[tl]->items) { for (tli = 0; text[t]->lines[tl]->items[tli]; tli++) { - out_pdf_text_show(page1_stream, text[t]->lines[tl]->items[tli], y); + out_pdf_text_show(page_stream, text[t]->lines[tl]->items[tli], y); } } - y -= text[t]->lines[tl]->height; + if (text[t]->lines[tl]->btype == BT_PAGE) { + if (!pdfioStreamClose(page_stream)) { + fprintf(stderr, "pdfioStreamClose failed.\n"); + return false; + } + p++; + page_stream = out_pdf_page_create(pdf, annots, p); + y = MEDIABOX_HEIGHT - VERTICAL_MARGIN; + } else { + y -= text[t]->lines[tl]->height; + } } } - text_free(text); - if (!pdfioStreamClose(page1_stream)) { + if (!pdfioStreamClose(page_stream)) { fprintf(stderr, "pdfioStreamClose failed.\n"); return false; } @@ -1040,6 +1086,7 @@ bool out_pdf_new(const char *cho_filepath, const char *output_folder_or_file, st fprintf(stderr, "pdfioFileClose failed.\n"); return false; } + text_free(text); out_pdf_fnts_free(g_fonts); return true; } diff --git a/out_pdf.h b/out_pdf.h @@ -2,9 +2,11 @@ #define MEDIABOX_HEIGHT 878.0 #define MEDIABOX_WIDTH 631.0 +#define VERTICAL_MARGIN 40.0 #define PADDING 80.0 #define LINE_LEN MEDIABOX_WIDTH - PADDING * 2 #define MIN_CHORD_GAP_WIDTH 5.0 +#define SECTION_GAP_WIDTH 10.0 enum Bool { B_ERROR = -1, @@ -38,6 +40,7 @@ struct TextLineItem { struct TextLine { struct TextLineItem **items; double height; + enum BreakType btype; }; struct Text { diff --git a/todo b/todo @@ -3,15 +3,20 @@ 'image' directive https://chordpro.org/chordpro/directives-image/ metadata directives - %{blabla} in lyrics and chords + %{blabla} in lyrics, chords and annotations conditional metadata directives chords transpose define chords chord diagrams + strict and relaxed parsing makes no difference!? introduce parse errors: make parser bulletproof still very unclear to me when the parser should warn and continue execution and when it should fail and stop execution *_to_string functions sometimes return stringified enum and sometimes config value allow {start_of_*},{end_of_*} custom directives + +# pdf output +break lines when too long +create a new page when text too long