commit a5b28eb3d9a33bedfb44568ad30ca23132c2e28c
parent 2997742861e389678c571664f4ba39f9363988c0
Author: nibo <nibo@relim.de>
Date: Sun, 11 Aug 2024 19:48:56 +0200
Add directive {new_song}
Diffstat:
| M | chordpro.c | | | 52 | +++++++++++++++++++++++++++++++++++++++++++--------- |
| M | chordpro.h | | | 10 | ++++++++++ |
| M | out_pdf.c | | | 123 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- |
| M | out_pdf.h | | | 3 | +++ |
| M | todo | | | 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