lorid

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

commit 930ce7cdcbe96051dc2545ecbcd2ecb7f11c2715
parent 29211b1c1663048aed7a0c35102c8f35b678a489
Author: nibo <nibo@relim.de>
Date:   Sat, 14 Sep 2024 16:18:14 +0200

out_pdf: break line if not fitting

For now this only applies to the title,
subtitle and sections name.

Diffstat:
Mfontconfig.c | 2+-
Mout_pdf.c | 407++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mout_pdf.h | 14+++++++-------
3 files changed, 318 insertions(+), 105 deletions(-)

diff --git a/fontconfig.c b/fontconfig.c @@ -5,7 +5,7 @@ #include <fontconfig/fontconfig.h> #include "fontconfig.h" -bool file_extension_is_ttc(const char *filepath) +static bool file_extension_is_ttc(const char *filepath) { int mark = -1; int i = 0; diff --git a/out_pdf.c b/out_pdf.c @@ -148,25 +148,6 @@ static struct Font **out_pdf_font_get_all(struct ChoSong **songs, struct Config return fonts; } -static size_t out_pdf_text_find_fitting_length(const char *str, size_t len) -{ - char tmp[512]; - strncpy((char *)&tmp, str, len); - tmp[len] = 0; - - double width; - width = pdfioContentTextMeasure(g_current_font_obj, tmp, g_current_font_size); - - while (width > LINE_LEN) { - if (tmp[len] == ' ' || tmp[len] == '\t') { - tmp[len] = 0; - width = pdfioContentTextMeasure(g_current_font_obj, tmp, g_current_font_size); - } - len--; - } - return len; -} - static char *out_pdf_filename_generate_from_songs(struct ChoSong **songs) { char *filename; @@ -352,6 +333,39 @@ static bool out_pdf_font_set(pdfio_stream_t *stream, struct Font *font) return true; } +static double text_line_set_lineheight(struct TextLine *line, enum SongFragmentType ftype) +{ + double biggest_font_size = 0.0; + int tli; + for (tli = 0; line->items[tli]; tli++) { + if (line->items[tli]->style->font->size > biggest_font_size) { + biggest_font_size = line->items[tli]->style->font->size; + } + } + switch (ftype) { + case SF_CHORD: + line->height = 2.0 + biggest_font_size; + break; + default: + line->height = 8.0 + biggest_font_size; + } + return line->height; +} + +static struct TextLine *text_line_create(const char *str, struct Style *style) +{ + struct TextLine *line = malloc(sizeof(struct TextLine)); + line->items = malloc(2 * sizeof(struct TextLineItem *)); + line->items[0] = malloc(sizeof(struct TextLineItem)); + line->items[0]->text = strdup(str); + line->items[0]->style = cho_style_duplicate(style); + line->items[0]->x = MARGIN_HORIZONTAL; + line->items[1] = NULL; + text_line_set_lineheight(line, SF_TEXT); + line->btype = BT_LINE; + return line; +} + static double text_width(struct TextLineItem *item) { char *name = out_pdf_fnt_name_create(item->style->font); @@ -361,14 +375,125 @@ static double text_width(struct TextLineItem *item) return -1.0; } free(name); - /* int i = 0; - while (item->text[i] != 0) { - printf("%02X ", item->text[i]); + return pdfioContentTextMeasure(font_obj, item->text, item->style->font->size); +} + +static enum Bool text_fits(const char *str, struct Style *style) +{ + double width; + char *name = out_pdf_fnt_name_create(style->font); + pdfio_obj_t *font_obj = out_pdf_fnt_obj_get_by_name(name); + if (!font_obj) { + fprintf(stderr, "out_pdf_fnt_obj_get_by_name failed.\n"); + return B_ERROR; + } + free(name); + width = pdfioContentTextMeasure(font_obj, str, style->font->size); + if (width > LINE_WIDTH) { + return B_FALSE; + } + return B_TRUE; +} + +static int find_whitespace(const char *str, size_t start) +{ + int i; + for (i = start; i>=0; i--) { + if (str[i] == ' ' || str[i] == '\t') { + return i; + } + } + return -1; +} + +static char *text_find_fitting(const char *str, struct Style *style) +{ + size_t len = strlen(str); + size_t start = len - 1; + char tmp[len+1]; + strcpy((char *)&tmp, str); + enum Bool fits; + int i; + do { + i = find_whitespace((const char *)&tmp, start); + if (i == -1) { + fprintf(stderr, "ERR: Can't split text because no whitespace was found.\n"); + return NULL; + } + tmp[i] = 0; + fits = text_fits((const char *)&tmp, style); + if (fits == B_ERROR) { + fprintf(stderr, "text_fits failed.\n"); + return NULL; + } + start = i - 1; + } while (!fits); + return strdup((char *)&tmp); +} + +static struct TextLine **text_split_by_whitespace(const char *str, struct Style *style) +{ + struct TextLine **lines = NULL; + struct TextLine *line; + int tl = 0; + char tmp[strlen(str)+1]; + strcpy((char *)&tmp, str); + char *text = (char *)&tmp; + size_t fitting_len; + char *fitting; + enum Bool fits; + while (1) { + fits = text_fits(text, style); + if (fits == B_ERROR) { + fprintf(stderr, "text_fits failed.\n"); + return NULL; + } + if (fits) { + if (text[0] == ' ' || text[0] == '\t') { + text++; + } + line = text_line_create(text, style); + lines = realloc(lines, (tl+2) * sizeof(struct TextLine *)); + lines[tl] = line; + tl++; + lines[tl] = NULL; + break; + } + fitting = text_find_fitting(text, style); + if (!fitting) { + fprintf(stderr, "text_find_fitting failed.\n"); + return NULL; + } + line = text_line_create(fitting, style); + lines = realloc(lines, (tl+1) * sizeof(struct TextLine *)); + lines[tl] = line; + tl++; + fitting_len = strlen(fitting); + text += fitting_len; + free(fitting); + } + return lines; +} + +static enum Bool text_line_fits(struct TextLine *line) +{ + double width; + int i = 0; + while (line->items[i] != NULL) { i++; } - printf("\n"); */ - // printf("size: %.1f\n", item->style->font->size); - return pdfioContentTextMeasure(font_obj, item->text, item->style->font->size); + if (i == 0) { + return B_TRUE; + } + width = text_width(line->items[i-1]); + if (width == EMPTY) { + fprintf(stderr, "text_width failed.\n"); + return B_ERROR; + } + if (line->items[i-1]->x + width > LINE_WIDTH) { + return B_FALSE; + } + return B_TRUE; } static bool out_pdf_draw_line(pdfio_stream_t *stream, struct TextLineItem *item, double y, double width, enum LineLocation line_location) @@ -586,25 +711,6 @@ static struct SpaceNeeded *needs_space(struct SpaceNeeded **spaces, int ly, int return NULL; } -static double text_line_set_lineheight(struct TextLine *line, enum SongFragmentType ftype) -{ - double biggest_font_size = 0.0; - int tli; - for (tli = 0; line->items[tli]; tli++) { - if (line->items[tli]->style->font->size > biggest_font_size) { - biggest_font_size = line->items[tli]->style->font->size; - } - } - switch (ftype) { - case SF_CHORD: - line->height = 2.0 + biggest_font_size; - break; - default: - line->height = 8.0 + biggest_font_size; - } - return line->height; -} - static struct Text **text_create(struct ChoSong **songs, struct Config *config) { struct PrintableItem *printable_item; @@ -616,67 +722,115 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) struct ChoLineItemAbove **text_above; struct SpaceNeeded space; struct SpaceNeeded **spaces = NULL; + struct TextLineMetadata **lines_metadata = NULL; + struct TextLine **text_lines = NULL; + int tlm = 0; int sn = 0; int t = 0; int tl = 0; int tli = 0; int m; struct Text **text = NULL; + enum Bool fits; for (so = 0; songs[so]; so++, t++) { text = realloc(text, (t+1) * sizeof(struct Text *)); text[t] = malloc(sizeof(struct Text)); text[t]->lines = NULL; for (m = 0; songs[so]->metadata[m]; m++) { if (strcmp(songs[so]->metadata[m]->name, "title") == 0) { - 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 = malloc(2 * sizeof(struct TextLineItem *)); - text[t]->lines[tl]->items[0] = malloc(sizeof(struct TextLineItem)); - text[t]->lines[tl]->items[0]->text = strdup(songs[so]->metadata[m]->value); - text[t]->lines[tl]->items[0]->style = cho_style_duplicate(songs[so]->metadata[m]->style); - width = text_width(text[t]->lines[tl]->items[0]); - if (width == EMPTY) { - fprintf(stderr, "text_width failed.\n"); + fits = text_fits(songs[so]->metadata[m]->value, songs[so]->metadata[m]->style); + if (fits == B_ERROR) { + fprintf(stderr, "text_fits failed.\n"); return NULL; } - text[t]->lines[tl]->items[0]->x = MARGIN_HORIZONTAL + (LINE_LEN - width) / 2; - text[t]->lines[tl]->items[1] = NULL; - text[t]->lines[tl]->btype = BT_LINE; - y -= text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); - if (y < MARGIN_BOTTOM) { - y = MEDIABOX_HEIGHT - MARGIN_TOP; - text[t]->lines[tl]->btype = BT_PAGE; + if (fits) { + 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 = malloc(2 * sizeof(struct TextLineItem *)); + text[t]->lines[tl]->items[0] = malloc(sizeof(struct TextLineItem)); + text[t]->lines[tl]->items[0]->text = strdup(songs[so]->metadata[m]->value); + text[t]->lines[tl]->items[0]->style = cho_style_duplicate(songs[so]->metadata[m]->style); + width = text_width(text[t]->lines[tl]->items[0]); + if (width == EMPTY) { + fprintf(stderr, "text_width failed.\n"); + return NULL; + } + text[t]->lines[tl]->items[0]->x = MARGIN_HORIZONTAL + (LINE_WIDTH - width) / 2; + text[t]->lines[tl]->items[1] = NULL; + text[t]->lines[tl]->btype = BT_LINE; + y -= text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); + if (y < MARGIN_BOTTOM) { + y = MEDIABOX_HEIGHT - MARGIN_TOP; + text[t]->lines[tl]->btype = BT_PAGE; + } + tl++; + } else { + fprintf(stderr, "WARN: Title doesn't fit on one line.\n"); + text_lines = text_split_by_whitespace(songs[so]->metadata[m]->value, songs[so]->metadata[m]->style); + for (int i = 0; text_lines[i]; i++) { + width = text_width(text_lines[i]->items[0]); + if (width == EMPTY) { + fprintf(stderr, "text_width failed.\n"); + return NULL; + } + text_lines[i]->items[0]->x = MARGIN_HORIZONTAL + (LINE_WIDTH - width) / 2; + text[t]->lines = realloc(text[t]->lines, (tl+1) * sizeof(struct TextLine *)); + text[t]->lines[tl] = text_lines[i]; + tl++; + } + free(text_lines); } - tl++; } } for (m = 0; songs[so]->metadata[m]; m++) { if (strcmp(songs[so]->metadata[m]->name, "subtitle") == 0) { - printable_item = config_printable_item_get(config->output->printable_items, "subtitle"); - if (!printable_item) { - fprintf(stderr, "config_printable_item_get failed.\n"); - return NULL; - } - 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 = malloc(2 * sizeof(struct TextLineItem *)); - text[t]->lines[tl]->items[0] = malloc(sizeof(struct TextLineItem)); - text[t]->lines[tl]->items[0]->text = strdup(songs[so]->metadata[m]->value); - text[t]->lines[tl]->items[0]->style = cho_style_duplicate(printable_item->style); - width = text_width(text[t]->lines[tl]->items[0]); - if (width == EMPTY) { - fprintf(stderr, "text_width failed.\n"); + fits = text_fits(songs[so]->metadata[m]->value, songs[so]->metadata[m]->style); + if (fits == B_ERROR) { + fprintf(stderr, "text_fits failed.\n"); return NULL; } - text[t]->lines[tl]->items[0]->x = MARGIN_HORIZONTAL + (LINE_LEN - width) / 2; - text[t]->lines[tl]->items[1] = NULL; - text[t]->lines[tl]->btype = BT_LINE; - y -= text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); - if (y < MARGIN_BOTTOM) { - y = MEDIABOX_HEIGHT - MARGIN_TOP; - text[t]->lines[tl]->btype = BT_PAGE; + if (fits) { + printable_item = config_printable_item_get(config->output->printable_items, "subtitle"); + if (!printable_item) { + fprintf(stderr, "config_printable_item_get failed.\n"); + return NULL; + } + 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 = malloc(2 * sizeof(struct TextLineItem *)); + text[t]->lines[tl]->items[0] = malloc(sizeof(struct TextLineItem)); + text[t]->lines[tl]->items[0]->text = strdup(songs[so]->metadata[m]->value); + text[t]->lines[tl]->items[0]->style = cho_style_duplicate(printable_item->style); + width = text_width(text[t]->lines[tl]->items[0]); + if (width == EMPTY) { + fprintf(stderr, "text_width failed.\n"); + return NULL; + } + text[t]->lines[tl]->items[0]->x = MARGIN_HORIZONTAL + (LINE_WIDTH - width) / 2; + text[t]->lines[tl]->items[1] = NULL; + text[t]->lines[tl]->btype = BT_LINE; + y -= text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); + if (y < MARGIN_BOTTOM) { + y = MEDIABOX_HEIGHT - MARGIN_TOP; + text[t]->lines[tl]->btype = BT_PAGE; + } + tl++; + } else { + fprintf(stderr, "WARN: Subtitle doesn't fit on one line.\n"); + text_lines = text_split_by_whitespace(songs[so]->metadata[m]->value, songs[so]->metadata[m]->style); + for (int i = 0; text_lines[i]; i++) { + width = text_width(text_lines[i]->items[0]); + if (width == EMPTY) { + fprintf(stderr, "text_width failed.\n"); + return NULL; + } + text_lines[i]->items[0]->x = MARGIN_HORIZONTAL + (LINE_WIDTH - width) / 2; + text[t]->lines = realloc(text[t]->lines, (tl+1) * sizeof(struct TextLine *)); + text[t]->lines[tl] = text_lines[i]; + tl++; + } + free(text_lines); } - tl++; } } text[t]->lines = realloc(text[t]->lines, (tl+1) * sizeof(struct TextLine *)); @@ -687,21 +841,44 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) tl++; for (se = 0; songs[so]->sections[se]; se++) { if (songs[so]->sections[se]->label) { - 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 = malloc(2 * sizeof(struct TextLineItem *)); - text[t]->lines[tl]->items[0] = malloc(sizeof(struct TextLineItem)); - text[t]->lines[tl]->items[0]->text = strdup(songs[so]->sections[se]->label->name); - text[t]->lines[tl]->items[0]->style = cho_style_duplicate(songs[so]->sections[se]->label->style); - text[t]->lines[tl]->items[0]->x = MARGIN_HORIZONTAL; - text[t]->lines[tl]->items[1] = NULL; - text[t]->lines[tl]->btype = BT_LINE; - y -= text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); - if (y < MARGIN_BOTTOM) { - y = MEDIABOX_HEIGHT - MARGIN_TOP; - text[t]->lines[tl]->btype = BT_PAGE; + fits = text_fits(songs[so]->sections[se]->label->name, songs[so]->sections[se]->label->style); + if (fits == B_ERROR) { + fprintf(stderr, "text_fits failed.\n"); + return NULL; + } + if (fits) { + 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 = malloc(2 * sizeof(struct TextLineItem *)); + text[t]->lines[tl]->items[0] = malloc(sizeof(struct TextLineItem)); + text[t]->lines[tl]->items[0]->text = strdup(songs[so]->sections[se]->label->name); + text[t]->lines[tl]->items[0]->style = cho_style_duplicate(songs[so]->sections[se]->label->style); + text[t]->lines[tl]->items[0]->x = MARGIN_HORIZONTAL; + text[t]->lines[tl]->items[1] = NULL; + text[t]->lines[tl]->btype = BT_LINE; + y -= text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); + if (y < MARGIN_BOTTOM) { + y = MEDIABOX_HEIGHT - MARGIN_TOP; + text[t]->lines[tl]->btype = BT_PAGE; + } + tl++; + } else { + fprintf(stderr, "WARN: Section name doesn't fit on one line.\n"); + text_lines = text_split_by_whitespace(songs[so]->sections[se]->label->name, songs[so]->sections[se]->label->style); + for (int i = 0; text_lines[i]; i++) { + width = text_width(text_lines[i]->items[0]); + if (width == EMPTY) { + fprintf(stderr, "text_width failed.\n"); + return NULL; + } + // text_lines[i]->items[0]->x = MARGIN_HORIZONTAL + (LINE_WIDTH - width) / 2; + text[t]->lines = realloc(text[t]->lines, (tl+1) * sizeof(struct TextLine *)); + text[t]->lines[tl] = text_lines[i]; + printf("text: '%s'\n", text_lines[i]->items[0]->text); + tl++; + } + free(text_lines); } - tl++; } lines = songs[so]->sections[se]->lines; for (li = 0; lines[li]; li++, tl++) { @@ -709,6 +886,12 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) int text_above_len = cho_text_above_count(text_above); // TODO: The code inside the if statement seems to me too complicated, so simplify if possible if (text_above_len > 0) { + lines_metadata = realloc(lines_metadata, (tlm+1) * sizeof(struct TextLineMetadata *)); + lines_metadata[tlm] = malloc(sizeof(struct TextLineMetadata)); + lines_metadata[tlm]->text_index = t; + lines_metadata[tlm]->text_line_index = tl; + lines_metadata[tlm]->is_lyrics = false; + tlm++; 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; @@ -781,6 +964,14 @@ 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; + fits = text_line_fits(text[t]->lines[tl]); + if (fits == B_ERROR) { + fprintf(stderr, "text_line_fits failed.\n"); + return NULL; + } + if (!fits) { + fprintf(stderr, "WARN: text line (chords/annotations) doesn't fit.\n"); + } text[t]->lines[tl]->btype = lines[li]->btype; y -= text_line_set_lineheight(text[t]->lines[tl], SF_CHORD); if (y < MARGIN_BOTTOM) { @@ -792,10 +983,17 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) tli = 0; tl++; } + lines_metadata = realloc(lines_metadata, (tlm+1) * sizeof(struct TextLineMetadata *)); + lines_metadata[tlm] = malloc(sizeof(struct TextLineMetadata)); + lines_metadata[tlm]->text_index = t; + lines_metadata[tlm]->text_line_index = tl; + lines_metadata[tlm]->is_lyrics = true; + tlm++; 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; for (ly = 0; lines[li]->lyrics[ly]; ly++) { + // out_pdf_text_find_fitting_length(lines[li]->lyrics[ly]); int ii; double last_text_line_item_width; struct SpaceNeeded *sp; @@ -845,6 +1043,14 @@ 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; + fits = text_line_fits(text[t]->lines[tl]); + if (fits == B_ERROR) { + fprintf(stderr, "text_line_fits failed.\n"); + return NULL; + } + if (!fits) { + fprintf(stderr, "WARN: text line (lyrics) doesn't fit.\n"); + } text[t]->lines[tl]->btype = lines[li]->btype; y -= text_line_set_lineheight(text[t]->lines[tl], SF_TEXT); if (y < MARGIN_BOTTOM) { @@ -869,6 +1075,13 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) } text = realloc(text, (t+1) * sizeof(struct Text *)); text[t] = NULL; + struct TextLineMetadata *me; + for (m = 0; m<tlm; m++) { + me = lines_metadata[m]; + printf("%d %d %d\n", me->text_index, me->text_line_index, me->is_lyrics); + free(lines_metadata[m]); + } + free(lines_metadata); return text; } diff --git a/out_pdf.h b/out_pdf.h @@ -5,7 +5,7 @@ #define MARGIN_TOP 40.0 #define MARGIN_BOTTOM 180.0 #define MARGIN_HORIZONTAL 80.0 -#define LINE_LEN MEDIABOX_WIDTH - MARGIN_HORIZONTAL * 2 +#define LINE_WIDTH MEDIABOX_WIDTH - MARGIN_HORIZONTAL * 2 #define MIN_CHORD_GAP_WIDTH 5.0 #define SECTION_GAP_WIDTH 10.0 @@ -16,12 +16,6 @@ enum Bool { B_TRUE }; -enum Alignment { - LEFT, - CENTER, - CONTINUE -}; - struct Fnt { char *name; pdfio_obj_t *font; @@ -39,6 +33,12 @@ struct TextLineItem { double x; }; +struct TextLineMetadata { + int text_index; + int text_line_index; + bool is_lyrics; +}; + struct TextLine { struct TextLineItem **items; double height;