commit d78118139fb627fe871f723fcc20703325e3ff47
parent fd7dabebe0ae11b609c96e3c2a25426663731f36
Author: nibo <nibo@relim.de>
Date: Sat, 21 Dec 2024 12:20:31 +0100
Add option to render table of contents
Make lorid take more than one file as input.
A table of contents makes only sense if you
can provide many files.
Diffstat:
| M | chordpro.c | | | 46 | ++++++++++++++++++++++++++++++++++++++++++---- |
| M | chordpro.h | | | 1 | + |
| M | config.c | | | 29 | ++++++++++++++++++++++++++--- |
| M | config.h | | | 6 | ++++++ |
| M | lorid.c | | | 56 | ++++++++++++++++++++++++++++++++------------------------ |
| M | out_pdf.c | | | 189 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- |
| M | out_pdf.h | | | 8 | ++++++++ |
7 files changed, 281 insertions(+), 54 deletions(-)
diff --git a/chordpro.c b/chordpro.c
@@ -33,7 +33,7 @@ static const char *state_enums[] = {
"STATE_COMMENT"
};
-struct StyleProperty default_style_properties[18] = {
+struct StyleProperty default_style_properties[21] = {
{ SF_CHORD, SPT_FONT, { .font_name = NULL } },
{ SF_CHORD, SPT_SIZE, { .font_size = EMPTY_DOUBLE } },
{ SF_CHORD, SPT_COLOR, { .foreground_color = NULL } },
@@ -52,6 +52,9 @@ struct StyleProperty default_style_properties[18] = {
{ SF_TITLE, SPT_FONT, { .font_name = NULL } },
{ SF_TITLE, SPT_SIZE, { .font_size = EMPTY_DOUBLE } },
{ SF_TITLE, SPT_COLOR, { .foreground_color = NULL } },
+ { SF_TOC, SPT_FONT, { .font_name = NULL } },
+ { SF_TOC, SPT_SIZE, { .font_size = EMPTY_DOUBLE } },
+ { SF_TOC, SPT_COLOR, { .foreground_color = NULL } },
};
static const char *chord_extensions_major[] = {
@@ -879,6 +882,9 @@ cho_style_new_from_config(enum SongFragmentType ftype)
case SF_SUBTITLE:
style = config_output_style_get(g_config->output->styles, "subtitle");
return cho_style_copy(style->style);
+ case SF_TOC:
+ style = config_output_style_get(g_config->output->styles, "toc");
+ return cho_style_copy(style->style);
default:
style = config_output_style_get(g_config->output->styles, "text");
return cho_style_copy(style->style);
@@ -3130,6 +3136,28 @@ cho_song_count(struct ChoSong **songs)
return i;
}
+const char *
+cho_song_get_title(struct ChoSong *song)
+{
+ struct ChoMetadata **m;
+ m = song->metadata;
+ while (*m) {
+ if (!strcmp((*m)->name, "title")) {
+ return (*m)->value;
+ }
+ m++;
+ }
+ return NULL;
+}
+
+int
+cho_song_compare(const void *a, const void *b)
+{
+ struct ChoSong **aa = (struct ChoSong **)a;
+ struct ChoSong **bb = (struct ChoSong **)b;
+ return str_compare(cho_song_get_title(*aa), cho_song_get_title(*bb));
+}
+
static void
cho_song_free(struct ChoSong *song)
{
@@ -3489,12 +3517,21 @@ cho_directive_parse(const char *name)
directive->sprop = SPT_COLOR;
directive->ftype = SF_TITLE;
goto END;
- } /* else if (!strcmp(name, "footerfont")) {
- } else if (!strcmp(name, "footersize")) {
- } else if (!strcmp(name, "footercolour")) {
} else if (!strcmp(name, "tocfont")) {
+ directive->dtype = DT_FONT;
+ directive->sprop = SPT_FONT;
+ directive->ftype = SF_TOC;
} else if (!strcmp(name, "tocsize")) {
+ directive->dtype = DT_FONT;
+ directive->sprop = SPT_SIZE;
+ directive->ftype = SF_TOC;
} else if (!strcmp(name, "toccolour")) {
+ directive->dtype = DT_FONT;
+ directive->sprop = SPT_COLOR;
+ directive->ftype = SF_TOC;
+ } /* else if (!strcmp(name, "footerfont")) {
+ } else if (!strcmp(name, "footersize")) {
+ } else if (!strcmp(name, "footercolour")) {
} */
if (!strcmp(name, "transpose")) {
directive->dtype = DT_CHORD;
@@ -4813,6 +4850,7 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config)
songs = erealloc(songs, (so+1) * sizeof(struct ChoSong *));
songs[so] = NULL;
free(g_transpose_history);
+ g_transpose_history = NULL;
for (e = 0; e<g_ia; e++) {
cho_image_free(g_image_assets[e]);
}
diff --git a/chordpro.h b/chordpro.h
@@ -322,6 +322,7 @@ struct ChoSong {
struct ChoSong **cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config);
int cho_song_count(struct ChoSong **songs);
+int cho_song_compare(const void *a, const void *b);
void cho_songs_free(struct ChoSong **song);
int cho_line_item_count(struct ChoLineItem **items);
diff --git a/config.c b/config.c
@@ -329,6 +329,9 @@ config_load_default(void)
{
struct Config *config = emalloc(sizeof(struct Config));
config->output = emalloc(sizeof(struct ConfigOutput));
+ config->output->toc = emalloc(sizeof(struct ConfigToc));
+ config->output->toc->show = false;
+ config->output->toc->title = strdup("Table Of Contents");
config->output->chorus = emalloc(sizeof(struct ConfigChorus));
config->output->chorus->label = strdup("Chorus");
config->output->chorus->quote = false;
@@ -336,7 +339,7 @@ config_load_default(void)
config->output->diagram->show = true;
config->output->diagram->instrument = INS_GUITAR;
config->output->system = NS_COMMON;
- config->output->styles = emalloc(12 * sizeof(struct OutputStyle *));
+ config->output->styles = emalloc(13 * sizeof(struct OutputStyle *));
config->output->styles[0] = config_output_style_new("title");
config->output->styles[0]->style->font->name = strdup("Inter");
config->output->styles[0]->style->font->weight = FW_BOLD;
@@ -367,7 +370,10 @@ config_load_default(void)
config->output->styles[10] = config_output_style_new("annotation");
config->output->styles[10]->style->font->name = strdup("Inter");
config->output->styles[10]->style->font->style = FS_ITALIC;
- config->output->styles[11] = NULL;
+ config->output->styles[11] = config_output_style_new("toc");
+ config->output->styles[11]->style->font->name = strdup("Inter");
+ config->output->styles[11]->style->font->size = 12.0;
+ config->output->styles[12] = NULL;
config->output->notes = config_notes_new_default(NS_COMMON);
config->parser = emalloc(sizeof(struct ConfigParser));
config->parser->chords = emalloc(sizeof(struct ConfigChords));
@@ -392,6 +398,9 @@ config_print_default(void)
config_notes_print_as_toml(config->output->system, config->output->notes);
printf("[output]\n\n");
printf("system = \"%s\"\n\n", config_naming_system_to_config_string(config->output->system));
+ printf("[output.toc]\n\n");
+ printf("show = %s\n", config->output->toc->show ? "true" : "false");
+ printf("title = \"%s\"\n\n", config->output->toc->title);
printf("[output.chorus]\n\n");
printf("label = \"Chorus\"\n");
printf("quote = false\n\n");
@@ -622,7 +631,7 @@ config_load(const char *filepath)
}
toml_table_t *output = toml_table_table(table, "output");
if (output) {
- toml_table_t *notes, *chorus, *diagram;
+ toml_table_t *notes, *chorus, *diagram, *toc;
toml_value_t value;
enum NamingSystem system;
enum Instrument instrument;
@@ -639,6 +648,18 @@ config_load(const char *filepath)
config->output->chorus->quote = value.u.b;
}
}
+ toc = toml_table_table(output, "toc");
+ if (toc) {
+ value = toml_table_bool(toc, "show");
+ if (value.ok) {
+ config->output->toc->show = value.u.b;
+ }
+ value = toml_table_string(toc, "title");
+ if (value.ok) {
+ free(config->output->toc->title);
+ config->output->toc->title = value.u.s;
+ }
+ }
diagram = toml_table_table(output, "chord_diagram");
if (diagram) {
value = toml_table_bool(diagram, "show");
@@ -756,6 +777,8 @@ config_free(struct Config *config)
{
struct OutputStyle **start_items = config->output->styles;
struct Note **start_notes = config->output->notes;
+ free(config->output->toc->title);
+ free(config->output->toc);
free(config->output->chorus->label);
free(config->output->chorus);
while (*config->output->styles) {
diff --git a/config.h b/config.h
@@ -61,7 +61,13 @@ struct ConfigChordDiagram {
enum Instrument instrument;
};
+struct ConfigToc {
+ bool show;
+ char *title;
+};
+
struct ConfigOutput {
+ struct ConfigToc *toc;
struct ConfigChorus *chorus;
struct ConfigChordDiagram *diagram;
enum NamingSystem system;
diff --git a/lorid.c b/lorid.c
@@ -24,9 +24,10 @@ main(int argc, char *argv[])
const char *chordpro_filepath = NULL;
char *config_filepath = NULL;
char *output = NULL;
- char *file_content = NULL;
- char *mem = NULL;
+ struct ChoSong **all_songs = NULL;
struct ChoSong **songs = NULL;
+ struct ChoSong **begin;
+ int s = 0;
FILE *fp;
while ((o = getopt_long(argc, argv, "pc:o:vh", long_options, &option_index)) != -1) {
switch(o) {
@@ -64,32 +65,41 @@ main(int argc, char *argv[])
return 1;
}
chordpro_filepath = argv[argc-1];
+ all_songs = cho_songs_parse(fp, chordpro_filepath, config);
+ if (!all_songs) {
+ LOG_DEBUG("cho_songs_parse failed.");
+ return 1;
+ }
+ fclose(fp);
} else {
int file_count = argc - optind;
int i;
- size_t size;
- fp = open_memstream(&mem, &size);
- if (!fp) {
- perror("open_memstream");
- return 1;
- }
for (i = argc-file_count; i<argc; i++) {
- chordpro_filepath = argv[i];
- file_content = file_read(chordpro_filepath);
- fprintf(fp, "%s", file_content);
- if (i != argc-1) {
- fprintf(fp, "{new_song}\n");
+ fp = fopen(argv[i], "r");
+ if (!fp) {
+ LOG_DEBUG("fopen failed.");
+ return 1;
}
- free(file_content);
+ songs = cho_songs_parse(fp, argv[i], config);
+ if (!songs) {
+ LOG_DEBUG("cho_songs_parse failed.");
+ return 1;
+ }
+ begin = songs;
+ fclose(fp);
+ while (*songs) {
+ all_songs = erealloc(all_songs, (s+1) * sizeof(struct ChoSong *));
+ all_songs[s] = *songs;
+ s++;
+ songs++;
+ }
+ free(begin);
}
- chordpro_filepath = NULL;
- }
- songs = cho_songs_parse(fp, chordpro_filepath, config);
- if (!songs) {
- LOG_DEBUG("cho_songs_parse failed.");
- return 1;
+ all_songs = erealloc(all_songs, (s+1) * sizeof(struct ChoSong *));
+ all_songs[s] = NULL;
}
- char *pdf_filename = out_pdf_create(argc == optind+1 ? argv[argc-1] : NULL, output ? output : NULL, songs, config);
+ qsort(all_songs, cho_song_count(all_songs), sizeof(struct ChoSong *), cho_song_compare);
+ char *pdf_filename = out_pdf_create(chordpro_filepath, output, all_songs, config);
if (!pdf_filename) {
LOG_DEBUG("out_pdf_new failed.");
return 1;
@@ -97,9 +107,7 @@ main(int argc, char *argv[])
util_log(LOG_INFO, "Writing pdf to file: '%s'.", pdf_filename);
free(pdf_filename);
free(output);
- free(mem);
- cho_songs_free(songs);
+ cho_songs_free(all_songs);
config_free(config);
- fclose(fp);
return 0;
}
diff --git a/out_pdf.c b/out_pdf.c
@@ -963,6 +963,13 @@ pdf_text_free(struct PDFText *text)
free(text);
}
+static void
+toc_entry_free(struct TocEntry *entry)
+{
+ free(entry->title);
+ free(entry);
+}
+
static struct PDFPage *
pdf_page_new(void)
{
@@ -1010,6 +1017,40 @@ pdf_page_free(struct PDFPage *page)
free(page);
}
+static struct PDFContent *
+pdf_content_new(void)
+{
+ struct PDFContent *content = emalloc(sizeof(struct PDFContent));
+ content->pages = NULL;
+ content->toc = NULL;
+ return content;
+}
+
+static void
+pdf_content_free(struct PDFContent *content)
+{
+ if (!content) {
+ return;
+ }
+ struct PDFPage **p;
+ struct TocEntry **toc;
+ p = content->pages;
+ while (*p) {
+ pdf_page_free(*p);
+ p++;
+ }
+ free(content->pages);
+ toc = content->toc;
+ if (toc) {
+ while (*toc) {
+ toc_entry_free(*toc);
+ toc++;
+ }
+ free(content->toc);
+ }
+ free(content);
+}
+
static void
spaces_free(struct SpaceNeeded **spaces)
{
@@ -1450,6 +1491,79 @@ pdf_texts_add_text(
return true;
}
+/* static pdfio_dict_t *
+annot_within_document_create(struct TocEntry *toc)
+{
+ pdfio_rect_t rect;
+ pdfio_dict_t *annot, *action;
+ rect.x1 = ctx->x;
+ rect.x2 = ctx->x + width;
+ rect.y1 = ctx->y - 2.0;
+ rect.y2 = ctx->y + style->font->size * 0.8;
+ annot = pdfioDictCreate(g_pdf_file);
+ if (!pdfioDictSetName(annot, "Subtype", "Link")) {
+ LOG_DEBUG("pdfioDictSetName failed.");
+ return false;
+ }
+ if (!pdfioDictSetRect(annot, "Rect", &rect)) {
+ LOG_DEBUG("pdfioDictSetRect failed.");
+ return false;
+ }
+ action = pdfioDictCreate(g_pdf_file);
+ if (!pdfioDictSetName(action, "S", "GoTo")) {
+ LOG_DEBUG("pdfioDictSetName failed.");
+ return false;
+ }
+ if (!pdfioDictSetName(action, "D", destination)) {
+ LOG_DEBUG("pdfioDictSetName failed.");
+ return false;
+ }
+ // if (!pdfioArrayAppendDict(ctx->content->pages[ctx->page]->annots, annot)) {
+ // LOG_DEBUG("pdfioArrayAppendDict failed.");
+ // return false;
+ // }
+ return true;
+} */
+
+static bool
+pdf_toc_create(
+ struct PDFContent **toc_content,
+ struct PDFContent *pdf_content,
+ struct ChoSong **songs,
+ struct Config *config
+)
+{
+ struct PDFContext ctx;
+ struct PDFText ***texts;
+ ctx.text = 0;
+ ctx.page = 0;
+ ctx.x = MARGIN_HORIZONTAL;
+ ctx.y = MEDIABOX_HEIGHT - MARGIN_TOP;
+ ctx.content = pdf_content_new();
+ ctx.content->pages = emalloc(sizeof(struct PDFPage *));
+ ctx.content->pages[ctx.page] = pdf_page_new();
+ texts = &ctx.content->pages[ctx.page]->texts;
+ struct OutputStyle *toc_style = config_output_style_get(config->output->styles, "toc");
+ struct TocEntry **toc;
+ toc = pdf_content->toc;
+ while (*toc) {
+ // annot_within_document_create(*toc);
+ if (!pdf_texts_add_text(&ctx, (*toc)->title, toc_style->style, A_LEFT)) {
+ LOG_DEBUG("pdf_texts_add_text failed.");
+ return false;
+ }
+ printf("%s\n", (*toc)->title);
+ toc++;
+ }
+ *texts = erealloc(*texts, (ctx.text+1) * sizeof(struct PDFText *));
+ (*texts)[ctx.text] = NULL;
+ ctx.page++;
+ ctx.content->pages = erealloc(ctx.content->pages, (ctx.page+1) * sizeof(struct PDFPage *));
+ ctx.content->pages[ctx.page] = NULL;
+ *toc_content = ctx.content;
+ return true;
+}
+
static bool
pdf_content_create(
struct PDFContent **out,
@@ -1458,7 +1572,6 @@ pdf_content_create(
struct Obj **img_objs
)
{
- struct ChoSong **s;
struct ChoMetadata **m;
struct ChoSection **se;
struct ChoLine **li;
@@ -1478,24 +1591,25 @@ pdf_content_create(
ctx.image = 0;
ctx.diagram = 0;
ctx.page = 0;
+ ctx.toc_entry = 0;
ctx.spaces = NULL;
- ctx.content = emalloc(sizeof(struct PDFContent));
+ ctx.content = pdf_content_new();
ctx.content->pages = emalloc(sizeof(struct PDFPage *));
ctx.content->pages[ctx.page] = pdf_page_new();
- s = songs;
texts = &ctx.content->pages[ctx.page]->texts;
imgs = &ctx.content->pages[ctx.page]->images;
diagrams = &ctx.content->pages[ctx.page]->diagrams;
- while (*s) {
+ int s;
+ for (s = 0; songs[s]; s++) {
if (config->output->diagram->show) {
struct ChoChord **chords = NULL;
- if (!out_pdf_get_chords(*s, &chords)) {
+ if (!out_pdf_get_chords(songs[s], &chords)) {
LOG_DEBUG("out_pdf_get_chords failed.");
return false;
}
if (chords) {
qsort(chords, cho_chords_len(chords), sizeof(struct ChoChord *), cho_chord_compare);
- dgrams = chord_diagrams_create(config, &chords, (*s)->diagrams);
+ dgrams = chord_diagrams_create(config, &chords, songs[s]->diagrams);
dgrams_begin = dgrams;
while (*dgrams) {
// debug_chord_diagram_print(*dgrams);
@@ -1508,9 +1622,15 @@ pdf_content_create(
}
cho_chords_free(chords);
}
- m = (*s)->metadata;
+ m = songs[s]->metadata;
while (*m) {
if (!strcmp((*m)->name, "title")) {
+ ctx.content->toc = erealloc(ctx.content->toc, (ctx.toc_entry+1) * sizeof(struct TocEntry *));
+ ctx.content->toc[ctx.toc_entry] = emalloc(sizeof(struct TocEntry));
+ ctx.content->toc[ctx.toc_entry]->title = strdup((*m)->value);
+ ctx.content->toc[ctx.toc_entry]->page_index = ctx.page;
+ ctx.content->toc[ctx.toc_entry]->page_y = ctx.y;
+ ctx.toc_entry++;
if (!pdf_texts_add_text(&ctx, (*m)->value, (*m)->style, A_CENTER)) {
LOG_DEBUG("pdf_texts_add_text failed.");
return false;
@@ -1518,7 +1638,7 @@ pdf_content_create(
}
m++;
}
- m = (*s)->metadata;
+ m = songs[s]->metadata;
while (*m) {
if (!strcmp((*m)->name, "subtitle")) {
/*
@@ -1540,7 +1660,7 @@ pdf_content_create(
m++;
}
ctx.y -= 30.0;
- se = (*s)->sections;
+ se = songs[s]->sections;
while (*se) {
if ((*se)->label) {
if (!pdf_texts_add_text(&ctx, (*se)->label->text, (*se)->label->style, A_LEFT)) {
@@ -1795,7 +1915,6 @@ pdf_content_create(
ctx.y -= SECTION_GAP_WIDTH;
se++;
}
- s++;
}
*texts = erealloc(*texts, (ctx.text+1) * sizeof(struct PDFText *));
(*texts)[ctx.text] = NULL;
@@ -1806,24 +1925,36 @@ pdf_content_create(
ctx.page++;
ctx.content->pages = erealloc(ctx.content->pages, (ctx.page+1) * sizeof(struct PDFPage *));
ctx.content->pages[ctx.page] = NULL;
+ ctx.content->toc = erealloc(ctx.content->toc, (ctx.toc_entry+1) * sizeof(struct TocEntry *));
+ ctx.content->toc[ctx.toc_entry] = NULL;
*out = ctx.content;
return true;
}
-static void
-pdf_content_free(struct PDFContent *content)
+static bool
+pdf_toc_render(struct PDFContent *content, pdfio_file_t *file)
{
- if (!content) {
- return;
- }
- struct PDFPage **p;
- p = content->pages;
- while (*p) {
- pdf_page_free(*p);
- p++;
+ struct PDFPage **pages;
+ struct PDFText **texts;
+ pdfio_stream_t *stream;
+ pages = content->pages;
+ int p;
+ for (p = 0; pages[p]; p++) {
+ texts = pages[p]->texts;
+ stream = out_pdf_page_create(file, NULL, pages[p]->annots);
+ while (*texts) {
+ if (!out_pdf_text_show(stream, *texts)) {
+ LOG_DEBUG("out_pdf_text_show failed.");
+ return false;
+ }
+ texts++;
+ }
+ if (!pdfioStreamClose(stream)) {
+ LOG_DEBUG("pdfioStreamClose failed.");
+ return false;
+ }
}
- free(content->pages);
- free(content);
+ return true;
}
static bool
@@ -1901,7 +2032,8 @@ out_pdf_create(
struct Font **needed_fonts;
struct Obj **img_objs = NULL;
struct Obj *fnt;
- struct PDFContent *pdf_content;
+ struct PDFContent *pdf_content = NULL;
+ struct PDFContent *toc_content = NULL;
pdfio_rect_t media_box_a4 = { 0.0, 0.0, MEDIABOX_WIDTH, MEDIABOX_HEIGHT };
pdfio_rect_t crop_box = { 0.0, 0.0, MEDIABOX_WIDTH, MEDIABOX_HEIGHT };
int f = 0;
@@ -1965,11 +2097,22 @@ out_pdf_create(
LOG_DEBUG("pdf_content_create failed.");
return NULL;
}
+ if (config->output->toc->show) {
+ if (!pdf_toc_create(&toc_content, pdf_content, songs, config)) {
+ LOG_DEBUG("pdf_toc_create failed.");
+ return false;
+ }
+ if (!pdf_toc_render(toc_content, g_pdf_file)) {
+ LOG_DEBUG("pdf_toc_render failed.");
+ return false;
+ }
+ }
if (!pdf_content_render(pdf_content, g_pdf_file)) {
LOG_DEBUG("pdf_content_render failed.");
return NULL;
}
objs_free(img_objs);
+ pdf_content_free(toc_content);
pdf_content_free(pdf_content);
if (!pdfioFileClose(g_pdf_file)) {
LOG_DEBUG("pdfioFileClose failed.");
diff --git a/out_pdf.h b/out_pdf.h
@@ -36,6 +36,12 @@ struct CharPosition {
int text_index;
};
+struct TocEntry {
+ int page_index;
+ double page_y;
+ char *title;
+};
+
struct PDFImage {
double x;
double y;
@@ -61,6 +67,7 @@ struct PDFPage {
struct PDFContent {
struct PDFPage **pages;
+ struct TocEntry **toc;
};
struct PDFContext {
@@ -71,6 +78,7 @@ struct PDFContext {
int image;
int diagram;
int page;
+ int toc_entry;
struct SpaceNeeded **spaces;
double biggest_font_size;
size_t consumed_lyrics;